开发者

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 that has an unknown (to me ;-) hierarchy in it (ViewGroups etc). Is there a way to "walk" a View and print out to a string / ddms all the components in the View (etc ViewGroups in it etc)?

This would be akin to HierarchyViewer tool - if I had adb level access to the device.

Update: I guess I could use the method

void dumpViewHierarchyWithProperties(Context context, ViewGroup group, BufferedWriter out, int level)

from the ViewDebug.java Android OS sources ...

Anyone have a better idea?

Thanks!


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);
        }
    }
}


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.


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()
}


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


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.


Just find menu item from BottomNavigationView and register long click.

View itemBag = bottomNavigationView.findViewById(R.id.action_bag);
    if (itemBag != null) {
      itemBag.setOnLongClickListener(BottomNavigationActivity.this::onLongClick);
    }

private boolean onLongClick(View v) {
    switch (v.getId()) {
      case R.id.action_bag: {
        showToastMessage(R.string.menu_shopping_bag);
      }
      break;
    }
    return false;
  }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜