Last active
August 30, 2024 19:20
-
-
Save OleksandrKucherenko/054b0331ec791edb39db76bd690ecdb2 to your computer and use it in GitHub Desktop.
Android View Hierarchy dump with transformation properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Author: Oleksandr Kucherenko, 2018-present */ | |
package {your.package.name}; | |
import android.annotation.TargetApi; | |
import android.app.Activity; | |
import android.content.res.Resources; | |
import android.graphics.Matrix; | |
import android.os.Build; | |
import android.support.annotation.NonNull; | |
import android.support.v4.util.Pair; | |
import android.util.Log; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.TextView; | |
import java.util.Locale; | |
import java.util.Stack; | |
import javax.annotation.Nullable; | |
import static android.view.View.GONE; | |
import static android.view.View.INVISIBLE; | |
import static android.view.View.VISIBLE; | |
@SuppressWarnings("WeakerAccess") | |
public final class DebugViews { | |
/** | |
* Chunk of the long log line. | |
*/ | |
public static final int LOG_MSG_LIMIT = 200; | |
/** | |
* Initial matrix without transformations. | |
*/ | |
public static final Matrix EMPTY_MATRIX = new Matrix(); | |
/** | |
* Log long message by chunks | |
* | |
* @param message message to log. | |
*/ | |
@SuppressWarnings("UnusedReturnValue") | |
public static int longDebug(@NonNull final String tag, @NonNull final String message) { | |
int counter = 0; | |
String msg = message; | |
while (msg.length() > 0) { | |
final int endOfLine = msg.indexOf("\n"); // -1, 0, 1 | |
final int breakPoint = Math.min(endOfLine < 0 ? LOG_MSG_LIMIT : endOfLine + 1, LOG_MSG_LIMIT); | |
final int last = Math.min(msg.length(), breakPoint); | |
final String out = String.format(Locale.US, "%02d: %s", counter, msg.substring(0, last)); | |
Log.d(tag, out); | |
msg = msg.substring(last); | |
counter++; | |
} | |
return counter; | |
} | |
/** | |
* Print into log activity views hierarchy. | |
*/ | |
@NonNull | |
public static String logViewHierarchy(@NonNull final Activity activity) { | |
final View view = activity.findViewById(android.R.id.content); | |
if (null == view) | |
return "Activity [" + activity.getClass().getSimpleName() + "] is not initialized yet. "; | |
return logViewHierarchy(view); | |
} | |
/** | |
* Print into log view hierarchy. | |
*/ | |
@NonNull | |
private static String logViewHierarchy(@NonNull final View root) { | |
final StringBuilder output = new StringBuilder(8192).append("\n"); | |
final Resources r = root.getResources(); | |
final Stack<Pair<String, View>> stack = new Stack<>(); | |
stack.push(Pair.create("", root)); | |
while (!stack.empty()) { | |
@NonNull final Pair<String, View> p = stack.pop(); | |
@NonNull final View v = p.second; | |
@NonNull final String prefix = p.first; | |
final boolean isLastOnLevel = stack.empty() || !prefix.equals(stack.peek().first); | |
final String graphics = "" + prefix + (isLastOnLevel ? "└── " : "├── "); | |
final String className = v.getClass().getSimpleName(); | |
final String line = graphics + className + dumpProperties(r, v); | |
output.append(line).append("\n"); | |
if (v instanceof ViewGroup) { | |
final ViewGroup vg = (ViewGroup) v; | |
for (int i = vg.getChildCount() - 1; i >= 0; i--) { | |
stack.push(Pair.create(prefix + (isLastOnLevel ? " " : "│ "), vg.getChildAt(i))); | |
} | |
} | |
} | |
return output.toString(); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
@NonNull | |
private static String dumpProperties(@NonNull final Resources r, @NonNull final View v) { | |
final StringBuilder sb = new StringBuilder(); | |
sb.append(" ").append("id=").append(v.getId()).append(resolveIdToName(r, v)); | |
switch (v.getVisibility()) { | |
case VISIBLE: | |
sb.append(", V--"); | |
break; | |
case INVISIBLE: | |
sb.append(", -I-"); | |
break; | |
case GONE: | |
sb.append(", --G"); | |
break; | |
default: | |
sb.append(", ---"); | |
break; | |
} | |
// transformation matrix exists, rotate/scale/skew/translate/ | |
if (!v.getMatrix().equals(EMPTY_MATRIX)) { | |
sb.append(", ").append("matrix=").append(v.getMatrix().toShortString()); | |
if (0.0f != v.getRotation() || 0.0f != v.getRotationX() || 0.0f != v.getRotationY()) { | |
sb.append(", rotate=[") | |
.append(v.getRotation()).append(",") | |
.append(v.getRotationX()).append(",") | |
.append(v.getRotationY()) | |
.append("]"); | |
// print pivote only if its not default | |
if (v.getWidth() / 2 != v.getPivotX() || v.getHeight() / 2 != v.getPivotY()) { | |
sb.append(", pivot=[") | |
.append(v.getPivotX()).append(",") | |
.append(v.getPivotY()) | |
.append("]"); | |
} | |
} | |
if (0.0f != v.getTranslationX() || 0.0f != v.getTranslationY() || 0.0f != v.getTranslationZ()) { | |
sb.append(", translate=[") | |
.append(v.getTranslationX()).append(",") | |
.append(v.getTranslationY()).append(",") | |
.append(v.getTranslationZ()) | |
.append("]"); | |
} | |
if (1.0f != v.getScaleX() || 1.0f != v.getScaleY()) { | |
sb.append(", scale=[") | |
.append(v.getScaleX()).append(",") | |
.append(v.getScaleY()) | |
.append("]"); | |
} | |
} | |
// padding's | |
if (0 != v.getPaddingStart() || 0 != v.getPaddingTop() || | |
0 != v.getPaddingEnd() || 0 != v.getPaddingBottom()) { | |
sb.append(", ") | |
.append("padding=[") | |
.append(v.getPaddingStart()).append(",") | |
.append(v.getPaddingTop()).append(",") | |
.append(v.getPaddingEnd()).append(",") | |
.append(v.getPaddingBottom()) | |
.append("]"); | |
} | |
// margin's | |
final ViewGroup.LayoutParams lp = v.getLayoutParams(); | |
if (lp instanceof ViewGroup.MarginLayoutParams) { | |
final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; | |
if (0 != mlp.leftMargin || 0 != mlp.topMargin || | |
0 != mlp.rightMargin || 0 != mlp.bottomMargin) { | |
sb.append(", ").append("margin=[") | |
.append(mlp.leftMargin).append(",") | |
.append(mlp.topMargin).append(",") | |
.append(mlp.rightMargin).append(",") | |
.append(mlp.bottomMargin) | |
.append("]"); | |
} | |
} | |
// width, height, size | |
sb.append(", position=[").append(v.getLeft()).append(",").append(v.getTop()).append("]"); | |
sb.append(", size=[").append(v.getWidth()).append(",").append(v.getHeight()).append("]"); | |
// texts | |
if (v instanceof TextView) { | |
final TextView tv = (TextView) v; | |
sb.append(", text=\"").append(tv.getText()).append("\""); | |
} | |
return sb.toString(); | |
} | |
/** | |
* @see <a href="https://stackoverflow.com/questions/10137692/how-to-get-resource-name-from-resource-id">Lookup resource name</a> | |
*/ | |
@NonNull | |
private static String resolveIdToName(@Nullable final Resources r, @NonNull final View v) { | |
if (null == r) return ""; | |
try { | |
return " / " + r.getResourceEntryName(v.getId()); | |
} catch (Throwable ignored) { | |
return ""; | |
} | |
} | |
} |
/**
* Log long message by chunks
*
* @param message message to log.
*/
@SuppressWarnings("UnusedReturnValue")
private int longDebug(@NonNull final String message) {
int counter = 0;
String msg = message;
while (msg.length() > 0) {
final int endOfLine = msg.indexOf("\n"); // -1, 0, 1
final int breakPoint = Math.min(endOfLine < 0 ? DebugViews.LOG_MSG_LIMIT : endOfLine + 1, DebugViews.LOG_MSG_LIMIT);
final int last = Math.min(msg.length(), breakPoint);
final String out = String.format(Locale.US, "%02d: %s", counter, msg.substring(0, last));
Log.d(TAG, out);
msg = msg.substring(last);
counter++;
}
return counter;
}
Logger helper, Android logs do not print well long lines. So we should do some help.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Typical output: