Autojs基础-用户界面(ui)

1.前言

脚本开发需要有界面与用户进行交互,许多脚本开发语言已经有自己的ui控件的使用方式,比如按键精灵的ui界面设置非常便捷。按键精灵的ui控件的取值和赋值方式都已经封装好了,但是autojs很多时候只提供控件,取值和赋值需要自己通过安卓进行封装,甚至需要自己手搓控件功能。以游戏为例,会有各种任务,我们可以根据选择的任务运行脚本,最好的就是多选框单选的方式,这样比较好看。但是,autojs没有让多选框单选的方式,我们又无法保证用户每次只选择一个任务,我们就需要手搓这个功能。当然,下拉框可以实现这个功能,但是这种方式不美观,我们不能为了功能而放弃美观。

由于autojs很多东西要自己封装,这个过程需要花大量时间,有些autojs开发者甚至找人设计页面,自己只专注于逻辑代码。这部分内容非常多,需要通过不断使用而增加经验。我会考虑分两块介绍这部分内容,包括基础篇和进阶篇。现在要介绍的是基础篇,其实基础篇就会把那些封装的东西都会介绍。而进阶篇更多的是对整个脚本的把控,进阶篇更多专注于经验方面的,按照我给出的架构设计界面,架构会自动匹配页面,我们只需要设置页面文件就好了。进阶篇会封装保存配置、加载配置、切换配置页面和关联悬浮窗等内容,会非常多。

2.架构

官方文档中习惯将ui页面和ui操作放在一个文件中,脚本开发过程中这种方式是非常不灵活的,也可能是学习阶段采用了这种方式方便。为了大家能够快速适应架构,我会采用ui操作(js文件)+ui页面(xml文件)架构介绍基础内容。我会将两个文件代码给出,具体文件自己创建就行了。最终的脚本架构应该是ui操作(js文件)+ui页面(xml文件)+逻辑操作(js)方式,这种架构我觉得是最灵活的方式,也易于维护。当然逻辑操作和ui操作可以考虑由多个模块组成,这个需要看个人需求了。

将ui页面放在xml文件中,会涉及文件加载问题。再次提醒下,涉及文件加载需要用到前面介绍的相对路径兼容方式,同时先将项目保存到设备才能正常运行。我们可以将相对路径设置为开发阶段,同时在项目配置文件(json文件)中随便指定个主程序。我们都是通过编辑器启动脚本,不会用到程序启动,这个指定,主要是用于打包后的程序入口,一般指定ui操作文件就好。我每次涉及到文件加载,都要啰嗦一下,请大家见谅。我怕小伙伴们跳着看或者前面的内容已经忘了,然后由于各种原因,运行不了脚本。

新建个"project.json"文件,用下面代码替换。我就指定主程序为"架构.js"了,可以随便指定个js文件,是为了能够将项目保存到设备(没有project.json文件的项目无法推送到设备)。

设置ui页面时,要保证父视图与子视图在宽度、高度、布局等方面符合基本常识,否则容易出现空白页。因为ui页面设计不会报错,出现空白页时,可以去考虑下布局等方面的合理性。

json 复制代码
{
  "name": "CSDN辅助",
  "main": "架构.js",
  "launchConfig": {
    "hideLogs": false, 
    "displaySplash": true
  },
  "ignore": [
    "build"
  ],
  "packageName": "com.py.csdn",
  "versionName": "1.0.0",
  "versionCode": 1
}

新建个ui文件夹,以后用于存放xml文件,新建个"架构.xml"文件,用下面代码替换。

xml 复制代码
<vertical>
    <text id="test" text="测试内容" />
</vertical>

新建个"架构.js"文件,用下面代码替换。由于加入了相对路径兼容的代码,会发现整个代码比较多,这也是方便编辑器启动和提前熟悉架构必不可少的。

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";
 
// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "架构.xml");

后面每个控件的介绍,都要按照这个架构创建"js"和对应的"xml"文件。我后面会给出相应的代码,然后自己按照架构创建文件。对于project.js文件就不用修改了,随便指定个就好了。

由于采用ui模式运行,程序会一直运行中,运行结束后,请在设备悬浮窗上手动关闭下程序。程序运行时间完全由关闭程序时间控制,如果只运行不关闭,最终会导致内存溢出。

3.视图

1.概况

视图View也是属于控件的一种,为什么要单独拿出来?这是因为视图View的属性类似于基础属性,这些属性也可以用到布局和控件中,作用也完全相同。到了布局和控件中,会讲解各自的特殊属性。我们可以理解为View视图的作用类似于js中的Object,就如js数据类型都是基于Object创建一样,布局和控件是基于View视图创建。我以最简单的控件text为例,介绍视图中的属性。

2.id与attr

id属性作为视图的唯一标识,可以通过这个属性来获取视图对象。

attr函数传递一个参数时,用于获取视图的属性值,需要传递属性名一个参数,参数类型为字符串,返回属性值,返回类型为字符串。这个函数取某些属性值时会报错,有时候会出现空值,取值最好使用布局或控件的专有函数。

attr函数传递两个参数时,用于设置视图的属性值,需要传递两个参数,分别为属性名和属性值,参数类型均为字符串。需要注意,并不是视图所有的属性都可以通过attr函数方式进行设置,不能设置会抛出异常。我们开发脚本时候,属性值会提前设置好,attr函数一般用于获取属性值。

attr.xml文件代码如下:

bash 复制代码
<vertical>
  <text id="test1" text="test1内容" bg="#ff00ff" />
</vertical>

attr.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "attr.xml");

// 延迟两秒,等待ui页面加载完成获取数据
ui.post(() => {
    // 获取ui中id为test1的视图
    // ui可以理解为一个对象,获取属性值方式可以为ui.id或者ui["id"]的方式
    // ui.id常用于固定id获取视图
    // ui["id"]常用于动态id获取视图
    let test1View = ui.test1;
    console.log(test1View);
    // 相当于
    // let test1View = ui["test1"];

    // 获取当前bg值
    let currentBgValue = test1View.attr("bg");
    console.log("当前bg值:" + currentBgValue);

    // 修改bg值
    test1View.attr("bg", "#ffffff");

    // 获取修改后bg值
    currentBgValue = test1View.attr("bg");
    console.log("修改后bg值:" + currentBgValue);
}, 2000);

xml配置的bg值无法取到,但是修改后bg值就可以取到了。说实话,attr函数比较不稳定,使用之前先判断下是否报错。不过,我后面都封装好各个控件的赋值和取值函数,不用考虑这些。

3.w与h

w属性和h属性分别是width和height的缩写,分别用于设置视图的宽度和高度,不同视图的默认值不同,大多数默认值均为auto。width和height可以接受的单位有dp(设备独立像素)、px(像素)、mm(毫米)和in(英寸),默认是单位是dp。dp是所有单位中兼容性最好的,我们设置大小时一般不用传递单位。除了具体数值外,我们还可以传递"auto"和"*",这两个值分别代表根据视图内容自动调整(自适应宽度或高度)和尽量占满父视图的宽度或高度。正常情况下,视图宽度和高度从来不需要手动设置,我们会通过边距属性来视图之间的距离,这些内容后面会介绍。下面介绍的边距等与距离有关的数据,允许单位都是类似的,我们一般都不需要传递单位,后面不会重复介绍。

w.xml文件代码如下:

xml 复制代码
<scroll>
    <vertical>
        <!-- 需要先指定父视图宽度和高度才能使用*,否则会报错 -->
        <vertical w="100" h="100">
            <text id="test1" w="*" h="*" text="test1内容" />
        </vertical>
        <!--一般不需要设置宽度和高度-->
        <text id="test2" w="auto" h="auto" text="test2内容" />
        <!-- 故意设置高度偏低,导致内容部分隐藏 -->
        <text id="test3" w="100" h="10" text="test3内容" />
    </vertical>
</scroll>

w.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "w.xml");

4.gravity

视图重力方向,实际上是视图中内容显示位置,允许传入left(靠左)、right(靠右)、top(靠顶部)、bottom(靠底部)、center(居中)、center_vertical(垂直居中)和center_horizontal(水平居中),默认值为top或left(上下显示默认值为top,左右显示默认值为left)。一般情况下使用默认值即可,不会设置这个属性,学习阶段才会介绍。需要注意的是,方向可以传递多个,中间用"|"分隔,比如靠右上就可以通过传值"right|top"来指定。会在后面介绍的layout_gravity属性中进行测试,两者使用方式完成相同,只是位置的相对对象不同罢了。

gravity.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 不设置 -->
    <text id="test1" gravity="left" text="test1内容" />

    <!-- 靠右 -->
    <text id="test2" gravity="right" text="test2内容" />

    <!-- 靠左 -->
    <text id="test3" gravity="left" text="test3内容" />

    <!-- 靠顶部,设置高度和分割线体现作用 -->
    <text id="test4" gravity="top" text="test4内容" h="100" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 靠底部,设置高度和分割线体现作用 -->
    <text id="test5" gravity="bottom" text="test5内容" h="100" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 居中显示,设置高度和分割线体现作用 -->
    <text id="test6" gravity="center" text="test6内容" h="100" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 垂直居中,设置高度和分割线体现作用 -->
    <text id="test7" gravity="center_vertical" text="test7内容" h="100" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 水平居中,设置高度和分割线体现作用 -->
    <text id="test8" gravity="center_horizontal" text="test8内容" h="100" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>
</vertical>

gravity.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "gravity.xml");

5.layout_gravity

父视图重力方向,实际上是子视图在父视图显示位置。这个属性的传值方式、作用等与上面介绍的gravity属性完全一致,只是gravity属性相对的是视图作用于视图中的内容,而layout_gravity属性相对的是父视图作用于子视图。已经说明了两个属性除相对对象外完全一致,layout_gravity属性也是允许传递多个方向。gravity属性以单方向举例,layout_gravity属性就以多方向举例。其实,所谓多方向也就是一次指定两个方向,一共就四个方向,不可能出现三方向或者四方向指定的情况,那样脚本都无法判断往哪个方向。

layoutGravity.xml文件代码如下:

xml 复制代码
<frame w="*" h="*">
    <!-- 指定宽度和高度占满屏幕 -->
    <!-- 需要指定为帧布局的方式,能保证对应的宽度或高度占满屏幕-->

    <!-- 靠左上 -->
    <text id="test1" gravity="left|top" text="test1内容" />

    <!-- 靠右上 -->
    <text id="test2" gravity="right|top" text="test2内容" />

    <!-- 靠左下,故意将方向先后顺序颠倒,判断需要按一定顺序传递方向 -->
    <text id="test3" gravity="bottom|left" text="test3内容" />

    <!-- 靠右下 -->
    <text id="test4" gravity="right|bottom" text="test4内容" />
</frame>

有些细心的小伙伴会问,为什么不将注释放在开头,因为注释放在开头会报错。这种问题有时候会出现,有时候不会出现。autojs加载包含注释的ui页面配置很容出现问题,我这种加载xml文件的方式还算好的,只要在文件开头不使用注释是没有问题的。通过ui.layout函数直接加载ui配置的方式,配置中是不能包含一点注释的,这也是我不喜欢用这种方式的主要原因。代码不加注释,时间久了都不知道是代码是干什么的了,大家一定要养成加注释的习惯。说这么多,就是告诉大家,为了避免出现报错,xml文件开头不要加注释。

注释加载xml文件开头,可能会出现类似于下面的这种报错:

layoutGravity.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "layoutGravity.xml");

根据下面显示情况可以判断,需要指定多方向时,方向设置的先后顺序不需要固定。

6.margin、marginLeft、marginRight、marginTop与marginBottom

这五个属性都是用于指定外边距,其中margin属性可以指定多个方向,剩余四个属性只指定一个方向。前端css样式设置,这五个属性用的非常多,autojs中五个属性作用与css样式中完全一致。但是,margin传递不同参数代表方向和css样式中不同,为了避免干扰大家,我就不提css样式中的顺序了,这个顺序应该深刻记在我们熟悉前端的程序员脑中。说实话,我也不习惯autojs中margin顺序,因此我一般会使用另外四个单方向指定的属性,这样总不会出错的。

margin属性可以传递一个值、两个值或者四个值,值中间用空格隔开,具体传递不同参数代表的意义,可以查看代码中的注释,已经介绍非常清楚了。

marginLeft、marginRight、marginTop和marginBottom属性分别代表左、右、上和下方向的外边距,具体可以看代码中的介绍。

margin.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- margin传递一个参数,表示指定四个方向的外边距 -->
    <!-- 设置水平布局,方便看右边距 -->
    <horizontal>
        <text id="test1" margin="10" text="test1内容" />
        <!-- 用于对照 -->
        <text id="test2" text="test2内容" />
    </horizontal>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- margin传递两个参数,分别表示左右和上下外边距 -->
    <!-- 为了测试先后顺序,将两个值设置差距大一点 -->
    <horizontal>
        <text id="test3" margin="50 10" text="test3内容" />
        <!-- 用于对照 -->
        <text id="test4" text="test4内容" />
    </horizontal>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- maring传递四个参数,分别表示左、上、右、下四个方向的外边距 -->
    <!-- 为了测试先后顺序,值采用递增的方式 -->
    <horizontal>
        <text id="test5" margin="10 25 40 55" text="test5内容" />
        <!-- 用于对照 -->
        <text id="test6" text="test6内容" />
    </horizontal>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 四个单方向外边距测试,根据名称就应该能够判断出作用 -->
    <!-- 为了测试先后顺序,值还是采用递增的方式 -->
    <!-- 并且设置和margin四参数测试时相同的值 -->
    <horizontal>
        <text id="test7" marginLeft="10" marginTop="25" marginRight="40" marginBottom="55" text="test7内容" />
        <!-- 用于对照 -->
        <text id="test8" text="test8内容" />
    </horizontal>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>
    
</vertical>

margin.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "margin.xml");

7.padding、paddingLeft、paddingRight、paddingTop与paddingBottom

这五个属性都是用于指定内边距,五个值用法和上面介绍的外边距一一对应。padding属性传不同数量的值代表方向和margin属性完全一致,另外四个属性也完全一一对应。只是两个边距代表的意义有所不同,外边距是相对于其他视图而言,内边距是视图中的内容相对于视图边而言。一般情况下,我们不用设置内边距,采用默认就好了。由于和上面外边距功能类似,这里就不介绍了。

8.bg、alpha与foreground

bg属性和foreground属性类似,分别用于设置背景和前景。背景代表在内容后面设置,前景代表在内容前面设置。两者只是方向不同罢了,真正使用时很难区分,我们一般习惯使用背景。两个属性均可以接受三个类型的值,分别为颜色值、图片路径和系统主题背景,分别会将背景设置为固定颜色、固定图片和固定主题。

下面对几个常见主题进行了介绍。我没有传递过主题,这些内容也是我通过AI搜索获取到的,我无法保证每个都好用。但是,我会在后面选择一个属性进行测试,保证传递主题是可行的,如果对这部分内容感兴趣,可以自行学习。

属性 作用 使用场景
?attr/selectableItemBackground 为可点击元素设置一个标准的涟漪(Ripple)效果,提供触摸反馈。 任何可点击的视图,如按钮、列表项、卡片等。
?attr/selectableItemBackgroundBorderless 提供一个无边界的涟漪效果。这个效果不会被视图的边界约束,视觉上会超出视图的边界。 适用于希望触摸反馈更突出的场景,通常在 Android 5.0 (API 21) 及以上版本使用。
?attr/actionBarItemBackground 为 ActionBar 上的操作项(如溢出菜单按钮)设置背景。 主要用于自定义 ActionBar 中的按钮或图标。
?attr/colorPrimary 引用应用的主题主色调。这通常用于设置状态栏背景、关键按钮的颜色等。 用于需要与应用主题色保持一致的元素。
?attr/colorAccent 引用应用的强调色。这是用于突出显示界面元素的颜色,如复选框的选中颜色、编辑框的光标颜色等。 用于需要突出强调的 UI 控件。

alpha属性用于设置视图透明度,允许传递0~1之间的浮点数,0表示完全透明,1表示完全不透明。

bg.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 背景传递颜色值,设置背景色 -->
    <text id="test1" text="test1内容" bg="#00ff00" />

    <!-- 背景传递文件总地址的图片路径,设置背景图 -->
    <!-- 设置为居中显示,更能体现背景效果 -->
    <!-- 地址前面需要加上file:// -->
    <text id="test2" gravity="center" text="test2内容"
        bg="file:///storage/emulated/0/com.py.test/baidu.png" />

    <!-- 背景传递主题路径,设置主题样式 -->
    <text id="test3" text="test3内容" bg="?attr/colorPrimary" />


    <!-- 前景传递颜色值,设置背景色 -->
    <text id="test4" text="test4内容" foreground="#00ff00" />

    <!-- 前景传递文件总地址的图片路径,设置背景图 -->
    <!-- 设置为居中显示,更能体现背景效果 -->
    <!-- 地址前面需要加上file:// -->
    <text id="test5" gravity="center" text="test5内容"
        foreground="file:///storage/emulated/0/com.py.test/baidu.png" />

    <!-- 前景传递主题路径,设置主题样式 -->
    <text id="test6" text="test6内容" foreground="?attr/colorPrimary" />

    <!-- 设置透明度 -->
    <!-- 随便测试一个上面的设置,0时看不见了,1时直接变成全黑 -->
    <text id="test7" text="test7内容" foreground="#00ff00" alpha="0.5" />
</vertical>

bg.js文件中有个图片,可以随便放一张名称叫"baidu.png"的图片到项目根目录,就能成功运行代码,具体代码如下:

java 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 由于背景图设置读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}


// 读取 XML 文件内容
ui.layoutFile(uiFolder + "bg.xml");

通过下面截图发现,前景色和毕竟色花式有点不一样的。前景色颜色深了会盖过内容,同时前景设置为图片时不会改变视图的宽度和高度,而背景设置为图片时视图宽度和高度默认设置为图片的宽度和高度。

9.minHeight与minWidth

minHeight和minWidth属性分别用于设置视图最小高度和最小宽度,也就是视图显示不能低于这个高度或宽度。但是,实际上这两个数据的设置受父视图限制,这也是正常的,本来就不应该出现子视图比父视图的大的情况。大部分情况下,父视图不用设置宽度和高度,如果使用这两个属性不生效时,可以去考虑父视图宽度限制。

10.visibility

此属性用于控制视图是否可见。可以填写gone、visible和invisible三个值,分别代表不可见、可见和不可见仍然占用位置。此属性默认情况下的值为visible,默认情况下是可见的。

visibility.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 不可见,不占用位置 -->
    <text id="test1" text="test1内容" visibility="gone" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 默认情况下显示 -->
    <text id="test2" text="test2内容" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 设置为可见,实际上和默认情况下一样 -->
    <text id="test3" text="test3内容" visibility="visible" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 不可见,仍然占用位置 -->
    <text id="test4" text="test4内容" visibility="invisible" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>
</vertical>

visibility.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "visibility.xml");

11.rotation、transformPivotX与transformPivotY

rotation属性用于将视图按照顺时针旋转角度,接收旋转角度。

transformPivotX属性用于设置旋转中心的横坐标,transformPivotY属性用于设置旋转中心的纵坐标。这两个属性值是以视图左上角为坐标系原点获取的,默认分别为视图中心点的横坐标和纵坐标。

需要注意的是,视图旋转后,内容消失了。我尝试了很多办法,没法将内容再显示。但是有背景色的视图,视图能够显示背景色。出现这种情况很奇怪,说明视图旋转完成了,但是内容由于其他原因无法显示。我一般不会使用这个属性,就没有过多测试,大家如果使用这个函数,请先测试下旋转后是否会导致内容消失。

rotation.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 不旋转 -->
    <text id="test1" text="test1内容" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 设置父视图,留出足够的控件旋转 -->
    <vertical w="300" h="300">
        <!-- 使用默认旋转中心,顺时针旋转90度 -->
        <text id="test2" text="test2内容" rotation="90" bg="#ff0000"/>
    </vertical>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 设置原点为旋转中心,顺时针旋转90度 -->
    <text id="test3" text="test3内容" rotation="90" transformPivotX="0" transformPivotY="0" />
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>
</vertical>

rotation.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "rotation.xml");

12.style

设置视图的样式。这个样式是非常多的,我就不介绍了。在我们使用到的视图基本样式设置无法满足需求时,可以考虑通过这个属性来设置特定样式。这个样式是非常多的,我还推荐大家通过AI生成,然后去使用。我这里就只介绍按钮控件使用这个属性的一种用法,其他情况类似。

style.xml文件代码如下:

ini 复制代码
<vertical>
    <button id="testButton" text="测试按钮" textSize="14"
        textColor="#FFFFFF" padding="1" w="70" h="40"
        style="Widget.AppCompat.Button.Colored" marginBottom="10" />
</vertical>

style.js文件代码如下:

ini 复制代码
<vertical>
    <button id="functionConfigButton" text="测试按钮" textSize="14"
        textColor="#FFFFFF" padding="1" w="70" h="40"
        style="Widget.AppCompat.Button.Colored" marginBottom="10" />
</vertical>

13.click、long_click与check

lick、long_click和check均代表事件,分别在视图被点击、长按和选择时触发。一般情况下,视图都是可以触发点击和长按事件的,对于单选框和复选框这种控件才能触发选择事件。大家可以理解为点击和长按事件在所有视图中均可使用,选择事件在特殊视图(某些控件)中才能用。后面介绍控件内容时,如果能够触发选择事件的控件会标明。需要注意的是,选择事件在可选择的控件被选择或取消选择时均可触发,我是为了名称简洁才叫选择事件。

click.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 用于测试点击的按钮 -->
    <button id="testButton" text="测试按钮" textSize="14"
        textColor="#FFFFFF" padding="1" w="70" h="40"
        style="Widget.AppCompat.Button.Colored" marginBottom="10" />

    <!-- 用于测试点击的文本 -->
    <text id="testText" text="测试文本"></text>

    <!-- 用于测试选择的复选框 -->
    <checkbox id="testCheckbox" text="测试复选框" />
</vertical>

click.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "click.xml");

// 监听按钮点击
ui.testButton.on("click", (currentView) => {
    console.log(currentView);
    console.log("按钮被点击了");
    console.log("-------------------------------------");
});

// 监听文本长按
ui.testText.on("long_click", (event, currentView) => {
    console.log(event);
    console.log(currentView);
    console.log("文本被长按了");
    console.log("-------------------------------------");
});

// 监听复选框选择
ui.testCheckbox.on("check", (checked, currentView) => {
    // 如果复选框当前状态为选择状态
    if (checked) {
        console.log("复选框被选择了");
    } else {
        console.log("复选框被取消选择了");
    }
    console.log(currentView);
    console.log("-------------------------------------");
});

我一直在说autojs兼容安卓方面非常好,那么我就以安卓方式实现上面三个事件的监听。两者功能方面类型,但是函数参数和使用方式方面可能有少许不同。我一般使用上面这三种方式即可,在封装功能时,我有时候会使用安卓的方式。我这是提前和小伙伴们分享下,如果突然使用这种方式,害怕有些小伙伴懵了。有小伙伴可能会问,android中那么多的类和接口,我怎么能记住的。说实话,我也记不住,我第一次使用一般通过AI搜索,然后测试和修复里面的bug,然后封装起来,以后直接用就好了。

下面代码和上面给出的autojs方式功能类似,小伙伴可以直接复制测试。

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "click.xml");

// 监听按钮点击
ui.testButton.setOnClickListener(new android.view.View.OnClickListener({
    onClick: function (currentView) {
        console.log(currentView);
        console.log("按钮被点击了(Android原生方式)");
        console.log("-------------------------------------");
    }
}));

// 监听文本长按
ui.testText.setOnLongClickListener(new android.view.View.OnLongClickListener({
    onLongClick: function (currentView) {
        console.log(currentView);
        console.log("文本被长按了(Android原生方式)");
        console.log("-------------------------------------");

        // 添加返回值,true表示时间已经被消费
        return true;
    }
}));

// 监听复选框选择
ui.testCheckbox.setOnCheckedChangeListener(function (currentView, checked) {
    // 如果复选框当前状态为选择状态
    if (checked) {
        console.log("复选框被选择了(Android原生方式)");
    } else {
        console.log("复选框被取消选择了(Android原生方式)");
    }
    console.log(currentView);
    console.log("-------------------------------------");
});

我一直说autojs使用的是ES5,不支持箭头函数,为什么第一种实现监听事件的三种方式都用了箭头函数?autojs官方提供的一些函数是允许通过箭头函数的方式使用,也有些官方文档中用的箭头函数,结果我们使用就报错,换成非箭头函数的方式又不报错了。在autojs中,并不是所有的函数都支持箭头函数,有些新版支持的箭头函数方式,免费版也不支持。我还推荐大家都使用普通函数的方式,这种方式在任何情况下都满足,哪怕官方文档中使用了箭头函数,我们也替换成普通函数使用。当然,如果你确定某些autojs函数的情况下,使用箭头函数也可以。

我们以第一种方式中click事件箭头为例,将其替换成普通函数。替换方式很简单,将"箭头"去掉,然后前面加个function即可,替换后的代码如下:

javascript 复制代码
ui.testButton.on("click", function (currentView) {
    console.log(currentView);
    console.log("按钮被点击了");
    console.log("-------------------------------------");
});

大家需要记住,非官方函数不可用箭头函数,官方函数谨慎用箭头函数。如果在不能用箭头函数的地方用了箭头函数,启动时就会报错,大家改过来就好了。

4.布局

1.前言

布局也是基于视图来完成的,视图中的属性在布局中也能用。布局一般都会使用基础属性,只有少数布局拥有个别特殊属性。布局一般作为父视图使用,其子视图可以是布局,也可以是控件。控件一般作为子视图使用,不会再包含任何子视图了。

2.vertical

将控件按照垂直的方式布置。vertical可以通过h属性设置高度,而vertical包含的控件可以通过layout_weight属性设置占用高度的比例,每个控件占用高度为(自己layout_weight值/当前vertical包含控件layout_weight值之和) * h。

下面例子中,vertical布局下包含三个控件,设置vertical布局高度h为100dp。

vertical.xml文件代码:

xml 复制代码
<vertical h="100">
    <!-- 设置垂直布局高度为100dp -->

    <!-- 设置权重为1 -->
    <text id="test1"  text="test1内容" layout_weight="1"/>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 设置权重为2 -->
    <text id="test2"  text="test2内容" layout_weight="2"/>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>

    <!-- 设置权重为1 -->
    <text id="test3"  text="test3内容" layout_weight="1"/>
    <!-- 设置分割线 -->
    <text w="*" h="1" bg="#cccccc"></text>
</vertical>

vertical.js文件代码:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "vertical.xml");

3.horizontal

horizontal将控件按照水平的方式布置,和vertical用法完全一致,也会通过layout_weight属性设置权重,权重使用方式也一致。只是horizontal布局按照水平方向划分大小,而vertical布局按照垂直方向划分大小。

horizontal.xml文件代码如下:

xml 复制代码
<horizontal w="350">
    <!-- 设置水平布局宽度为500dp -->
    <!-- 设置权重为1 -->
    <text id="test1"  text="test1内容" layout_weight="1"/>
    <!-- 设置分割线 -->
    <text w="1" h="*" bg="#cccccc"></text>

    <!-- 设置权重为2 -->
    <text id="test2"  text="test2内容" layout_weight="2"/>
    <!-- 设置分割线 -->
    <text w="1" h="*" bg="#cccccc"></text>

    <!-- 设置权重为1 -->
    <text id="test3"  text="test3内容" layout_weight="1"/>
    <!-- 设置分割线 -->
    <text w="1" h="*" bg="#cccccc"></text>
</horizontal>

horizontal.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "horizontal.xml");

4.linear

线性布局。上面介绍vertical和horizontal布局均属于线性布局。linear布局的orientation属性传递两个值,分别为vertical和horizontal。orientation属性传递vertical值时与vertical布局功能一致,传递horizontal值时与horizontal布局功能一致。

5.frame

帧布局,默认从左上角开始布局,多个控件层叠排序,后面的控件会覆盖前面的控件。这个布局可以传递三个属性,分别为gravity、foreground和foregroundGravity,分别代表重力方向、前景和前景位置。其中,gravity属性与视图中的gravity属性的作用和用法完全一致,只是作用于布局下的视图;foreground属性与视图中的foreground属性完全一致;foregroundGravity属性与视图中的gravity属性的作用和用法完全一致,只是作用与前景。gravity属性默认为"left|top"也就是左上角。foreground属性一般传递图片路径等,通过foregroundGravity属性显示在没有使用的区域。

frame.xml文件代码:

xml 复制代码
<frame gravity="right|top"
    foreground="file:///storage/emulated/0/com.py.test/baidu.png"
    foregroundGravity="right|bottom">
    <!-- 右默认重力方向左上,改为右上 -->
    
    <!-- 由于采用覆盖的方式显示,最后设置的视图显示在最前面 -->
    <!-- 设置宽度和高度依次递减,才能都显示 -->
    <text w="300dp" h="300dp" background="#2F2F4F" />
    <text w="200dp" h="200dp" background="#FF1CAE" />
    <text w="100dp" h="100dp" background="#6B4226" />
</frame>

frame.js文件代码:

java 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 由于背景图设置读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "frame.xml");

6.relative

相对布局,以父视图作为参照物的布局。与其他布局直接将属性设置在布局上不同,relative布局只作为父视图使用,其包含的子视图会增加七个属性。子视图的七个属性分别为layout_alignParentLeft、layout_alignParentRight、layout_alignParentTop、layout_alignParentBottom、layout_centerHorizontal、layout_centerVertical与layout_centerInParent,分别表示子视图放置于相对布局的左、右、顶部、底部、水平居中、垂直居中和中心位置,均需要传递boolean类型的字符串,默认都是字符串false,并且前六个属性可以配合使用。前六个属性配合使用之前,为了避免出现既左又右的情况,先要将属性分为水平方向和垂直方向。水平方向包含左、右和水平居中,垂直方向包含顶部、底部和垂直居中。每个子视图可以接受水平方向一个值,和垂直方向一个值,最终会组成九个位置。其中,水平居中和垂直居中与第七个属性中心位置作用是一样的。当然也可以给子视图传递一个值或者相同的两个值,但是这样会涉及方向不明确和覆盖问题。使用相对布局的主要作用就是将子视图按照第一规律快速布局,这种布局方式对于数量少于九个的子视图排布非常友好,并且在不同分辨率下的兼容性也好。当然多于九个子视图时也能用,需要自己通过外边距属性等将子视图分开,同时考虑分辨率兼容性问题。

由于官方文档写的案例很好,我直接复制过来了,relative.xml文件代码如下:

ini 复制代码
<relative>
    <button text="居中" layout_centerInParent="true" />
    <button
        text="左上角"
        layout_alignParentLeft="true"
        layout_alignParentTop="true"
    />
    <button
        text="右上角"
        layout_alignParentRight="true"
        layout_alignParentTop="true"
    />
    <button
        text="左下角"
        layout_alignParentLeft="true"
        layout_alignParentBottom="true"
    />
    <button
        text="右下角"
        layout_alignParentRight="true"
        layout_alignParentBottom="true"
    />
    <button
        text="上居中"
        layout_alignParentTop="true"
        layout_centerHorizontal="true"
    />
    <button
        text="下居中"
        layout_alignParentBottom="true"
        layout_centerHorizontal="true"
    />
    <button
        text="左居中"
        layout_alignParentLeft="true"
        layout_centerVertical="true"
    />
    <button
        text="右居中"
        layout_alignParentRight="true"
        layout_centerVertical="true"
    />
</relative>

relative.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "relative.xml");

7.scroll

滚动布局。滑动布局作用是内容超出部分增加滚动条,我们可以通过滑动屏幕查看超出部分。滚动布局里面必须有且只有一个布局,否则会报错。我们一般会使用vertical(垂直)布局,我们一般在配置根节点使用scroll布局,防止内容过多不显示。当然,也可以设置一个水平布局,但是我们一般不会这样设置样式。如果水平内容超过时,我们一般会换行显示,而不是增加水平滚动。因此,大家只要记得,在配置根节点中使用滚动布局,然后在滚动布局里面内置一个垂直布局,我们以后的ui都在垂直布局中进行设置。由于,scroll属于一种常用布局,后面内容中会使用,这里就不举例说明了。

5.控件

1.前言

控件也是基于视图来完成的,视图中的属性在控件中也能用。控件的父视图一般为布局,控件一般作为子视图使用,控件下不会再包含任何子视图了。每个控件都有非常多的特有属性,有些功能和视图中的属性类似,但是优先使用特有属性来完成功能。

2.text

属于文本控件,用于文本的显示。此控件包含text、textColor、textSize、textStyle、lines、maxLines、typeface、ellipsize、ems和autoLink十个特有属性。

1.text属性用于设置文本内容。

2.textColor属性用于设置文本颜色。

3.textSize属性用于设置文本字体大小。

4.textStyle属性用于设置文本字体样式,可以传递三个值,分别为bold、italic和normal,分别代表加粗、斜体和正常字体,并且第一值(加粗)可以与后两个值(斜体和正常字体)共同使用,中间用"|"分割。传值"bold|italic"代表斜体加粗,由于属性默认值为normal(正常字体),"bold|normal"和直接传值"bold"均代表正常字体加粗。

5.lines属性用于设置文本显示行数,如果内容超过函数是不会显示的,如果内容不超过行数也会显示空白行。显示多行内容时,可以通过\n来手动换行(必须通过js文件赋值,xml文件赋值无效),内容占满屏幕也会自动换行。

6.maxLines属性用于设置文本显示最大行数,如果内容超过函数是不会显示的,如果内容不超过行数不会显示空白行。显示多行内容时,可以通过\n来手动换行(必须通过js文件赋值,xml文件赋值无效),内容占满屏幕也会自动换行。这个属性比lines属性好用,但是一般情况下,我们不会设置这两个属性,系统会根据内容多少自动换行。

7.typeface属性用于设置字体风格,可以传递四个值,分别为normal、sans、serif和monospace,分别代表正常字体、衬线字体、非衬线字体和等宽字体,默认值为normal(正常字体)。

8.ellipsize属性用于设置文本省略号的位置,可以传递end一个值,代表文本末尾显示省略号。官方文档显示的五个值,要么传递时报错,要么传递无效,要么传递时和end一个效果,因此只有end值有效。

9.ems属性用于设置内容显示字符数,超出部分会隐藏,配合ellipsize属性使用非常方便。但是ems属性设置后不生效,我们可以通过设置视图基本属性w(宽度)来限制显示内容,也能达到类似ems属性的效果。

10.autoLink属性无法通过"|"设置多个值,并且设置后无法转换为可点击链接。

此控件拥有text函数,此函数不传值时,代表获取控件text属性中的内容,返回文本内容,返回类型为字符串;此函数传值时,只能传递文本内容一个参数,参数类型为字符串,代表修改控件text属性中的内容。

text.xml文件代码如下:

xml 复制代码
<vertical>

    <!-- 基础风格,使用默认值,用于对比 -->
    <text id="test1" text="test1内容" />

    <!-- 测试内容颜色、字体大小和风格 -->
    <text id="test2" text="test2内容" textColor="#ff00ff" textSize="16" textStyle="bold|italic" />

    <!-- 如果不设置行,内容过多时 -->
    <text id="test3"
        text="test3内容test3内容test3内容test3内容test3内容test3内容test3内容test3内容test3内容test3内容test3内容..." />

    <!-- 行数设置为1,内容显示不完全 -->
    <text id="test4"
        lines="1"
        text="test4内容test4内容test4内容test4内容test4内容test4内容test4内容test4内容test4内容test4内容test4内容..." />

    <!-- 行数设置过大,会有空白行 -->
    <text id="test5"
        lines="5"
        text="test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容..." />

    <!-- 设置最大行数为1,内容显示不完全 -->
    <text id="test6"
        maxLines="1"
        text="test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容..." />

    <!-- 行数设置过大,不会有空白行 -->
    <text id="test7"
        maxLines="5"
        text="test7内容test7内容test7内容test7内容test7内容test7内容test7内容test7内容test7内容test7内容test7内容..." />

    <!-- 通过\n手动换行,需要在js赋值,在xml直接通过text传值无效 -->
    <text id="test8" />

    <!-- 测试字体风格,衬线字体 -->
    <text id="test9" text="test9内容" typeface="serif"/>

    <!-- 限制内容显示长度和内容超过长度显示样式 -->
    <text id="test10" w="100" ellipsize="end" 
        text="test10内容test10内容test10内容test10内容test10内容test10内容test10内容test10内容test10内容test10内容test10内容..." />

    <!-- 转换链接无效 -->
    <text id="test11" text="http://www.baidu.com" autoLink="web"/>
</vertical>

text.js文件代码如下:

swift 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "text.xml");

// 通过\n方式手动换行,直接在xml中text属性中传值是不支持的
// 并且验证text函数传参时,用于修改文本控件的内容
ui.test8.text("test8内容\ntest8内容\ntest8内容");

// 获取id为test10的内容
let currentContent = ui.test10.text();
console.log(currentContent);

3.button

button控件是基于视图和text控件来完成的,因此视图和text控件的属性和函数在button控件中也有效。其中,text控件的属性和函数主要控制button控件的显示内容,视图中的属性和函数主要控制button控件本身。button控件一般不会改变或者获取里面的内容,而是监听控件的点击操作。由于在视图的click事件中介绍了监听点击事件的方法,在text控件中已经详细就介绍了内容的设置方式,这里就不重复介绍了。

4.input

input控件是基于视图和text控件来完成的,因此视图和text控件的属性和函数在button控件中也有效。其中,text控件的属性和函数主要控制input控件的显示内容,视图中的属性和函数主要控制input控件本身。input控件一般用于与用户之间的交互,输入账号、区服等信息,然后我们将信息保存到本地,然后在ui加载时将本地保存信息赋值给input控件。input控件修改和读取内容也是通过text函数完成,用法和text控件完全一致。

input控件包含hint、textColorHint、textSizeHint、inputType、password、numeric、phoneNumber、digit和singleLine九个特有属性。

1.hint属性用于色好孩子输入内容为空时的提示文字。

2.textColorHint属性用于设置提示文字的颜色。

3.textSizeHint属性用于设置提示文字的大小。

4.inputType属性用于设置输入类型,有非常多的值,具体可以参考官方文档中的介绍。这个属性可以接收多个值,中间用"|"分隔。

5.password属性用于指定输入类型是否为密码,接收boolean类型数据,默认为false。

6.numeric属性用于指定输入类型是否为数字,接收boolean类型数据,默认为false。

7.phoneNumber属性用于指定输入类型是否为电话号码,接收boolean类型数据,默认为false。

8.digit属性用于指定可输入的字符,接收boolean类型数据,默认为false。

9.singleLine属性用于指定是否为单行输入框,接收boolean类型数据,默认为false。

第5、6和7属性相当于第4属性的封装,这三个属性完全可以通过第4属性完成,由于这三个功能用的比较多,因此又封装了三个属性。实际使用时,我一般不使用第4属性,如果是密码框我会使用第5属性,如果是数字我会使用第6属性,如果是输入为字符串,我可以不设置特有属性。我们只有需要设置邮箱、日期等特殊类型时,才会选择使用第4属性。

input.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 基础输入框,用于比对-->
    <input id="test1"></input>

    <!-- 设置提示内容 -->
    <input id="test2" hint="请输入内容" textColorHint="#d97700" textSizeHint="20"></input>

    <!-- 设置输入类型为数字 -->
    <input id="test3" inputType="number"></input>

    <!-- 指定输入类型为密码,设置个初值 -->
    <input id="test4" password="true" text="123456"></input>

    <!-- 不指定单行输入框,并且设置足够长的内容 -->
    <input id="test5"
        text="test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容test5内容"></input>

    <!-- 指定单行输入框,并且设置足够长的内容 -->
    <input id="test6" singleLine="true"
        text="test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容test6内容"></input>
</vertical>

input.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "input.xml");

// 模拟加载本地文件,修改输入框中的值
ui.test3.text("987654");

// 获取输入框中的内容
console.log(ui.test3.text());

5.img

用于显示图片。此控件包含src、tint、scaleType、radius、radiusTopLeft、radiusTopRight、radiusBottomLeft、radiusBottomRight、borderWidth、borderColor和circle十一个特有属性。

1.src属性用于指定图片的绝对路径。

2.tint属性用于设置图片着色。

3.scaleType属性用于设置缩放宽高的模式,允许传递八个值,具体可以查看官方文档中的说明。我一般情况下,不会设置这个值,甚至很少去加载图片。

4.radius属性用于设置图片的半径。

5.radiusTopLeft属性用于设置图片左上角的半径。

6.radiusTopRight属性用于设置图片右上角的半径。

7.radiusBottomLeft属性用于设置图片左下角的半径。

8.radiusBottomRight属性用于设置图片右下角的半径。

9.borderWidth属性用于设置图片边框宽度。

10.borderWidth属性用于设置图片边框颜色。

11.circle属性用于指定图片是否圆形显示,接收boolean类型数据,默认为false。

需要注意的是第4、第5-8以及第11属性设置了后,没有明显效果。出现这个原因是我选图造成的,还是本来就无效,我并不清楚。一般情况下,我们很少使用这些属性,就不会探索原因了,大家清楚就好了。

img.xml文件代码如下:

xml 复制代码
<scroll>
    <!-- 内容过多,允许滚动,滚动下面需要设置一个布局-->
    <vertical>
        <!-- 采用默认样式的图片,用于比对 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" />

        <!-- 设置着色 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" tint="#d97700" />

        <!-- 设置缩放模式,指定为靠右下角显示 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" scaleType="fitEnd" />

        <!-- 设置图片半径为50 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" radius="50" />

        <!-- 设置图片四个方向半径 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" radiusTopLeft="50"
            radiusTopRight="50" radiusBottomLeft="50" radiusBottomRight="50" />

        <!-- 设置图片边框宽度和颜色 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" borderWidth="20"
            borderColor="#d97700" />

        <!-- 设置图片圆形显示 -->
        <img src="file:///storage/emulated/0/com.py.test/baidu.png" circle="true" />
    </vertical>
</scroll>

img.js文件代码如下:

java 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// 由于图片读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}


// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "img.xml");

6.checkbox

checkbox控件是基于button控件完成的,button控件又是基于多个控件完成的,因此checkbox控件可用属性和函数非常多。官方文档中介绍checkbox控件的属性有三个,其中text属性在text控件就有,而checkbox通过button控件也能关联上text控件,不算特有属性。因此,checkbox控件包含checked和enabled三个特有属性。

1.checked属性用于设置复选框是否勾选,接收boolean类型数据,默认为false。

2.enabled属性用于设置复选框是否可用,接收boolean类型数据,默认为true。实际使用过程中,这个属性值无效。

后面控件会有很多基于前面介绍的控件,他们通过继承关系关联在一起。大家需要知道,基于某个或者某些控件完成的控件,是允许使用父控件的属性和函数,他们通过树形结构关联在一起。

复选框就是允许多选的,但是实际上脚本开发过程中,由于单选框不好看,我一般会将复选框设置为单选框然后进行使用,下面实例代码中也会介绍这部分内容。

checkbox控件有isChecked特有函数,用于判断复选框是否被勾选了。并且,视图中的check事件在此控件中是可以用的,控件中的checked属性可以通过属性赋值的方式进行修改。

checkbox.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 通过多个水平布局来更好划分任务 -->
    <horizontal>
        <!-- 设置复选框默认选择 -->
        <checkbox id="test1" text="任务1" checked="true" />
        <checkbox id="test2" text="任务2" />
        <checkbox id="test3" text="任务3" />
    </horizontal>
    <horizontal>
        <!-- 设置复选框不可用 -->
        <checkbox id="test4" text="任务4" enabled="false" />
        <checkbox id="test5" text="任务5" />
        <checkbox id="test6" text="任务6" />
    </horizontal>
</vertical>

checkbox.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "checkbox.xml");

// 需要改为单选的复选框id列表
// 通过id查找,避免将需要多选的复选框也改成了单选
let checkIdList = ["test1", "test2", "test3", "test4", "test5", "test6"];

// 判断初始值是否被勾选
console.log("任务1是否被勾选:" + ui.test1.isChecked());

// 需要复选的单选框加上监听器
checkIdList.forEach(currentCheckId => {
    // 获取当前控件
    let currentView = ui[currentCheckId];

    // 为控件增加点击事件
    currentView.on("check", function (checked) {
        // 如果当前控件被选择
        // 除了这种方式外,也可以通过通过isChecked函数判断,也就是currentView.isChecked()
        // 我们这里已经通过事件中获取到了,就没必要通过函数获取了
        // 事件函数还可以接受第二个参数-当前控件信息,我们已经通过当前控件添加的事件,就没必要再次获取了
        if (checked) {
            // 将其他复选框取消勾选
            checkIdList.forEach(currentUncheckId => {
                // 如果不是当前id,就取消勾选
                if (currentCheckId != currentUncheckId) {
                    // 可以直接通过属性赋值的方式取消勾选
                    ui[currentUncheckId].checked = false;
                }
            });
        }
    });
})

我前面说过,非官方函数不要使用箭头函数,但是js中forEach遍历是允许箭头函数的,我确定可以使用才用的。当然,如果不熟悉autojs箭头函数的使用规则,最好还是改成普通函数比较安全。复选框变成单选框的功能,在截图中是无法演示的,大家可以运行代码后自行演示。

7.radio与radiogroup

radio控件是基于button控件完成的,基本属性和特有属性与checkbox完全一致,同时enabled属性设置无效。radio控件单独使用是无法完成单选功能的,单独使用时可以选多个框。需要用radiogroup控件包裹某些radio控件,来实现这些radio控件的单选功能。radiogroup控件完全是为了将radio组件封装成一个整体而设置的,一般不会单独使用。使用时,radiogroup控件作为父视图,而radio控件作为子视图来整体使用。

radiogroup控件是基于linear布局完成的,linear布局有orientation属性用于设置布局方向,这个属性在radiogroup控件中也能使用,作用于radio控件,表现为控制单选框的显示方向。orientation属性默认值为vertical,也就是垂直显示;如果设置属性值为horizontal会变成水平显示。

radiogroup控件通过对象分析发现有非常多的函数,但是这里先介绍官方文档标明的三个函数,分别为setOnCheckedChangeListener、getCheckedRadioButtonId和clearCheck,分别用于设置单选框组合的监听器、获取单选框组合中已选中单选框的id和清空单选框组合中已选中单选框的选择状态。setOnCheckedChangeListener函数设置的监听器,返回函数会有两个参数,分别为单选框组合控件和选中单选框的id编号。id编号为遗传数字,当没有选择项时,会返回-1。说实话,数字对于我们来说没有用处,我们需要通过这个数字来获取到对象的radio控件,然后再获取radio控件的各种信息。

通过分析radiogroup控件对象发现,下面包含大量public类型的函数,说实话,我们一般也用不到。当然还有大量private类似的函数,这种函数我们无法调用,只能看下了。这种分析对象的函数,我已经封装好了,能够兼容分析java(android)类型、autojs类型以及js类型的对象,然后根据传值分析对象的属性或者函数,这是我写文章过程中做的工具,感觉挺好用的。这种好用的工具,我肯定会做到后期的插件中,大家可以等待。如果插件效果好,我也会考虑增加各种参数的,比如只获取public类型的函数等。

一般情况下,radiogroup控件的监听器一般用于ui需要联动情况下,比如选择某不同单选框,显示不同的配置项;获取已选中单选框id编码的函数一般用于保存配置信息等情况,由于现在获取的id编码,我们要通过编码获取radio控件。我上面介绍了分析对象的函数,分析到的public类型的函数肯定可以用,但是有些根据继承关系获得的底层函数根本分析不到。比如findViewById函数,这个能够通过id编码获取到radio控件,后面例子中会有介绍。大家一定要把控件id和id编码分开,id编码也是我给他起的名字,id编码实际是一串数字,而id为我们xml文件中控件的id属性设置的字符串。

对于通过text设置内容的控件,并不一定包含text函数,但是一定包含text属性。有些控件通过继承关系能够找到text控件,那么通过text函数获取内容是可以的,否则调用会出错。其实,除了上述通过text属性获取text内容的方式外,通过getText函数也能万能获取text内容,这应该是在底层类中所拥有的属性,通过打印是无法获取的。

毕竟是学习阶段,我尽量将各种方式和功能都介绍给大家。我有时候给大家介绍各种方式的优缺点,真正使用时,我也是想到哪种用那种。脚本开发,不用考虑那么多性能问题,不出现内存溢出问题,这种小细节可以忽略。说实话,我上面介绍很多知识都是这次写文章才学到的,我以前用的很多方式也挺复杂的。我开始也是通过AI生成一部分代码,然后完成功能后,就会一直复制使用,根本不考虑优缺点问题。这次写文章和大家一起又学习了很多内容,虽然哪种方式都可以,我还是建议大家优先使用更好的方式。

radio.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 通过多个水平布局来更好划分任务 -->
    <horizontal>
        <!-- 设置单选框默认选择,这组单选控件不设置单选框组合,作为对比-->
        <radio id="test1" text="任务1" checked="true" />
        <radio id="test2" text="任务2" />
        <!-- 设置单选框不可用,但是设置无效 -->
        <radio id="test3" text="任务3" enabled="false" />
    </horizontal>

    <!-- 设置单选框组合,默认选择id为test6的控件,并且设置水平显示单选框-->
    <radiogroup id="testGroup" checkedButton="@+id/test6" orientation="horizontal">
        <radio id="test4" text="任务4" />
        <radio id="test5" text="任务5" />
        <radio id="test6" text="任务6" />
    </radiogroup>


</vertical>

radio.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "radio.xml");

// 获取单选框组合控件
let testGroupView = ui.testGroup;

// 获取下默认选中的单选框id
let getCheckedIdCode = testGroupView.getCheckedRadioButtonId();
// 通过id编码获取radio控件
let currentRadioView = testGroupView.findViewById(getCheckedIdCode);
// currentRadioView.text相当于 currentRadioView.getText()
console.log("默认选中单选框的id编码为" + getCheckedIdCode + ",名称为" + currentRadioView.text);

// 设置监听器
// 监听器函数会返回两个值,分别为单选框组合控件和选中单选框的id编号
testGroupView.setOnCheckedChangeListener(function (checkedGroupView, checkedIdCode) {
    console.log("单选框组合状态改变");
    console.log(checkedGroupView);
    console.log("id编码为" + checkedIdCode + "的单选框被选中");
    console.log("---------------------------");
});

// 清空单选框组合中已选中单选框的选中状态
testGroupView.clearCheck();

// 打印清空后,单选框组合当前选中的单选框
console.log("清空后,当前选中单选框id编码为" + testGroupView.getCheckedRadioButtonId());

8.Swtich

Swtich控件是基于button控件完成的,基本属性和特有属性与radio完全一致,同时enabled属性设置无效。大家可以将Swtich控件理解为一个radio控件,只是switch控件一般单独使用,而radio控件组合使用,并且显示样式不同罢了。又因为radio控件的属性和函数与checked一致,那么三者属性和函数是一致的。由于这三者这么相似,就不过多介绍,直接上案例。需要注意,这个控件首字母要大写。

switch.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 使用默认 -->
    <Switch id="test1"></Switch>
    
    <!-- 设置为开启状态 -->
    <Switch id="test2" checked="true"></Switch>

    <!-- 设置为禁用状态 -->
    <Switch id="test3" enabled="false"></Switch>
</vertical>

switch.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "switch.xml");

// 获取id为test2的开关状态
let currentView = ui.test2;
console.log("test2开关状态:" + currentView.isChecked());

// 获取id为test3的开关,增加监听器
currentView = ui.test3;
currentView.on("check", function (checked) {
    console.log("test3被点击,当前状态为" + checked);
})

9.seekbar

用于显示拖动条。此控件是基于进度条progressbar完成的,但是由于以为是一个东西,看过去了。这部分介绍的进度条,大家可以理解为拖动条就好了。进度条控件部分大家也不用考虑使用了,这个控件感觉像是为了拖动条打基础,实用性并不高。此控件包含indeterminate、progress、min、max、progressDrawable、progressBackgroundTint、progressBackgroundTintMode、progressTint、progressTintMode、secondaryProgress、secondaryProgressTint、secondaryProgressTintMode和style十三个特有属性。

1.indeterminate属性用于设置是否为无限进度条,接收boolean类型数据,默认为false。如果设置为true,会变成加载进度条,并且有些其他属性无法使用,更多用于加载过程中的样式显示。

2.progress属性用于设置当前进度,当前进度要求在最小进度和最大进度之间。

3.min属性用于设置最小进度,默认为0。但是不能使用,使用时会报错。

4.max属性用于设置最大进度,默认为100。

5.progressDrawable属性通过xml文件方式,指定背景、第一进度和第二进度的样式。需要自己写xml,然后将xml配置保存到安卓中,再通过id调用,过程非常复杂。这种方式是自定义程度最高的样式设置,使用自定义方式就不要使用下面传值方式。如果两种方式结合使用,很容易导致冲突而产生意想不到的问题。我感觉没必要使用这种复杂样式设置,如果有小伙伴感兴趣,请自行学习。

6.progressBackgroundTint属性用于设置进度条背景色,颜色效果会与下面的设置的混合模式有关。

7.progressBackgroundTintMode属性用于设置进度条背景色的混合默认,可以传递add、multiply、screen、src_atop、src_in和src_over,猜测默认值为multiply(颜色相乘)。总体感觉,multiply效果比较暗。

8.progressTint属性用于设置进度条颜色。

9.progressTintMode属性用于设置进度条颜色的混合模式,值与progressBackgroundTintMode属性一致。混合模式在设置进度条背景色时,有些模式会出现比较暗的情况。但是,混合模式在设置进度条颜色时,几乎区分不出各个模式之间的差距。

10.secondaryProgress属性用于设置第二进度的当前进度,第二进度感觉像是推荐第一进度设置位置,类似于推荐值,对应第一进度的progress属性。

11.secondaryProgressTint属性用于设置第二进度的进度条颜色,对应第一进度的progressTint属性,两者功能类似。

12.secondaryProgressTintMode属性用于设置第二进度的进度条颜色混合模式,对应第一进度的progressTintMode属性,两者功能类似。

13.style用于设置进度条整体布局样式,不推荐大家使用,很容出现奇奇怪怪的问题。

seekbar控件包含三个主要函数,分别为getProgress、setProgress和setOnSeekBarChangeListener,分别用于获取进度条当前进度、设置进度条当前进度和设置进度条监听器。

1.getProgress函数会返回当前进度,返回类型为字符串。

2.setProgress函数需要传递当前进度一个参数,参数类型为字符串。

3.setOnSeekBarChangeListener函数用于设置监听器,监听器通过对象的方式可以绑定多个函数。我给出了三个常用函数,函数参数请参考代码注释。

上面介绍了进度条无法设最小值,我们可以设置最大值,然后件获取的结果计算一下就可以获取到想要的值。比如,我们想要获取51~100的进度,我们可以设置进度条最大值为49,然后将获取的当前进度都加51,就能变成范围为51-100了。

说实话,我真正脚本开发时,我宁愿通过输入框让用户填值,也不会采用进度条控件的方式来获取值。进度条设置值的方式感觉花里胡哨,让用户用起来很不方便,也增加了我们获取值的难度,使用价值不高。

seekbar.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 使用默认 -->
    <seekbar id="test1"></seekbar>

    <!-- 设置为加载进度条 -->
    <seekbar id="test2" indeterminate="true"></seekbar>

    <!-- 设置当前进度为100 -->
    <seekbar id="test3" progress="100"></seekbar>

    <!-- 设置最小进度为0,最大进度为100,当前进度为100 -->
    <seekbar id="test4" max="200" progress="100"></seekbar>

    <!-- 设置进度条背景色,并使用默认值 -->
    <seekbar id="test5" progressBackgroundTint="#ff0000"></seekbar>

    <!-- 设置进度条背景色,并设置add混合模式 -->
    <seekbar id="test6" progressBackgroundTint="#ff0000" progressBackgroundTintMode="add"></seekbar>

    <!-- 设置进度条背景色,并设置multiply混合模式-->
    <seekbar id="test7" progressBackgroundTint="#ff0000" progressBackgroundTintMode="multiply"></seekbar>

    <!-- 设置进度条背景色,并设置screen混合模式 -->
    <seekbar id="test8" progressBackgroundTint="#ff0000" progressBackgroundTintMode="screen"></seekbar>


    <!-- 设置进度条颜色,并使用默认值 -->
    <seekbar id="test9" progressTint="#ff0000" progress="50"></seekbar>

    <!-- 设置进度条背景色,并设置src_atop混合模式 -->
    <seekbar id="test10" progressTint="#ff0000" progress="50" progressTintMode="src_atop"></seekbar>

    <!-- 设置进度条背景色,并设置src_in混合模式 -->
    <seekbar id="test11" progressTint="#ff0000" progress="50" progressTintMode="src_in"></seekbar>

    <!-- 设置进度条背景色,并设置src_over混合模式 -->
    <seekbar id="test12" progressTint="#ff0000" progress="50" progressTintMode="src_over"></seekbar>

    <!-- 设置第二进度,使用默认值 -->
    <seekbar id="test13" secondaryProgress="50"></seekbar>

    <!-- 设置第二进度颜色和颜色混合模式 -->
    <seekbar id="test14" secondaryProgress="50" secondaryProgressTint="#ff0000"
        secondaryProgressTintMode="add"></seekbar>
</vertical>

seekbar.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "seekbar.xml");

// 获取id为test4的进度条控件
let currentView = ui.test4;

// 获取进度条当前进度
let currentProgress = currentView.getProgress();
console.log("test4当前进度为" + currentProgress);

// 添加进度条监听器
currentView.setOnSeekBarChangeListener({
    // 监听进度条改变
    onProgressChanged: function (seekBar, progress, fromUser) {
        // seekBar:当前进度条控件
        // progress: 当前的进度值
        // fromUser: 是否是用户操作导致的改变
        if (fromUser) {
            console.log("用户操作,当前进度: " + progress);
        } else {
            console.log("代码操作,当前进度:" + progress);

        }
    },

    // 监控进度条开始拖动
    onStartTrackingTouch: function (seekBar) {
        console.log("开始拖动滑块");
    },

    // 监控进度条结束拖动
    onStopTrackingTouch: function (seekBar) {
        console.log("停止拖动滑块,最终进度: " + seekBar.getProgress());
    }
});

// 通过代码设置进度条当前进度
currentView.setProgress("150");

// 获取进度条当前进度
console.log("修改后当前进度为" + currentView.getProgress());

10.progressbar

用于显示进度条。按正常应该在seekbar控件以前介绍的,因为seekbar控件是基于progressbar控件完成的。由于官方文档对于progressbar控件的介绍没有案例,我看到后面拖动条的内容了,然后使用又没报错。我又将上面内容替换到progressbar控件中使用,发现这个控件几乎不能用,大家以后就只使用拖动条内容吧,可以将拖动条和进度条看成一个东西。progressbar控件加载后,就是一个旋转的圆圈,如果加载页面慢的话,可以用这个来显示等待。但是我们又不用从数据库查询数据,根本不需要这种东西。

11.spinner

用于显示下拉菜单。此控件包含spinnerMode、dropDownHorizontalOffset、dropDownVerticalOffset、dropDownWidth、popupBackground、prompt、textStyle、textColor、textSize、entries、entryTextStyle、entryTextColor和entryTextSize十三个特有属性。

1.spinnerMode属性用于设置呈现模式,可以传递两个值,分别为dialog和dropdown,分别用于对话框模式和下拉框模式,默认为dropdown(下拉框模式)。但是,设置为dialog(对话框模式)无效,下拉菜单只能使用下拉框模式,如果想使用对话框模式,可能需要自己手搓一个。我感觉下拉框模式已经足够日常使用了,对话框模式反而交互不方便。

2.dropDownHorizontalOffset属性用于设置下拉框模式下弹框水平偏移,但是没有效果。

3.dropDownVerticalOffset属性用于设置下拉框模式下弹框垂直偏移。

4.dropDownWidth属性用于设置下拉框模式下弹框宽度。

5.popupBackground属性用于设置下拉框模式下弹框背景。

6.prompt属性用于设置对话框模式下弹框提示,由于对话框模式设置无效,这个属性也没有效果。

7.textStyle属性用于设置被选择项的文本字体样式,可以传递三个值,分别为bold、italic和normal,分别代表加粗、斜体和正常字体,并且第一值(加粗)可以与后两个值(斜体和正常字体)共同使用,中间用"|"分割。传值"bold|italic"代表斜体加粗,由于属性默认值为normal(正常字体),"bold|normal"和直接传值"bold"均代表正常字体加粗。

8.textColor属性用于设置被选择项的文本字体颜色。

9.textSize属性用于设置被选择项的文本字体大小。

10.entries属性用于设置选择项,每个选择项之间用"|"隔开。

11.entryTextStyle属性用于设置选择项的文本字体样式,和textStyle属性使用方式类似,只是作用对象不同。

12.entryTextColor属性用于设置选择项的文本字体颜色。

13.entryTextSize属性用于设置选择项的文本字体大小。

spinner控件有两个重要函数,分别为getSelectedItem和setSelection,分别用于获取当前选择项和设置选择项。

1.getSelectedItem函数会返回当前选择项,返回类型为字符串。

2.setSelection函数用于设置当前选择项,需要传递选择项索引一个参数,参数类型为数字。通过这个函数发现,选择项需要通过转换才能再次设置到下拉菜单,我已经给大家封装好。小伙伴们可以根据后面案例中的代码查看,以后只需要调用封装函数即可。

由于很多样式是针对选择项的,截图过程中会看不到,我也不能将每个小功能都截图。大家可以运行代码后,再查看下样式。除了我上面标注"无效"的属性外,所有属性都是有效的,我是挨着测试的。

spinner.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 使用默认设置 -->
    <spinner id="test1" entries="选项A|选项B|选项C|选项D"></spinner>

    <!-- 设置菜单呈现模式为对话框模式,没有效果 -->
    <spinner id="test2" entries="选项A|选项B|选项C|选项D" spinnerMode="dialog"></spinner>

    <!-- 设置水平偏移,没有效果 -->
    <spinner id="test3" entries="选项A|选项B|选项C|选项D" dropDownHorizontalOffset="100"></spinner>

    <!-- 设置水平偏移垂直 -->
    <spinner id="test4" entries="选项A|选项B|选项C|选项D" dropDownVerticalOffset="100"></spinner>

    <!-- 设置弹框背景 -->
    <spinner id="test5" entries="选项A|选项B|选项C|选项D" popupBackground="#0077d9"></spinner>

    <!-- 设置被选择项的文本字体样式 -->
    <spinner id="test6" entries="选项A|选项B|选项C|选项D" textStyle="bold"></spinner>

    <!-- 设置被选择项的文本字体颜色和大小 -->
    <spinner id="test7" entries="选项A|选项B|选项C|选项D" textColor="#0077d9" textSize="20"></spinner>

    <!-- 将选择项的文本字体样式、颜色和大小均改变 -->
    <spinner id="test7" entries="选项A|选项B|选项C|选项D" entryTextStyle="italic" entryTextColor="#0077d9"
        entryTextSize="20"></spinner>
</vertical>

spinner.js文件代码如下:

ini 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "spinner.xml");

// 获取id为test3的下拉菜单控件
let currentView = ui.test3;


// 获取控件当前选择项的值
let currentValue = currentView.getSelectedItem();
console.log("下拉菜单test3的当前选项为" + currentValue);

// 将控件当前选项设置为选项B
setSpinnerSelectedValue(currentView, "选项B");
console.log("修改后,下拉菜单test3的当前选项为" + currentView.getSelectedItem());


// 设置Spinner初始值
function setSpinnerSelectedValue(currentSpinner, value) {
  let entries = getSpinnerEntries(currentSpinner);
  let currentIndex = entries.indexOf(value);
  currentSpinner.setSelection(currentIndex);
}

// 获取Spinner选项值
function getSpinnerEntries(currentSpinner) {
  const adapter = currentSpinner.getAdapter();
  let entries = [];
  for (let i = 0; i < adapter.getCount(); i++) {
    entries.push(adapter.getItem(i));
  }
  return entries;
}

12.timepicker

用于显示时间。此控件包含timePickerMode一个特有属性。

timePickerMode属性用于设置时间选择器的呈现方式,可以传递两个值,分别为spinner和clock,分别代表滑动样式和时钟样式,默认为clock(时钟样式)。

时间选择器控件有四个重要函数,分别为getHour、getMinute、setHour和setMinute,分别用于获取当前时间的小时、获取当前时间的分钟、设置当前时间的小时,设置当前时间的分钟。其中,setHour和setMinute函数只允许传递小时或分钟一个参数,参数类型均为数字。

像时间选择器这种控件,虽然好看,但是我们脚本开发过程中很少使用。脚本中定时任务功能使用时,我宁愿通过输入框来获取时间,而不是这种方式。后面,我会整体说明下我们需要掌握的控件,有些控件了解即可。虽然整个用户界面的内容非常多,但是我们只需要掌握关键控件的使用就好了。如果不是学习阶段,这些花哨的控件,我都不会去讲解。

timepicker.xml文件代码如下:

xml 复制代码
<scroll>
    <!-- 防止超出高度,设置滑动 -->
    <vertical>
        <!-- 使用默认样式 -->
        <timepicker id="test1"></timepicker>

        <!-- 设置为滑动样式 -->
        <timepicker id="test2" timePickerMode="spinner"></timepicker>
    </vertical>
</scroll>

timepicker.js文件代码如下:

ini 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "timepicker.xml");

// 获取id为test2的时间选择器控件
let currentView = ui.test2;

// 获取当前时间
let currentHour = currentView.getHour();
let currentMinute = currentView.getMinute();
console.log("当前时间为:" + currentHour + ":" + currentMinute);

// 设置时间
currentView.setHour(1);
currentView.setMinute(11);

// 获取修改后的当前时间
currentHour = currentView.getHour();
currentMinute = currentView.getMinute();
console.log("修改后时间为:" + currentHour + ":" + currentMinute);

13.datepicker

用于显示日期。此控件包含datePickerMode、spinnersShown、calendarViewShown、firstDayOfWeek、maxDate和minDate六个特有属性。

1.datePickerMode属性用于设置日期选择器的呈现模式,可以传递两个值,分别为spinner和calendar,分别代表滑动样式和日期样式,默认值为calendar(日期样式)。

2.spinnersShown属性用于设置是否显示滑动样式日期选择器,接收boolean类型的数据,默认为true,如果设置为false会导致滑动样式的日期选择器隐藏。

3.calendarViewShown属性用于设置是否显示日期样式,此属性设置无效。

4.firstDayOfWeek属性用于设置每周的第一天,可以传递17七个值,默认为1,也就是周天。需要注意,17的对应关系为周天至周六。

5.maxDate属性用于设置支持的最大日期,接收格式为yyyy/MM/dd。这个属性不要使用了,设置上会出现奇奇怪怪的问题,后面给出了设置最大日期的方式。

6.minDate属性用于设置支持的最小日期,接收格式为yyyy/MM/dd。这个属性不要使用了,设置上会出现奇奇怪怪的问题,后面给出了设置小日期的方式。

datepicker控件有几个重要函数,具体使用方式可以查看后面案例中的代码。我能介绍那么多控件的函数,是我都记住了吗?我当然没有那么厉害,我都是通过封装的对象分析函数,来打印这些函数,然后通过函数名来判断功能,最后测试使用。细心的小伙伴已经发现,我介绍控件函数重要函数都是遵循取值和改值两个原则,我们的控件部分是需要和用户交互的。取值是为了获取用户设置的值,改值是为了设置初始值。

很多函数其实根据函数名就能判断出功能了,以后我就不介绍这些函数了。大家可以详细看下代码,代码都有这些函数的使用方式。

datepicker.xml文件代码如下:

xml 复制代码
<scroll>
    <vertical>
        <!-- 使用默认样式 -->
        <datepicker id="test1"></datepicker>

        <!-- 设置滑动样式 -->
        <datepicker id="test2" datePickerMode="spinner"></datepicker>

        <!-- 设置滑动样式,并且隐藏 -->
        <datepicker id="test3" datePickerMode="spinner" spinnersShown="false"></datepicker>

        <!-- 使用默认样式,并隐藏 -->
        <datepicker id="test4" datePickerMode="calendar" calendarViewShown="false"></datepicker>

        <!-- 设置每周第一天为周一 -->
        <datepicker id="test5" firstDayOfWeek="2"></datepicker>

        <!-- 设置支持的日期为2026年1月 -->
         <datepicker id="test6"></datepicker>
    </vertical>
</scroll>

datepicker.js文件代码如下:

ini 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "datepicker.xml");
// 获取id为test6的日期选择器控件
let currentView = ui.test6;

// 设置最小值为2026年1月1日
currentView.setMinDate(new Date(2026, 0, 1).getTime());
// 设置最小值为2026年1月31日
currentView.setMaxDate(new Date(2026, 0, 31).getTime());

// 获取id为test1的日期选择器控件
currentView = ui.test1;
// 获取时间选择器的当前年
let currentYear = currentView.getYear();
// 获取时间选择器的当前月
let currentMonth = currentView.getMonth();
// 获取时间选择器的当前日
let currentDay = currentView.getDayOfMonth();
console.log("当前日期为" + currentYear + "-" + currentMonth + "-" + currentDay);


// 修改日期为2026年1月15日
currentView.updateDate(2026, 1, 15);
// 获取时间选择器的当前年
currentYear = currentView.getYear();
// 获取时间选择器的当前月
currentMonth = currentView.getMonth();
// 获取时间选择器的当前日
currentDay = currentView.getDayOfMonth();
console.log("修改后日期为" + currentYear + "-" + currentMonth + "-" + currentDay);

14.fab

用于显示悬浮按钮。此控件函数src一个特殊属性,src属性与img控件的src属性作用和使用方式一致,用于指定显示图片。此控件一般配合frame布局使用,更方便指定悬浮位置。同时,通过监听点击等事件,方便与用户的交互。

为了更好的模拟使用场景,我将主要内容放在scroll(滚动)控件中,这样能够保证内容过多时也能通过滑动查看。并且,固定好了悬浮按钮的位置,不会影响ui的整体布局。

fab.xml文件代码如下:

xml 复制代码
<frame>
    <scroll h="*">
        <vertical>
            <!-- ui样式设置 -->
            <!-- 设置超过屏幕高度,检查滚动条是否生效 -->
            <text text="1111" marginTop="300"></text>
            <text text="1111" marginTop="300"></text>
            <text text="1111" marginTop="300"></text>
            <text text="1111" marginTop="300"></text>
            <text text="1111" marginTop="300"></text>
            <text text="1111" marginTop="300"></text>
        </vertical>
    </scroll>
    <!-- 设置悬浮按钮位置为左下角 -->
    <fab id="testFab" w="auto" h="auto" src="@drawable/ic_add_black_48dp" layout_gravity="bottom|right"
        margin="10"></fab>
</frame>

fab.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "fab.xml");

// 监听悬浮按钮点击事件
ui.testFab.on("click", function () {
    console.log("悬浮按钮被点击了");
});

15.toolbar

用于显示标题栏。此控件函数logo、logoDescription、navigationIcon、popupTheme、title、titleTextColor、titleMargin、titleMarginBottom、titleMarginTop、titleMarginStart、titleMarginEnd、subtitle和subtitleTextColor十三个特有属性。

1.logo属性用于设置标题栏的logo,与img控件的src属性作用和使用方式一致。

2.logoDescription属性用于设置logo的描述,不会直接显示,但是无障碍点击图片能够获取图片的描述。

3.navigationIcon属性用于设置导航栏按钮,设置后无效。

4.popupTheme属性用于设置弹出菜单的主题,不清楚具体作用,并且尝试了多个值都会报错。

5.title属性用于设置主标题内容。

6.titleTextColor属性用于设置主标题字体颜色。

7.titleMargin属性用于设置主标题外边距,此属性与视图的margin属性有所不同,titleMargin属性只能传一个值,然后在四个方向生效。margin属性传递两个值和四个值指定边距的方式,在titleMargin属性中无法使用,传递值超过一个会报错。

8.titleMarginBottom属性用于设置主标题底部边距。

9.titleMarginTop属性用于设置主标题顶部边距。

10.titleMarginStart属性用于设置主标题左侧边距。

11.titleMarginEnd属性用于设置主标题右侧边距。

12.subtitle属性用于设置副标题内容。

13.subtitleTextColor属性用于设置副标题字体颜色。

需要注意,边距设置不能过大,一般不需要设置边距,否则会导致内容隐藏。后面,我将边距设置为20后,都会导致副标题部分隐藏。

toolbar.xml文件代码如下:

xml 复制代码
<scroll>
    <vertical>
        <!-- 使用默认主题,默认是白色字体,几乎看不清楚,后面都设置黑色字体 -->
        <toolbar title="主标题" subtitle="副标题"></toolbar>

        <!-- 设置黑色字体 -->
        <toolbar title="主标题" titleTextColor="#000000" subtitle="副标题" subtitleTextColor="#000000"></toolbar>

        <!-- 设置主标题边距,titleMargin只能接受一个值 -->
        <toolbar title="主标题" titleTextColor="#000000" titleMargin="20" subtitle="副标题"
            subtitleTextColor="#000000"></toolbar>

        <!-- 通过单方向边距方式,实现titleMargin边距设置方式 -->
        <toolbar title="主标题" titleTextColor="#000000" titleMarginStart="20" titleMarginTop="20"
            titleMarginEnd="20" titleMarginBottom="20" subtitle="副标题"
            subtitleTextColor="#000000"></toolbar>

        <!-- 设置logo -->
        <toolbar title="主标题" titleTextColor="#000000" subtitle="副标题" subtitleTextColor="#000000"
            logo="file:///storage/emulated/0/com.py.test/baidu.png"></toolbar>

        <!-- 设置logo描述,不会显示。在无障碍功能时,点击图片自动获取图片描述 -->
        <toolbar title="主标题" titleTextColor="#000000" subtitle="副标题" subtitleTextColor="#000000"
            logo="file:///storage/emulated/0/com.py.test/baidu.png" logoDescription="百度搜索图片"></toolbar>

    </vertical>
</scroll>

toolbar.js文件代码如下:

java 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// 由于图片读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "toolbar.xml");

16.card

用于显示卡片。此控件属于特殊控件,类似于布局,控件里面可以包含其他控件或布局。此控件包含cardBackgroundColor、cardCornerRadius、cardElevation、contentPadding、contentPaddingLeft、contentPaddingRight、contentPaddingTop、contentPaddingBottom和foreground九个特有属性。

1.cardBackgroundColor属性用于设置卡片背景,类似于视图的bg属性,只是作用对象不同。

2.cardCornerRadius属性用于设置卡片的圆角半径。

3.cardElevation属性用于设置卡片在z轴上的高度。说实话,我没看出这个属性的作用,不知道是失效了,还是怎么了。

4.contentPadding属性用于设置卡片内边距,传值和功能与控件的padding属性类似,只是作用对象不同。

5.contentPaddingLeft属性用于设置卡片左侧内边距。

6.contentPaddingRight属性用于设置卡片右侧内边距。

7.contentPaddingTop属性用于设置卡片顶部内边距。

8.contentPaddingBottom属性用于设置卡片底部内边距。

9.foreground属性用于设置卡片前景,类似于视图的foreground属性,只是作用对象不同。

其中,内边距设置时,会导致卡片的背景色消失,需要注意下。

card.xml文件代码如下:

xml 复制代码
<scroll>
    <vertical>
        <!-- 使用默认样式 -->
        <card>
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>

        <!-- 设置背景色 -->
        <card cardBackgroundColor="#0077d9">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>

        <!-- 设置卡片圆角半径 -->
        <card cardCornerRadius="20" cardElevation="50">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>

        <!-- 设置卡片z轴上的高度 -->
        <card cardCornerRadius="20" cardElevation="50">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>

        <!-- 设置卡片内边距 -->
        <card contentPadding="10 20 30 40">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>
        <!-- 设置分割线 -->
        <text w="*" h="1" bg="#cccccc"></text>

        <!-- 设置卡片四个方向边距,与上面设置对应 -->
        <card contentPaddingLeft="10" contentPaddingTop="20" contentPaddingRight="30"
            contentPaddingBottom="40">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>
        <!-- 设置分割线 -->
        <text w="*" h="1" bg="#cccccc"></text>
        <!-- 设置前景,设置为主题背景色 -->
        <card foreground="?attr/colorPrimary">
            <vertical padding="16">
                <text text="测试内容1"></text>
                <text text="测试内容2"></text>
            </vertical>
        </card>
    </vertical>


</scroll>

card.js文件代码如下:

java 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// 由于图片读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "card.xml");

17.list

列表布局。此控件属于特殊控件,类似于布局,控件里面可以包含其他控件或布局。由于我们使用的是免费版autojs,ui中无法使用动态值,但是此控件能够实现将js的值数组对象传入ui,然后在ui中动态赋值对象属性,并且循环显示子视图。list控件类似于前端显示过程中for属性的作用,都是用于循环显示。

list控件有一个重要赋值函数和多个可以监听的事件,具体使用方式可以查看后面的案例。其中,item_data_bind事件监听无效,item_bind事件中一定要等页面加载完成后再操作数据。需要特别注意的是,列表初次加载时,只会加载当前页面列表项视图,当滑动后,再加载后面未加载的视图。这样设计优点很多,如果列表项非常多,初次加载时间大大减少,后面再按需加载视图,没有查看到的视图不会加载。一定好好看下后面案例代码中的注释和打印信息规律,有些注意事项在注释中说明了。

list.xml文件代码如下:

xml 复制代码
<scroll>
    <list id="testList">
        <!-- 动态获取每组数据中的内容,并动态设置id -->
        <horizontal marginTop="60" id="obj{{index+1}}">
            <text id="text{{index+1}}" text="内容:{{text}}"></text>
            <text id="content{{index+1}}" text="具体内容:{{content}}" marginLeft="20"></text>
        </horizontal>
    </list>
</scroll>

list.js文件代码如下:

javascript 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// 由于图片读取绝对路径,并且无法动态读取
// 我们可以将图片保存一份到固定文件夹,再读取就不会出错了
// 保存文件总地址
let fileRootPath = "/storage/emulated/0/com.py.test/";
let imgFilePath = fileRootPath + "baidu.png";
// 如果图片不存在,复制一份过去
if (!files.exists(imgFilePath)) {
    // 读取图片
    let img = images.read(relativeFolder + "baidu.png");
    //复制一份图片到总地址
    images.save(img, imgFilePath, "png");
    // 回收图片
    img.recycle();
}

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "list.xml");

let items = [
    {
        index: 0,
        text: "内容1",
        content: "具体内容1"
    },
    {
        index: 1,
        text: "内容2",
        content: "具体内容2"
    },
    {
        index: 2,
        text: "内容3",
        content: "具体内容3"
    },
    {
        index: 3,
        text: "内容4",
        content: "具体内容4"
    },
    {
        index: 4,
        text: "内容5",
        content: "具体内容5"
    },
    {
        index: 5,
        text: "内容6",
        content: "具体内容6"
    },
    {
        index: 6,
        text: "内容7",
        content: "具体内容7"
    },
    {
        index: 7,
        text: "内容8",
        content: "具体内容8"
    },
    {
        index: 8,
        text: "内容9",
        content: "具体内容9"
    },
    {
        index: 9,
        text: "内容10",
        content: "具体内容10"
    },
    {
        index: 10,
        text: "内容11",
        content: "具体内容11"
    },
    {
        index: 11,
        text: "内容12",
        content: "具体内容12"
    },
];

// 获取列表控件
let currentListView = ui.testList;
// 将数组传递给列表控件
currentListView.setDataSource(items);

// 监听列表项点击事件
// 返回四个参数
// item:列表项数据
// index:列表项索引
// itemView:列表项视图
// listView:列表视图
currentListView.on("item_click", function (item, index, itemView, listView) {
    console.log("第" + (index + 1) + "列表项被点击了,text的值:" + item.text + ",content的值:" + item.content);
    console.log("列表项视图为" + itemView);
    console.log("列表视图为" + listView);
    console.log("------------------------------------------------------------------");
});

// 监听列表项长按事件
// 返回五个参数
// event:事件
// item:列表项数据
// index:列表项索引
// itemView:列表项视图
// listView:列表视图
currentListView.on("item_long_click", function (event, item, index, itemView, listView) {
    console.log("第" + (index + 1) + "列表项被长按了,text的值:" + item.text + ",content的值:" + item.content);
    console.log("当前事件" + event);
    console.log("列表项视图为" + itemView);
    console.log("列表视图为" + listView);
    console.log("------------------------------------------------------------------");
});

// 监听列表项视图绑定事件
// 返回两个参数
// itemView:列表项视图
// itemHolder:列表项管理对象,包含item和position两个属性,item:列表项数据;position:列表项位置
currentListView.on("item_bind", function (itemView, itemHolder) {

    // 等待页面加载完成
    // 一定要保证页面加载完成后再执行,否则会出现空数据
    // 这里一定要设置时间,如果不设置时间,后面滑动加载的视图会获取不到数据
    ui.post(() => {
        let index = itemHolder.position;
        let item = itemHolder.item;
        console.log("第" + (index + 1) + "个列表项视图完成绑定,text的值:" + item.text + ",content的值:" + item.content);
        // 获取列表项视图的id,并替换掉id前缀
        console.log("列表项视图的id为" + itemView.attr("id").replace("@+id/", ""));
        console.log("------------------------------------------------------------------");
    }, 1000);

});

// 监听列表项数据绑定,
currentListView.on("item_data_bind", function (itemView, itemHolder) {

    // 等待页面加载完成
    // 一定要保证页面加载完成后再执行,否则会出现空数据
    ui.post(() => {
        let index = itemHolder.position;
        let item = itemHolder.item;
        console.log("第" + (index + 1) + "个列表项数据完成绑定,text的值:" + item.text + ",content的值:" + item.content);
        // 获取列表项视图的id,并替换掉id前缀
        console.log("列表项视图的id为" + itemView.attr("id").replace("@+id/", ""));
        console.log("------------------------------------------------------------------");
    });

});

// 等待页面加载完成
ui.post(() => {
    // 循环获取控件中的信息
    for (let index = 0; index < items.length; index++) {
        let currentNum = index + 1;
        let currentTextView = ui["text" + currentNum];
        let currentContentView = ui["content" + currentNum];
        if (currentTextView && currentContentView) {
            console.log("第" + currentNum + "条数据,text的值:" + currentTextView.text() + ",content的值" + currentContentView.text());
        } else {
            console.log("第" + currentNum + "条数据获取失败");

        }
    }
});

18.tabs

用于显示选项卡。此控件含有tabGravity、tabIndicatorColor、tabIndicatorHeight、tabMode、tabTextColor和tabSelectedTextColor六个特有属性。

1.tabGravity属性用于设置设置选项卡重力方向,类似于视图的gravity属性,只是作用对象不同。此属性通常与tabMode配合使用。

2.tabIndicatorColor属性用于设置选项卡指示器颜色,选项卡指示器也就当前选中选项卡下方的小条。

3.tabIndicatorHeight属性用于设置选项卡指示器的高度。

4.tabMode属性用于设置选项卡的行为模式,可以传递两个值,分别为fixed和scrollable,分别用于设置固定选项卡和可滚动选项卡,默认为fixed(固定选项卡)。在选项过多时,固定选定卡超出屏幕部分会隐藏,可滚动选项卡超出屏幕部分可以滑动查看。

5.tabTextColor属性用于设置未选中选项卡的字体颜色,由于tabSelectedTextColor属性不能生效,此属性也不用在xml中设置,而是统一通过函数进行设置。

6.tabSelectedTextColor属性用于设置选中选项卡的字体颜色,此属性不能生效。由于默认这个属性为白色,如果不能设置会导致整个选项卡无法使用。因此,我找到了一种通过函数设置字体颜色方式,由于函数必须传递两个参数,tabTextColor属性也会通过此函数设置。

在选项卡部分,我们使用到了新视图viewpager,这个视图可以只在选项卡部分进行使用,就不单独开一个标题进行介绍了。viewpager是一个视图容器,用于包裹多个视图页面。我们将这个视图容器绑定到选项卡中,选项卡会自动将视图容器下第一层的视图绑定到不同选项卡中。按照正常情况下,我们在视图容器中绑定不同视图的标题,到了选项卡中,会分别解析成对应名称。但是,现在无法解析成功,导致选型卡名称为空,我后来通过循环修改名称的方式完成了这个功能。

由于选项卡部分在js设置内容过多,我只选择一个选项卡进行测试属性了。如果再设置多个选项卡,每个选项卡都得需要js配置,会导致js内容非常多。不过,大家放心,我会测试每个属性,除了我表明无效的属性外,大家可以放心使用。

tabs控件不能放在scroll布局下面,否则会报错。tabs.xml文件代码如下:

xml 复制代码
<vertical>
    <!-- 选项卡,尽量多使用属性来测试功能 -->
    <tabs id="testTabs" tabGravity="left" tabIndicatorColor="#f4c51f" tabIndicatorHeight="10"
        tabMode="scrollable" />
    <!-- 视图容器 -->
    <viewpager id="testViewpager">
        <vertical padding="16" id="test1">
            <text text="测试内容1"></text>
            <text text="测试内容2"></text>
        </vertical>
        <vertical padding="16" id="test2">
            <text text="测试内容3"></text>
            <text text="测试内容4"></text>
        </vertical>
        <vertical padding="16" id="test3">
            <text text="测试内容5"></text>
            <text text="测试内容6"></text>
        </vertical>
        <vertical padding="16" id="test4">
            <text text="测试内容7"></text>
            <text text="测试内容8"></text>
        </vertical>
        <vertical padding="16" id="test5">
            <text text="测试内容9"></text>
            <text text="测试内容10"></text>
        </vertical>
        <vertical padding="16" id="test6">
            <text text="测试内容11"></text>
            <text text="测试内容12"></text>
        </vertical>
        <vertical padding="16" id="test7">
            <text text="测试内容13"></text>
            <text text="测试内容14"></text>
        </vertical>
    </viewpager>
</vertical>

tabs.js文件代码如下:

ini 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "tabs.xml");

// 获取id为testTabs的选项卡
let currentTabsView = ui.testTabs;

// 获取id为testViewpager的视图容器
let currentViewPager = ui.testViewpager;

// 为选项卡绑定视图容器
currentTabsView.setupWithViewPager(currentViewPager);

// 为视图容器多个页面设置标签
let titleArray = ["选项一", "选项二", "选项三", "选项四", "选项五", "选项六", "选项七"];

// 设置每个页面标题,不会生效
// 有可能是视图容器设置成功,然后选项卡没有绑定
// 实际使用时可以去掉,我是为了表示不生效才留着的
// currentViewPager.setTitles(titleArray);

// 设置未选中和选中状态下字体颜色
currentTabsView.setTabTextColors(colors.parseColor("#000000"), colors.parseColor("#0077d9"));

// 由于选项卡的标题不生效,遍历修改下,强制其生效
titleArray.forEach((currentTitle, index) => {
    let currentTabView = currentTabsView.getTabAt(index);
    currentTabView.setText(currentTitle);
});

19.appbar

用于顶端栏布局。此控件不会单独使用,会配置toolbar控件使用。appbar控件作为父视图,而toolbar作为子视图。toolbar控件前面已经介绍,具体可以查看序号15的内容。appbar控件一般不用设置属性,只作为toolbar控件的父视图。

appbar.xml文件代码如下:

xml 复制代码
<vertical>
    <appbar>
        <!-- 默认颜色 -->
        <toolbar id="toolbar" title="测试标题"/>
        <!-- 设置背景色为蓝色 -->
        <toolbar id="toolbar" title="测试标题" bg="#0077d9"/>
    </appbar>
</vertical>

appbar.js文件代码如下:

csharp 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "appbar.xml");

6.函数

1.前言

官方文档中的使用这部分函数是,ui前面会加上" <math xmlns="http://www.w3.org/1998/Math/MathML"> ",我也不知道这是为什么。我们使用时,一定不要加" ",我也不知道这是为什么。我们使用时,一定不要加" </math>",我也不知道这是为什么。我们使用时,一定不要加"",直接通过"ui.函数"的方式调用即可。

2.layout

将xml配置加载到ui中,需要传递xml配置一个参数,参数类型为xml。需要注意的是,xml配置中不能包含注释,否则会报错。这种加载方式非常不灵活,我一般不会使用。

layout.js文件代码如下:

javascript 复制代码
"ui";

ui.layout(<vertical>
    <text text="测试内容"></text>
</vertical>)

3.layoutFile

将xml文件加载到ui中,需要传递xml文件路径,参数类型为字符串。这是我推荐加载xml的方式,由于经常使用,这里就不介绍了。

4.inflate

将xml文件挂载到视图下面,需要传递三个参数,分别为xml内容、父视图和是否渲染的视图加载到父视图(默认为false),参数类型分别为字符串、视图和boolean,返回加载后的视图,返回类型为视图。我们使用时,一般传递前两个参数即可,最后那个参数使用默认。这个函数应用场景非常多,比如我们通过勾选不同的任务配置不同的信息,就可以通过这个函数挂载不同的xml完成。由于我推荐使用xml文件的方式来显示ui,我们可以先将xml文件读取后,再挂载到视图下面。过程需要两步,先需要获取到xml挂载到父视图的视图,然后将视图绑定到父视图。

为了少写个文件,我就直接将text控件的案例xml挂载到inflate案例中了。

inflate.xml文件代码如下:

bash 复制代码
<vertical id="container">

</vertical>

inflate.js文件代码如下:

ini 复制代码
// 指定采用ui运行的方式
"ui";

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "用户界面";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
// 采用开发环境
let relativeFolder = devRelativeFolder;

// ui保存文件夹
let uiFolder = relativeFolder + "ui/";

// 读取 XML 文件内容
ui.layoutFile(uiFolder + "inflate.xml");

// text控件文件地址
let textViewFilePath = uiFolder + "text.xml";
// 读取text控件xml文件内容
let textXmlContent = files.read(textViewFilePath);

// 将text控件直接挂载到id为container的视图上
let textView = ui.inflate(textXmlContent, ui.container);

// 将视图绑定到id为container的视图上
ui.container.addView(textView);

5.registerWidget

注册自定义组件,需要两个参数,分别为组件名称和组件视图,参数类型分别为字符串和函数。这个函数一般用于自定义ui组件时使用,过程非常复杂,不推荐大家学习。如果对这部分感兴趣的小伙伴,可自行学习。细心的小伙伴们已经发现,控件部分没有介绍有关控制台的控件。因为免费版没有这部分控件,使用会报错,因此没有介绍。我后期会考虑在插件中写个注册控制台控件的函数,控件注册的详细过程会在插件中完成,大家直接使用就好了。

6.isUiThread

用于判断当前线程是否为ui线程,返回是否为ui线程,返回类型为boolean。此函数一般用于使用ui的多线程判断。

7.findView

通过id获取视图,需要传递视图id一个参数,参数类型为字符串,返回获取到的视图,返回类型为视图。

8.finish

销毁ui。我不推荐大家使用这个函数,使用ui部分不用销毁,我们可以通过返回手机主界面再运行的方式完成。

9.setContentView

将视图对象设置为当前视图,需要传递视图对象一个参数,参数类型为视图。我目前都没有找到此函数的使用场景,不推荐大家使用。

10.post

等待ui加载完成后再运行,需要传递两个参数,分别为回调函数和延迟时间,参数类型分别为函数和整数。一般情况下,第二个参数延迟时间不用传递,但是list控件中那个懒加载时,不加延迟会报错。此函数非常重要,但是上面介绍很多控件时使用,这里就不再介绍。当我们获取ui中控件或者值为空时,可以考虑下用此函数等待页面加载完成后再运行。此函数支持箭头的的方式传递第一个参数,一般情况下,最好使用箭头函数。

11.run

用于在ui线程中运行代码,需要传递回调函数一个参数,参数类型为函数,支持箭头函数传递方式,推荐使用箭头函数。此函数常用于使用ui的多线程情况,尤其是在非ui线程操作ui时,最好通过此函数保证ui代码生效。此函数使用场景很简单,如果无法区分当前线程是否为ui线程,可以考虑ui基本函数都报错时,再采用此函数执行代码。

12.statusBarColor

用于设置当前界面状态栏颜色,需要传递颜色一个参数,参数类型为字符串或整数,一般不会使用此函数。

13.useAndroidResources、clearDiskCache与clearMemory

这个三个函数一般不会使用,我无法保证一定存在或者使用正常,如果感兴趣可以自行学习。

7.总体情况

由于ui部分内容非常多,有些小伙伴可能无法一下学习,我们可以先学习重要部分,其他部分用到时再查文档。

视图部分所有属性、函数和事件建议全部掌握,毕竟这是一个基础,用到部分非常多。

布局部分内容也非常重要,建议大家全部掌握。

控件部分优先掌握序号2-7六个控件、第11控件和序号15-19六个控价,其他控件了解即可。

函数部分优先账号序号2-7六个函数、第10函数和第11函数,其他函数了解即可。
特别注意,只有通过个人主页博客或者个人介绍中方式,才能获取源码

相关推荐
炫饭第一名2 小时前
速通Canvas指北🦮——图形、文本与样式篇
前端·javascript·程序员
进击的尘埃2 小时前
React useEffect 的闭包陷阱与竞态条件:你以为的 cleanup 真的在正确时机执行了吗
javascript
进击的尘埃2 小时前
TypeScript 类型体操进阶:用 Template Literal Types 实现编译期路由参数校验
javascript
滕青山2 小时前
文本字符数统计 在线工具核心JS实现
前端·javascript·vue.js
十二7402 小时前
前端缓存踩坑实录:从版本号管理到自动化构建
前端·javascript·nginx
进击的尘埃2 小时前
前端大文件上传全方案:切片、秒传、断点续传与 Worker 并行 Hash 计算实践
javascript
西梯卧客2 小时前
[1-2] 数据类型检测 · typeof、instanceof、toString.call 等方式对比
javascript
wuhen_n2 小时前
响应式探秘:ref vs reactive,我该选谁?
前端·javascript·vue.js
wuhen_n2 小时前
setup 的艺术:如何组织我们的组合式函数?
前端·javascript·vue.js