Skip to content Skip to sidebar Skip to footer

Print A View Hierarchy On A Device

Debugging my app with strange results on Samsung phones, which I don't have physical access to. I'd like to ask a user to run an instrumented App to help debug. My App gets a view

Solution 1:

Here's the utility function I just made for this purpose:

public static void printViewHierarchy(ViewGroup vg, String prefix) {
    for (int i = 0; i < vg.getChildCount(); i++) {
        View v = vg.getChildAt(i);
        String desc = prefix + " | " + "[" + i + "/" + (vg.getChildCount()-1) + "] "+ v.getClass().getSimpleName() + " " + v.getId();
        Log.v("x", desc);

        if (v instanceof ViewGroup) {
            printViewHierarchy((ViewGroup)v, desc);
        }
    }
}

Solution 2:

I've created utility method which returns hierarchy in a pretty printed way with human readable view ids. Here is an example of the output:

[LinearLayout] no_id
  [CardView] com.example:id/card_view
    [RelativeLayout] no_id
      [LinearLayout] com.example:id/image_container
        [AppCompatImageView] com.example:id/incident_icon
        [CustomTextView] com.example:id/road_number
      [RelativeLayout] no_id
        [CustomTextView] com.example:id/distance_to_user
        [CustomTextView] com.example:id/obstruction_title
        [CustomTextView] com.example:id/road_direction
        [CustomTextView] com.example:id/obstruction_description
        [AppCompatImageView] com.example:id/security_related

Here is the utility method:

public static String getViewHierarchy(@NonNull View v) {
    StringBuilder desc = new StringBuilder();
    getViewHierarchy(v, desc, 0);
    return desc.toString();
}

private static void getViewHierarchy(View v, StringBuilder desc, int margin) {
    desc.append(getViewMessage(v, margin));
    if (v instanceof ViewGroup) {
        margin++;
        ViewGroup vg = (ViewGroup) v;
        for (int i = 0; i < vg.getChildCount(); i++) {
            getViewHierarchy(vg.getChildAt(i), desc, margin);
        }
    }
}

private static String getViewMessage(View v, int marginOffset) {
    String repeated = new String(new char[marginOffset]).replace("\0", "  ");
    try {
        String resourceId = v.getResources() != null ? (v.getId() > 0 ? v.getResources().getResourceName(v.getId()) : "no_id") : "no_resources";
        return repeated + "[" + v.getClass().getSimpleName() + "] " + resourceId + "\n";
    } catch (Resources.NotFoundException e) {
        return repeated + "[" + v.getClass().getSimpleName() + "] name_not_found\n";
    }
}

Tip: we use this method to add view hierarchies to some crash reports. In some cases it is really helpful.


Solution 3:

The format of other outputs dissatisfied my eyes and some codes where an overhead. So, I improved the output with:

/**
 * Prints the view hierarchy.
 */
public static void printViewHierarchy(ViewGroup parent, String intent) {
    for (int i = 0, max = parent.getChildCount(), numLenght = (max + "").length(); i < max; i++) {
        View child = parent.getChildAt(i);
        String childString = child.getClass().getSimpleName() + " " + child.getId();
        String format = "|— %0" + numLenght + "d/%0" + numLenght + "d %s";
        Log.d("debug", intent + String.format(format, i + 1, max, childString));

        if (child instanceof ViewGroup)
            printViewHierarchy((ViewGroup) child, intent + "  ");
    }
}

to:

|— 1/4 ScrollView 2131296482
|— 2/4 MaterialTextView 2131296445
|— 3/4 FloatingActionButton 2131296374
|— 4/4 MaterialTextView 2131296363
|— 1/4 ScrollView 2131296482
  |— 1/1 LinearLayout 2129243036
    |— 01/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
    |— 02/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
    |— ...
    |— 74/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
|— 2/4 MaterialTextView 2131296445
|— 3/4 FloatingActionButton 2131296374
|— 4/4 MaterialTextView 2131296363

Solution 4:

These Views are not mine, they are coming from other Apps, so I cannot touch code related to them (I'm inflating them from a RemoteView)

If you are receiving a RemoteViews, and you are applying it to something in your activity, you have direct access to the resulting Views, no different than if you had inflated them from XML yourself. Given the root View, it is simply a matter of walking the children (probably depth-first, but that's your call) and logging whatever information you want.


Solution 5:

almost the same with this answer but with Kotlin:

fun getViewTree(root: ViewGroup): String{
    fun getViewDesc(v: View): String{
        val res = v.getResources()
        val id = v.getId()
        return "[${v::class.simpleName}]: " + when(true){
            res == null -> "no_resouces"
            id > 0 -> try{
                res.getResourceName(id)
            } catch(e: android.content.res.Resources.NotFoundException){
                "name_not_found"
            }
            else -> "no_id"
        }
    }

    val output = StringBuilder(getViewDesc(root))
    for(i in 0 until root.getChildCount()){
        val v = root.getChildAt(i)
        output.append("\n").append(
            if(v is ViewGroup){
                getViewTree(v).prependIndent("  ")
            } else{
                "  " + getViewDesc(v)
            }
        )
    }
    return output.toString()
}

Post a Comment for "Print A View Hierarchy On A Device"