Skip to content

Instantly share code, notes, and snippets.

@OleksandrKucherenko
Last active August 30, 2024 19:20
Show Gist options
  • Save OleksandrKucherenko/054b0331ec791edb39db76bd690ecdb2 to your computer and use it in GitHub Desktop.
Save OleksandrKucherenko/054b0331ec791edb39db76bd690ecdb2 to your computer and use it in GitHub Desktop.
Android View Hierarchy dump with transformation properties
/** 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 "";
}
}
}
@OleksandrKucherenko
Copy link
Author

Typical output:

01: └── FrameLayout id=16908290 / content, V--, position=[0,72], size=[1080,1704]
02:     └── ReactRootView id=1, V--, position=[0,0], size=[1080,1704]
03:         └── ReactViewGroup id=176, V--, position=[0,0], size=[1080,1704]
04:             └── ReactSwipeRefreshLayout id=173, V--, position=[0,0], size=[1080,1704]
05:                 ├── ReactScrollView id=172, V--, position=[0,0], size=[1080,1704]
06:                 │   └── ReactViewGroup id=170, V--, position=[0,0], size=[1080,2620]
07:                 │       ├── ReactViewGroup id=9, V--, position=[0,0], size=[1080,496]
08:                 │       │   └── ReactViewGroup id=195, V--, position=[0,0], size=[1080,496]
09:                 │       │       ├── ReactTextView id=182, V--, padding=[0,3,0,12], position=[45,45], size=[630,91], text="Incoming!"
10:                 │       │       ├── ReactTextView id=185, V--, padding=[0,6,0,9], position=[45,136], size=[630,315], text="We're introducing a whole new delivery experience –and you're the first to t
11: ry it in the Beta."
12:                 │       │       ├── ReactViewGroup id=190, V--, matrix=[0.9396926, -0.34202012, 93.867096][0.34202012, 0.9396926, -36.34679][0.0, 0.0, 1.0], rotate=[20.0,0.0,0.0], translate=[0.0,0.0,0
13: .0], scale=[1.0,1.0], position=[660,0], size=[300,496]
14:                 │       │       │   └── ARTSurfaceView id=189, V--, position=[0,98], size=[300,300]
15:                 │       │       └── ReactViewGroup id=194, V--, position=[975,45], size=[60,60]
16:                 │       │           └── ARTSurfaceView id=193, V--, position=[0,0], size=[60,60]
17:                 │       ├── ReactViewGroup id=14, V--, position=[0,496], size=[1080,210]
18:                 │       │   └── ReactTextView id=12, V--, padding=[0,3,0,12], position=[60,90], size=[960,105], text="Ready to pickup"
19:                 │       ├── ReactViewGroup id=73, V--, position=[0,706], size=[1080,599]
20:                 │       │   └── ReactViewGroup id=70, V--, position=[60,0], size=[960,599]
21:                 │       │       └── ReactViewGroup id=69, V--, position=[0,0], size=[960,599]
22:                 │       │           ├── ReactViewGroup id=17, V--, position=[0,60], size=[150,150]
23:                 │       │           │   └── ARTSurfaceView id=16, V--, position=[45,45], size=[60,60]
24:                 │       │           ├── ReactTextView id=26, V--, padding=[0,6,0,9], position=[195,60], size=[720,90], text="Savoy - 1.3kg"
25:                 │       │           ├── ReactTextView id=34, V--, padding=[0,6,0,9], position=[195,120], size=[234,90], text="1 day ago - "
26:                 │       │           ├── ReactTextView id=37, V--, padding=[0,6,0,9], position=[429,120], size=[486,90], text="ICA Nära Hindås"
27:                 │       │           ├── ReactViewGroup id=48, V--, position=[195,240], size=[150,150]
28:                 │       │           │   └── BlendedLayout id=47, V--, position=[0,0], size=[150,150]
29:                 │       │           │       └── ReactImageView id=46, V--, position=[0,0], size=[150,150]
30:                 │       │           ├── ReactViewGroup id=52, V--, position=[360,240], size=[150,150]
31:                 │       │           │   └── BlendedLayout id=50, V--, position=[0,0], size=[150,150]
32:                 │       │           │       └── ReactImageView id=49, V--, position=[0,0], size=[150,150]
33:                 │       │           ├── ReactViewGroup id=55, V--, position=[525,240], size=[150,150]
34:                 │       │           │   └── BlendedLayout id=54, V--, position=[0,0], size=[150,150]
35:                 │       │           │       └── ReactImageView id=53, V--, position=[0,0], size=[150,150]
36:                 │       │           ├── ReactViewGroup id=62, V--, position=[195,435], size=[414,101]
37:                 │       │           │   └── ReactTextView id=60, V--, position=[30,25], size=[354,51], text="Pick up code: 4569"
38:                 │       │           └── ReactViewGroup id=67, V--, position=[195,596], size=[765,3]
39:                 │       │               └── ReactViewGroup id=66, V--, position=[0,0], size=[765,3]
40:                 │       ├── ReactViewGroup id=75, V--, position=[0,1305], size=[1080,0]
41:                 │       ├── ReactViewGroup id=79, V--, position=[0,1305], size=[1080,210]
42:                 │       │   └── ReactTextView id=77, V--, padding=[0,3,0,12], position=[60,90], size=[960,105], text="On the way"
43:                 │       ├── ReactViewGroup id=122, V--, position=[0,1515], size=[1080,453]
44:                 │       │   └── ReactViewGroup id=119, V--, position=[60,0], size=[960,453]
45:                 │       │       └── ReactViewGroup id=118, V--, position=[0,0], size=[960,453]
46:                 │       │           ├── ReactViewGroup id=85, V--, position=[0,60], size=[150,150]
47:                 │       │           │   └── ARTSurfaceView id=84, V--, position=[45,45], size=[60,60]
48:                 │       │           ├── ReactTextView id=88, V--, padding=[0,6,0,9], position=[195,60], size=[720,90], text="Savoy"
49:                 │       │           ├── ReactTextView id=90, V--, padding=[0,6,0,9], position=[960,60], size=[0,90], text=""
50:                 │       │           ├── ReactTextView id=94, V--, padding=[0,6,0,9], position=[195,120], size=[393,90], text="Expected in 3 days"
51:                 │       │           ├── ReactViewGroup id=103, V--, position=[195,240], size=[150,150]
52:                 │       │           │   └── BlendedLayout id=102, V--, position=[0,0], size=[150,150]
53:                 │       │           │       └── ReactImageView id=100, V--, position=[0,0], size=[150,150]
54:                 │       │           ├── ReactViewGroup id=106, V--, position=[360,240], size=[150,150]
55:                 │       │           │   └── BlendedLayout id=105, V--, position=[0,0], size=[150,150]
56:                 │       │           │       └── ReactImageView id=104, V--, position=[0,0], size=[150,150]
57:                 │       │           ├── ReactViewGroup id=109, V--, position=[525,240], size=[150,150]
58:                 │       │           │   └── BlendedLayout id=108, V--, position=[0,0], size=[150,150]
59:                 │       │           │       └── ReactImageView id=107, V--, position=[0,0], size=[150,150]
60:                 │       │           └── ReactViewGroup id=116, V--, position=[195,450], size=[765,3]
61:                 │       │               └── ReactViewGroup id=115, V--, position=[0,0], size=[765,3]
62:                 │       ├── ReactViewGroup id=124, V--, position=[0,1968], size=[1080,0]
63:                 │       ├── ReactViewGroup id=128, V--, position=[0,1968], size=[1080,210]
64:                 │       │   └── ReactTextView id=126, V--, padding=[0,3,0,12], position=[60,90], size=[960,105], text="Delivered"
65:                 │       ├── ReactViewGroup id=168, V--, position=[0,2178], size=[1080,453]
66:                 │       │   └── ReactViewGroup id=166, V--, position=[60,0], size=[960,453]
67:                 │       │       ├── ReactViewGroup id=133, V--, position=[0,60], size=[150,150]
68:                 │       │       │   └── ARTSurfaceView id=132, V--, position=[45,45], size=[60,60]
69:                 │       │       ├── ReactTextView id=136, V--, padding=[0,6,0,9], position=[195,60], size=[720,90], text="Savoy"
70:                 │       │       ├── ReactTextView id=138, V--, padding=[0,6,0,9], position=[960,60], size=[0,90], text=""
71:                 │       │       ├── ReactTextView id=142, V--, padding=[0,6,0,9], position=[195,120], size=[420,90], text="Ordered 1 week ago"
72:                 │       │       ├── ReactViewGroup id=150, V--, position=[195,240], size=[150,150]
73:                 │       │       │   └── BlendedLayout id=149, V--, position=[0,0], size=[150,150]
74:                 │       │       │       └── ReactImageView id=148, V--, position=[0,0], size=[150,150]
75:                 │       │       ├── ReactViewGroup id=154, V--, position=[360,240], size=[150,150]
76:                 │       │       │   └── BlendedLayout id=153, V--, position=[0,0], size=[150,150]
77:                 │       │       │       └── ReactImageView id=152, V--, position=[0,0], size=[150,150]
78:                 │       │       ├── ReactViewGroup id=157, V--, position=[525,240], size=[150,150]
79:                 │       │       │   └── BlendedLayout id=156, V--, position=[0,0], size=[150,150]
80:                 │       │       │       └── ReactImageView id=155, V--, position=[0,0], size=[150,150]
81:                 │       │       └── ReactViewGroup id=164, V--, position=[195,450], size=[765,3]
82:                 │       │           └── ReactViewGroup id=163, V--, position=[0,0], size=[765,3]
83:                 │       └── ReactViewGroup id=179, V--, position=[0,2631], size=[1080,0]
84:                 └── CircleImageView id=-1, --G, position=[480,-120], size=[120,120]

@OleksandrKucherenko
Copy link
Author

OleksandrKucherenko commented Nov 1, 2018

    /**
     * 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