AutoJs6 中文文档 下载 App

控件定位属性详解 — id、text、desc 与特殊技巧

Android 控件树基础

Android 界面本质是一棵 ViewGroup → View 的层级树。AutoJs6 的无障碍服务能读取整棵树,每个 View 节点称为一个 UiObject。

Activity
└── ConstraintLayout (根)
    ├── TextView "标题" id: tv_title
    ├── EditText id: et_username
    ├── EditText id: et_password
    └── LinearLayout (按钮组)
        ├── Button "登录" id: btn_login
        └── Button "注册"

每个 UiObject 包含以下常用属性:

属性类型说明
idstringandroid:id,开发者设置的唯一标识
textstring控件上显示的文字
descstringcontent-description,无障碍描述
classNamestring控件的类名(如 android.widget.Button)
packageNamestring所属应用包名
boundsRect屏幕坐标 {left, top, right, bottom}
childCountnumber子控件数量
clickableboolean是否可点击
enabledboolean是否启用
selectedboolean是否选中
checkedboolean是否勾选
focusableboolean是否可聚焦
visibleToUserboolean是否对用户可见

各属性对比

id — 最推荐

优点:稳定、精确、不受语言和主题影响缺点:部分应用不给控件设 id(尤其是第三方 App、游戏、WebView 内控件)

// ✅ 最佳场景:开发者设了 id 的标准应用
let loginBtn = id("btn_login").findOne();
let usernameInput = id("et_username").findOne();

text — 次推荐

优点:人类可读、直观缺点:受系统语言影响、可能变化(如 "登录" → "登陆")、可能有多个同名控件

// ⚠️ 需要小心使用
let confirmBtn = text("确定").findOne();
// 如果界面上有多个"确定"按钮,会返回第一个

desc — 被低估的选项

优点:独立于 text,部分应用只设 desc 缺点:不是所有应用都设 desc

// 图标按钮通常有 desc 但没 text
let backBtn = desc("返回").findOne();
let settingsIcon = descContains("设置").findOne();

className + 位置 — 兜底方案

优点:总在缺点:太笼统,需要结合 bounds 或层级过滤

// 屏幕底部中间的 Button
let bottomBtn = className("Button")
    .bounds(0, 1500, 1080, 1800) // 屏幕下方区域
    .clickable(true)
    .findOne();

WebView 内控件

部分应用使用 WebView 加载 H5 页面,此时无障碍服务看到的是一个整体的 WebView 控件,内部细节不可见。

处理方案:

  1. 用 className("android.webkit.WebView") 定位 WebView
  2. 用坐标点击代替控件选择器(click(x, y))
  3. 或用图像识别(images 模块的找色找图)
// WebView 里的按钮可能无法用选择器定位
// 退而求其次用坐标
click(540, 1200);

动态列表控件

列表(ListView、RecyclerView、GridView)中的子项会动态复用,每次滚动后控件属性可能变化。

// ⚠️ 错误:固定索引可能失效
let firstItem = id("list").findOne().childAt(0); // 滚动后第一个可能变了

// ✅ 正确:每次滚动后重新查找
function scrollAndFind(targetText) {
    for (let i = 0; i < 10; i++) {
        let target = textContains(targetText).findOne(1000);
        if (target) return target;
        scrollDown();
    }
    return null;
}

特殊控件类型

CheckBox / Switch / ToggleButton

// 检查当前状态
let wifiSwitch = id("wifi_switch").findOne();
if (wifiSwitch.checked()) {
    console.log("已开启");
} else {
    wifiSwitch.click(); // 点击切换
}

EditText(输入框)

// 清空并输入内容
let input = id("search_input").findOne();
input.clear();           // 清空
input.setText("搜索词"); // 直接设置文本

// 或模拟输入
input.click();           // 先聚焦
input.input("搜索词");   // 逐个字符输入(更像人工)

ImageView(图片控件)

ImageView 通常没有 text 和 desc,需要结合 bounds 或上下文定位:

// ImageView 通常通过父子关系定位
let avatar = className("ImageView")
    .bounds(0, 200, 300, 500) // 左侧头像区域
    .findOne();

动态查找技巧

遍历子控件

let parent = id("list_container").findOne();
let children = parent.children(); // 获取所有子控件

children.forEach((child, index) => {
    console.log(`${index}: text=${child.text()}, id=${child.id()}`);
});

向上找父控件

let item = text("某个文字").findOne();
let grandParent = item.parent().parent(); // 向上两级
let packageName = grandParent.packageName(); // 所属包名

找兄弟控件

let title = id("item_title").findOne();
let parent = title.parent();
let siblings = parent.children(); // 标题的所有兄弟

实战:多级筛选

// 场景:在列表中找到"价格低于 500 的商品项"
let listContainer = id("goods_list").findOne(5000);
if (listContainer) {
    let items = listContainer.children();
    items.forEach(item => {
        let priceText = item.findOne(textMatches(/¥\d+/));
        if (priceText) {
            let price = parseInt(priceText.text().replace(/¥/, ""));
            if (price < 500) {
                item.click(); // 点击这个商品
            }
        }
    });
}

避坑清单

坑原因解决
找控件找不到就崩溃findOne 超时返回 null先判空 if (btn)
同属性多个控件同名按钮 / 同类控件太多加更多条件 / 用索引
文本随语言变化多语言应用优先用 id 或 desc
列表滚动后控件失效RecyclerView 复用机制每次滚动后重新查找
WebView 内无法定位H5 渲染无原生控件用坐标点击或图像识别