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;
}
精彩评论