运行条
运行条不是底座接口。它是用 悬浮窗 FloatPage 挂到系统悬浮层上的小胶囊:左边圆点淡入淡出,中间一行任务文案,右边停止图标。
任务开始时显示;任务自己结束或用户点停止后关闭,并提示「任务已关闭」。文案用 FloatPage.setData 写入悬浮页,不要用 Storage 再定时去读。
手机端 DeekeScript Pro 底栏「示例」→「能力 → 悬浮窗」里有「运行条示例」。源码在 deekeScriptV2Demo:
floats/runBar/:悬浮窗页面tasks/runBar.js:显示、改文案、结束
先看效果
贴在状态栏上的一条胶囊。圆点会轻轻变淡,文案过长时末尾省略。点右侧方块后运行条消失,并提示「任务已关闭」。
工程目录
floats/
runBar/
page.json
page.js
tasks/
runBar.js
img/
stop.svg不必注册成自定义组件,也不必放进 pages。
1. 悬浮窗页面
外壳设成全透明,只露出黑色胶囊。标题用 lines: 1 限制成一行,多出来的用省略号截掉,不要换行。 就是本页 data,任务 FloatPage.setData('runBar', { title }) 会直接改这行字。胶囊用黑色,避免和页面主题色混在一起。
floats/runBar/page.json:
{
"style": { "padding": 0, "background": "#00000000" },
"body": [
{
"type": "row",
"style": {
"gap": 6,
"background": "#000000",
"radius": 999,
"paddingLeft": 8,
"paddingRight": 6,
"paddingTop": 2,
"paddingBottom": 2
},
"children": [
{
"type": "text",
"text": "●",
"style": { "color": "#8ECCC4", "fontSize": 10, "opacity": "{{dotOpacity}}" }
},
{
"type": "text",
"text": "{{title}}",
"style": { "color": "#FFFFFF", "fontSize": 11, "weight": 1, "width": 0, "lines": 1 }
},
{
"type": "image",
"src": "img/stop.svg",
"onTap": "onStop",
"style": { "width": 16, "height": 16 }
}
]
}
]
}floats/runBar/page.js。圆点在不透明和半透明之间切换,颜色反差不要太大。
点停止不能在这里调用 Engines.closeAll():悬浮窗回调不在任务线程上,那样关不掉脚本。先发 run_stop 让任务知道是用户停的,再调用 FloatWindow.stopTask() 结束整项任务并关掉这条。
Page({
data: {
title: '任务正在执行',
dotOpacity: 1
},
onShow: function () {
this.blink();
},
onClose: function () {
this.stopBlink();
},
blink: function () {
if (this._dotTimer) {
return;
}
var that = this;
var bright = true;
this._dotTimer = setInterval(function () {
bright = !bright;
that.setData({ dotOpacity: bright ? 1 : 0.45 });
}, 700);
},
stopBlink: function () {
if (this._dotTimer) {
clearInterval(this._dotTimer);
this._dotTimer = null;
}
},
onStop: function () {
if (this._stopped) {
return;
}
this._stopped = true;
this.stopBlink();
Events.emit('run_stop');
FloatWindow.stopTask();
}
});停止图标 img/stop.svg 是一枚小方块,不要用关闭叉号。
2. 任务脚本
inStatusBar: true 把窗口盖到状态栏上,坐标从屏幕物理顶部算。无障碍已开启时,手指才能点到停止图标;没开时条仍能画出来,但状态栏会吃掉点击。启动前要检查 Access.isAccessibilityServiceEnabled(),未开启则引导去打开。gestureTouchable: false 让脚本自己的坐标点击穿过这条,不会点到停止图标。opacity: 0 只去掉黑色外壳,不淡化文字。
FloatPage.show 返回后再 setData。悬浮页还没建好时写入会丢掉。
var stopped = false;
function waitOrStop(ms) {
var left = ms;
while (left > 0 && !stopped) {
var step = left > 200 ? 200 : left;
System.sleep(step);
left = left - step;
}
return stopped;
}
if (!Access.isFloatWindowsEnabled()) {
Dialogs.confirm('提示', '运行条需要系统悬浮窗权限', function (ok) {
if (ok) {
Access.openFloatWindowsSetting();
}
});
Engines.closeAll();
} else if (!Access.isAccessibilityServiceEnabled()) {
Dialogs.confirm('提示', '运行条贴在状态栏上,需要开启无障碍才能点击', function (ok) {
if (ok) {
Access.openAccessibilityServiceSetting();
}
});
Engines.closeAll();
} else {
var density = Device.width() / 360;
if (density < 1) {
density = 1;
}
var w = Math.floor(168 * density);
var shown = FloatPage.show('runBar', {
x: Math.floor((Device.width() - w) / 2),
y: Math.floor(6 * density),
width: w,
height: Math.floor(28 * density),
opacity: 0,
background: '#00000000',
draggable: false,
focusable: false,
gestureTouchable: false,
inStatusBar: true
});
if (!shown) {
System.toast('打开运行条失败');
Engines.closeAll();
} else {
Events.on('run_stop', function () {
stopped = true;
});
Events.on('script_stop', function () {
FloatPage.close('runBar');
System.toast('任务已关闭');
});
var steps = [
{ title: '任务正在执行', ms: 1500 },
{ title: '休眠3秒', ms: 3000 },
{ title: '准备点赞', ms: 1500 }
];
var i = 0;
while (i < steps.length && !stopped) {
FloatPage.setData('runBar', { title: steps[i].title });
if (waitOrStop(steps[i].ms)) {
break;
}
i++;
}
Engines.closeAll();
}
}怎么串起来
| 步骤 | 谁来做 |
|---|---|
| 任务开始 | FloatPage.show('runBar', { inStatusBar: true, ... }) |
| 改中间那行字 | 任务 FloatPage.setData('runBar', { title }) |
| 用户点停止 | 悬浮页 Events.emit('run_stop'),再 FloatWindow.stopTask() |
| 任务发现停止 | run_stop 把循环停掉;整项结束靠 FloatWindow.stopTask() |
| 任务自然结束 | 同样 Engines.closeAll() |
| 关掉运行条并提示 | Events.on('script_stop') 里 FloatPage.close + System.toast |
