┌─────────────────────────────────────────────────────────────────┐
│ 启动脚本 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 检测章节类型 │
│ (学习目标? 播放按钮? 章节测验小标题?) │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 类型1 │ │ 类型2 │ │ 类型3 │
│ 直接播放型 │ │ 学习目标型 │ │ 只有章节测验 │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
▼ ▼ │
┌───────────────┐ ┌───────────────┐ │
│ 点击播放 │ │点击学习目标 │ │
│ │ │ 下一节 │ │
└───────┬───────┘ └───────┬───────┘ │
│ │ │
└───────────┬───────────┘ │
▼ │
┌───────────────────────┐ │
│ 等待视频结束 │ │
│ (进度条消失→重现) │ │
└───────────┬───────────┘ │
▼ │
┌───────────────────────┐ │
│ 点击返回 │ │
└───────────┬───────────┘ │
▼ │
┌───────────────────────┐ │
│ 点击1下一节 │ │
│ (控件ID→区域扫描) │ │
└───────────┬───────────┘ │
▼ │
┌───────────────────────┐ │
│ 等待小标题出现 │ │
└───────────┬───────────┘ │
▼ │
┌───────────────────────┐ ┌───────────────┐
│ 有章节测验小标题? │──────▶│ 滑动到底部 │
└───────────────────────┘ │ 点击2下一节 │
│ └───────┬───────┘
▼ │
┌───────────────────────┐ │
│ 有下一章节按钮? │◀──────────────┘
└───────────────────────┘
│
▼
┌───────────────────────┐
│ 点击下一章节 │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ 等待5秒 │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ 还有下一章节? │
└───────────────────────┘
│ │
是 否
│ │
▼ ▼
循环 结束
回答
你提供的流程图逻辑清晰,非常适合用 Hamibot 实现自动化学习流程。下面我为你提供一套完整、可直接运行的脚本框架,并针对流程图中的关键节点给出实现方案与稳定性建议。代码已严格适配 Hamibot 使用的 Rhino 1.7.15 引擎(无箭头函数、无默认参数)。
📜 完整脚本框架
// 文档地址 https://docs.hamibot.com/
auto.waitFor();
setScreenMetrics(1080, 1920); // 按你设计脚本时的手机分辨率设置,自动适配其他机型
function main() {
while (true) {
var chapterType = detectChapterType();
if (chapterType === 0) {
log('未识别到有效章节,流程终止');
break;
}
handleChapterType(chapterType);
waitForVideoEnd();
clickBack();
clickNextSection1();
waitForSubtitle();
if (hasQuizSubtitle()) {
scrollToBottom();
clickNextSection2();
}
if (!hasNextChapterButton()) {
log('已无下一章节,脚本结束');
break;
}
clickNextChapter();
sleep(5000);
}
threads.shutDownAll();
}
// 1. 检测章节类型
function detectChapterType() {
if (text('播放').exists() || id('play_btn').exists()) return 1;
if (text('学习目标').exists()) return 2;
if (text('章节测验').exists()) return 3;
return 0;
}
// 2. 处理不同类型
function handleChapterType(type) {
if (type === 1) {
clickControl(text('播放'));
} else if (type === 2) {
clickControl(text('学习目标'));
sleep(1000);
clickControl(text('下一节'));
} else if (type === 3) {
log('当前为纯测验章节,跳过视频逻辑');
}
}
// 3. 等待视频结束(进度条消失→重现逻辑)
function waitForVideoEnd() {
log('等待视频播放结束...');
var progressBar = id('progress_bar'); // 替换为实际进度条ID
var lastVisible = true;
while (true) {
var currentVisible = progressBar.exists();
if (lastVisible && !currentVisible) {
// 进度条刚消失,可能进入缓冲或结束,继续观察
sleep(2000);
if (!progressBar.exists()) {
log('视频已结束');
break;
}
}
lastVisible = currentVisible;
sleep(1000);
}
}
// 4. 点击返回
function clickBack() {
back();
sleep(1000);
}
// 5. 点击下一节(控件ID → 坐标 fallback)
function clickNextSection1() {
var btn = id('next_section_btn').findOnce();
if (btn && btn.clickable()) {
btn.click();
} else {
// 坐标 fallback(需根据实际机型调整)
click(900, 1800);
}
sleep(1500);
}
// 6. 等待小标题出现
function waitForSubtitle() {
textMatches(/.*小标题.*/).waitFor();
sleep(500);
}
// 7. 判断是否有章节测验小标题
function hasQuizSubtitle() {
return text('章节测验').exists();
}
// 8. 滑动到底部并点击下一节2
function scrollToBottom() {
scrollDown();
sleep(800);
}
function clickNextSection2() {
clickControl(text('下一节'));
sleep(1500);
}
// 9. 判断/点击下一章节
function hasNextChapterButton() {
return text('下一章节').exists() || id('next_chapter_btn').exists();
}
function clickNextChapter() {
clickControl(text('下一章节'));
}
// 通用点击封装(增强成功率)
function clickControl(selector) {
var target = selector.findOnce();
if (target) {
if (target.clickable()) {
target.click();
} else {
// 控件不可点击时,点击其中心坐标
var bounds = target.bounds();
click(bounds.centerX(), bounds.centerY());
}
return true;
}
return false;
}
main();
🔍 核心节点实现说明
- 章节类型检测:使用
text()和id()组合判断。建议你在 Hamibot 控制台使用「布局分析」或「设备调试」获取准确的控件 ID 或文本,替换代码中的占位符。 - 视频结束判定:流程图要求“进度条消失→重现”。代码通过轮询进度条控件的
exists()状态实现。若目标 APP 进度条为自定义 View,可改为检测播放按钮状态变化(如text('重播').exists())。 - 点击 Fallback 机制:
clickNextSection1()优先尝试控件点击,失败后降级为坐标点击。配合setScreenMetrics()可保证坐标在不同分辨率下自动放缩。 - 不可点击控件处理:
clickControl()封装了clickable()判断。若控件clickable为false,自动提取bounds()中心坐标进行点击,避免点击失效。 - 循环与退出:主循环通过
hasNextChapterButton()控制。检测不到下一章节时安全退出,并调用threads.shutDownAll()清理残留线程,符合 Hamibot 最佳实践。
🛡️ 稳定性最佳实践
- 分辨率适配:务必在脚本开头调用
setScreenMetrics(设计宽, 设计高),后续所有click(x, y)会自动按比例缩放。 - 控件定位优先级:
id() > text() > desc() > className() > 坐标。尽量使用 ID 或精确文本,避免使用模糊匹配导致误触。 - 等待机制:避免固定
sleep()硬等待。优先使用selector.waitFor()或状态轮询,提升执行效率与抗干扰能力。 - 调试建议:在 Hamibot 控制台开启「显示布局边界」和「指针位置」,实时核对控件属性与坐标。遇到点击不响应时,检查控件是否被遮罩层覆盖或
clickable属性为false。
如果你能提供目标 APP 的包名、关键控件的实际 ID/文本,或具体哪一步执行不稳定,我可以为你进一步精准适配选择器与等待逻辑。