Skip to content

运行条 ​

运行条不是底座接口。它是用 悬浮窗 FloatPage 挂到系统悬浮层上的小胶囊:左边圆点淡入淡出,中间一行任务文案,右边停止图标。

任务开始时显示;任务自己结束或用户点停止后关闭,并提示「任务已关闭」。文案用 FloatPage.setData 写入悬浮页,不要用 Storage 再定时去读。

手机端 DeekeScript Pro 底栏「示例」→「能力 → 悬浮窗」里有「运行条示例」。源码在 deekeScriptV2Demo:

  • floats/runBar/:悬浮窗页面
  • tasks/runBar.js:显示、改文案、结束

先看效果 ​

贴在状态栏上的一条胶囊。圆点会轻轻变淡,文案过长时末尾省略。点右侧方块后运行条消失,并提示「任务已关闭」。

工程目录 ​

text
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:

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() 结束整项任务并关掉这条。

javascript
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。悬浮页还没建好时写入会丢掉。

javascript
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

注意 ​

  • 悬浮窗回调里的 Engines.closeAll() 无效。点停止用 FloatWindow.stopTask()。
  • inStatusBar: true 必须开启无障碍。没连上时会退回普通悬浮窗,条还能画在状态栏里,但点击会被系统吃掉,停止图标点不到。
  • 相关:悬浮窗、悬浮球。