背景
我们在Android编写一些自动化工具时,往往是使用无障碍开发。通过无障碍中监听节点变化和事件,然后获取活动窗口界面,通过界面去找一下特定的节点执行一些动作。例如:通过找到x的节点元素执行点击事件关闭广告。
显示
我们可以通过是使用Android Device Monitor去截取头条当前的界面,如下:
看起来好方便,左边点那个节点,右边对应显示,这样可视化的操作确实方便。比如我们想要找到北京商报节点,看右边是可以通过过ID去查找,但是实际上我们获取的节点没有打印出ID,北京商报在69行,如下:
└─ (0,0) [0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
├─ (0,1) [0-0] Class: androidx.viewpager.widget.ViewPager, ID: null, Text: null, desc: null
│ ├─ (0,2) [0-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ ├─ (0,3) [0-0-0-0] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 叙局势突变, desc: 叙局势突变
│ │ ├─ (1,3) [0-0-0-1] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 已更新至最新, desc: 已更新至最新
│ │ ├─ (2,3) [0-0-0-2] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 韩国戒严风波, desc: 韩国戒严风波
│ │ ├─ (3,3) [0-0-0-3] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 实时快讯, desc: 实时快讯
│ │ ├─ (4,3) [0-0-0-4] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 实时更新, desc: 实时更新
│ │ ├─ (5,3) [0-0-0-5] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 阿萨德行踪披露, desc: 阿萨德行踪披露
│ │ ├─ (6,3) [0-0-0-6] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 尹锡悦被禁止离境, desc: 尹锡悦被禁止离境
│ │ ├─ (7,3) [0-0-0-7] Class: com.lynx.tasm.behavior.ui.text.UIText, ID: null, Text: 7×24小时, desc: 7×24小时
│ │ ├─ (8,3) [0-0-0-8] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 4, desc: 4
│ │ ├─ (9,3) [0-0-0-9] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 传台积电2nm芯片生产良率达60%以上, desc: 传台积电2nm芯片生产良率达60%以上
│ │ ├─ (10,3) [0-0-0-10] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 5, desc: 5
│ │ ├─ (11,3) [0-0-0-11] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 中铁四局集团申请新专利, desc: 中铁四局集团申请新专利
│ │ ├─ (12,3) [0-0-0-12] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 展开更多, desc: 展开更多
│ │ ├─ (13,3) [0-0-0-13] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 游戏榜, desc: 游戏榜
│ │ ├─ (14,3) [0-0-0-14] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: TheShy发文感谢粉丝, desc: TheShy发文感谢粉丝
│ │ ├─ (15,3) [0-0-0-15] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 英雄联盟传奇杯S2落幕, desc: 英雄联盟传奇杯S2落幕
│ │ ├─ (16,3) [0-0-0-16] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: TGA 2024提名作品宣传片公布, desc: TGA 2024提名作品宣传片公布
│ │ ├─ (17,3) [0-0-0-17] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 4, desc: 4
│ │ ├─ (18,3) [0-0-0-18] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 纯血鸿蒙版QQ飞车体验服开启预约, desc: 纯血鸿蒙版QQ飞车体验服开启预约
│ │ ├─ (19,3) [0-0-0-19] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 5, desc: 5
│ │ ├─ (20,3) [0-0-0-20] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 曝Switch 2将大幅升级, desc: 曝Switch 2将大幅升级
│ │ ├─ (21,3) [0-0-0-21] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 展开更多, desc: 展开更多
│ │ ├─ (22,3) [0-0-0-22] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 汽车榜, desc: 汽车榜
│ │ ├─ (23,3) [0-0-0-23] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 迈腾哪款最值得买, desc: 迈腾哪款最值得买
│ │ ├─ (24,3) [0-0-0-24] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 雷军:小米汽车12月将新增50家门店, desc: 雷军:小米汽车12月将新增50家门店
│ │ ├─ (25,3) [0-0-0-25] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 德总理:大众汽车关闭工厂是错误, desc: 德总理:大众汽车关闭工厂是错误
│ │ ├─ (26,3) [0-0-0-26] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 4, desc: 4
│ │ ├─ (27,3) [0-0-0-27] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 阿维塔E16正式定名阿维塔06, desc: 阿维塔E16正式定名阿维塔06
│ │ └─ (28,3) [0-0-0-28] Class: com.lynx.tasm.behavior.ui.text.FlattenUIText, ID: null, Text: 正在努力加载, desc: 正在努力加载
│ ├─ (1,2) [0-0-1] Class: androidx.recyclerview.widget.RecyclerView, ID: null, Text: null, desc: null
│ │ ├─ (0,3) [0-0-1-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ ├─ (0,4) [0-0-1-0-0] Class: android.view.View, ID: null, Text: null, desc: [尬笑][尬笑][尬笑] 突然有种想学习的冲动呢??? 毕业二十...
│ │ │ └─ (1,4) [0-0-1-0-1] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ │ ├─ (0,5) [0-0-1-0-1-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,认真的宇宙JackyQi,按钮
│ │ │ │ ├─ (0,6) [0-0-1-0-1-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ │ └─ (1,6) [0-0-1-0-1-0-1] Class: android.widget.TextView, ID: null, Text: 认真的宇宙JackyQi, desc: null
│ │ │ └─ (1,5) [0-0-1-0-1-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ └─ (0,6) [0-0-1-0-1-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞156
│ │ │ └─ (0,7) [0-0-1-0-1-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ │ ├─ (1,3) [0-0-1-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ ├─ (0,4) [0-0-1-1-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 视频
│ │ │ ├─ (1,4) [0-0-1-1-1] Class: android.view.View, ID: null, Text: null, desc: 你敢和车模对视吗?
│ │ │ └─ (2,4) [0-0-1-1-2] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ │ ├─ (0,5) [0-0-1-1-2-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,儿童对话加油,按钮
│ │ │ │ ├─ (0,6) [0-0-1-1-2-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ │ └─ (1,6) [0-0-1-1-2-0-1] Class: android.widget.TextView, ID: null, Text: 儿童对话加油, desc: null
│ │ │ └─ (1,5) [0-0-1-1-2-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ └─ (0,6) [0-0-1-1-2-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞7876
│ │ │ └─ (0,7) [0-0-1-1-2-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ │ ├─ (2,3) [0-0-1-2] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ ├─ (0,4) [0-0-1-2-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 视频
│ │ │ ├─ (1,4) [0-0-1-2-1] Class: android.view.View, ID: null, Text: null, desc: 这次不再帮你找借口了
│ │ │ └─ (2,4) [0-0-1-2-2] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ │ ├─ (0,5) [0-0-1-2-2-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,CyndiAhua,按钮
│ │ │ │ ├─ (0,6) [0-0-1-2-2-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ │ └─ (1,6) [0-0-1-2-2-0-1] Class: android.widget.TextView, ID: null, Text: CyndiAhua, desc: null
│ │ │ └─ (1,5) [0-0-1-2-2-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ └─ (0,6) [0-0-1-2-2-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞97
│ │ │ └─ (0,7) [0-0-1-2-2-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ │ ├─ (3,3) [0-0-1-3] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ ├─ (0,4) [0-0-1-3-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 视频
│ │ │ ├─ (1,4) [0-0-1-3-1] Class: android.view.View, ID: null, Text: null, desc: 小杨哥被冻结51万股权 近日,张庆杨(疯狂小杨哥)...
│ │ │ └─ (2,4) [0-0-1-3-2] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ │ ├─ (0,5) [0-0-1-3-2-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,北京商报,认证用户 按钮
│ │ │ │ ├─ (0,6) [0-0-1-3-2-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ │ └─ (1,6) [0-0-1-3-2-0-1] Class: android.widget.TextView, ID: null, Text: 北京商报, desc: null
│ │ │ └─ (1,5) [0-0-1-3-2-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ └─ (0,6) [0-0-1-3-2-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞29
│ │ │ └─ (0,7) [0-0-1-3-2-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ │ ├─ (4,3) [0-0-1-4] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ ├─ (0,4) [0-0-1-4-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 视频
│ │ │ ├─ (1,4) [0-0-1-4-1] Class: android.view.View, ID: null, Text: null, desc: 俄媒:阿萨德及其家人抵达莫斯科,俄罗斯已向其提供...
│ │ │ └─ (2,4) [0-0-1-4-2] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ │ ├─ (0,5) [0-0-1-4-2-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,中媒汇,认证用户 按钮
│ │ │ │ ├─ (0,6) [0-0-1-4-2-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ │ └─ (1,6) [0-0-1-4-2-0-1] Class: android.widget.TextView, ID: null, Text: 中媒汇, desc: null
│ │ │ └─ (1,5) [0-0-1-4-2-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ │ └─ (0,6) [0-0-1-4-2-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞2555
│ │ │ └─ (0,7) [0-0-1-4-2-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ │ └─ (5,3) [0-0-1-5] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ ├─ (0,4) [0-0-1-5-0] Class: android.view.View, ID: null, Text: null, desc: 大冰:从作家到直播红人,揭秘其爆火背后的原因
│ │ └─ (1,4) [0-0-1-5-1] Class: android.view.ViewGroup, ID: null, Text: null, desc: null
│ │ ├─ (0,5) [0-0-1-5-1-0] Class: android.view.ViewGroup, ID: null, Text: null, desc: 作者,好奇奇好,按钮
│ │ │ ├─ (0,6) [0-0-1-5-1-0-0] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 头像
│ │ │ └─ (1,6) [0-0-1-5-1-0-1] Class: android.widget.TextView, ID: null, Text: 好奇奇好, desc: null
│ │ └─ (1,5) [0-0-1-5-1-1] Class: android.widget.FrameLayout, ID: null, Text: null, desc: null
│ │ └─ (0,6) [0-0-1-5-1-1-0] Class: android.widget.Button, ID: null, Text: null, desc: 赞
│ │ └─ (0,7) [0-0-1-5-1-1-0-0] Class: android.widget.ImageView, ID: null, Text: null, desc: 赞
│ └─ (2,2) [0-0-2] Class: androidx.recyclerview.widget.RecyclerView, ID: null, Text: null, desc: null
├─ (1,1) [0-1] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: null
│ └─ (0,2) [0-1-0] Class: android.widget.TextView, ID: null, Text: 小米第三款车型曝光 售价预计15万 | 深圳福田连夜拆不锈钢盲道, desc: 搜索框,小米第三款车型曝光 售价预计15万 | 深圳福田连夜拆不锈钢盲道
├─ (2,1) [0-2] Class: android.widget.LinearLayout, ID: null, Text: null, desc: null
│ └─ (0,2) [0-2-0] Class: android.widget.TextView, ID: null, Text: 发布, desc: 发布,按钮
├─ (3,1) [0-3] Class: android.widget.LinearLayout, ID: null, Text: null, desc: null
│ └─ (0,2) [0-3-0] Class: android.widget.TextView, ID: null, Text: 头条AI, desc: AI回答,按钮
├─ (4,1) [0-4] Class: android.widget.HorizontalScrollView, ID: null, Text: null, desc: null
│ ├─ (0,2) [0-4-0] Class: android.widget.Button, ID: null, Text: null, desc: 关注
│ ├─ (1,2) [0-4-1] Class: android.widget.Button, ID: null, Text: null, desc: 推荐
│ ├─ (2,2) [0-4-2] Class: android.widget.Button, ID: null, Text: null, desc: 热榜
│ ├─ (3,2) [0-4-3] Class: android.widget.Button, ID: null, Text: null, desc: 发现
│ ├─ (4,2) [0-4-4] Class: android.widget.Button, ID: null, Text: null, desc: 深圳
│ ├─ (5,2) [0-4-5] Class: android.widget.Button, ID: null, Text: null, desc: 兴趣
│ ├─ (6,2) [0-4-6] Class: android.widget.Button, ID: null, Text: null, desc: 视频
│ ├─ (7,2) [0-4-7] Class: android.widget.Button, ID: null, Text: null, desc: 畅听
│ ├─ (8,2) [0-4-8] Class: android.widget.Button, ID: null, Text: null, desc: 图片
│ ├─ (9,2) [0-4-9] Class: android.widget.Button, ID: null, Text: null, desc: 短剧
│ ├─ (10,2) [0-4-10] Class: android.widget.Button, ID: null, Text: null, desc: 直播
│ ├─ (11,2) [0-4-11] Class: android.widget.Button, ID: null, Text: null, desc: 娱乐
│ ├─ (12,2) [0-4-12] Class: android.widget.Button, ID: null, Text: null, desc: 科技
│ ├─ (13,2) [0-4-13] Class: android.widget.Button, ID: null, Text: null, desc: 懂车帝
│ ├─ (14,2) [0-4-14] Class: android.widget.Button, ID: null, Text: null, desc: 财经
│ ├─ (15,2) [0-4-15] Class: android.widget.Button, ID: null, Text: null, desc: 军事
│ ├─ (16,2) [0-4-16] Class: android.widget.Button, ID: null, Text: null, desc: 体育
│ ├─ (17,2) [0-4-17] Class: android.widget.Button, ID: null, Text: null, desc: 国际
│ ├─ (18,2) [0-4-18] Class: android.widget.Button, ID: null, Text: null, desc: 健康
│ ├─ (19,2) [0-4-19] Class: android.widget.Button, ID: null, Text: null, desc: 幸福里
│ ├─ (20,2) [0-4-20] Class: android.widget.Button, ID: null, Text: null, desc: 国风
│ ├─ (21,2) [0-4-21] Class: android.widget.Button, ID: null, Text: null, desc: 法律
│ ├─ (22,2) [0-4-22] Class: android.widget.Button, ID: null, Text: null, desc: NBA
│ ├─ (23,2) [0-4-23] Class: android.widget.Button, ID: null, Text: null, desc: 漫画
│ ├─ (24,2) [0-4-24] Class: android.widget.Button, ID: null, Text: null, desc: 热点
│ ├─ (25,2) [0-4-25] Class: android.widget.Button, ID: null, Text: null, desc: 小说
│ ├─ (26,2) [0-4-26] Class: android.widget.Button, ID: null, Text: null, desc: 小视频
│ ├─ (27,2) [0-4-27] Class: android.widget.Button, ID: null, Text: null, desc: 政法
│ ├─ (28,2) [0-4-28] Class: android.widget.Button, ID: null, Text: null, desc: 眼界
│ ├─ (29,2) [0-4-29] Class: android.widget.Button, ID: null, Text: null, desc: 每日必看
│ ├─ (30,2) [0-4-30] Class: android.widget.Button, ID: null, Text: null, desc: 读书
│ ├─ (31,2) [0-4-31] Class: android.widget.Button, ID: null, Text: null, desc: 古籍
│ └─ (32,2) [0-4-32] Class: android.widget.FrameLayout, ID: null, Text: null, desc: 岛屿读书
│ └─ (0,3) [0-4-32-0] Class: android.widget.TextView, ID: null, Text: 岛屿读书, desc: null
├─ (5,1) [0-5] Class: android.widget.ImageView, ID: null, Text: null, desc: null
├─ (6,1) [0-6] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: 听一听开关
├─ (7,1) [0-7] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: null
│ └─ (0,2) [0-7-0] Class: android.widget.TextView, ID: null, Text: 头条, desc: null
├─ (8,1) [0-8] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: null
│ └─ (0,2) [0-8-0] Class: android.widget.TextView, ID: null, Text: 视频, desc: null
├─ (9,1) [0-9] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: null
│ └─ (0,2) [0-9-0] Class: android.widget.TextView, ID: null, Text: 商城, desc: null
└─ (10,1) [0-10] Class: com.bytedance.platform.raster.viewpool.cache.compat.MeasureOnceRelativeLayout2, ID: null, Text: null, desc: null
├─ (0,2) [0-10-0] Class: android.widget.TextView, ID: null, Text: 我的, desc: null
└─ (1,2) [0-10-1] Class: android.view.View, ID: null, Text: null, desc: 42条更新
按照我之前是通过可视化一个个节点展开寻找,或者通过附近有唯一的节点(ID和NAME)查找。
查找节点非常头痛的事件,我发现了一个比较好的方法,你可以看到我上面每行打印都有个()和[],()里面表示第几个和第几层辅助查看的。
重点是[]里面的,每个-表示每一层,多看几个就能理解了。通过上面的理解,我们可以编写这样的查找方法,通过入参根节点和0-0-1-3-2-0-1找到北京商报。下面查找代码:
fun findNodeByPath(rootNode: AccessibilityNodeInfo?, path: String): AccessibilityNodeInfo? {
// 检查根节点和路径是否有效
if (rootNode == null) return null
// 如果路径为空,可以返回根节点
if (path.isEmpty()) return rootNode
// 将路径字符串按 '-' 拆分为索引数字
val indices = path.split("-").mapNotNull { it.toIntOrNull() }
// 使用一个可变变量来遍历节点
var currentNode: AccessibilityNodeInfo? = rootNode
for ((i, index) in indices.withIndex()) {
if (i == 0) {
if (index != 0) {
currentNode = null
break
}
} else {
if (currentNode == null || index < 0 || index >= currentNode.childCount) {
return null // 如果索引越界或当前节点为空,返回 null
}
// 更新 currentNode 为当前层的子节点
currentNode = currentNode.getChild(index)
}
}
return currentNode // 返回找到的节点或 null
}
//使用
findNodeByPath(rootNode, "0-0-1-3-2-0-1")
想找那个节点,就复制节点前面[0-0-1-3-2-0-1]里面字符串,这可简单多了,而且效率也会比递归查找高。下面是递归打印所有节点信息代码:
fun printNodeTreeWithCoordinates(
node: AccessibilityNodeInfo?,
indent: String = "",
path: String = "",
x: Int = 0,
y: Int = 0,
isLast: Boolean = true
) {
if (node == null) return
// 获取当前节点的信息
val className = node.className?.toString() ?: "null"
val viewId = node.viewIdResourceName ?: "null"
val text = node.text?.toString() ?: "null"
val desc = node.contentDescription?.toString() ?: "null"
// 构建并打印当前节点的信息,包括坐标 (x, y) 和连接符
val coordinates = "($x,$y)" // 这里 y 可以根据层级结构需要进行调整
val connector = if (isLast) "└─" else "├─"
val currentPath = if (path.isEmpty()) "$x" else "$path-$x"
// 打印节点信息
Log.d(
TAG,
"$indent$connector $coordinates [${currentPath}] Class: $className, ID: $viewId, Text: $text, desc: $desc"
)
// 准备下一层级的前缀
val newIndent = if (isLast) "$indent " else "$indent│ "
// 递归遍历子节点
val childCount = node.childCount
for (i in 0 until childCount) {
val child = node.getChild(i)
// 更新 y 值,可以简单设置 y 为当前层级的索引
printNodeTreeWithCoordinates(
child,
newIndent,
currentPath,
i,
y + 1,
i == childCount - 1
)
// 不回收子节点,因为只是递归过程,且不影响 parent node
}
}
//使用
printNodeTreeWithCoordinates(rootNode)