脚本UI界面

静态UI

  用户可以通过在脚本中定义一个全局变量UI来实现定义脚本的配置界面, 该界面会在脚本选定时显示给用户, 用于让用户设置一些脚本的参数, 这些参数的值会赋值给指定的变量, 脚本运行时可以通过访问这些变量来获取用户的配置。下面是一个完整的例子:

UI = {
        { 'TextView{-请如实填写哦-}'                   },
        { 'InputBox{}',             'ui_user', '用户:'},
        { 'InputBox{}',             'ui_pass', '密码:', {type = "password"}},
        { 'InputBox{18}',           'age',     '年龄:' },
        { 'DropList{是|否}',         'married', '婚否:' },
};
function main()
  if not ui_user or not ui_pass or not age or not married then 
    sys.dialog("请先选定脚本,配置UI");
    return false
  end
  sys.dialog(string.format("用户:%s\n密码:%s\n年龄:%s\n婚否:%s", ui_user, ui_pass, age, married));
end

上述脚本点击选定会显示一个如下界面:

image.png

用户设定完保存配置并选定脚本后,脚本运行时可以通过访问这些变量来获取用户的配置

  全局变量UI是一个Table类型的变量, 其中包含若干个子Table, 每一个子Table都是一个界面上的控件, 控件按照顺序自上而下排列, 目前支持3中类型的控件:

1. TextView (静态文本)

1.1 控件说明
该类型的控件只用来显示一行文字, 不需要用户操作
1.2 控件定义

{ 'TextView{显示的内容}' }

这个Table只有一个字符串成员, 即'TextView{显示的内容}'

其中的TextView是指定此控件的类型为静态文本, {}中的内容即为该静态文本显示的内容

2. InputBox (输入框)

2.1 控件说明
  该类型的控件可以用于让用户输入一些内容, 并可以指定一个变量名, 脚本开始后通过该变量就可以访问到用户输入的内容
2.2 控件定义

{ 'InputBox{默认值}', 'var', '注释', '设置' }

这个Table中有4个字符串成员:

'InputBox{默认值}', 其中的InputBox是指定此控件的类型为输入框, {}中的内容是该输入框中的默认值
'var', 定义一个变量的名字, 脚本开始后可以通过访问这个变量来获取用户输入的内容
'注释', 显示在输入框上方, 用于说明该输入框的用途
'设置', 非必填选项,填入{type = "password"}可使输入内容以*显示

2.3 使用提示

需要注意的是获取到的变量的值是默认是字符串类型, 你可以使用tonumber()函数来转换成数字类型来使用。例如
var = tonumber(var);

3. DropList (下拉列表)

3.1 控件说明
  该类型的控件可以用于让用户在指定的若干个值中选择其中一个, 并可以指定一个变量名, 脚本开始后通过该变量就可以访问到用户选择的内容
  
3.2 控件定义

{ 'DropList{选项1|选项2|选项3|...}', 'var', '注释' }

这个Table中有3个字符串成员:

'DropList{选项1|选项2|选项3|...}', 其中的DropList是指定此控件的类型为下拉列表, {}中的内容是指定的若干个选项, 每个选项间用|分隔
'var', 定义一个变量的名字, 脚本开始后可以通过访问这个变量来获取用户选择的内容
'注释', 显示在下拉列表上方, 用于说明该下拉列表的用途

动态UI

动态UI允许用户在脚本运行当中弹出一个UI框做交互.
动态UI使用的是HTML文件,需要有HTML JS CSS语言基础.
动态UI可使用的任何CSS框架,在HTML文件中引入框架文件即可.
触摸自带了Bootstrap5框架,Bootstrap文档请看:https://v5.bootcss.com
由于使用了HTML文件,脚本需使用tep或者tepe的项目格式.

函数:sys.ui.show() 弹出UI

函数说明 : 弹出一个动态UI框
函数方法 : sys.ui.show(路径 path, 左上角坐标x?, 左上角坐标y?, 右下角坐标x?, 右下角坐标y?);
返回值 : json
适用版本 : 6.2.0以上

参数类型说明必填
pathstring弹出的动态UI路径必填
ltxnumber动态UI左上角的X坐标非必填,默认全屏
ltynumber动态UI左上角的Y坐标非必填,默认全屏
rbxnumber动态UI右下角的X坐标非必填,默认全屏
rbynumber动态UI右下角的Y坐标非必填,默认全屏
返回值类型说明
resjson返回的UI数据

示例:

function main()
    --脚本未项目格式首先要获取项目路径
    wd = script.workingDir()
    --弹出项目脚本res文件夹内的index.html UI
    res = sys.ui.show(wd .. "/res/index.html",100,100,500,300)
    --res为UI的返回值,例如 {"type":"ok","data":{"input":"touchelf"}}
    sys.log(res)
end

教程示例下载地址:https://share.weiyun.com/4hvVPhBm

教程:弹出一个确认框

本教程演示功能:

弹出一个确认框
通过返回值判断脚本是否继续

main.lua :

function main()
  wd = script.workingDir()
  res = sys.ui.show(wd .. "/res/alert.html",100,100,500,300)
  --返回数据{"type":"ok"}
  res = codec.json.decode(res)
  if res.type == "ok" then 
    --脚本继续
  elseif res.type == "cancel" then
    --脚本停止
    script.stop();
  end
end

alert.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
  </head>

  <body>
    <!-- 提示语句 -->
    <h3>是否继续</h3>
    <!-- 按钮区域,此处必须写,否则无法关闭动态UI-->
    <div style="padding-top: 10px">
        <!-- onOk()和onCancel()需要和JS内的function onOk() function onCancel()对应 -->
      <button onclick="onOk()">确定</button>
      <button onclick="onCancel()">取消</button>
    </div>

    <script>
      function onOk() {
        // touchelf.return()为触摸内置函数, 此函数将关闭UI, 并将数据发送给sys.ui.show(), 作为其返回值
        touchelf.return(JSON.stringify({ type: "ok" }));
      }
      function onCancel() {
        touchelf.return(JSON.stringify({ type: "cancel" }));
      }
    </script>
  </body>
</html>

教程:弹出一个输入框并保存配置

本教程演示功能:

UI中加载本地图片
UI中增加输入框
保存,读取UI中的设置
脚本输出动态UI中输入框内容

main.lua :

function main()
  wd = script.workingDir()
  --截图用作动态UI里面显示
  screen.snapshot("/var/touchelf/a.png", 100, 100, 200, 200)
  res = sys.ui.show(wd .. "/res/input.html",100,100,500,500)
  --返回数据{"type":"ok","data":{"input":"touchelf"}}
  res = codec.json.decode(res)
  if res.type == "ok" then 
    --脚本继续
    sys.log(res.data.input)
  elseif res.type == "cancel" then
    --脚本停止
    script.stop();
  end
end

input.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
  </head>

  <body>
    <!-- 加载本地图片 -->
    <img src="file:///var/touchelf/a.png"/>
    <!-- 输入框 id内的input名字随意修改-->
    <input id="input" type="text" />

    <!-- 按钮区域,此处必须写,否则无法关闭动态UI-->
    <div style="padding-top: 10px">
        <!-- onOk()和onCancel()需要和JS内的function onOk() function onCancel()对应 -->
      <button onclick="onOk()">确定</button>
      <button onclick="onCancel()">取消</button>
    </div>

    <script>
      // 定义一个变量,名字随意,请勿重复
      var SAVE_KEY = "uitest";

      // 加载上次保存的设置到界面
      window.onload = function() {
        var data = localStorage.getItem(SAVE_KEY);
        if (data) {
          var obj = JSON.parse(data);
          // input跟输入框内的id相对应,多个配置在下面添加
          // 例如:
          // document.getElementById("input2").value = obj.input2;
          //document.getElementById("select").value = obj.select;
          document.getElementById("input").value = obj.input;
        }
      };

      function onOk() {
        // 获取输入框内的值
        var data = {
          input: document.getElementById("input").value,
        };

        // 保存设置以供下次加载
        localStorage.setItem(SAVE_KEY, JSON.stringify(data));

        // touchelf.return()为触摸内置函数, 此函数将关闭UI, 并将数据发送给sys.ui.show(), 作为其返回值
        touchelf.return(
          JSON.stringify({
            type: "ok",
            data: data,
          })
        );
      }

      function onCancel() {
        touchelf.return(JSON.stringify({ type: "cancel" }));
      }
    </script>
  </body>
</html>

教程:加载UI框架美化动态UI

本教程演示功能:

加载UI框架
UI中增加各种常用组件
脚本输出动态UI中各种组件内容

效果展示:
image.png

main.lua :

function main()
  wd = script.workingDir()
  res = sys.ui.show(wd .. "/res/ui.html")
  sys.log(res)
end

ui.html :

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
    <!-- 加载UI框架 (也可以从网络加载你熟悉的任何框架)-->
    <!-- IOS路径 -->
    <link
      href="file:///var/touchelf/var/res/www/bootstrap/bootstrap.min.css"
      rel="stylesheet"
    />
    <!-- 安卓路径
     <link
      href="file:///mnt/sdcard/touchelf/var/res/www/bootstrap/bootstrap.min.css"
      rel="stylesheet"
    />
    -->
  </head>

  <body>
    <div class="shadow-sm p-3 mb-5 bg-white rounded">
      <h4>脚本配置</h4>

      <!-- 输入框 -->
      <div class="input-group input-group-sm mb-3">
        <span class="input-group-text" id="inputGroup-sizing-sm">输入</span>
        <input id="input" type="text" class="form-control" />
      </div>

      <!-- 下拉框 -->
      <select id="select" class="form-select">
        <option selected>下拉菜单</option>
        <option value="1">One</option>
        <option value="2">Two</option>
        <option value="3">Three</option>
      </select>

      <!-- 复选框 -->
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="Checkbox1" value="option1">
        <label class="form-check-label" for="inlineCheckbox1">复选1</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="Checkbox2" value="option2">
        <label class="form-check-label" for="inlineCheckbox2">复选2</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="Checkbox3" value="option3" disabled>
        <label class="form-check-label" for="inlineCheckbox3">复选3 (禁止选择)</label>
      </div>

      <!-- 范围条 -->
      <input id="range" type="range" class="form-range" />

      <!-- 单选框 -->
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="inlineRadioOptions" id="Radio1" value="option1" checked>
        <label class="form-check-label" for="inlineRadio1">单选1 (默认选择)</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="inlineRadioOptions" id="Radio2" value="option2">
        <label class="form-check-label" for="inlineRadio2">单选2</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="inlineRadioOptions" id="Radio3" value="option3" disabled>
        <label class="form-check-label" for="inlineRadio3">单选3 (禁止选择)</label>
      </div>

      <!-- 开关 -->
      <div>
        <div class="form-check form-switch">
          <input
            class="form-check-input"
            type="checkbox"
            id="Switch1"
          />
          <label class="form-check-label" for="flexSwitchCheckDefault"
            >开关</label
          >
        </div>
        <div class="form-check form-switch">
          <input
            class="form-check-input"
            type="checkbox"
            id="Switch2"
            checked
          />
          <label class="form-check-label" for="flexSwitchCheckChecked"
            >开关默认开启</label
          >
        </div>
      </div>


      <!-- 按钮 -->
      <div style="padding-top: 10px">
        <button type="button" class="btn btn-primary" onclick="onOk()">
          确定
        </button>
        <button type="button" class="btn btn-danger" onclick="onCancel()">
          取消
        </button>
      </div>
    </div>

    <script>
      // touchelf.return()为触摸内置函数, 此函数将关闭UI, 并将数据发送给sys.ui.show(), 作为其返回值
      function onOk() {
        touchelf.return(
          JSON.stringify({
            type: "ok",
            // 输入框值
            input: document.getElementById("input").value,
            // 选择框值
            select: document.getElementById("select").value,
            // 复选框值
            Checkbox1: document.getElementById("Checkbox1").checked,
            Checkbox2: document.getElementById("Checkbox2").checked,
            Checkbox3: document.getElementById("Checkbox3").checked,
            // 滑块值
            range: document.getElementById("range").value,
            // 单选框值
            Radio1: document.getElementById("Radio1").checked,
            Radio2: document.getElementById("Radio2").checked,
            Radio3: document.getElementById("Radio3").checked,
            // 开关值 
            Switch1: document.getElementById("Switch1").checked,
            Switch2: document.getElementById("Switch2").checked,
          })
        );
      }
      function onCancel() {
        touchelf.return(JSON.stringify({ type: "cancel" }));
      }
    </script>
    <!-- 加载框架JS -->
    <!-- IOS路径 -->
    <script
      src="file:///var/touchelf/var/res/www/bootstrap/bootstrap.bundle.min.js">
    </script>
    <!-- 安卓路径 
    <script
      src="file:///mnt/sdcard/touchelf/var/res/www/bootstrap/bootstrap.bundle.min.js">
    </script>
    -->
  </body>
</html>

教程:调试动态UI中JS错误

本教程演示功能:

由于JS错误会导致点击确认或取消按钮无反应,可使用下面代码调试动态UI中的JS错误,该代码遇到错误会将错误返回到触摸脚本

main.lua:

function main()
  wd = script.workingDir()
  res = sys.ui.show(wd .. "/res/error.html")
  res = codec.json.decode(res)
  if res.type == "ok" then 
    --脚本继续
  elseif res.type == "cancel" then
    --脚本停止
    script.stop();
  elseif res.type == "error" then
    --遇到错误,返回错误内容
    sys.log(res)
    --[[返回数据示例
    {
        data = "TypeError: null is not an object (evaluating 'document.getElementById('input').value')",
        type = "error"
    }]]
    --脚本停止
    script.stop();
  end
end

error.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
  </head>

  <body>
    <!-- 提示语句 -->
    <h3>返回错误</h3>
    <!-- 按钮区域,此处必须写,否则无法关闭动态UI-->
    <div style="padding-top: 10px">
        <!-- onOk()和onCancel()需要和JS内的function onOk() function onCancel()对应 -->
      <button onclick="onOk()">确定</button>
      <button onclick="onCancel()">取消</button>
    </div>

    <script>
      function onOk() {
        // touchelf.return()为触摸内置函数, 此函数将关闭UI, 并将数据发送给sys.ui.show(), 作为其返回值
        try{
          
          touchelf.return(
            JSON.stringify({
              type: 'ok',
              data: {
                //这里获取一个不存在的值,会导致错误
                input: document.getElementById('input').value,
              },
            })
          )
        } catch (e) {
          //返回错误内容
          touchelf.return(
            JSON.stringify({
              type: 'error',
              data: e.toString(),
            })
          )
        }
      }
      function onCancel() {
        touchelf.return(JSON.stringify({ type: "cancel" }));
      }
    </script>
  </body>
</html>

常见问题

确认取消按钮点击无反应:

按钮点击无反应可能存在以下问题
1.HTML页面内的JS代码错误.
2.低版本系统(ios10 以下)不支持es6语法,改用es5语法编写JS


select选择框问题:

由于select选择框调用的是系统的原生选择框,各别系统下无法正常弹出,可尝试先点击下输入框弹出输入法后在点击选择框.
如以上方案不能解决则可以使用下面代码替换原生select选择框
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- 使用select2库解决原生select不能弹出选择框的问题 -->
    <link
      href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css"
      rel="stylesheet"
    />
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
    <!-- 以上文件可以自行下载后打包在脚本内,然后使用本地文件的加载方式,避免每次从网络加载耗费流量-->
  </head>

  <body>
    <select id="select" style="width: 100%">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
    </select>
    <div style="padding-top: 10px">
      <button onclick="onOk()">确定</button>
      <button onclick="onCancel()">取消</button>
    </div>

    <script>
      $(document).ready(function () {
        // 转换所有select标签
        $("select").select2();
      });
      function onOk() {
        touchelf.return(
          JSON.stringify({
            type: "ok",
            data: {
              select: document.getElementById("select").value,
            },
          })
        );
      }
      function onCancel() {
        touchelf.return(JSON.stringify({ type: "cancel" }));
      }
    </script>
  </body>
</html>