【问题标题】:print a view hierarchy on a device在设备上打印视图层次结构
【发布时间】:2011-07-02 20:45:18
【问题描述】:

在我无法实际访问的三星手机上调试我的应用时出现奇怪的结果。我想请用户运行一个检测的应用程序来帮助调试。我的应用程序得到一个view,其中有一个未知的(对我来说;-)层次结构(ViewGroups 等)。有没有办法“走”View 并打印到字符串/ddms View 中的所有组件(等等ViewGroups 等等)?

这类似于HierarchyViewer 工具 - 如果我对设备具有adb 级别的访问权限。

更新:我想我可以使用该方法

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

来自ViewDebug.java Android OS 来源...

谁有更好的主意?

谢谢!

【问题讨论】:

    标签: android android-layout


    【解决方案1】:

    这是我刚刚为此目的制作的实用函数:

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

    【讨论】:

    • 我只会因为在 var 名称中使用 $ 而给予 +2 ups,我认为这是不可能的,从未尝试过。
    • 我喜欢这个答案,但我会使用以下方法使它变得更好,以便您还可以打印 id(如果可用):String desc = $prefix + " | " + "[" + i + "/" + ($vg.getChildCount()-1) + "] "+ v.getClass().getSimpleName(); String id = null; try { id = v.getResources().getResourceName(v.getId()); } catch (Exception e) { // no-op (no id found) } desc += " " + (id == null ? v.getId() : id);
    【解决方案2】:

    我创建了实用程序方法,它以漂亮的打印方式返回层次结构,并带有人类可读的视图 ID。以下是输出示例:

    [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
    

    实用方法如下:

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

    提示:我们使用此方法将视图层次结构添加到一些崩溃报告中。 在某些情况下它真的很有帮助

    【讨论】:

      【解决方案3】:

      其他输出的格式不满意我的眼睛和一些开销的代码。所以,我改进了输出:

      /**
       * 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 + "  ");
          }
      }
      

      到:

      |— 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
      

      【讨论】:

        【解决方案4】:

        这些视图不是我的,它们来自其他应用程序,所以我无法触摸与它们相关的代码(我是从 RemoteView 膨胀它们)

        如果您收到RemoteViews,并且将其应用于活动中的某些内容,则可以直接访问生成的Views,这与您自己从XML 膨胀它们没有什么不同。给定根 View,只需让孩子们步行(可能是深度优先,但这是您的决定)并记录您想要的任何信息。

        【讨论】:

        • 这实际上是我所做的。我一直在寻找的是一个提供“预装”功能的类或库。我利用了 ViewDebug 的 Android 源代码中的一些代码,并且基本上按照您在此处调用的内容进行了操作。
        【解决方案5】:

        this answer 几乎相同,但使用 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()
        }
        

        【讨论】:

        • 这就是你打印的方式:println(getViewTree(getWindow().getDecorView().getRootView() as ViewGroup))
        【解决方案6】:

        只需从 BottomNavigationView 中找到菜单项并注册长按即可。

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

        【讨论】:

          猜你喜欢
          • 2015-06-10
          • 1970-01-01
          • 1970-01-01
          • 2016-01-17
          • 1970-01-01
          • 1970-01-01
          • 2021-12-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多