# MAML 概述[10th Sept 2023]

  • MAML 引擎脚本语言
    MIUI Application Markup Language for MORE (MIUI MORE 引擎应用标记语言)

  • 概述
    最初用于百变锁屏,使用 xml 用特定的语法描述锁屏界面。后来不断增强功能,逐步演化成一套接近通用的界面描述语言和图形渲染引擎,在一定需求下可用于开发风格多变的用户界面。可方便地通过更换皮肤改变界面风格、动画甚至交互方式。

    MAML 语言和 Android 的界面描述 xml 类似. 所不同的是 Android 描述的是静态界面,对界面元素的更改依赖 java 代码。MAML 描述的是静态界面+动态属性,UI 在时间线上按一定的帧率不断刷新,UI 显示根据元素属性的变量表达式的计算结果实时更新。MAML 语言和运行时引擎已经从锁屏中独立出来作为 MIUI 内置的通用框架,除了显示时间日期等,还支持查询标准 Content Provider 来获取各种信息如天气。显示图片文本等各种元素,各种动画,滑动点击等界面交互控件,适于实现展示信息或有简单交互操作的界面。比如时钟、天气小部件、闹钟响铃界面。

    框架支持动态帧率,不必按照固定帧率不停渲染,在没有动画和更新的时侯停止渲染,此时仅占用极少资源,对于缓慢变化的动画使用低帧率渲染,高动态的动画开始后立即调整到高帧率全速渲染。全速渲染时全屏帧率基本可以达到 60-120 帧。合理使用可以既炫酷又不费电。

MAML 在百变锁屏中的应用

百变锁屏在主题包里的 lockscreen/advance 目录下,manifest.xml 文件是描述脚本。

<?xml version="1.0" encoding="utf-8"?>
<Lockscreen version="1" frameRate="30" screenWidth="1080" useHardwareCanvas="true">
    <Image />
    <Image />
    <Unlocker/>
    <Unlocker/>
    <DateTime/><Text/>
</Lockscreen>
属性 类型 表达式/变量 释义
frameRate int x/x 指定帧率,如果动画缓慢,可以指定小一点的值,省电。默认为 30。
screenWidth int x/x 设定屏幕宽度标准。如果指定为 720,锁屏中所有元素的位置都按 720p 的布局编写,1080p、480p 等分辨率的手机会自动进行缩放。
如果指定为 1080,锁屏中所有元素的位置都按 1080px 的布局编写,示例:screenWidth="1080"
displayDesktop boolean x/x 默认为 false,透视到桌面,如果没有锁屏壁纸或者锁屏壁纸可以被移开或透明时可以看到桌面 launcher 或者是锁屏前的应用程序。(目前系统已不支持)。
showSysWallpaper boolean x/x 默认为 false,是否在锁屏界面显示桌面壁纸。开启后如果没有指定锁屏壁纸<Wallpaper/>,会将桌面壁纸作为锁屏的壁纸(目前系统已不支持)。
useHardwareCanvas boolean x/x 为支持硬件加速的设备开启硬件加速。MIUI13新增

MAML 在百变壁纸及动态图标中的应用

  • 百变壁纸

百变壁纸在主题包的 miwallpaper 目录下,描述文件也是 manifest.xml。根节点表示与百变锁屏基本一致:

<MiWallpaper version="1" frameRate="30" screenWidth="1080" useHardwareCanvas="true">
    <Image/>
    <Group/></MiWallpaper>
screenWidth                 表示百变壁纸默认分辨率
wallpaper_offset_pixel_x    偏移的像素数 (0 ~ -1*屏宽)
wallpaper_offset_x          偏移百分比 (0 ~ 1.0)

要实现元素跟随手指滑动的效果,需要借助#wallpaper*offset_pixel_x,#wallpaper_offset_x

二者关系:

#wallpaper_offset_x * 屏宽 = -1\_#wallpaper_offset_pixel_x

滑动时: 在第一屏,wallpaper_offset_pixel_x = 0, wallpaper_offset_x = 0;
滑到最后一屏,wallpaper_offset_pixel_x = -1*屏宽,wallpaper_offset_x = 1.0
为了适配不同分辨率机型,建议使用#wallpaper_offset_x。

建议的设计方式:

  1. 壁纸切成双屏宽 (屏宽指的是 screenWidth)
  2. 壁纸定位:x="-#wallpaper_offset_x * 屏宽"
  3. 需要跟随滑动的元素定位:x="-#wallpaper_offset_x * 屏宽 + 相对壁纸的位置"

  • 动态图标

动态图标在主题包 icons\fancy_icons\目录下,每个动态图标是一个文件夹,文件夹的名字是对应的 app 包名。例如日历的动态图标是一个叫"com.android.calendar"的文件夹,里面包含 manifest.xml 描述文件。

<Icon version="1" frameRate="30" width="168" height="168" screenWidth="1080" useVariableUpdater="" hideApplicationMessage="">
    <Image/>
    <Text/>
    <DateTime/></Icon>

hideApplicationMessage: 默认 false,屏蔽右上角的通知标志。
useVariableUpdater: 指定需要哪些系统变量,目前包括电量和时间,而且可以指定时间的更新周期,如果时间小工具每秒更新一次,需要指定更新周期为 1 秒,否则默认会每分钟更新一次。如果小工具或动态图标只显示日期,则每天更新一次,可以指定更新周期为 1 天。尽量选择较长的更新周期,以节约系统资源。

目前支持的 tag: Battery, DateTime.Day, DateTime.Hour,DateTime.Minute,DateTime.Second

如果某个小工具不需要电量或时间变量,则需要使用 useVariableUpdater="none",指定不做时间变量等更新。
如果某个小工具显示系统电量并且显示根据时间每小时更换背景图片: useVariableUpdater="Battery,DateTime.Hour"
如果需要每秒进行多次刷新,可以直接使用 frameRate

# 机型适配

一份 manifest 文件,可以同时运行在多个设备,只需要设置好在不同尺寸或密度的设备上使用的资源和缩放比例就可以了。

  • 资源适配:
extraResources="sw1000-den320:den320:1.2,sw1000-den320::1.2,sw1000-den320-large:den320:1.2"

sw1000-den320:den320:1.2 在屏宽是 1000 密度是 320 的机型上,使用的是 den320 文件夹下的资源,并且放大 1.2 倍;
sw1000-den320::1.2 在屏宽 1000 密度 320 的机型上,使用的是默认的资源,并且放大 1.2 倍)
extraResources="sw2000-den480:1.8,sw1000-den320-large:1.8 这两句话中间都是只有一个冒号,意思是 sw2000-den480 的机型使用的资源就是在 sw2000-den480 这一同名文件夹下的资源,并且放大 1.8 倍)

  • 布局适配:
extraScales="sw1000-den320:1.2,sw2000-den480-large:1.8"

适配原理:

  1. 首先把你所有自定义中出现的 den 和 sw 做一个列表 (比如这里extraResources="sw1440-den440::0.916, sw720-den320::0.667, sw480-den240::0.444,涉及到的 den 有 440, 320, 240, 再加上一个默认的 480; sw 有 1440, 720, 480, 再加一个默认的 1080(与 den480 对应) )
  2. 然后当你的主题放到一个设备上时,首先拿你的设备密度去上面的 den 列表中找,找到最贴近的一个,然后如果同样 den 有多个 sw,那么再拿设备的屏宽去这几个 sw 中找最贴近的一个,这样就找到了最合适的 swXXX-denXXX
  3. 最后,资源就取这个 sw-den 对应的目录下的资源,及缩放比例 Sr。代码中数字的缩放比例就使用 sw-den 对应的 Ss.
  4. 最最后,就是缩放比例
如果按密度缩放(即scaleByDensity="true"):   资源真正的缩放比例是Sr * 设备den / 找到的den,代码中的数字缩放比例是Ss * 设备den / 找到的den
如果按屏宽缩放(即scaleByDensity="false"):  资源真正的缩放比例是Sr * 设备sw / 找到的sw  ,代码中的数字缩放比例是Ss * 设备sw / 找到的sw

# 入门教程

看本篇教程之前,建议先看一下入门教程:锁屏入门 (opens new window)


# 壁纸元素

Wallpaper 元素引用系统设置的壁纸,除了不能指定图片源外其他和 Image 元素相同,可以有动画和其他属性控制。如果没有此元素则不显示壁纸。可以有多个。

示例:

<Wallpaper/>
<Wallpaper x="#screen_width/2" y="#screen_height/2" align="center" alignV="center" blur="100" alpha="255*#defaultScreen_x/2"/>

# 数据类型

类型 释义
int 整数数据类型,代表整数值,例如-2、0、1、100等。
number 数字数据类型,代表数字值,包括整数和浮点数。
string 字符串数据类型,代表一个字符序列,例如“hello”、“world”等。字符串通常由一系列字符组成,这些字符可以是字母、数字或符号。
boolean 布尔数据类型,只有两个值:true(真) 和 false(假)。它用于表示逻辑值,例如条件语句的结果或开关状态。

# 变量

变量的值可以在运行期间被更改

<!-- (变量用 Var 开头) -->
<Var name="" expression="" type="" const="" threshold=""/>
代码 类型 表达式/变量 释义
name string x/x 自定义变量名
expression number/string o/o 变量对应的表达式或常量。注意:字符串常量需要多一套单引号 例如expression="'我是文字'"
type number/string x/x 定义数值变量或字符串变量 默认:number
const boolean x/x 为 true 时变量会在初始化后不会重新计算,但是可以使用命令重新赋值,合理的使用const可以提高运行效率。
persist boolean x/x 变量持久化。指定为 true 后,如果没有重新给定该变量其他的值,那么这个值会一直保存,无论解锁后重新锁定或者重新应用主题都不会还原;默认 false
threshold int x/x 阈值触发,当变量值的变化超过设定的阈值时,可以触发一些命令。

数组

在变量的类型中加上 []来申明这是一个数组type="number[]" 数字类型数组; type="string[]" 文本类型数组;

可以把数组看作是一个盒子,里面可以存放一系列相同类型的数据,例如一组数字或一组字符串等。每个数据项都有一个对应的编号,这个编号称为数组的索引。

可以通过name[索引]的方式获取数组中的值,例如下方#numVar[2]的值是500,@strVar[0]的值是水瓶座。需要注意的是[]中的索引顺序是从左到右并且从0开始的

<!-- 数字类型 -->
<Var name="numVar" type="number[]" const="true" expression="" values="100,150,500,550,800,850"/>

<!-- 文本类型;注意:文本类型 必须要 包含 expression="''" 属性 -->
<Var name="strVar" type="string[]" const="true" expression="''" values="'水瓶座','双鱼座','白羊座','金牛座','双子座','巨蟹座','双子座','处女座','天秤座','天蝎座','射手座','摩羯座'"/>

<!-- 阈值示例:下面代码表示#time3 的值每变化 1,就会执行<Trigger/>里面的所有命令。 -->
<Var name="time3" expression="#minute%10" threshold="1">
     <Trigger>
         <Command target="time3_anim.visibility" value="true"/>
         <Command target="time3_anim.animation" value="play"/>
         <Command target="time3_anim_new.visibility" value="false"/>
         <Command target="time_3_anim_new.visiblity" value="true" delay="400"/>
         <Command target="time_3_anim_new.animation" value="play" delay="400"/>
     </Trigger>
</Var>

# 全局变量

触摸

#touch_x             当前触摸点的 x 坐标
#touch_y             当前触摸点的 y 坐标
#touch_begin_x       按下屏幕时的初始 x 坐标
#touch_begin_y       按下屏幕时的初始 y 坐标
#touch_begin_time    按下屏幕时的时间

屏幕

#screen_width        屏幕宽度
#screen_height       屏幕高度
#view_width          部件宽度(各插件中才使用,比如时钟)
#view_height         部件高度
#raw_screen_width    物理宽度(当前设备的屏幕分辨率,不受根节点screenWidth的影)
#raw_screen_height   物理高度

日期

#time                当前时间,long
#time_sys            系统时间毫秒数
#year                年份
#month+1             月份(取值范围是 1~12,1表示一月,2表示二月,以此类推)
#date                日期(取值范围是 1~31,1表示1日,2表示2日,以此类推)
#day_of_week-1       星期(0表示星期日,1表示星期一,2表示星期二 ... 6表示星期六)
#hour12              12小时制
#hour24              24小时制
#minute              分钟
#second              秒
#ampm                0:上午,1:下午
#time_format         0:12小时制,1:24小时制

农历

#year_lunar          农历年份
#year_lunar1864      用来计算天干地支
#month_lunar         农历月份 从0开始计
#month_lunar_leap    是否润月 0不是, 1是 
#date_lunar          农历日期 从1开始计

充电

#battery_level       当前电量,1~100
#battery_state       0正常 1充电 2电量低 3已充满

<!-- MIUI 11 开发版支持 -->
#ChargeSpeed         0 普通充电, 1 快充, 2 超级快充,3 极速秒充(120w±)
#ChargeWireState     11 有线充电, 10 无线充电, -1 未充电

图片

下面imageName需要替换成你自己图片标签的name,
例如你有一个<Image name="img" />,你在使用的时候就需要把imageName换成img

#imageName.actual_x      图片实时位置的x坐标(获取(name="imageName")图片实时位置的x坐标)
#imageName.actual_y      图片实时位置的y坐标
#imageNmae.actual_w      图片显示宽度(获取(name="imageName")图片实时宽度)
#imageNmae.actual_h      图片显示高度
#imageNmae.bmp_width     图片文件的宽度(不受裁切、缩放的影响,只根据src找到指定的图片,并检测该文件的宽度)
#imageNmae.bmp_height    图片文件的高度

文本

下面textName需要替换成你自己文本标签的name,
例如你有一个<Text name="aa" />,你在使用的时候就需要把textName换成aa

#textName.text_width     文本宽度,可以用来排版(获取(name="textName")文本宽度)
#textName.text_height    文本高度

音乐

下面musicName需要替换成你自己音乐标签的name,
例如你有一个<MusicControl name="music" />,你在使用的时候就需要把musicName换成music

#musicName.music_state           音乐当前播放状态:0暂停 1播放(musicName是你为音乐模块定义的名称)
#musicName.user_rating_style     播放源是否是系统默认音乐APP:0不是 1是
@musicName.package               当前播放源包名
@musicName.class                 当前播放源类名

解锁

#unlocker.move_x     解锁部件在x方向的偏移
#unlocker.move_y     解锁部件在y方向的偏移
#unlocker.move_dist  解锁部件移动的距离
#unlocker.state      解锁部件的状态: 0 normal, 1 pressed, 2 reached

屏下指纹

#fod_enable          系统是否启用了屏下指纹:0 关闭, 1 开启
#fod_x               指纹区域 x坐标
#fod_y               指纹区域 y坐标
#fod_width           指纹区域 宽度
#fod_height          指纹区域 高度
#fod_state_msg       指纹状态:1 手指按下,2 手指抬起,3 识别失败,4 识别成功

深色模式

#__darkmode_wallpaper       是否开启深色模式且支持调暗壁纸:0 未开启,1 已开启
#__darkmode                 是否开启深色模式:0 未开启,1 开启

#applied_light_wallpaper    壁纸主色调,仅在桌面时钟生效:0 深色,1 浅色 (有miwallpaper模块时不生效)

其他

#sms_unread_count           未读短信数
#call_missed_count          未接电话数
@next_alarm_time            下一个闹钟时间
#volume_level               现在音量
#volume_level_old           调节之前的音量 取值: 1-15 根据二者比较判断是增大还是减小
#volume_type                0 通话音量、1 系统音量、2 电话铃声、短信铃声、3 音乐播放器音量、4 闹钟音量、5 通知音量、
                            6 连接蓝牙时的通话音量、7 在某些国家强制的系统音量、8 DTMF音量、 9 TTS音量、10 FM音量
                            注意:一般锁屏下只能调3(音量播放器音量)。volume_type>=0 表示正在调节音量,调节完毕后值为-1 可根据这个显示或隐藏音量显示
#frame_rate                 当前屏幕帧率
@__miui_version_code        MIUI版本(MIUI9=6,MIUI10=8,MIUI11=9,MIUI12=10)

# 表达式

操作符 优先级 释义
+ 4
- 4
* 3 乘以
/ 3 除以
% 3 取模(这里不是百分比,是取模,注意两者的区别)
^ 10 按位进行异或运算
~ 2 按位进行取反运算
{{ 5 左移位运算符
}} 5 右移位运算符
! 2 逻辑非,相当于以前的 not
== 7 等于
!= 7 不等于
** 11 与,必须同时满足才为真
|| 12 或,两个条件满足其中一个就为真
} 6 大于
}= 6 大于等于
{ 6 小于
{= 6 小于等于

<!--示列1:#num != #num-1-->
<Iamge src="icon.png" visibility="#num != #show"/>
<!--示列2:#num ** #num-1-->
<Iamge src="icon.png" visibility="#num ** #show"/>
<!--示列3:#num == #num-1-->
<Iamge src="icon.png" visibility="#num == #show"/>


下面表格中的:eq、ne、ge、gt、le、lt、not 等为旧条件判断,目前 MIUI11 与 MIUI12 都已支持上面新表达式条件判断,建议使用新的方式。

参数 释义
eq(x, y) x==y(x 等于 y,结果为 1,否则为 0)
ne(x, y) x!=y(x 不等于 y,结果为 1,否则为 0)
ge(x, y) x>=y(x 大于等于 y,结果为 1,否则为 0)
gt(x, y) x>y(x 大于 y,结果为 1,否则为 0)
le(x, y) x<=y(x 小于等于 y,结果为 1,否则为 0)
lt(x, y) x<y(x 小于 y,结果为 1,否则为 0)
not(x) 逻辑非;x=0,not(x) 结果为 1;x=1,not(x) 结果为 0
ifelse(x, y, z) x>0(如果 x 大于 0,则结果为 y;否则取值 z)
ifelse(x1, y1, x2, y2, ... , z) 如果 x1 大于 0,则结果为 y1;否则检测 x2,如果 x2 大于 0,则为 y2,以此类推,到最后才取 z
+ 可以拼接字符串;'qwe'+'asd' 结果为 'qweasd'
eqs(@string1, @string2) 字符串比较函数,如:@string1 和 @string2 结果一致时,eqs(@string1, @string2) 结果为 1 ,否则为 0

# 内置函数

sin、cos、tan、asin、acos、atan、sinh、cosh、sqrt、abs、min、max、pow

参数 释义
len(数字) 获取变量和字符串位数
digit(数字, 第几位) 取给定数字的第几位 digit(12345, 2) = 4 (注意:原数字位数不能超过 10 位,下标从右向左,并且从 1 开始)
substr(原字符串,字串开始位置,字串长度) substr('今天真热啊',1,2) = '天真'(注意:字符位置是从左至右,并从 0 开始)
strIsEmpty(字符串变量) 判断字符串变量是否为空 strIsEmpty(@abc) 为空则为 1,不为空则为 0
isnull(数字型变量) 判断变量是否为空 isnull(#abc) 为空则为 1,不为空则为 0
ceil() 向上取整;如:6.1 或者 6.99 都取值为 7
int() 向下取整;如:6.1 或者 6.99 都取值为 6
round() 四舍五入取整
rand() 取 0 到 1 之间的随机数;如果需要随机生成 0-100 随机数,可以这样写 rand()*100
formatDate('string',#time_sys) 日期格式化成字符串;'string' 写作 'HH:mm'
strStartsWith('123456789','12') 判断字符串是否是某字符串开头,是则为 1,不是则为 0
strEndsWith('123456789','89') 判断字符串是否是某字符串结束,是则为 1,不是则为 0
strIndexOf(@string_a,'string_b') 字符 string_b 第一次出现在字符串@string_a 中的位置 如: strIndexOf('string','str')=0
strLastIndexOf(@string_a,'string_b') 字符 string_b 最后出现在字符串@string_a 中的位置 如:strLastIndexOf('starina','a')=6
strContains(@string_a,'string_b') 字符串@string_a 是否包含字符 string_b 如: strContains('string','str')=1或(true)
strReplaceAll(@string_a,'string_b','string_c') 用 string_c 替换@string_a 中所有的 string_b (strReplaceAll('abc','a','1')='1bc' ,支持正则表达式。
preciseeval('@string_a',3) 计算字符串的值,并精确到小数点后 3 位,如:preciseeval('5*5+0.333',3)==25.333 ,小数点后位数最小为1
formatFloat('%.3f',#accx) 格式化小数点后几位,并转换成字符串(%.3f 代表四舍五入到小数点后 3 位)与preciseeval不同的是,formatFloat会占位,例如3.000
strMatches(@str,'.[\+/-]$') 正则表达式
strTrim(' 123 ') 裁切字符串的开头和尾部的空格、制表、回车符('123')
strReplaceFirst('ABCdefABC','ABC','666') 替换第一个;666defABC
strToLowerCase('ABCdef') 转换成小写;abcdef
strToUpperCase('ABCdef') 转换成大写;ABCDEF

# 锁屏元素

锁屏添加文字

<Text x="50" y="500" color="#ffffff" size="48" text="hello,world!" />
color           支持16进制颜色值:#FFFFFF;支持字符串变量,如:@abc;支持函数argb(255,255,255,255)
size            文字大小
text            文字内容

锁屏插入图片

<Image x="0" y="0" src="lock_bg.jpg" />
src             图片名称路径

元素相关内容详解

<Text x="50" y="500" align="center" alignV="center" color="#ffffff" size="48" text="hello,world!" alpha="255*0.8" visibility="1" />
<Image x="0" y="0" w="512" h="512" align="center" alignV="center" src="lock_bg.jpg" alpha="255*0.8" visibility="1" />
x y             相对于屏幕左上角的坐标
h w             宽和高(这个不用解释了吧)
align           坐标点水平对齐方式left, center, right
alignV          坐标点垂直对齐方式top, center, bottom
alpha           透明度0-255,小于等于0不显示
visibility      支持表达式,大于0时显示

时间、日期

<!--图片类写法-->
<Time x="540" y="400" align="center" alignV="center" src="time.png" space="0" format="HH:mm"/>
<!--文本类写法-->
<DateTime x="540" y="400" align="center" alignV="center" size="200" color="#ffffff" formatExp="ifelse(#time_format,'HH:mm','h:mm')" fontFamily="miui-thin" />
space           图片的间隙,可以使用这个功能来对时间图片进行排版,支持正负值
format          标准日期格式;时间类型为图片时,可以不写,默认跟随系统 是否为显示 24小时制
formatExp       日期表达式格式
fontFamily      指定字体,可使用的字体在后面“可调用字体预览”中

# 添加解锁

用 Button 滑动一段距离来解锁

虽然代码量比 Unlocker 要多,但其实它比 Unlocker 更加简单和容易理解一些,并且对之后的扩展带来可能性,比如做 左右滑动与上滑解锁 不冲突的手势。

<!--解锁文字提示-->
<Text x="#screen_width/2" y="#screen_height-100-#unlockMove" align="center" alignV="center" color="#ffffff" size="42" text="向上滑动解锁"/>
<!--解锁相关 变量、动画、按钮-->
<Group name="Unlock" >
    <!--实时变量-->
    <Var name="unlockMove" expression="ifelse(#unlockDown==1,max(#touch_begin_y-#touch_y,0),max(#touch_begin_y-#touch_y,0) { 300,max(#touch_begin_y-#touch_y,0)*(1-#unlockBack),0)" />
    <!--动画-->
    <Var name="unlockBack">
        <VariableAnimation initPause="true" loop="false">
            <Item value="0" time="0" easeType="BounceEaseOut" />
            <Item value="1" time="300" />
        </VariableAnimation>
    </Var>
    <!--解锁按钮-->
    <Button w="#screen_width" h="#screen_height" >
        <Triggers>
            <Trigger action="down">
                <VariableCommand name="unlockDown" expression="1"/>
            </Trigger>
            <Trigger action="up,cancel">
                <VariableCommand name="unlockDown" expression="0" />
                <Command target="unlockBack.animation" value="play" />
                <!--condition 执行条件,当 max(#touch_begin_y-#touch_y,0) 大于等于 300px 时 unlock-->
                <ExternCommand command="unlock" condition="max(#touch_begin_y-#touch_y,0) }= 300" />
            </Trigger>
        </Triggers>
    </Button>
</Group>

看不明白这个解锁?没关系,下一章详细介绍了 按钮 的各种用途和写法。这里还用到了一些变量,不明白的变量可以先简单阅读下全局变量

Unlocker 与 Slider

Unlocker 与 Slider 的用法是一样的,都是通过滑动来激活某些操作,只不过 Unlocker 能直接解锁, Slider 需要加入解锁命令。点击这里查看更加详细的介绍

<!--解锁文字提示-->
<Text x="#screen_width/2" y="#screen_height-100-#Unlocker.move_dist" align="center" alignV="center" color="#ffffff" size="42" text="向上滑动解锁"/>
<!--name="Unlocker" 解锁名称-->
<Unlocker name="Unlocker">
    <!--触摸开始区域-->
    <StartPoint x="0" y="0" w="1080" h="#screen_height" />
    <!--达到解锁区域,注意写坐标位置-->
    <EndPoint x="0" y="-#screen_height" w="1080" h="#screen_height-200">
        <Path tolerance="800">
            <!--解锁路径-->
            <Position x="0" y="0" />
            <Position x="0" y="-#screen_height" />
        </Path>
    </EndPoint>
</Unlocker>

MAML 练习题:试着把下列简单锁屏做出来

锁屏 PSD 附件 (opens new window)


练习题


# 文本介绍

Text 文本

<Text x="50" y="500" align="center" alignV="center" color="#ffffff" size="48" textExp="'hello,world!'"/>
属性 类型 表达式/变量 释义
x number o/o 相对于屏幕左上角的坐标
y number o/o 相对于屏幕左上角的坐标
color string x/o 文字颜色,支持常量:#ffffff;支持字符串变量,@abc;支持函数 argb(255,255,255,255)(argb(透明度,红,绿,蓝) ,不支持表达式。
size number o/o 文字大小
bold boolean x/x 粗体,true 表示加粗
format string/numbe x/x paras中变量是数字类型用%d,是字符串类型用%s
paras string/number o/o 如果指定了 format , 需要在 paras 里指定 %d和%d 对应的变量表达式, 可以有多个变量表达式用"," 隔开
text string/number x/x 文字显示内容,后续带有 Exp 的参数,都表示该参数支持 表达式
textExp string/number o/o 文字显示内容表达式,可以直接调用变量等,示例:“现在时间是 9 点”,可以写成 textExp="'现在时间是'+#hour12+'点'"
width number o/o 文字宽度,当文字超过指定宽度时会被切掉。如果指定了多行显示,则会折行显示。如果指定了文字滚动,则会在指定的位置滚动显示文字
marqueeSpeed number x/x 文字滚动速度,配合上面的宽度使用 marqueeSpeed="30"
marqueeGap number x/x 滚动间隔。当文字显示完后再次出现的间隔,默认为四个汉字的宽度
rotation number o/o 旋转角度
multiLine boolean x/x true/false 是否支持多行显示,默认 false。开启后支持换行符(&#10;和\n)
spacingMult number x/x 行距倍数 默认 1
spacingAdd number x/x 行距增加量 默认 0
shadowDx number x/x 水平方向的阴影相对文字的偏移距离
shadowDy number x/x 竖直方向的阴影相对文字的偏移距离
shadowRadius number x/x 阴影的模糊半径,可以实现模糊的阴影效果
align string x/x 坐标点水平对齐方式 left, center, right
alignV string x/x 坐标点垂直对齐方式 top, center, bottom
shadowColor string x/o 阴影的颜色,支持透明度
alpha number o/o 不透明度 0 - 255
visibility number/string o/o 元素可见性支持 表达式 {=0 不可见,}0 可见
text.text_width number x/x 某行文本的宽度,可以用来排版
fontFamily string x/x 支持指定系统字体;可查看: 可调用字体预览

注意:

  • 文字在 表达式 中需要用单引号引用,数字变量和字符串变量 则不需要;如:'hello,world!',@abc,#num;
  • alpha 值可以写一个简单的表达式;如 alpha="255 * 0.8" 或 alpha="2.55 * 80",都表示 80% 的不透明度
  • visibility 支持 表达式 {=0 不可见,}0 可见;即使值为 0.00001 也可见,总之大于 0 就可见

几个实例:

<Text shadowDx="3" shadowDy="4" shadowRadius="6" shadowColor="#9c000000" .../>

<Text name="tt" .../>   //#tt.text_width为此文本的长度

显示下一个闹钟时间
<Text text="@next_alarm_time" .../>
<Text format="下一个闹钟:[%s] 电池:[%d%%]" paras="@next_alarm_time,#battery_level"/>
  • 显示当前时间
<Text x="50" y="500"  color="#ffffff" size="48" textExp="'现在时间是'+#hour12+'点'"/>

或者

<Text x="50" y="500"  color="#ffffff" size="48" textExp="'当前温度'+#weather_temperature+'°'"/>

#hour12 为系统全局变量,传递当前小时数给 maml,#weather_temperature 为天气接口获取到的当前温度,后面会讲到


  • 文字滚动
<Text x="50" y="500" w="300" color="#ffffff" size="50" marqueeSpeed="30" text="永远相信美好的事情即将发生"/>

# 可调用字体预览

Mitype:仅支持数字类型 样式 小米兰亭Pro:支持中文/英文/数字类型 样式
mitype-thin 0123456789:%+-×÷ mipro-thin 0123456789:%+-×÷
mitype-extralight 0123456789:%+-×÷ mipro-extralight 0123456789:%+-×÷
mitype-light 0123456789:%+-×÷ mipro-light 0123456789:%+-×÷
mitype-normal 0123456789:%+-×÷ mipro-normal 0123456789:%+-×÷
mitype-regular 0123456789:%+-×÷ mipro-regular 0123456789:%+-×÷
mitype-medium 0123456789:%+-×÷ mipro-medium 0123456789:%+-×÷
mitype-demibold 0123456789:%+-×÷ mipro-demibold 0123456789:%+-×÷
mitype-semibold 0123456789:%+-×÷ mipro-semibold 0123456789:%+-×÷
mitype-bold 0123456789:%+-×÷ mipro-bold 0123456789:%+-×÷
mitype-heavy 0123456789:%+-×÷ mipro-heavy 0123456789:%+-×÷

# 时间日期

时间

这里说的时间,其实是由 10 个数字图片加“:”号图片依时间格式组成的图片组合。

<!--图片类写法-->
<Time x="540" y="400" align="center" alignV="center" src="time.png" space="0" format="HH:mm"/>
<!--文本类写法-->
<DateTime x="540" y="400" align="center" alignV="center" size="200" color="#ffffff" formatExp="ifelse(#time_format,'HH:mm','h:mm')" fontFamily="mitype-light" />

src 表示时间图片的前缀,如下表示使用 time_0.png, time_1.png, ... time_9.png, time_dot.png 坐标属性支持变量表达式
space 表示时间图片的间隙,我们可以使用这个功能来对时间图片进行排版,使用正值时图片间距变大,这时可以将图片切小,节省内存。对于有投影的图片,将 space 写成负值,可以使投影重叠以节省空间

# 日期格式

显示指定格式的日期:
    formatExp:  支持日期表达式格式(formatExp="ifelse(#time_format,'HH:mm','h:mm')")
    format:     支持标准日期格式(format="HH:mm")

Text 与 DateTime 的区别 DateTime 主要用于快速格式化显示时间,支持 Text 的所有参数,DateTime 直接用下列参数显示不同的时间参数。

代码 释义 示例可能出现的结果
A 十二生肖年 鼠、牛、羊
G 公元 公元
Y 汉字年(农历) 二〇一五
YY 干支年 甲子
yy 数字年(2 位) 20
yyyy 数字年 2020
M 1
MM 月( 1 - 9 月加 0) 01
MMM 月(汉字)
MMMM 完整月(汉字) 九月
N 农历月 正,二,三
NN 干支月 乙丑
NNNN 农历完整日期+节气 八月廿八 秋分(只适用于DateTime)
D 一年中的第几天 168
d 数字日 23
e 农历日 初三
ee 干支日 丙寅
t 二十四节气 冬至
E 星期 周三
EEEE 星期 星期三
EEEEE 星期
H 24 小时制 0~23
h 12 小时制 0~12
I 时辰地支
II 时辰地支 丁酉
m 分钟 6
mm 分钟(两位) 06
s 6
ss 06
S 豪秒 666
a 上下午 上午,下午
aa 上下午(详细) 上午,下午,傍晚,凌晨,晚上
Z/ZZ/ZZZ 时区 +0800
ZZZZ 时区 GMT+08:00
ZZZZZ 时区 08:00
zzzz 时区 中国标准时间

DateTime 实例

<!-- 二〇一五年八月初十23时42分(农历) -->
<DateTime x="20" y="450" color="#000000" size="40" format="Y年N月eH时m分" />
<!-- 今天日期:2020年09月15日 -->
<DateTime x="540" y="400" align="center" alignV="center" size="200" color="#ffffff" formatExp="'今天日期:yyyy年MM月dd日'"  />

DateTime 可指定 value 值,当不指定时默认以当前时间戳来计算时间,当指定时间戳时,会按照指定的时间戳来计算时间

<DateTime x="540" y="400" align="center" alignV="center" size="200" color="#ffffff" formatExp="'明天日期:yyyy年MM月dd日'" value="#time_sys+86400000" />

# 组的运用

Group

<Group name="" x="" y="" w="" h="" clip="true" layered="true" visibility="">
    <PositionAnimation/>
    <SizeAnimation/>
    <Image/>
    <Time/>
    <DateTime/>
    <Text/>
</Group>
Group       组的意思,相当于一个容器,用来包含住其他元素,如图片,时间,控件……等
            可理解为一个图层,能便捷地调整内部多个元素的位置和大小,与Image一样添加各种动画,内部元素的坐标是相对坐标(相对于组坐标)
clip        true/false,为true时,会裁剪掉超出w h标注范围的内容,不给予显示(有clip与layered时,必须添加w h)

AutoScaleGroup

新增加一个AutoScaleGroup元素,继承于Group元素,区别在于AutoScaleGroup元素会记录下初始的w和h,当后面w或h有变化的时候按比例做scale MIUI12.5新增

<AutoScaleGroup name="" x="" y="" w="" h="" clip="true" layered="true" visibility="">
    <PositionAnimation/>
    <SizeAnimation/>
    <Image/>
    <Time/>
    <DateTime/>
    <Text/>
</AutoScaleGroup>

组命令

如果某组操作会被很多处调用,可以将这组操作写到一个空的 Group 中作为一个 trigger action,然后可以被多处调用。

<!-- 组命令 -->
<Group name="triggersContainer" >
    <Triggers>
        <Trigger action="content_1" >
            <AnimationCommand target="numAni" command="play" />
            <VariableCommand name="number001" expression="1"/>
        </Trigger>
        <Trigger action="content_2" >
            <VariableCommand name="month_num" expression="#month_num+1" />
        </Trigger>
    </Triggers>
</Group>
<!-- 执行组命令 -->
<Button x="540" y="0" w="540" h="540">
    <Triggers>
        <Trigger action="up">
            <MethodCommand target="triggersContainer" method="performAction" paramTypes="String" params="'content_1'"/>
            <MethodCommand target="triggersContainer" method="performAction" paramTypes="String" params="'content_2'"/>
        </Trigger>
    </Triggers>
</Button>

自定义函数

增加函数元素,作为函数式调用 MIUI12.5新增

<!--Function为仿照函数的元素, 包含各种ActionCommand-->
<Function name="XXXX">
    <VariableCommand />
    <XXXXCommand/>
    <!-- 条件执行命令 子元素Consequent和Alternate -->
    <IfCommand ifCondition="XXXX">
        <!-- Consequent 满足ifCondition执行 -->
        <Consequent>
            <VariableCommand />
            <AnimationCommand />
            <XXXXCommand/>
        </Consequent>
        <!-- Alternate 不满足ifCondition执行 -->
        <Alternate>
            <VariableCommand />
            <AnimationCommand />
            <XXXXCommand />
        </Alternate>
    </IfCommand>
</Function>
<!-- 增加一个command 用来调用Function -->
<FunctionCommand target="XXX" />

# 图片

图片部件用来在锁屏界面上显示一个图片,可以指定各种属性

<Image x="" y="" w="" h="" pivotX="" pivotY="" rotation="" src="" srcid="" alpha="" align="" antiAlias="" visibility=""/>
<Image x="" y="" w="" h="" pivotX="" pivotY="" pivotZ="" rotationX="" rotationY="" rotationZ="" src="" />
属性 类型 表达式/变量 释义
x,y number o/o 相对于屏幕左上角的坐标
w,h number o/o 宽和高
pivotX,pivotY number o/o 旋转中心
rotation number o/o 旋转角度,一周 360 度
rotationX number o/o X 轴旋转角度,一周 360 度
rotationY number o/o Y 轴旋转角度,一周 360 度
rotationZ number o/o Z 轴旋转角度,一周 360 度
src string x/x 图片名称路径
srcid number/string o/o 图片序列后缀数字,一般用变量表示,可以根据变量显示不同的图片,如果 src="pic.png" srcid="1"则最后会显示图片"pic_1.png"
alpha number o/o 透明度 0-255,小于等于 0 不显示
antiAlias boolean x/x true/false 抗锯齿,如果为 true 图片在变形旋转时不会有锯齿,但是速度会慢
srcExp number/string o/o 图片源表达式
srcFormat string x/x 图片源格式 srcParas中是number类型用%d,是string类型用%s
srcForamtExp number/string o/o 图片源格式表达式
srcParas number/string o/o 图片源参数,多个参数用,分隔
visibility number o/o 支持表达式,大于 0 时则显示
align/alignV string x/x 对齐方式
tint string x/o 色调(染色);tint="#ffff0000" MIUI12 新增 支持变量不支持表达式。
tintmode int o/o 色调(染色)混合模式,需要与tint一起使用,效果同图片混合xfermodeNum一致。默认tintmode="5" MIUI12 新增 支持变量。

图片的几个应用实例:

<Image x="300" y="100" align="center" srcExp="'weather/weather_' + #weather_id + '.png'"/>
<Image x="300" y="100" align="center" src="weather/weather.png" srcid="#weather_id"/>

上面两段代码效果相同,都会读取 weather 目录下 weather_ 对应数字的 png 文件,例如 #weather_id 传递过来的数字是 3,则会读取 weather 目录下的 weather_3.png 这张图片,可以用来根据不同的天气显示对应图标等,天气数据的获取后面会讲到。srcid 引用图片序列时,图片必须命名为类似 pic_0.png pic_1.png 这样的格式,srcExp 则直接拼接图片的路径,srcExp 可以和 srcid 一起使用,如

<Image x="210" y="100" align="center" srcExp="'weather/'+#hour24}10+'/weather.png'" srcid="#weather_id"/>

#hour24}13 表示当前小时数(24 小时制,后面全局变量会讲到)大于 13 点时条件成立,输出值为 1,引用 weather/1/ 这个目录下的 weather_xx.png 的序列图片,当时间小于等于 13 点时 #hour24}13 条件不成立,输出值为 0,此时引用 weather/0/ 这个目录下的 weather_xx.png 的序列图片,可以用来判断不同时间段显示不同的图片,比如白天或者晚上显示不同的图片你可以这样写

<Image x="300" y="100" align="center" srcExp="'weather/'+#hour24}=6**#hour24{18+'/weather.png'" srcid="#weather_id"/>

表示从时间大于等于 6 点且小于 18 点时显示 weather/1/ 目录下的图片,其他时间则显示 weather/0/目录下的图片,当然你也可以用其他变量来代替 #hour24}=6**#hour24{18 的判断,后面会讲到变量及表达式的使用

数字图片映射

<ImageNumber number="" src=""/>
属性 释义
number 要显示的数字表达式
src 图片源的文件名,支持 SourceAnimation

如果 src="number.png" 则会使用 number_0.png number_1.png ... 图片文件来绘制数字。

文本图片映射

简单说就是把图片拼接在一起显示;比如需要用图片显示 '2019.03.27'或者 'Wednesday 03.27' 时,之前用 ImageNumber 或者是 Time 控件都有缺陷,要么不能显示长字符,要么不能显示两个 '.',用 ImageChars 可以完美解决这个问题。

<ImageChars x="540" y="300" align="center" alignV="center" src="num/string.png" string="formatDate('HH:mm MM.dd',#time_sys)" charNameMap=".spot,:colon,%pct, space"/>
属性 释义
string 字符串表达式
number 数字表达式
space 显示间隔
charNameMap 映射列表,用英文逗号分隔,每项第一个字符是原字符,后面的是映射字符串,可以映射到一个或多个字符

示例中映射图片名称有:

原字符 映射名称(图片后缀) 释义
. spot
: colon 冒号
% pct 百分比
(注意这里是空格) space

注意:

  • 因同一目录下图片名称不区分大小英文,所以大写英文命名为两个小写英文字符,以表示为大写;比如:A 图片名称为 num_aa.png
  • 某些字符不能直接用,需要转义才可以使用;比如:&

# 图片混合

<!-- 例子:(使用时,混合范围要尽可能小,否则会卡) -->
<Group x="300" y="500" w="200" h="200" layered="true">
    <Image src="test.png"/>
    <Image src="mask.png" xfermode="dst_in" />
</Group>
<!-- 或者 -->
<Group x="300" y="500" w="200" h="200" layered="true">
    <Image src="test.png"/>
    <Image src="mask.png" xfermodeNum="6" />
</Group>

这俩个例子实现的结果是一样的,都是遮罩效果,即按照 mask.png 的形状对 test.png 进行裁剪。

layered         和Group里面的xfermode配合使用,增加该属性旨在让xfermode只作用于Group内部的元素。
xfermode        混合模式,取值有:clear,src,dst,src_over,dst_over,src_in,dst_in,src_out,dst_out,src_atop,dst_atop,xor
xfermodeNum     混合模式支持变量表达式。表达式的取值对应一种混合模式,src, dst, src_over,...xor依次取值 1,2,3 ...11

注意事项:

  • 在 Group 使用 layered 时,请务必指定作用区域 w h,否则无法生效(使用时,混合范围要尽可能小,否则会卡)
  • 一个组里可以有 }=2 张图,最后一个有 xfermode 的 Image 会将这个组内前面所有的图片看作一个整体的 Image ,按照 xfermode 的取值与之混合

对应关系:

xfermodeNumxfermode混合效果0clear1src2dst3src_over4dst_over5src_inxfermodeNumxfermode混合效果6dst_in7src_out8dst_out9src_atop10dst_atop11xor

上图中被混合处理的两张图


# 笔刷

笔刷绝对是一个很神奇的功能,与混合功能搭配,可以做出刮奖、擦玻璃的效果

笔刷代码:

<Paint x="0" y="0" w="1080" h="1920" color="#ff6600" weight="40" />
属性 释义
weight 笔刷宽度,支持表达式
xfermoderow 混合模式,参考 Image
xfermoderow 宽高,定义此笔刷能涂抹的区域

与图片混合功能搭配的示例:

<Wallpaper />                                                   // 调用系统壁纸,在这里是你手指擦过后露出的背景图
<Group x="0" y="0" w="1080" h="1920" layered="true">            // 图片混合
    <Image x="0" y="0" src="fg.png"/>                           // fg.png是被擦的图片(高斯模糊+水珠雾气效果)
    <Paint weight="50" w="1080" h="1920" xfermode="clear" />    // clear能产生擦除效果
</Group>

示图:

image


# 图片变形

示例主题:摇一摇 demo【图片变形】.mtz (opens new window)

如图:

image

注意了:控制图片节点用的数组,它的类型必须为 float[],这样

但是在后面计算中暂时用 number[]

图片变形暂时要自己计算在其它分辨率下的缩放比,示例:

<Var name="img_w" expression="400" const="true" />
<Var name="img_h" expression="400" const="true" />
<Var name="img_m" expression="2" const="true" />
<Var name="img_n" expression="2" const="true" />

<!-- mesharr[]数组中,同时要有X坐标、Y坐标,所以申明范围时是:(#img_m+1)*(#img_n+1)*2=18-->
<Var name="mesharr" type="float[]" size="18" />
<ExternalCommands>
    <Trigger action="init">
        <VariableCommand name="scale_wh" expression="int(#raw_screen_width/1.08)/1000" />      //(你锁屏按1080p写的就用这个计算)
        <!-- 先分网格 -->
        <LoopCommand count="(#img_m+1)*(#img_n+1)" indexName="__a">
            <VariableCommand name="mesharr" type="float[]" index="#__a*2" expression="int(#img_w/#img_m)*(#__a%(#img_m+1))" />
            <VariableCommand name="mesharr" type="float[]" index="#__a*2+1" expression="int(#img_h/#img_n)*int(#__a/(#img_m+1))" />
        </LoopCommand>
        <VariableCommand name="mesh5_x" expression="#mesharr[4*2]" />
        <VariableCommand name="mesh5_y" expression="#mesharr[4*2+1]" />
    </Trigger>
</ExternalCommands>
<Var name="mesharr" type="float[]" index="8" expression="#mesh5_x+int(#touch_x-#touch_begin_x)" />
<Var name="mesharr" type="float[]" index="9" expression="#mesh5_y+int(#touch_y-#touch_begin_y)" />
<Image x="0" y="0" scale="#scale_wh" src="bg3.jpg" mesh="2,2" meshVertsArr="mesharr" />

附件:img0927.mtz (opens new window)

# 几何图形

目的:在一些内存要求高的场景下,使用绘制几何图形的方式替代<Image>以减小内存。

Rectangle(矩形、圆角矩形)

<Rectangle x="10" y="20" w="100" h="200" cornerRadius="5,10" strokeColor="#ff00ff00" fillColor="#ff000000" weight="5" cap="round" dash="1,2,3,4" strokeAlign="center">
    <!-- FillShaders 填充着色;StrokeShaders 描边着色 -->
    <FillShaders>
        <!-- 线性渐变 -->
        <LinearGradient x="#screen_width" y="0" x1="0" y1="#screen_height" tile="clamp">
            <GradientStop color="#050B2A" position="0"/>
            <GradientStop color="#81425D" position="0.5"/>
            <GradientStop color="#C87960" position="1"/>
        </LinearGradient>
        <!-- 放射渐变 -->
        <RadialGradient x="" y="" rX="" rY="" tile="">
            <GradientStop color="" position=""/>
            <GradientStop color="" position=""/>
        </RadialGradient>
        <!-- 扫描渐变 -->
        <SweepGradient x="" y="" rotation="" tile="">
            <GradientStop color="" position=""/>
            <GradientStop color="" position=""/>
        </SweepGradient>
    </FillShaders>
    <StrokeShaders>
    </StrokeShaders>
</Rectangle>
属性 类型 表达式/变量 释义
x y number o/o 图形起始点;Rectangle 为左上角,其余的几何图形均为 中心点
strokeColor string x/o 描边颜色。支持多种 color 指定方式,以下所有 color 类的属性都是如此。
fillColor string x/o 填充色
weight number o/o 描边的线宽
cap string x/x 线头形状。butt 无线头(默认), round(半圆), squre(方形)
dash number x/x 虚线模式。格式“线长,间隔,线长,间隔....”,e.g. dash="1,2,3,4":1 像素宽的线段,2 像素的间隔,3 像素的线段,4 像素的间隔,如此重复...
strokeAlign string x/x 描边对齐方式,inner 内描,center 中心描边,outer 外描(默认)
xfermode int o/o 混合模式,与<Image/>相同
cornerRadius number x/x 倒角半径;格式"x 向半径,y 向半径"
cornerRadiusExp number o/o 倒角半径;支持表达式,格式"x 向半径,y 向半径";<font color=#ff

FillShaders 与 StrokeShaders

填充着色器 和 描边着色器 语法一致:支持线性渐变着色,放射渐变着色(径向渐变),扫描渐变着色(圆周渐变),位图着色。

  • LinearGradient 线性渐变
属性 类型 表达式/变量 释义
x y x1 y1 number o/o 渐变轴线(x , y) --> (x1 , y1)
tile string x/x 铺展模式;clamp 拉伸(默认),mirror 镜像,repeat 重复
  • RadialGradient 放射渐变
属性 类型 表达式/变量 释义
x y number o/o 圆心位置
rX rY number o/o x y 方向的半径
  • SweepGradient 扫描渐变
属性 类型 表达式/变量 释义
x y number o/o 中心点位置
rotation number o/o 旋转角
  • GradientStop 渐变点;在指定的几个渐变点颜色之间做渐变
属性 类型 表达式/变量 释义
color string x/o 渐变点的颜色
position number o/o 渐变点位置,0~1.0 的浮点数

Ellipse(椭圆)

<Ellipse x="10" y="20" w="100" h="200" strokeColor="#ff00ff00" fillColor="#ff000000" weight="5" cap="round" dash="1,2,3,4" strokeAlign="center" >
    <StrokeShaders>
    </StrokeShaders>
    <FillShaders>
    </FillShaders>
</Ellipse>
x y     椭圆的中心位置;注意:这里是椭圆的中心点,而不是左上角的起点
w h     椭圆的宽,高

Circle(圆)

<Circle x="10" y="20" r="50" strokeColor="#ff00ff00" fillColor="#ff000000" weight="5" cap="round" dash="1,2,3,4" strokeAlign="center">
    <StrokeShaders>
    </StrokeShaders>
    <FillShaders>
    </FillShaders>
</Circle>
x y     圆心位置
r       半径

Arc(扇形、弧线)

<Arc x="10" y="20" w="100" h="200" startAngle="10" sweep="50" close="true" strokeColor="#ff00ff00" fillColor="#ff000000" weight="5" cap="round" dash="1,2,3,4" strokeAlign="center">
    <StrokeShaders>
    </StrokeShaders>
    <FillShaders>
    </FillShaders>
</Arc>
x y w h         定位方式与 Ellipse 相同
startAngle      起始角
sweep           扫描角(扇形的角度)
close           是否闭合,true 闭合是扇形,false 不闭合是弧线

Line(直线)

<Line x="10" y="20" x1="100" y1="200" strokeColor="#ff00ff00" weight="5" cap="round" dash="1,2,3,4" strokeAlign="center">
    <StrokeShaders>
    </StrokeShaders>
</Line>
x y x1 y1       直线起点(x,y),终点(x1,y1)

注意事项:

  • 对齐方式 align alignV:只有 Rectangle 支持,其他的都不支持,它们的 x y 都是指的中心点位置,不需要对齐方式。
  • 填充:线条类的图形忽略 fillColor 和 <FillShaders>;有面积的图形同时支持 stroke 和 fill,分别用于描边和填充。
  • 优先级:当 strokeColor 和 <StrokeShaders> 同时存在时,优先使用 <StrokeShaders> ;fillColor 和 <FillShaders> 亦如此。
  • 描边颜色:如果要出现描边,则 strokeColor 和 <StrokeShaders> 必须至少一个存在;都不存在就会没有描边。
  • 渐变定位:<LinearGradient x="" y=""> ... 中的 x y 都是相对它所在的图形元素定位的。

# 自定义图片

选择任意图片显示:

config.xml 新增 ImagePicker 条目,可以让用户选择手机中的某个图片,获取其地址到变量中(注意:图片过大可能不能显示,比如像素过大的照片。)

<Config>
       <Group text="自定义图片" summary="请先将图片裁剪到合适大小和部位以确保显示效果,尺寸过大的照片请先缩小至屏幕大小,否则可能不能正常显示或费内存">
               <ImagePicker text="图片一" summary="选择图片一" id="img1"/>
               <ImagePicker text="图片二" summary="选择图片二" id="img2"/>
       </Group>
</Config>

manifest.xml 中 Image 支持 Uri 类型的 src,即 config 中选择的图片地址,@img1 @img2

<!-- 个性化定制锁屏设置中 文件管理和相册选择图片不生效,故使用下列代码即可解决 -->
<Var name="img1" expression="ifelse(strContains(@img1,'com.android.fileexplorer'),strReplaceAll(@img1,'content://com.android.fileexplorer.myprovider/external_files/','file:///sdcard/'),strContains(@img1,'com.miui.gallery'),strReplaceAll(@img1,'content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2F','file:///sdcard/'),@img1)" type="string" const="true"/>
<Var name="img2" expression="ifelse(strContains(@img2,'com.android.fileexplorer'),strReplaceAll(@img2,'content://com.android.fileexplorer.myprovider/external_files/','file:///sdcard/'),strContains(@img2,'com.miui.gallery'),strReplaceAll(@img2,'content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2F','file:///sdcard/'),@img2)" type="string" const="true"/>

<Image  x="300" y="300" w="300" h="300" src="@img1" srcType="Uri"/>
<Image  x="300" y="800" w="300" h="300" src="@img2" srcType="Uri"/>

清除图片(将相应变量置空即可):

<Button x="300" y="300" w="60" h="60" visibility="#img1.has_bitmap">
    <Triggers>
        <Trigger action="up">
            <VariableCommand name="img1" expression="''" type="string" persist="true"/>
        </Trigger>
    </Triggers>
</Button>

示例文件:image_picker.mtz (opens new window)

# 常用动画

变量动画

<Var name="numAni">
    <VariableAnimation initPause="true" loop="false">
        <Item value="0" time="0" />
        <Item value="100" time="300" />
    </VariableAnimation>
</Var>
<!-- 按钮触发  -->
<Button x="0" y="0" w="1080" h="#screen_height">
    <Triggers>
        <Trigger action="up">
            <AnimationCommand target="numAni" command="play" />
        </Trigger>
    </Triggers>
</Button>
属性 释义
initPause true/false initPause="true" 无命令执行这个动画时,它停在初始态
loop true/false 是否循环,默认 true,loop="false" 时,播放一次就停了
time 数字 毫秒时间,不支持表达式 。绝对时间
dtime 数字 或者 表达式 毫秒时间,支持表达式(新)。相对时间(相对上一个的时间)

dtime和time的区别

<!-- 两段动画结果一致 -->
<Var name="numAni_0">
    <VariableAnimation initPause="false" loop="true">
        <Item value="0" time="0" />
        <Item value="100" time="1000" />
    </VariableAnimation>
</Var>
<Var name="numAni_1">
    <VariableAnimation initPause="false" loop="true">
        <Item value="0" dtime="0" />
        <Item value="50" dtime="500" />
        <Item value="100" dtime="500" />
    </VariableAnimation>
</Var>

灵活运用示例:

<Var name="numAni">
    <VariableAnimation name="numAniVar" initPause="true" loop="false">
        <Item value="0" time="0" />
        <Item value="100" time="300" />
        <Triggers>
            <!-- 动画播放完时,若#numAni等于100,则执行名为 moveAni 的动画 -->
            <Trigger action="end" condition="#numAni==100">
                <AnimationCommand target="moveAni" command="play" />
            </Trigger>
            <!-- 或者:动画时间#numAniVar等于-1时,执行名为 moveAni 的动画 -->
            <Trigger action="end" condition="#numAniVar.current_frame==-1">
                <AnimationCommand target="moveAni" command="play" />
            </Trigger>
        </Triggers>
    </VariableAnimation>
</Var>

数组动画

<!-- 自由选择播放 -->
<Array count="6" indexName="_ani">
    <Var name="arrayAni" size="6" index="#_ani" type="number[]">
        <VariableAnimation initPause="true" loop="false">
            <Item value="0" time="0" easeType="CubicEaseOut"/>
            <Item value="1" time="600"/>
        </VariableAnimation>
    </Var>
</Array>
<!-- 点击触发 -->
<Button x="540" y="0" w="540" h="540">
    <Triggers>
        <Trigger action="up">
            <!-- 播放第一个动画;targetIndex 从 0 开始到 5,共 6 个 -->
            <AnimationCommand target="arrayAni" targetIndex="0" command="play" />
            <!-- 播放第二个动画 -->
            <AnimationCommand target="arrayAni" targetIndex="1" command="play" delay="300"/>
        </Trigger>
    </Triggers>
</Button>
<!-- 按延迟时间依次播放 -->
<Array count="10" indexName="_i">
    <Var name="Ani" type="number[]" index="#_i" size="10">
        <VariableAnimation tag="show1" loop="false" initPause="true">
            <Item value="0" dtime="0" />
            <Item value="0" dtime="#delay" easeType="BackEaseOut"/>
            <Item value="1" dtime="500+#delay"/>
        </VariableAnimation>
        <VariableAnimation tag="show2" loop="false" initPause="true">
            <Item value="1" dtime="0" />
            <Item value="1" dtime="#delay" easeType="BackEaseOut"/>
            <Item value="0" dtime="500+#delay"/>
        </VariableAnimation>
    </Var>
    <!-- 点击触发 -->
    <Button x="0" y="0" w="1080" h="#screen_height">
        <Triggers>
            <Trigger action="up">
                <!-- 延迟时间 50ms -->
                <VariableCommand name="delay" expression="#_i*50"/>
                <!-- 播放动画 -->
                <AnimationCommand target="Ani" targetIndex="#_i" tags="show1" command="play" condition="#Ani[0]==0 ** #Ani[9]==0" />
                <AnimationCommand target="Ani" targetIndex="#_i" tags="show2" command="play" condition="#Ani[0]==1 ** #Ani[9]==1" />
            </Trigger>
        </Triggers>
    </Button>
    <Text x="100+#Ani[#_i]*300" y="100+#_i*100" color="#ff0000" size="50" textExp="'Ani'+#_i+'= '+#Ani[#_i]" />
</Array>

# 元素动画

所有元素都支持动画(下面用 Image 来举例);动画分为:图片源,位置,大小,旋转,透明度
每种动画相互独立,各自循环播放,动画由若干关键帧组成,关键帧包括帧属性和时间,除图片源动画外,其它动画会根据当前时间找到相邻的两个关键帧,然后线性插值计算当前的属性。 如果第一帧时间不从 0 开始,则默认时间为 0 的第一帧为图片原始属性,时间单位为毫秒。 位置动画中的位置是相对于图片自身的坐标。

源动画

<Image x="0" y="0" src="pic.png">
    <SourcesAnimation>
        <Source x="0" y="0" src="pic1.png" time="0"/>
        <Source x="0" y="0" src="pic2.png" time="100"/>
        <Source x="0" y="0" src="pic3.png" time="200"/>
    </SourcesAnimation>
</Image>

注意: 只有图片支持图片源动画

图片源动画稍有不同,没有插值;x, y 可选,表示相对图片的位置,当前的图片是在列表里的找到第一个大于当前时间的那个点指定的。

等比缩放

<Image x="0" y="0" pivotX="50" pivotY="50" src="bg2.png">
    <ScaleAnimation loop="false">
        <Item value="0" time="0" />
        <Item value="1" time="1000" />
    </ScaleAnimation>
</Image>
<Image x="0" y="0" pivotX="50" pivotY="50" src="bg.png">
    <!--位移-->
    <PositionAnimation>
        <Item x="10" y="0" time="100" />
        <Item x="10" y="200" time="1000"/>
    </PositionAnimation>
    <!--平面旋转-->
    <RotationAnimation>
        <Item value="0" time="0" />
        <Item value="360" time="2000"/>
    </RotationAnimation>
    <!--非等比缩放-->
    <SizeAnimation>
        <Item w="100" h="100" time="0"/>
        <Item w="200" h="400" time="1000"/>
        <Item w="100" h="100" time="2000"/>
    </SizeAnimation>
    <!--透明度-->
    <AlphaAnimation>
        <Item value="255" time="0"/>
        <Item value="100" time="1000"/>
        <Item value="255" time="2000"/>
    </AlphaAnimation>
</Image>

例子:位置动画表示 1秒 从屏幕最左端到最右端,停留1秒,透明度动画表示开始透明度为175,在从最左端到最右端过程中透明度不变,到达最右端后0.5秒渐变为不透明,然后 500毫秒 变为透明消失。然后循环播放。
<Image x="0" y="#screen_height-177" src="charging_light.png" category="Charging">
    <PositionAnimation>
        <Item x="0" y="0" time="1000"/>
        <Item x="1080" y="0" time="2000"/>
    </PositionAnimation>
    <AlphaAnimation>
        <Item value="175" time="0"/>
        <Item value="175" time="1000"/>
        <Item value="255" time="1500"/>
        <Item value="0" time="2000"/>
    </AlphaAnimation>
</Image>

# 动画控制

<Image name="wao" x="0" y="0" w="1080" h="114" src="dst.png" >
    <PositionAnimation tag="show1" initPause="true" loop="false" >
        <Item y="0" time="0" easeType="ExpoEaseOut"/>
        <Item y="300" time="300"/>
    </PositionAnimation>
</Image>
<Image name="goal" x="0" y="0" w="1080" h="114" src="dst.png" >
    <PositionAnimation tag="show1" initPause="true" loop="false" >
        <Item y="0" time="0" easeType="ExpoEaseOut"/>
        <Item y="300" time="300"/>
    </PositionAnimation>
    <PositionAnimation tag="show2" initPause="true" loop="false">
        <Item y="300" time="0" easeType="ExpoEaseOut"/>
        <Item y="600" time="300"/>
    </PositionAnimation>
</Image>
<Button x="0" y="0" w="1080" h="#screen_height">
    <Triggers>
        <Trigger action="up" >
            <AnimationCommand target="wao" command="play(0,300)" />
            <AnimationCommand target="goal" command="play" tags="show2" />
        </Trigger>
    </Triggers>
</Button>
属性 释义
AnimationCommand 播放动画现在统一用这个标签,原因是旧标签不支持 tags,你怀旧的话可以记两个
target 控制的动画目标名
Index 为数组动画添加索引;既Index="#__ani"需要索引数组开头中indexName属性的名称;
数组介绍可查看:元素数组;动画示例可查看:数组动画
targetIndex 数组动画索引角标,表示数组动画索引值为 1;例如targetIndex="1"则表示控制数组动画的第二个动画(索引从 0 开始)
command play (从头播放), pause(暂停), resume(从当前位置继续播放)
tag 用此标签可以实现 一个元素预置多个动画效果,用 tags 来选择性地播放其中一种
play(startTime,endTime,loop,delay)  4个参数都支持表达式,可只写前面的参数,后面的忽略
startTime   动画开始时间
endTime     动画结束时间
loop        是否循环播放,默认为0,需要写数字 0,1 或者数值表达式,如果为0表示只播放一遍就停止
delay       是否支持delay,默认0,需要写数字 0,1 或者数值表达式
示例 释义
play(100) 从 time 100 开始播放到结束,不循环播放
play(100, 500) 从 time 100 开始播放到 500,停止
play(100,500,1) 从 time 100 开始播放到 500,循环播放

# 缓动函数

<Image name="wao" x="0" y="0" w="1080" h="114" src="dst.png" >
    <PositionAnimation tag="show1" initPause="true" loop="false" >
        <Item y="0" time="0" easeType="ExpoEaseOut"/>
        <Item y="300" time="300"/>
    </PositionAnimation>
</Image>

缓动动画有两种实现方法:easeType 与 easeExp

# easeType 缓动类型,对照表

SineEaseIn

SineEaseOut

SineEaseInOut

QuadEaseIn

QuadEaseOut

QuadEaseInOut

CubicEaseIn

CubicEaseOut

CubicEaseInOut

QuartEaseIn

QuartEaseOut

QuartEaseInOut

QuintEaseIn

QuintEaseOut

QuintEaseInOut

ExpoEaseIn

ExpoEaseOut

ExpoEaseInOut

CircEaseIn

CircEaseOut

CircEaseInOut

BackEaseIn

BackEaseOut

BackEaseInOut

ElasticEaseIn

ElasticEaseOut

ElasticEaseInOut

BounceEaseIn

BounceEaseOut

BounceEaseInOut


具体点击查看:缓动函数速查表

  • BackEase 可以带 1 个参数,overshot
  • ElasticEase 可以带 2 个参数,priod 和 amplitude
  • 参数为常数,比如 BackEaseIn(1.5) ElasticEase(2,3)
  1. 用 easeExp 来填表达式的方式,引用一个内置变量 #__ratio ,可以自定义缓动函数
  • 比如 easeType="QuadEaseIn" 也可以用 easeExp="#__ratio*#__ratio" 来实现
  • 当有 easeExp 时,easeType 不起作用
  • 属性作用于从该帧到下一帧,最后一帧没用
<Var name="easeAni">
    <VariableAnimation loop="false" initPause="true">
        <Item value="0" time="0" easeExp="#__ratio*#__ratio"/>
        <Item value="100" time="2000"/>
    </VariableAnimation>
 </Var>

# 命令汇总

示例:

<Button x="0" y="0" w="1080" h="#screen_height">
    <Triggers>
        <Trigger action="up">
            <!-- 控制对应name元素可见性 如控制name为test_a元素可见性的两种方式 -->
            <Command target="test_a" property="visibility" value="true/false" />
            <Command target="test_a.visibility" value="true/false" />
            <!-- 控制对应name播放动画 如控制name为test_b元素播放动画的两种方式 -->
            <Command target="test_b.animation" value="play" />
            <AnimationCommand target="test_b" command="play" />
            <!-- 变量赋值,将name为test1的变量赋值为0,注意变量类型, -->
            <VariableCommand name="test1" expression="0" />
            <!-- 播放声音文件 -->
            <SoundCommand sound="famous.wav" volume="0.5" loop="false" keepCur="true" />
            <!-- 关闭系统充电动画 -->
            <ExternCommand command="disableChargeAnim" numPara="1"/>
            <!-- 开启系统充电动画 -->
            <ExternCommand command="disableChargeAnim" numPara="0"/>
            <!-- 关闭屏下指纹 -->
            <ExternCommand command="disableFod" numPara="1"/>
            <!-- 开启屏下指纹 -->
            <ExternCommand command="disableFod" numPara="0"/>
            <!-- 关闭指纹识别动画 -->
            <ExternCommand command="disableFodAnim" numPara="1"/>
            <!-- 开启指纹识别动画 -->
            <ExternCommand command="disableFodAnim" numPara="0"/>
            <!-- 解锁 -->
            <ExternCommand command="unlock" />
        </Trigger>
    </Triggers>
</Button>

# 可见性命令

<Command target="test_a" property="visibility" value="true" />
<Command target="test_a.visibility" value="true" />

(注意:上面只不过是两种不同的写法,效果一样)

代码 释义
Command 基础命令,可以通过对象名和对象的属性来控制界面里的其他元素。通常控制的是元素的可见性(visibility)和动画播放(animation)。
target 控制目标名
property 属性名,目前支持:visibility
value 属性值,目前针对 boolean 有: true, false, toggle

# 动画命令

<Command target="test_c.animation" value="play" />
<AnimationCommand target="test_c" command="play"/>

上面两行是播放动画的两种写法,效果一样!

# 声音命令

<SoundCommand sound="famous.wav" volume="0.5" loop="false" keepCur="true" />
代码 释义
sound 填声音文件路径名
volume 声音大小,0 - 1 的一个浮点数
loop 是否循环播放,true/false,默认是 false.
keepCur 播放此音频时,是否保持当前正在播放的声音,true/false,默认 false

注意:声音文件的大小要求不超过 500kB,时长不超过 10 秒(10 秒之后的声音播放不出来)。

# 变量命令

<!--VariableCommand:用来控制变量(Var)的值-->
<VariableCommand name="w" expression="#screen_width" delay="100" condition="#switch" />
代码 释义
name 变量名
expression 赋值表达式
condition 条件判断,支持表达式。当 condition 里的条件判断为真时,执行命令;为假时,不执行。
delay 延迟,以毫秒记。读取该命令后延迟一段时间再执行
delayCondition 是延时判断,在 delay 的时间之后再进行判断

# 通用命令


1.ExternCommand:用来向外部程序发送命令 <ExternCommand command="命令名" numPara="参数1,数字表达式" strPara="参数2,字符串表达式"/>

<!-- 解锁命令,100 毫秒后解锁 -->
<ExternCommand command="unlock" delay="100" />
<!-- 清理缓存 帧动画播放结束后执行可释放内存 -->
<ExternCommand command="__clearResource" />

2.ExternalCommand 与 ExternCommand 相对,是用来接收外部命令的命令,典型的用法:在锁屏中,通常用来接收开屏/关屏命令,从而执行一些命令;在桌面插件中,用来检测切屏从而执行命令

<!--resume 开屏时执行的命令,pause 关屏时执行的命令-->
<ExternalCommands>
    <Trigger action="resume">
        <Command target="target.animation" value="play"/>
    </Trigger>
    <Trigger action="pause">
        <VariableCommand name="pause_time" expression="#time_sys"/>
    </Trigger>
</ExternalCommands>
<!--桌面插件切屏时使用的示例-->
<ExternalCommands>
    <Trigger action="resume">
        <Command target="__root.animation" value="play"/>
    </Trigger>
    <Trigger action="pause">
        <Command target="__root.animation" value="play"/>
    </Trigger>
</ExternalCommands>

# 打开程序

<IntentCommand action="android.intent.action.MAIN" package="com.android.thememanager" class="com.android.thememanager.ThemeResourceTabActivity" />
<ExternCommand command="unlock" delay="100" />

要记得加上解锁命令,否则只是在后台打开了,还要解锁才能看到。

不解锁打开手电筒:

<!-- 使用2张切图通过#lightSwitch引用可展模拟开与关的状态,切图命名为:flashlight_0.png,flashlight_1.png -->
<Image x="800" y="#screen_height-150" src="flashlight.png" srcid="#lightSwitch"/>
<Button x="800" y="#screen_height-150" w="150" h="150" >
    <Triggers>
        <Trigger action="up">
            <VariableCommand name="lightSwitch" expression="!(#lightSwitch)" />
            <IntentCommand action="miui.intent.action.TOGGLE_TORCH" broadcast="true" >
                <Extra name="miui.intent.extra.IS_ENABLE" type="boolean" expression="ifelse(int(@__miui_version_code)}=8,#lightSwitch,1)" />
            </IntentCommand>
        </Trigger>
    </Triggers>
</Button>

常用的几个系统开关写法

1.蓝牙value="on" value="off"

<Command target="Bluetooth" value="toggle"/>

2.数据value="on" value="off"

<Command target="Data" value="toggle"/>
<Command target="Data" value="on"/>

3.正常/静音/振动 (3 种状态切换)

<Command target="RingMode" value="toggle"/>
<!--下面和上面同样效果-->
<Command target="RingMode" value="normal,silent,vibrate"/>

<!--仅正常和静音切换-->
<Command target="RingMode" value="normal,silent"/>

<!--仅正常和振动切换-->
<Command target="RingMode" value="normal,vibrate"/>

<!--仅切换到静音,用其他button切换到其他状态-->
<Command target="RingMode" value="silent"/>

4.Wifi (toggle/on/off)

<Command target="Wifi" value="toggle"/>
属性 名词 释义
bluetooth_state 蓝牙状态 0 关,1 开,2 连接中
data_state 数据状态 0 关,1 开
ring_mode 声音模式 0 静音,1 振动,2 正常
wifi_state wifi 状态 0 禁用,1 启用,2 问题,3 连接中

注意:上面四个开关的状态变量必须有相应的按钮才会正常显示

实例

<!-- 4个系统常用开关 -->
<Group nmame="toggle_four" x="0" y="0" >
    <!-- 1 正常/静音开关;切图为:ring_0.png、ring_1.png -->
    <Image x="94" y="500" src="toggle/ring.png" srcid="ifelse(#ring_mode==2,0,1)"/>
    <Button x="94" y="500" w="178" h="178">
        <Triggers>
            <Trigger action="up">
                <Command target="RingMode" value="normal,silent"/>
            </Trigger>
        </Triggers>
    </Button>
    <!-- 2 蓝牙开关;切图为:bluetooth_0.png、bluetooth_1.png -->
    <Image x="332" y="500" src="toggle/bluetooth.png" srcid="#bluetooth_state!=0"/>
    <Button x="332" y="500" w="178" h="178">
        <Triggers>
            <Trigger action="up">
                <Command target="Bluetooth" value="toggle"/>
            </Trigger>
        </Triggers>
    </Button>
    <!-- 3 wifi开关;切图为:wifi_0.png、wifi_1.png -->
    <Image x="570" y="500" src="toggle/wifi.png" srcid="#wifi_state!=0"/>
    <Button x="570" y="500" w="178" h="178">
        <Triggers>
            <Trigger action="up">
                <Command target="Wifi" value="toggle"/>
            </Trigger>
        </Triggers>
    </Button>
    <!-- 4 数据开关;切图为:data_0.png、data_1.png -->
    <Image x="808" y="500" src="toggle/data.png" srcid="#data_state"/>
    <Button x="808" y="500" w="178" h="178">
        <Triggers>
            <Trigger action="up">
                <Command target="Data" value="toggle"/>
            </Trigger>
        </Triggers>
    </Button>
</Group>

示例附件:常用开关示例 lock915.mtz (opens new window)

# 充电动画

随着手机充电方式的强大,快充、超级快充、无线充电、有线充电,各种效果的添加,大家是不是也希望主题可以做到各种效果呢?(参数仅在 MIUI11 开发版之后的版本支持,跨版本时请注意兼容性)

  • 锁屏支持 显示/关闭 进入充电状态时刻的默认充电动画
属性 释义
disableChargeAnim 0 显示,1 不显示(默认为 0)

示例:

<!-- 关闭系统充电动画 -->
<ExternCommand command="disableChargeAnim" numPara="1"/>
<!-- 开启系统充电动画 -->
<ExternCommand command="disableChargeAnim" numPara="0"/>
  • 获取充电状态
#ChargeSpeed            0 普通充电, 1 快充, 2 超级快充,3 极速秒充(120w±)
#ChargeWireState        11 有线充电, 10 无线充电, -1 未充电

# 屏下指纹

目前部分小米手机已支持屏下指纹功能,屏下指纹开启时,可能会影响百变锁屏上的内容展示或带来较差的用户体验。
为此,百变锁屏支持关闭屏下指纹功能。
注意⚠️:仅支持产生必要交互时关闭指纹功能(如 锁屏进入相机、进入负一屏时、进入锁屏游戏等情况),
禁止默认非交互场景下关闭指纹功能

锁屏默认的状态时 开启屏下指纹功能,每次解锁后锁屏会重置这个状态,所以每次加载锁屏后如果要关掉功能均需下发命令给锁屏。


控制屏下指纹功能和动画是否开启(关闭功能指纹图标会消失)

ExternCommand 命令 说明 参数
disableFod 指纹开关 0开启;1关闭
disableFodAnim 指纹识别动画开关 0开启;1关闭

示例:

<Trigger>
    <!-- 关闭屏下指纹 -->
    <ExternCommand command="disableFod" numPara="1"/>
    <!-- 关闭指纹识别动画 -->
    <ExternCommand command="disableFodAnim" numPara="1"/>
</Trigger>

<Trigger>
    <!-- 开启屏下指纹 -->
    <ExternCommand command="disableFod" numPara="0"/>
    <!-- 开启指纹识别动画 -->
    <ExternCommand command="disableFodAnim" numPara="0"/>
</Trigger>

新增 指纹相关全局变量

属性 释义
fod_enable 系统是否启用了屏下指纹:0 关闭, 1 开启
fod_x 指纹区域 x 坐标
fod_y 指纹区域 y 坐标
fod_width 指纹区域 宽度
fod_height 指纹区域 高度
fod_state_msg 指纹状态:1 手指按下,2 手指抬起,3 识别失败,4 识别成功

演示 demo.mtz (opens new window)


# 控件用法

按钮

Trigger 在按钮、Unlocker、Slider、命令组调用中的用法基本一样,但各控件有它们各自的特性和各条件下命令的不同用法,这里给大家详细讲下

# 按钮

按钮元素可以用来接收 点击、按下、双击、移动 等事件,并可根据 trigger 的定义来控制界面上其他元素

属性 释义
x, y, w, h 指定坐标、区域大小
haptic haptic="true"时振动 ,前提是用户没有在系统设置中关闭
alignChildren true/false,默认为 false,当为 true 时,内部元素按绝对坐标排布,反之是相对坐标
action down (按下);move(移动); up (抬起);double (双击)
interceptTouch 是否截获以后的触摸事件,避免被其他 View 捕获,例如在自由桌面 widget 中可以防止在 widget 上进行触摸操作时桌面滚动和进入编辑模式
Normal 正常状态,所包含的元素只有在此状态下显示
Pressed 按下状态,所包含的元素只有在此状态下显示
注意:
    1.按钮中trigger被triggers包含
    2.Button中 Image等各元素的坐标和Button自己的坐标是独立的,都是相对于Button的父元素。
<Button x="" y="" w="" h="" interceptTouch="true">
    <Triggers>
        <Trigger action="down">
            <VariableCommand name="test1" expression="0" />
        </Trigger>
        <Trigger action="up">
            <VariableCommand name="test2" expression="0" />
        </Trigger>
        <Trigger action="double">
            <VariableCommand name="test3" expression="0" />
        </Trigger>
    </Triggers>
    <Normal>
        <Image/>
        <Text/>
    </Normal>
    <Pressed>
        <Image/>
        <Text/>
    </Pressed>
</Button>

打开应用程序

<Button name="WeChatButton" x="#screen_width/2" y="#screen_height/2" w="182" h="182" alignChildren="true" >
    <!-- 正常显示状态 -->
    <Normal>
        <!-- 这里写 正常显示的元素,比如图片、文字等 -->
    </Normal>
    <!-- 按下显示状态 -->
    <Pressed>
        <!-- 这里写 按下显示的元素,比如图片、文字等 -->
    </Pressed>
    <Triggers>
        <Trigger action="up">
            <!-- 执行解锁命令;要记得加上解锁命令,否则只是在后台打开了,还要解锁才能看到 -->
            <ExternCommand command="unlock" />
            <!-- 根据 action & package(包名) & class(类名) 启动 应用程序 -->
            <IntentCommand action="android.intent.action.MAIN" package="com.tencent.mm" class="com.tencent.mm.ui.LauncherUI" />
        </Trigger>
    </Triggers>
</Button>

【condition:条件】;满足 condition 条件时,执行 command,command="unlock" 解锁命令

<Button w="#screen_width" h="#screen_height" >
    <Triggers>
        <!--down (按下);move(移动); up (抬起);double (双击)-->
        <Trigger action="up,cancel">
            <!--condition;当 #touch_begin_y-#touch_y 大于等于 300 时,解锁-->
            <ExternCommand command="unlock" condition="#touch_begin_y-#touch_y }= 300" />
        </Trigger>
    </Triggers>
</Button>

用 Button 来做一个上滑手势,并解锁

<!--解锁文字提示-->
<Text x="#screen_width/2" y="#screen_height-100-#unlockMove" align="center" alignV="center" color="#ffffff" size="42" text="向上滑动解锁"/>
<!--解锁相关 变量、动画、按钮-->
<Group name="unlock" >
    <!--实时变量-->
    <Var name="unlockMove" expression="ifelse(#unlockDown==1,max(#touch_begin_y-#touch_y,0),max(#touch_begin_y-#touch_y,0) { 300,max(#touch_begin_y-#touch_y,0)*(1-#unlockBack),0)" />
    <!--动画-->
    <Var name="unlockBack">
        <VariableAnimation initPause="true" loop="false">
            <Item value="0" time="0" easeType="BounceEaseOut" />
            <Item value="1" time="300" />
        </VariableAnimation>
    </Var>
    <!--解锁按钮-->
    <Button w="#screen_width" h="#screen_height" >
        <Triggers>
            <Trigger action="down">
                <VariableCommand name="unlockDown" expression="1"/>
            </Trigger>
            <Trigger action="up,cancel">
                <VariableCommand name="unlockDown" expression="0" />
                <Command target="unlockBack.animation" value="play" />
                <!--condition 执行条件,当 max(#touch_begin_y-#touch_y,0) 大于等于 300px 时 unlock-->
                <ExternCommand command="unlock" condition="max(#-#touch_y,0) }= 300" />
            </Trigger>
        </Triggers>
    </Button>
</Group>
unlockMove  数值变量;
unlockBack  动画变量,播放时,值从 0-1 ;
Button      按钮的大小,通过 condition(条件) 来判断是否解锁 。
down 时 unlockDown 等于 1,up,cancel 时 unlockDown 等于 0

从 unlockMove 开始解释,别被变量吓到了,简化之后实际是这样的 ifelse(x0,y0,x1,y1,0)

unlockDown==1 时, unlockMove 值等于 max(#touch_begin_y-#touch_y,0),
#touch_begin_y 减去 #touch_y 等于滑动距离,
max(#touch_begin_y-#touch_y,0) 小于 300 时,unlockMove 值等于max(#touch_begin_y-#touch_y,0)*(1-#unlockBack),否则结果为 0

按钮执行的命令
1.down 时 unlockDown 等于 1,up,cancel 时 unlockDown 等于 0
2.播放动画 unlockBack.animation
3.condition;当 max(#touch_begin_y-#touch_y,0) 大于等于 300 时,解锁

注意:

  • 按钮中 trigger 被 triggers 包含
  • 当定义 alignChildren="true" 时,Button 内的元素坐标都是基于按钮的坐标来计算;否则坐标均是独立计算
  • <ExternCommand command="unlock" /> 解锁命令;要记得加上,否则只是在后台打开了

# 滑动

Unlocker,Slider

请先看《百变锁屏入门》-解锁 Unlocker (opens new window)
Unlocker 与 Slider 的用法是一样的,都是通过滑动来激活某些操作,只不过 Unlocker 能直接解锁。

属性 释义
StartPoint 起始点
EndPoint 目标点
haptic haptic="true"时振动 ,前提是用户没有在系统设置中关闭
alignChildren true/false,为 true 时,内部元素按绝对坐标排布
bounceInitSpeed(旧方案) 回弹动画初始速度(距离单位为像素,时间单位为秒),支持表达式
bounceAcceleration(旧方案) 回弹动画加速度(距离单位为像素,时间单位为秒),支持表达式
easeType=“”或 easeExp=“” (新方案) 缓动类型;可查看:缓动函数
easeTime=“”(新方案) 缓动时间
alwaysShow true/false" 默认是 false,当一个 Slider 可见时,其他 Slider 消失。
normalSound(StartPoint 下) 指定在 normal 状态播放的音效
pressedSound(StartPoint 下) 指定在 press 状态播放的音效
reachedSound(EndPoint 下) 到达该 endpoint 后播放的音效
NormalState 正常状态
PressedState 按下状态
ReachedState 激活状态

注意:

  • Unlocker 与 Slider 都有三种状态(NormalState、PressedState、ReachedState),都可以包含任意界面元素,如 Image Text 等,也可以不指定,相关元素在该状态下才显示
  • 示例中,起始点(StartPoint)和目标点(EndPoint)它们都可以包含 NormalState、PressedState、ReachedState 三个状态,但是在起始点(StartPoint)中的按下状态(PressedState)包含的元素会随你手指移动

示例:

<Slider name="slider">
    <StartPoint x="0" y="1600" w="1080" h="320" easeType="QuadEaseOut" easeTime="1000">
        <NormalState>
            <Image x="31" y="#screen_height-117" src="unlock_button.png" />
        </NormalState>
        <!-- Trigger Slider的状态切换时,支持Trigger触发 -->
        <PressedState>
            <Text x="640" y="240" w="640"  color="#FE9D01" size="28" text="小米主题" />
        </PressedState>
        <ReachedState>
            <DateTime/>
            <Trigger>
                <SoundCommand sound="reached.mp3" volume="1"/>
            </Trigger>
        </ReachedState>
    </StartPoint>
    <EndPoint x="359" y="#screen_height-117" w="90" h="90">
        <Path x="0" y="#screen_height-117">
            <Position x="31" y="0" />
            <Position x="359" y="0" />
        </Path>
        <NormalState>
        </NormalState>
        <PressedState>
        </PressedState>
        <ReachedState>
        </ReachedState>
    </EndPoint>
</Slider>

# 音乐播放器

<MusicControl name="music_control" y="800" w="1080" h="226" autoShow="true" defAlbumCover="music/default_bg.jpg" enableLyric="true" updateLyricInterval="100" visibility="false" >
    <!-- 音乐窗口背景 -->
    <Image x="40" y="0" w="1000" h="226" src="music/music_bg.9.png" />
    <!-- 专辑图 -->
    <Group x="40" w="226" h="226" layered="true">
        <Image name="music_album_cover" w="226" h="226" />
        <Image w="226" h="226" src="music/music_bg.9.png" xfermode="dst_in" />
    </Group>
    <!-- 歌曲名、歌手名、播放时间 -->
    <Text x="296" y="61" w="674" alignV="center" textExp="ifelse(strIsEmpty(@music_control.title),'暂无音乐',@music_control.title)" color="#000000" size="39" marqueeSpeed="30" bold="true"/>
    <Text x="296" y="117" w="350" alignV="center" textExp="ifelse(strIsEmpty(@music_control.artist),'--',@music_control.artist)" color="#000000" size="36" marqueeSpeed="30" alpha="128" />
    <Text x="296" y="169" w="350" alignV="center" textExp="ifelse(strIsEmpty(@music_control.title),'--:--/--:--',formatDate('mm:ss / ',#music_control.music_position)+formatDate('mm:ss',#music_control.music_duration))" color="#000000" size="33" marqueeSpeed="30" alpha="128" />
    <!-- 上一曲、播放、暂停、下一曲 -->
    <Button name="music_prev" x="#screen_width - 308" y="76" w="120" h="150" align="right" alignChildren="true">
        <Normal>
            <Image x="60" y="75" align="center" alignV="center" src="music/prev.png" />
        </Normal>
        <Pressed>
            <Image x="60" y="75" align="center" alignV="center" src="music/prev.png" alpha="128"/>
        </Pressed>
    </Button>
    <Button name="music_play" x="#screen_width - 188" y="76" w="120" h="150" align="right" alignChildren="true">
        <Normal>
            <Image x="60" y="75" align="center" alignV="center" src="music/play.png" />
        </Normal>
        <Pressed>
            <Image x="60" y="75" align="center" alignV="center" src="music/play.png" alpha="128"/>
        </Pressed>
    </Button>
    <Button name="music_pause" x="#screen_width - 188" y="76" w="120" h="150" align="right" alignChildren="true">
        <Normal>
            <Image x="60" y="75" align="center" alignV="center" src="music/pause.png" />
        </Normal>
        <Pressed>
            <Image x="60" y="75" align="center" alignV="center" src="music/pause.png" alpha="128"/>
        </Pressed>
    </Button>
    <Button name="music_next" x="#screen_width - 68" y="76" w="120" h="150" align="right" alignChildren="true">
        <Normal>
            <Image x="60" y="75" align="center" alignV="center" src="music/next.png" />
        </Normal>
        <Pressed>
            <Image x="60" y="75" align="center" alignV="center" src="music/next.png" alpha="128"/>
        </Pressed>
    </Button>
</MusicControl>

示例:音乐播放器 示例 lock915.mtz (opens new window)

属性 释义
autoShow 是否根据系统播放音乐自动展现和关闭音乐组件
defAlbumCover 指定默认专辑图片,填路径文件名
enableLyric true/false,是否开启歌词支持。开启歌词后会有一定性能的损失,不需要歌词的时候不要打开
updateLyricInterval 音乐信息的更新间隔。间隔越小,更新越及时,但带来的性能损失也越大,适当将间隔设小
music_album_cover 专辑图
clip Group 内的元素只显示 w h 规定的区域,超出部份切除
musicName.title 歌曲名称;获取 name="musicName" 标题文本
musicName.artist 歌手名称;获取 name="musicName" 专辑文本
musicName.album 专辑名称;ps:专辑名可能会无数据
musicName.lyric_before 已播放的歌词
musicName.lyric_after 未播放的歌词
musicName.lyric_last 上一句歌词
musicName.lyric_current 正在播放的歌词
musicName.lyric_next 下一句歌词
musicName.lyric_current_line_progress 当前行歌词的行内播放进度,浮点数 0 ~ 1.0
musicName.music_duration 歌曲长度,单位 ms
musicName.music_position 歌曲当前播放位置,单位 ms
musicName.package 当前播放的音乐应用包名
musicName.class 当前播放的音乐应用类名

要点:

  • 在从未开始播放音乐前,歌曲信息无法获取到,可以为 歌曲、歌手、专辑、时间 等文本写一个为空时判断,以显示无数据状态下的信息。示例:ifelse(strIsEmpty(@musicName.title),'暂无音乐',@musicName.title);当strIsEmpty(@musicName.title)==1 时,显示 '暂无音乐',否则显示 @music.title

  • 音乐播放时长和总时长,可以用 DateTime 标签来写,示例:

<DateTime x="" y="" color="" size="42" format="mm:ss" value="#musicName.music_position"/>
  • 专辑图为一张比例为 1:1 的图,可以用 图片混合 方式做成任意形状;比如:矩形圆角、圆盘;也可以给专辑图加上旋转,可以使专辑图在播放音乐时有旋转效果。

# 日历

<!-- layer对内部刷新不高的元素有着非常大的优化作用,但有一点要注意:它会自动移至当前界面图层的最上方 -->
<Layer x="200" y="300" width="560" height="560" frameRate="0" >
    <!-- 每月的第一天在日历表第一排的位置 -->
    <Var name="dw0_1" expression="(7-(#date-#day_of_week)%7)%7+1" const="true" />
    <!-- 闰年么? -->
    <Var name="leap_year" expression="eq((#year%4),0)*ne((#year%100),0)+eq((#year%400),0)" const="true" />
    <!-- 这个月有多少天 -->
    <Var name="max_date" expression="28+ne(#leap_year+ne(#month+1,2),0)+ne((#month+1),2)+ne(#month,1)*ne(#month,3)*ne(#month,5)*ne(#month,8)*ne(#month,10)" const="true" />
    <Array name="fde" count="42" indexName="__i" >
        <Text textExp="substr('日一二三四五六',#__i,1)" size="36" x="20+#__i%7*80" y="0"  color="#ffffff"  visibility="lt(#__i,7)" />
        <Rectangle x="#__i%7*80" y="int(#__i/7)*80+70" w="79" h="79" fillColor="argb(55,255,255,255)" visibility="int(#__i-#dw0_1+2)!=#date" />
        <!-- 当天背景 -->
        <Rectangle x="#__i%7*80" y="int(#__i/7)*80+70" w="79" h="79" fillColor="argb(255,50,177,105)" visibility="int(#__i-#dw0_1+2)==#date" />
        <DateTime format="d" value="#time_sys+(#__i+2-#date-#dw0_1)*86400000" size="36" x="40+#__i%7*80" y="int(#__i/7)*80+70" align="center" color="#ffffff" alpha="int(120+135*eq(#__i-#dw0_1+2,#date))"  visibility="int(#__i-#dw0_1+2)*le(int(#__i-#dw0_1+2),#max_date)" />
        <!-- 农历 -->
        <DateTime formatExp="ifelse(eqs(formatDate('e',#time_sys+(#__i+2-#date-#dw0_1)*86400000),'初一'),'N月e','e')"  value="#time_sys+(#__i+2-#date-#dw0_1)*86400000" size="18" x="40+#__i%7*80" y="50+int(#__i/7)*80+70" align="center" color="#ffffff" alpha="int(120+135*eq(#__i-#dw0_1+2,#date))" visibility="int(#__i-#dw0_1+2)*le(int(#__i-#dw0_1+2),#max_date)"  />
    </Array>
</Layer>

返回手势

<!-- 返回手势 -->
<Group name="Return" >
    <Image x="#about*1080" y="#touch_begin_y" w="#ReturnMove*0.5" h="858" alignV="center" rotation="#about*180" pivotY="429" src="bg/gesture_back_background.png" />
    <Image x="#ReturnMove/4+#about*(1080-#ReturnMove/2)" y="#touch_begin_y" align="center" alignV="center" src="bg/gesture_back_arrow.png" visibility="#ReturnMove }= 100" />
    <Var name="ReturnMove" expression="ifelse(#ReturnDown==1,min(abs(#touch_begin_x-#touch_x),168),min(abs(#touch_begin_x-#touch_x),168)*(1-#ReturnBack)*int(#ReturnMove}0))"/>
    <Var name="ReturnBack">
        <VariableAnimation initPause="true" loop="false">
            <Item value="0" time="0" easeType="QuadEaseOut" />
            <Item value="1" time="200" />
        </VariableAnimation>
    </Var>
    <Button w="1080" h="#screen_height" >
        <Triggers>
            <Trigger action="down" condition="#touch_x{50 || #touch_x}1030" >
                <VariableCommand name="ReturnDown" expression="1"/>
                <VariableCommand name="about" expression="#touch_x}540"/>
            </Trigger>
            <Trigger action="up,cancel" >
                <VariableCommand name="ReturnDown" expression="0" />
                <AnimationCommand target="ReturnBack" command="play" />
                <!-- 当移动距离大于100px时,执行动画 -->
                <AnimationCommand target="" command="play" condition="#ReturnMove}100" />
            </Trigger>
        </Triggers>
    </Button>
</Group>

返回手势附件素材 (opens new window)


# 外部数据

ContentProvider

content provider 提供了查询应用程序信息的通用接口,定义了新的 xml 代码来查询 content provider,并查询到的信息绑定到变量上,用来显示第三方应用程序的信息,只要第三方应用提供相应的 content provider。比如:可以显示 天气信息、运动计步、待办事项、便签 等。

<VariableBinders>
    <ContentProviderBinder name="name1" uri="content://sample/test" uriFormat="" uriParas="" columns="col1,col2" where="" args="" order="" countName="count_name">
        <Variable name="variable_name1" type="int" column="col1" row="0"/>
        <Variable name="variable_name2" type="string" column="col2" row="0"/>
    </ContentProviderBinder>
    <ContentProviderBinder name="name2" dependency="name1" />
</VariableBinders>
  • VariableBinders 定义各种变量绑定到的源。支持 ContentProviderBinder、WebServiceBinder、SensorBinder

  • ContentProviderBinder 定义一个 content provider 源和绑定到它上面的变量

属性 释义
uir 指定选用哪个 content provider
uriFormat 如果 uri 需要添加变量,可以用格式化,需要和 uriParas 一起使用
uriParas 同 Text element 的格式
columns 需要查询的列名,用逗号分隔
where 查询条件,同 SQL
args “where” 的参数.
order 排序条件, 同 SQL
countName 将查询结构数量绑定到该变量名
  • Variable 定一个绑定变量
属性 释义
name 变量名
type content provider 中的数据类型: string/double/float/int/long
column 变量绑定到的列的名称.
row 变量绑定到的行数,默认为 0.
  • 支持 where 的格式化 where="" whereFormat="" whereParas=""

  • dependency 支持依赖关系,即某个 ContentProviderBinder 查询结束后获取的变量作为下一个 ContentProviderBinder 查询的参数()


<ContentProviderBinder name="name1" />
<ContentProviderBinder name="name2" dependency="name1" />
  • name1 查询结束后会触发 name2 的查询
  • name2 的查询可以使用 name1 的变量
  • 并且如果 name1 数据发生变化重新查询后,会触发 name2 的重新查询

# 消息通知

<!-- 查询通知 -->
<VariableBinders>
    <ContentProviderBinder name="data" uri="content://keyguard.notification/notifications" columns="icon,title,content,time,info,subtext,key" countName="hasnotifications">
        <List name="notice_list" />
    </ContentProviderBinder>
</VariableBinders>
<!-- 通知列表;maxHeight 列表最大显示高度,超出部分不显示,可上下滚动列表 -->
<List name="notice_list" x="0" y="0" w="858" maxHeight="370" data="icon:bitmap,title:string,content:string,time:string,info:string,subtext:string,key:int" visibility="#hasnotifications">
    <Item x="0" y="0" w="858" h="185">
        <!-- 单条消息的显示大小和点击区域 -->
        <Button x="0" y="0" w="858" h="185" alignChildren="true">
            <Normal>
                <Image x="0" y="0" src="notice_bg.png" />
            </Normal>
            <Pressed>
                <Image x="0" y="0" src="notice_bg.png" alpha="200" />
            </Pressed>
            <Image x="50" y="21" w="114" h="114" name="notice_icon" />
            <Text x="190" y="42" size="32" color="#ee000000" bold="true" w="500" h="36" marqueeSpeed="30" name="notice_title" />
            <Text x="190" y="80" size="28" color="#b4000000" w="500" h="42" marqueeSpeed="30" name="notice_content" />
            <Text x="800" y="64" size="28" align="right" color="#b4000000"  name="notice_time" />
            <Triggers>
                <Trigger action="up">
                    <IntentCommand action="com.miui.app.ExtraStatusBarManager.action_remove_keyguard_notification" broadcast="true">
                        <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_key" type="int" expression="#notice_list.key" />
                        <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_click" type="int" expression="1" />
                    </IntentCommand>
                </Trigger>
            </Triggers>
        </Button>
    </Item>
    <AttrDataBinders>
        <AttrDataBinder target="notice_icon" attr="bitmap" data="icon" />
        <AttrDataBinder target="notice_title" attr="text" data="title" />
        <AttrDataBinder target="notice_content" attr="text" data="content" />
        <AttrDataBinder target="notice_time" attr="text" data="time" />
    </AttrDataBinders>
</List>

# 带滑动删除的消息通知

<VariableBinders>
    <!-- 查询通知 -->
    <ContentProviderBinder name="data" uri="content://keyguard.notification/notifications" columns="icon,title,content,time,key" countName="hasnotifications">
        <Variable name="notice_icon0" type="blob.bitmap" column="icon" row="0"/>
        <Variable name="notice_icon1" type="blob.bitmap" column="icon" row="1"/>
        <Variable name="notice_icon2" type="blob.bitmap" column="icon" row="2"/>
        <Variable name="notice_icon3" type="blob.bitmap" column="icon" row="3"/>
        <Variable name="notice_title" type="string[]" column="title"/>
        <Variable name="notice_content" type="string[]" column="content"/>
        <Variable name="notice_time" type="string[]" column="time"/>
        <Variable name="notice_key" type="string[]" column="key"/>
        <Trigger>
            <AnimationCommand target="noticeUp" command="play(0,0)" />
            <AnimationCommand target="_noticeAni" command="play(0,0)" />
        </Trigger>
    </ContentProviderBinder>
</VariableBinders>

<!-- 通知 -->
<Group y="688">
    <Var name="_noticeMove" expression="int(#touch_x-#touch_begin_x)*#noticeMoveDown+#_noticeAni" />
    <Var name="_noticeAni">
        <VariableAnimation loop="false" initPause="true">
            <Item value="0" time="0" />
            <Item value="int(#touch_x-#touch_begin_x)" time="100" />
            <Item value="ifelse(int(#touch_x-#touch_begin_x)}=300,1080,0)" time="300"/>
            <Triggers>
                <Trigger action="end" condition="#_noticeAni == 1080">
                    <AnimationCommand target="noticeUp" command="play" />
                </Trigger>
            </Triggers>
        </VariableAnimation>
    </Var>
    <Var name="noticeUp">
        <VariableAnimation name="noticeUpAni" loop="false" initPause="true">
            <Item value="0" time="0" />
            <Item value="1" time="300"/>
            <Triggers>
                <Trigger action="end" condition="#noticeUpAni.current_frame == -1" >
                    <MultiCommand>
                        <IntentCommand action="com.miui.app.ExtraStatusBarManager.action_remove_keyguard_notification" broadcast="true" >
                            <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_key" type="int" expression="@notice_key[#noticeDown]" />
                            <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_click" type="int" expression="0" />
                        </IntentCommand>
                    </MultiCommand>
                </Trigger>
            </Triggers>
        </VariableAnimation>
    </Var>
    <!-- count="4" 最多显示4条消息;size="4" 消息时间,显示几条消息就有几个时间 -->
    <Array count="4" indexName="_notice" visibility="#hasnotifications}0" >
        <Button x="ifelse(#noticeDown==#_notice,#_noticeMove,0)" y="#_notice*193 - ifelse(#_notice}#noticeDown,#noticeUp*193,0)" w="1080" h="183" alignChildren="true" alpha="ifelse(#noticeDown==#_notice,255-int(#_noticeMove)/3,255)" visibility="#_notice { #hasnotifications" >
            <Normal>
                <Rectangle x="28" w="1024" h="183" fillColor="#ffffff" cornerRadius="28" />
            </Normal>
            <Pressed>
                <Rectangle x="28" w="1024" h="183" fillColor="#eeeeee" cornerRadius="28" />
            </Pressed>
            <Text name="noticeTime" x="#screen_width-66" y="62" align="right" alignV="center" color="#BFB7BE" size="32" spacingAdd="1" textExp="@notice_time[#_notice]"/>
            <Text x="194" y="62" w="#screen_width-194-66- ifelse(#time_format,100,150)" alignV="center" color="#000000" size="38" marqueeSpeed="30" textExp="@notice_title[#_notice]" bold="true" />
            <Text x="194" y="121" w="#screen_width-194-66" alignV="center" color="#606160" size="36" marqueeSpeed="30" textExp="@notice_content[#_notice]" fontFamily="miui-regular" />
            <Triggers>
                <Trigger action="down">
                    <VariableCommand name="noticeDown" expression="#_notice"/>
                    <VariableCommand name="noticeMoveDown" expression="1"/>
                </Trigger>
                <Trigger action="up,cancel">
                    <VariableCommand name="noticeMoveDown" expression="0"/>
                    <AnimationCommand target="_noticeAni" command="play(100,300)" />
                    <IntentCommand action="com.miui.app.ExtraStatusBarManager.action_remove_keyguard_notification" broadcast="true" condition="abs(#touch_x-#touch_begin_x){10" >
                        <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_key" type="int" expression="@notice_key[#noticeDown]" />
                        <Extra name="com.miui.app.ExtraStatusBarManager.extra_notification_click" type="int" expression="1" />
                    </IntentCommand>
                </Trigger>
            </Triggers>
        </Button>
    </Array>
    <!-- 头像坐标;50 x坐标,30 y坐标;60 w宽度;60 h高度 -->
    <Var name="noticeIconXYWH" type="number[]" values="60,36,110,110" const="true" />
    <!-- 通知图标;name 图标名称,从0-3,表示共计四条;visibility,用 hasnotifications 控制是否需要显示头像  -->
    <Image name="notice_icon0" x="#noticeIconXYWH[0]+ifelse(#noticeDown==0,#_noticeMove,0)" y="#noticeIconXYWH[1]+0*193 - ifelse(0}#noticeDown,#noticeUp*193,0)" w="#noticeIconXYWH[2]" h="#noticeIconXYWH[3]" alpha="ifelse(#noticeDown==0,255-int(#_noticeMove)/3,255)" visibility="#hasnotifications}=1" />
    <Image name="notice_icon1" x="#noticeIconXYWH[0]+ifelse(#noticeDown==1,#_noticeMove,0)" y="#noticeIconXYWH[1]+1*193 - ifelse(1}#noticeDown,#noticeUp*193,0)" w="#noticeIconXYWH[2]" h="#noticeIconXYWH[3]" alpha="ifelse(#noticeDown==1,255-int(#_noticeMove)/3,255)" visibility="#hasnotifications}=2" />
    <Image name="notice_icon2" x="#noticeIconXYWH[0]+ifelse(#noticeDown==2,#_noticeMove,0)" y="#noticeIconXYWH[1]+2*193 - ifelse(2}#noticeDown,#noticeUp*193,0)" w="#noticeIconXYWH[2]" h="#noticeIconXYWH[3]" alpha="ifelse(#noticeDown==2,255-int(#_noticeMove)/3,255)" visibility="#hasnotifications}=3" />
    <Image name="notice_icon3" x="#noticeIconXYWH[0]+ifelse(#noticeDown==3,#_noticeMove,0)" y="#noticeIconXYWH[1]+3*193 - ifelse(3}#noticeDown,#noticeUp*193,0)" w="#noticeIconXYWH[2]" h="#noticeIconXYWH[3]" alpha="ifelse(#noticeDown==3,255-int(#_noticeMove)/3,255)" visibility="#hasnotifications}=4" />
</Group>

# 天气查询

# 接口返回字段

uri="content://weather/actualWeatherData/1"
ColIndex 字段名 说明 类型(type) 字段值示例 备注
0 publish_time 实时天气信息发布时间(time mills) string 1508143200000 可用于实时信息的时效判断
1 city_id 城市唯一标识 string 39.959_116.298 经纬度(北纬_东经)
2 city_name 城市/街道名称 string 安宁庄南路 能定位到街道时优先返回街道名,否则返回城市名
3 description 天气现象(实时) string 多云
4 temperature 气温(实时) string 18℃
5 temperature_range 气温(预报) string 8℃~18℃ 支持数组,type="string[]"
6 aqilevel AQI 等级 int 90 参考见:AQI 等级
7 locale 语言 string zh_CN
8 weather_type 天气类型(实时) int
string
1 type="int"时不支持数组,type="string[]"时支持数组
与天气现象对应关系参考见:天气现象代码对照表
9 humidity 湿度 int 68% 单位:%
10 sunrise 日出时间 int 80760000 距 0 点过去的毫秒数(换算成时间时只取 Hour 和 Minute,例如 80760000 换算后为 06:26)
11 sunset 日落时间 int 34440000 距 0 点过去的毫秒数(换算成时间时只取 Hour 和 Minute,34440000 换算后为 17:34)
12 wind 风向,风力 string 东南风,2 级
13 day 日期偏移量 int 1 0 代表昨天,1 代表今天,2 代表明天
14 pressure 气压 int 1016hPa 单位:hPa
15 timestamp 预报天气信息发布时间(time mills) string 1508055000000
16 tmphighs 最高温(预报) string 18 支持数组,type="string[]"
17 tmplows 最低温(预报) string 8 支持数组,type="string[]"
18 forecast_type 天气类型(预报) int
string
1 type="int"时不支持数组,type="string[]"时支持数组
与天气现象对应关系参考见:天气现象代码对照表
19 weathernamesfrom 天气现象(预报) string 多云 支持数组,type="string[]"
20 weathernamesto 同上
21 temperature_unit 气温单位 int 1 1 代表摄氏度,0 代表华氏度
22 water 暂时无用(降水概率) - 50% 暂时无用

# AQI等级 中国大陆标准

等级 范围
aqi >= 0 && aqi <= 50
aqi > 50 && aqi <= 100
轻度污染 aqi > 100 && aqi <= 150
中度污染 aqi > 150 && aqi <= 200
重度污染 aqi > 200 && aqi <= 300
严重污染 aqi > 300
  • 最多可以获取到未来 5 天天气详情(包含今天)
<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="WeatherProvider" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
    <!-- MIUI天气代码;uri 老接口:content://weather/weather,旧主题更新时请更换为新接口:content://weather/actualWeatherData/1 -->
    <ContentProviderBinder name="WeatherProvider" uri="content://weather/actualWeatherData/1" columns="city_id,city_name,weather_type,aqilevel,description,temperature,temperature_range,forecast_type,tmphighs,tmplows,wind,humidity,sunrise,sunset,pressure,weathernamesfrom,publish_time,temperature_unit,somatosensory,kinect" countName="hasweather">
        <!-- 城市北纬东经 -->
        <Variable name="city_id" type="string" column="city_id"/>
        <!-- 城市/街道名称 -->
        <Variable name="weather_location" type="string" column="city_name"/>
        <!-- 天气类型(实时) -->
        <Variable name="weather_id" type="int" column="weather_type"/>
        <Variable name="weather_type" type="string[]" column="weather_type"/>
        <!-- 今日温度 -->
        <Variable name="weather_temperature" type="string" column="temperature"/>
        <!-- 今日温度区间(20℃~30℃) -->
        <Variable name="weather_temperature_range" type="string" column="temperature_range"/>
        <!-- 天气现象(实时):晴 -->
        <Variable name="weather_description" type="string" column="description"/>
        <!-- 日出时间 -->
        <Variable name="weather_sunrise" type="int" column="sunrise"/>
        <!-- 日落时间 -->
        <Variable name="weather_sunset" type="int" column="sunset"/>
        <!-- 风力 -->
        <Variable name="weather_wind" type="string" column="wind"/>
        <!-- 气压 -->
        <Variable name="weather_pressure" type="int" column="pressure"/>
        <!-- 湿度 -->
        <Variable name="weather_humidity" type="int" column="humidity"/>
        <!-- 天气类型(预报) -->
        <Variable name="weather_forecast_type" type="string[]" column="forecast_type"/>
        <!-- 天气现象(预报) -->
        <Variable name="weather_weathernamesfrom" type="string[]" column="weathernamesfrom"/>
        <!-- 最高温度 -->
        <Variable name="weather_temphigh" type="string[]" column="tmphighs"/>
        <!-- 最低温度 -->
        <Variable name="weather_templow" type="string[]" column="tmplows"/>
        <!-- 实时天气信息发布时间 -->
        <Variable name="weather_publish_time" type="string" column="publish_time"/>
        <!-- 气温单位,1代表摄氏度,0代表华氏度 -->
        <Variable name="weather_temperature_unit" type="int" column="temperature_unit" />
        <!-- AQI等级 -->
        <Variable name="weather_aqi" type="int" column="aqilevel"/>
        <Trigger>
            <!-- 空气质量 -->
            <VariableCommand name="air_quality" expression="ifelse(#weather_aqi}=0**#weather_aqi{=50,'空气优',#weather_aqi}50**#weather_aqi{=100,'空气良好',#weather_aqi}100**#weather_aqi{=150,'轻度污染',#weather_aqi}150**#weather_aqi{=200,'中度污染',#weather_aqi}200**#weather_aqi{=300,'重度污染',#weather_aqi}300,'严重污染','获取信息异常')" type="string"/>
            <!-- 天气现象简化版;可用于天气图标展示。例如:srcid="#weatherId" -->
            <VariableCommand name="weatherId" expression="ifelse(#weather_id}25||#weather_id{0,0, (#weather_id}=4**#weather_id{=6||#weather_id}=8**#weather_id{=11||#weather_id==25),4,#weather_id}=13**#weather_id{=17,13 ,#weather_id}=18**#weather_id{=21||#weather_id==23,18,#weather_id)"/>
        </Trigger>
    </ContentProviderBinder>
    <ContentProviderBinder name="WeatherAqi" dependency="WeatherProvider" uriFormat="content://weatherinfo/aqi/%s" uriParas="@city_id" columns="aqi,pm25,pm10,so2,no2" countName="hasweatherinfo">
        <Variable name="aqi" type="int" column="aqi"/>
        <Variable name="pm25" type="int" column="pm25"/>
        <Variable name="no2" type="int" column="no2"/>
        <Variable name="pm10" type="int" column="pm10"/>
        <Variable name="so2" type="int" column="so2"/>
    </ContentProviderBinder>
</VariableBinders>

<!-- 把数据以文本的方式展示出来 -->
<Text x="100" y="200" size="24" color="#ffffff" textExp="@weather_description+' '+@weather_location+' '+#pm25+' '+@air_quality"/>

<Array count="5" indexName="__w" >
    <Text x="100" y="300+60*#__w"  color="#FFFFFF" size="40" textExp="@weather_forecast_type[#__w]"/>
</Array>

就是将查询到的数据以文本或者是图形的方式展示出来,具体作用可以翻译 column 的值,再通过 Variable 自定义的变量名 展示出来。

# 天气现象代码对照表:

#weather_id=0代表晴天,=1代表多云...
天气现象编码 / 天气现象 天气现象编码 / 天气现象 天气现象编码 / 天气现象
0 :晴 9 : 大雨 18 : 强沙尘暴
1 :多云 10 : 中雨 19 : 沙尘暴
2 :阴 11 : 小雨 20 : 沙尘
3 :雾 12 : 雨夹雪 21 : 扬沙
4 :特大暴雨 13 : 暴雪 22 : 冰雹
5 :大暴雨 14 : 阵雪 23 : 浮尘
6 :暴雨 15 : 大雪 24 : 霾
7 :雷阵雨 16 : 中雪 25 : 冻雨
8 :阵雨 17 : 小雪 99 : 无

# 简化版天气图标使用

在日常制作中如果画 26 个天气图标工作量较大,所以我们可以把一个类型的图标集合为一个图标即可。

如下示例:

<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="WeatherProvider" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
    <ContentProviderBinder name="WeatherProvider" uri="content://weather/actualWeatherData/1" columns="city_name,weather_type,aqilevel,description,temperature,temperature_range" countName="hasweather">
        <Variable name="weather_location" type="string" column="city_name"/>
        <Variable name="weather_id" type="int" column="weather_type"/>
        <Variable name="weather_temperature" type="string" column="temperature"/>
        <Variable name="weather_description" type="string" column="description"/>
        <Variable name="weather_aqi" type="int" column="aqilevel"/>
        <Trigger>
            <!-- 空气质量 -->
            <VariableCommand name="air_quality" expression="ifelse(#weather_aqi}=0**#weather_aqi{=50,'空气优',#weather_aqi}50**#weather_aqi{=100,'空气良好',#weather_aqi}100**#weather_aqi{=150,'轻度污染',#weather_aqi}150**#weather_aqi{=200,'中度污染',#weather_aqi}200**#weather_aqi{=300,'严重污染',#weather_aqi}300,'重度污染','获取信息异常')" type="string"/>
            <!-- 天气类型简化版;可用于天气图标展示。例如:srcid="#weatherId" -->
            <VariableCommand name="weatherId" expression="ifelse(#weather_id}25||#weather_id{0,0, (#weather_id}=4**#weather_id{=6||#weather_id}=8**#weather_id{=11||#weather_id==25),4,#weather_id}=13**#weather_id{=17,13 ,#weather_id}=18**#weather_id{=21||#weather_id==23,18,#weather_id)"/>
        </Trigger>
    </ContentProviderBinder>
</VariableBinders>

<!-- 用图展示天气现象 -->
<Image x="100" y="100" src="weather.png" srcid="#weatherId"/>

简化版图标对应天气现象示图:

image

11 个天气图标附件下载:11 个天气图标素材.zip (opens new window)


# 运动计步

新版计步(数据来源 小米健康;仅支持 miui12 使用)

旧版计步数据获取比较复杂,且无法获取 目标步数、公里数、卡路里 等,故增加 通过「小米健康」获取数据的方式 (仅支持 miui12 使用)

<ExternalCommands>
    <Trigger action="resume">
        <!-- 开屏刷新 运动记录数据 -->
        <BinderCommand name="MiSteps" command="refresh" />
    </Trigger>
</ExternalCommands>

<VariableBinders>
    <!-- 注意:不可卸载 小米健康App -->
    <ContentProviderBinder name="MiSteps" uri="content://com.mi.health.provider.main/activity/steps/brief" column="steps,goal,distance,energy,strength_duration,summary" countName="hasSteps" >
        <!-- 步数 -->
        <Variable name="MiSteps_steps" type="string" column="steps"/>
        <!-- 目标步数 -->
        <Variable name="MiSteps_goal" type="string" column="goal"/>
        <!-- 距离 -->
        <Variable name="MiSteps_distance" type="string" column="distance"/>
        <!-- 消耗卡路里 -->
        <Variable name="MiSteps_energy" type="string" column="energy"/>
        <!-- 运动中高强度时长 -->
        <Variable name="MiSteps_strength_duration" type="string" column="strength_duration"/>
        <!-- 运动是否达标 -->
        <Variable name="MiSteps_summary" type="string" column="summary"/>
    </ContentProviderBinder>
</VariableBinders>

<Text x="100" y="300" alignV="center" color="#ffffffff" size="42" textExp="'今日步数:' +@MiSteps_steps+ '步'"/>

属性 释义 单位 类型
steps 步数 步 steps string
goal 目标步数 步 steps string
distance 距离 公里 km string
energy 消耗卡路里 千卡 kcal string
strength_duration 运动中高强度时长 分钟 minute string
summary 运动是否达标;
0 暂无
1 尚未达标
2 运动不足
3 运动达标
string

获取数据时异常处理,参考下面示例代码:

  • 未安装「小米健康」app 时,点击跳转至应用商店下载页面
  • 已安装 但从未打开提示用户获取数据,并且点击后跳转 健康运动 页面
<!-- 判断是否有 小米健康应用 -->
<Image name="app_health" x="0" y="0" w="168" h="168" srcType="ApplicationIcon" srcExp="'com.mi.health,com.mi.health.home.HomeActivity'" alpha="0"/>
<Button x="540" y="1000" w="300" h="140" align="center" alignV="center" alignChildren="true">
    <Normal>
        <Rectangle w="300" h="140" fillColor="#33ffffff" cornerRadius="70"/>
    </Normal>
    <Pressed>
        <Rectangle w="300" h="140" fillColor="#66ffffff" cornerRadius="70"/>
    </Pressed>
    <Text x="150" y="70" align="center" alignV="center" color="#ffffffff" size="42" textExp="ifelse(#app_health.bmp_width,'打开','安装')"/>
    <!-- 提示 -->
    <Text x="150" y="-60" align="center" alignV="center" color="#ffffffff" size="39" textExp="ifelse(!#app_health.bmp_width,'无法获取数据,请安装小米健康',strIsEmpty(@MiSteps_goal),'点击获取数据','查看运动详情')"/>
    <Triggers>
        <Trigger action="up">
            <ExternCommand command="unlock"/>
            <!-- 未安装 跳转到应用商店 -->
            <IntentCommand action="android.intent.action.VIEW" uri="market://details?id=com.mi.health&amp;ref=mithemelocksreen" flags="268435456" condition="!#app_health.bmp_width" />
            <!-- 已安装 打开运动页面 -->
            <IntentCommand action="android.intent.action.VIEW" uri="com.mi.health://localhost/d?action=steps&amp;origin=mithemelocksreen" condition="#app_health.bmp_width"/>
        </Trigger>
    </Triggers>
</Button>

注意:上面的新版计步只有MIUI 12支持。
适配MIUI 11时注意设计与制作兼容,在V11的主题版本上可使用下面的旧版代码。

旧版计步(数据来源 小米计步)

<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="MiStep" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
  <ContentProviderBinder name="MiStep" uri="content://com.miui.providers.steps/item" columns="_id,_begin_time,_end_time,_mode,_steps" countName="hassteps" whereFormat="_begin_time>='%d'" whereParas="int((#time_sys-#hour24*3600000-#minute*60000-#second*1000)/1000)*1000">
    <Variable name="Mi_step" type="string[]" column="_steps"/>
    <Variable name="Mi_begin_time" type="string[]" column="_begin_time"/>
    <Variable name="Mi_end_time" type="string[]" column="_end_time"/>
    <Trigger>
      <!-- 初始化今天的总步数 -->
      <VariableCommand name="step_today" expression="0" />
      <!-- 计算今天的总步数 -->
      <LoopCommand count="#hassteps" indexName="__s">
        <VariableCommand name="step_today" expression="#step_today+int(@Mi_step[#__s])" />
      </LoopCommand>
    </Trigger>
  </ContentProviderBinder>
</VariableBinders>
<Text x="100" y="100" color="#ffffff" size="40" textExp="#step_today"/>
参数名称 含义 类型
_id 记录在 sqlite 的 id,从 1 开始计 string[]
_begin_time 计步打点开始时间(计步每次打点都有个开始时间和结束时间,隔一段时间存储到手机一次) string[]
_end_time 计步打点结束时间(计步每次打点都有个开始时间和结束时间,隔一段时间存储到手机一次) string[]
_steps 每次打点的步数 string[]
_mode 计步模式: 0:不支持模式, 1:静止, 2:走路, 3:跑步, 11:骑车, 12:交通工具 string[]

# 语音文字

用来获取用户说了什么内容?可以用语音来加强锁屏的能力,也给了更多的可能性。比如:锁屏语音输入法,锁屏记事本 等。发挥你的想象,没有什么是做不到的。不是很明白怎么写?可以先看关于 外部数据的介绍

<VariableBinders>
    <!-- 启动录音 -->
    <ContentProviderBinder name="MiAi" uriFormat="content://com.miui.voiceassist.speech.api/%s" uriParas="@t1" queryAtStart="false" />
    <!-- 识别结果 -->
    <ContentProviderBinder name="MiAiResult" uri="content://com.miui.voiceassist.speech.api/status" columns="AsrStatus,AsrResult" countName="MiAiResult" queryAtStart="true">
        <Variable name="Asr_Status" type="string" column="AsrStatus"/>
        <Variable name="Asr_Result" type="string" column="AsrResult"/>
    </ContentProviderBinder>
</VariableBinders>
<Var expression="#second" threshold="5" >
    <Trigger>
        <ExternCommand command="pokewakelock"/>
    </Trigger>
</Var>
<Rectangle w="1080" h="#screen_height" fillColor="#f2f2f2" />
<Rectangle y="#screen_height-160" w="1080" h="2" fillColor="#000000" alpha="255*0.05" />
<Text x="540" y="1000" align="center" alignV="center" color="#000000" size="45" textExp="@AsrResult"/>
<!-- 识别结果刷新 -->
<Var expression="#time" threshold="10" visibility="#MiAiResultRefresh" >
    <Trigger>
        <BinderCommand name="MiAiResult" command="refresh" />
        <VariableCommand name="AsrResult" expression="@Asr_Result" type="string"/>
    </Trigger>
</Var>
<Button x="540" y="#screen_height - 80" w="#screen_width-140" h="100" align="center" alignV="center" alignChildren="true" interceptTouch="true">
    <Normal>
        <Rectangle x="0" y="0" w="#screen_width-140" h="100" fillColor="#FFFFFF" cornerRadius="20"/>
    </Normal>
    <Pressed>
        <Rectangle x="0" y="0" w="#screen_width-140" h="100" fillColor="#E8E8E8" cornerRadius="20"/>
    </Pressed>
    <Text x="(#screen_width-140)/2" y="50" align="center" alignV="center" color="#000000" size="42" textExp="ifelse(#MiAiResultRefresh,'松开结束','按下说话')"/>
    <Triggers>
        <Trigger action="down">
            <!-- 开始刷新识别结果 -->
            <VariableCommand name="MiAiResultRefresh" expression="1" />
            <AnimationCommand target="moveAni" command="play(0,200)"/>
            <!-- exe 是否需要语音内容执行  -->
            <VariableCommand name="t1" type="string" expression="'start?session=20190401&amp;vad=false&amp;exe=false"/>
            <BinderCommand name="MiAi" command="refresh" />

            <VariableCommand name="Asr_Result" expression="''" type="string" />
        </Trigger>
        <Trigger action="up,cancel">
            <VariableCommand name="MiAiResultRefresh" expression="0" />
            <AnimationCommand target="moveAni" command="play(1000,1200)"/>
            <VariableCommand name="t1" type="string" expression="'cancel'"/>
        </Trigger>
    </Triggers>
</Button>

通过 ContentProviderBinder 刷新接口 来启动启动小爱录音或者是停止录音

<!-- 启动录音 -->
content://com.miui.voiceassist.speech.api/start?session=20190401&amp;vad=false&amp;exe=false

<!-- 取消 -->
content://com.miui.voiceassist.speech.api/cancel

<!-- 停止收音 表示说完话了,区别于取消 -->
content://com.miui.voiceassist.speech.api/stop
属性 释义
vad 是否开启自动判停,若开启了,则不需要松手停止说(false,true)
exe 是否需要执行语音内容,既用户说的内容(false,true)

重点来了

根据 ContentProviderBinder 返回的值做一些交互,比如显示文字,录音时的动画等

content://com.miui.voiceassist.speech.api/status
属性 释义
AsrStatus 状态;0 识别结束,空闲中,1 表示录音中,2 录音结束识别中
AsrResult 识别结果
AsrVoiceVolume 实时录音的音量大小,最大 100

# 作息

MIUI13新增

<!-- 作息 -->
<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="clockProvider" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
    <ContentProviderBinder name="clockProvider" uri="content://com.android.deskclock.bedtimeProvider/bedtime" countName="hasdeskclock">
        <!-- 未设置作息管理,获取的睡眠数据无效 -->
        <Variable name="clock_bedtime_state" type="int" column="bedtime_state"/>
        <!-- 入睡时间的小时,24小时制 -->
        <Variable name="clock_sleep_hour" type="int" column="sleep_hour"/>
        <!-- 入睡时间的分 -->
        <Variable name="clock_sleep_minute" type="int" column="sleep_minute"/>
        <!-- 起床时间的小时,24小时制 -->
        <Variable name="clock_wake_hour" type="int" column="wake_hour"/>
        <!-- 起床时间的分 -->
        <Variable name="clock_wake_minute" type="int" column="wake_minute"/>
        <!-- 重复周期 -->
        <Variable name="clock_repeat_type" type="int" column="repeat_type"/>
    </ContentProviderBinder>
</VariableBinders>
uri content://com.android.deskclock.bedtimeProvider/bedtime
字段名 说明 数据类型 字段名示意 备注
bedtime_state 作息管理设置状态,0关闭;1开启 int
sleep_hour 入睡时间的小时 int 24小时制
sleep_minute 睡时间的分 int
wake_hour 起床时间的小时 int 24小时制
wake_minute 起床时间的分 int
repeat_type 重复周期 int 每天:127
法定工作日:-1
周一至周五:31
自定义:其他数值

# 闹钟

MIUI13新增

<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="DeskClockProvider" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
    <ContentProviderBinder name="DeskClockProvider" uri="content://com.android.deskclock/alarm" countName="hasdeskclock">
        <!-- 备注 -->
        <Variable name="clock_message" type="string[]" column="message"/>
        <!-- 开关 -->
        <Variable name="clock_enabled" type="string[]" column="enabled"/>
        <!-- 时 -->
        <Variable name="clock_hour" type="string[]" column="hour"/>
        <!-- 分 -->
        <Variable name="clock_minute" type="string[]" column="minutes"/>
        <!-- 响铃时间 -->
        <Variable name="clock_alarmtime" type="string[]" column="alarmtime"/>
        <!-- 重复方式 -->
        <!-- 0: 一次性 -->
        <!-- 1: 周一 -->
        <!-- 2: 周二 -->
        <!-- 4: 周三 -->
        <!-- 8: 周四 -->
        <!-- 16: 周五 -->
        <!-- 32: 周六 -->
        <!-- 64: 周日 -->
        <!-- 128: 法定工作日 -->
        <!-- 256: 法定节假日 -->
        <Variable name="clock_daysofweek" type="string[]" column="daysofweek"/>
    </ContentProviderBinder>
</VariableBinders>
uri "content://com.android.deskclock/alarm
字段名 说明 数据类型 字段名示意 备注
message 备注 string[]
enabled 开关 string[]
hour string[]
minutes string[]
alarmtime 响铃时间 string[]
daysofweek 重复方式 string[] 0: 一次性
1: 周一
2: 周二
4: 周三
8: 周四
16: 周五
32: 周六
64: 周日
128: 法定工作日
256: 法定节假日

# 待办事项

<ExternalCommands>
    <Trigger action="init,resume">
        <!-- ContentProvider接口在切屏时必须主动更新一次  -->
        <BinderCommand name="calendarEvents" command="refresh" />
    </Trigger>
</ExternalCommands>
<VariableBinders>
    <ContentProviderBinder name="calendarEvents" uri="content://com.android.calendar/events" order="dtstart ASC" whereFormat="hasExtendedProperties=='%d' AND dtstart&gt;='%d' AND dtstart&lt;='%d'" whereParas="0,int((#time_sys-#hour24*3600000-#minute*60000-#second*1000)/1000)*1000,int((#time_sys-#hour24*3600000-#minute*60000-#second*1000+86400000)/1000)*1000" countName="events">
        <!-- 标题 -->
        <Variable name="calendar_title" type="string[]" column="title"/>
        <!-- 地点,没有则为null -->
        <Variable name="calendar_eventLocation" type="string[]" column="eventLocation"/>
        <!-- 提醒开始时间-->
        <Variable name="calendar_dtstart" type="string[]" column="dtstart"/>
        <!-- 提醒结束时间 -->
        <Variable name="calendar_dtend" type="string[]" column="dtend"/>
        <!-- 全天事件 -->
        <Variable name="calendar_allDay" type="string[]" column="allDay"/>
        <!-- 事件的最后时间, 空表示无限 -->
        <Variable name="calendar_lastDate" type="string[]" column="lastDate"/>
        <!-- 日程的类型 -->
        <Variable name="calendar_hasExtendedProperties" type="string[]" column="hasExtendedProperties"/>
    </ContentProviderBinder>
</VariableBinders>
uri content://com.android.calendar/events
字段名 说明 数据类型 字段名示意 备注
title 标题 string[]
eventLocation 地点,没有则为null string[]
dtstart 提醒开始时间 string[]
dtend 提醒结束时间 string[]
allDay 全天事件 string[] 0不是;1是
lastDate 事件的最后时间, 空表示无限 string[] 10800000 距离0点的时间戳表示形式
hasExtendedProperties 类型 string[] 日程的类型(倒数日;纪念日;生日;日程)

# 传感器调用

SensorBinder 支持重力感应,方向感应,加速度感应,气压感应(海拔高度)

# 重力传感器

<VariableBinders>
    <SensorBinder type="gravity"  rate="2">
        <Variable name="gravity_x" index="0"/>
        <Variable name="gravity_y" index="1"/>
        <Variable name="gravity_z" index="2" />
    </SensorBinder>
</VariableBinders>
属性 释义
index="0" x 方向的重力加速度
index="1" y 方向的重力加速度
index="2" z 方向的重力加速度

rate;常量,单位是微秒,有 4 种特殊值 0,1,2,3;默认为 3 (不写的话)。

注意:值越小刷新越高/运动越流畅,也会相对耗电一点。

  • 0 表示 0 微秒
  • 1 是 20000 微秒
  • 2 是 66667 微秒
  • 3 是 200000 微秒

# 方向传感器

<VariableBinders>
    <SensorBinder type="orientation">
        <Variable name="orientation0" index="0"/>
        <Variable name="orientation1" index="1"/>
        <Variable name="orientation2" index="2" />
    </SensorBinder>
</VariableBinders>
属性 释义
index="0" 方位角,0~359,0=北,90=东,180=南,270=西
index="1" 俯仰角,-180 ~ 180,z 轴转向 y 轴为正方向
index="2" 滚转角,-90 ~ 90,x 轴转向 z 轴为正方向

# 加速度传感器

<VariableBinders>
    <SensorBinder type="accelerometer">
        <Variable name="accelerometer_x" index="0"/>
        <Variable name="accelerometer_y" index="1"/>
        <Variable name="accelerometer_z" index="2"/>
    </SensorBinder>
</VariableBinders>
属性 释义
index="0" x 方向的加速度
index="1" y 方向的加速度
index="2" z 方向的加速度

# 线性加速度传感器

<VariableBinders>
    <SensorBinder type="linear_acceleration">
        <Variable name="line_x" index="0"/>
        <Variable name="line_y" index="1"/>
        <Variable name="line_1" index="2"/>
    </SensorBinder>
</VariableBinders>

线性加速度是去掉了重力加速度影响的:加速度 = 线性加速度 + 重力加速度


# 气压传感器

<VariableBinders>
    <SensorBinder type="pressure">
        <Variable name="pressure" index="0"/>
    </SensorBinder>
</VariableBinders>
属性 释义
index="0" 气压值,单位 hPa。海平面的平均气压是 1013.25hPa,可以根据气压值估计海拔高度


# 广播

广播分成两部分:发送与接收

  • 发送部分一般放 Trigger 中
<ExternalCommands>
    <Trigger action="pause">
        <IntentCommand action="initialization" broadcast="true">
            <Extra name="bg_number" type="number" expression="#rand_num" />
        </IntentCommand>
    </Trigger>
</ExternalCommands>
  • 接收部分在 VariableBinders 里
<VariableBinders>
    <BroadcastBinder action="initialization">
        <Variable name="wallpaper_num" type="number" extra="bg_number" />
    </BroadcastBinder>
</VariableBinders>

action 属性中的名字可自己定义,一致即可。


# 深色模式

使用说明

设计师可根据全局变量 #__darkmode_wallpaper #__darkmode 自定义开启 【深色模式】后的显示效果。

首先需要在 xml 的根节点增加 customizedDarkModeWallpaper="true" 属性,如:


  • 百变壁纸
<!-- customizedDarkModeWallpaper="true" 自定义深色模式 开启 -->
<MiWallpaper version="2" frameRate="5" screenWidth="1080" customizedDarkModeWallpaper="true">

    <Var name="bgScale" expression="ifelse(#screen_height}2160,#screen_height/2160,1)" const="true" />
    <!-- #__darkmode_wallpaper 打开深色模式 并 启用调暗效果 时值为 1;srcid="1" 则显示图片 "bg_1.jpg" -->
    <Image pivotX="540" pivotY="0" scale="#bgScale" src="bg.jpg" srcid="#__darkmode_wallpaper" />

</MiWallpaper>

  • 百变锁屏
<!-- customizedDarkModeWallpaper="true" 自定义深色模式 开启 -->
<Lockscreen version="2" frameRate="60" screenWidth="1080" customizedDarkModeWallpaper="true">

</Lockscreen>

customizedDarkModeWallpaper 默认为 false,在【深色模式且壁纸调暗】开启时,默认 统一调暗效果;若为 true,则支持自定义调暗效果。

MIUI12 20.6.1 之后的开发版 支持


全局变量 释义 说明
__darkmode_wallpaper 是否开启深色模式且支持调暗壁纸 0 表示未开启
1 表示已开启
__darkmode 是否开启深色模式 0 表示未开启
1 表示已开启

# 锁屏设置

锁屏个性化设置
主题可以带一个配置描述文件描述可以个性化配置的项目(config.xml 和 manifest.xml 放在同一个目录下) 可配置项目有:

  • 开关
  • 文字输入
  • 文字选择
  • 数字输入
  • 数字选择
  • 程序快捷方式
  • 自定义图片

示例:

<!-- 根节点 -->
<Config>
    <!-- 设置组  text 设置组名称,显示在设置界面中的文本 -->
    <Group text="日期时间">
        <!-- 开关(summary:设置项详细说明;id:设置项对应的变量名称;default:缺省值) -->
        <CheckBox text="" summary="" id="show_date" default="0"/>
        <!-- 文字输入 -->
        <StringInput text="日期格式" summary="" id="format" default="M月d日"/>
        <!-- 数字输入 -->
        <NumberInput text="文字大小" summary="文字大小" id="size_date" default="28"/>
    </Group>
    <Group text="请选择">
        <!-- 文字选择 -->
        <StringChoice text="" summary="" customizable="true" id="time_format">
            <!-- 文字选择项目 value变量值 text界面显示文字 -->
            <Item value="hh:mm" text="12小时"/>
            <Item value="kk:mm" text="24小时"/>
        </StringChoice>
        <!-- 数字选择 -->
        <NumberChoice text="" summary="" id="">
            <!-- 数字选择项目 value数值 text界面显示文字 -->
            <Item value="0" text="模式1"/>
            <Item value="1" text="模式2"/>
        </NumberChoice>
    </Group>
    <Group text="自定义图片" summary="请先将图片裁剪到合适大小和部位以确保显示效果">
        <ImagePicker text="图片一" summary="选择图片一" id="img1"/>
        <ImagePicker text="图片二" summary="选择图片二" id="img2"/>
    </Group>
    <Group text="快捷方式">
        <AppPicker text="左边快捷方式" id="left_task"/>
        <AppPicker text="右边快捷方式" id="right_task"/>
    </Group>
</Config>

清除图片(将相应变量置空即可);最新自定义图片用法可查看:自定义图片

<Image x="0" y="0" src="@img1" srcType="Uri"/>
<Image x="0" y="500" src="@img2" srcType="Uri"/>
<Button x="540" y="#screen_height/2" w="280" h="280" alignChildren="true" visibility="#img1.has_bitmap">
    <Rectangle w="280" h="280" fillColor="#ffffff" alpha="128" />
    <Triggers>
        <Trigger action="up">
            <VariableCommand name="img1" expression="" type="string" persist="true" />
        </Trigger>
    </Triggers>
</Button>

快速进入锁屏个性化设置界面的快捷方式(Button)

<Button x="540" y="#screen_height/2+300" w="280" h="280" alignChildren="true">
    <Rectangle w="280" h="280" fillColor="#ffffff" alpha="128" />
    <Triggers>
        <Trigger action="up">
            <IntentCommand action="android.intent.action.MAIN" package="com.android.thememanager" class="miui.maml.MamlConfigSettings">
                <Extra name="maml_code" type="string" expression="'lockstyle'"/>
            </IntentCommand>
            <ExternCommand command="unlock"/>
        </Trigger>
    </Triggers>
</Button>

# 多语言适配

MAML 支持多语言,下面以锁屏为例(锁屏的文件都在 lockscreen/advance 文件夹下,在这我们就其为根目录) 多语言的适配有图片资源的适配和字符串的适配两种:

  • 图片资源的适配 都放在根目录下,只不过非默认的需新建相应语言的文件夹(如:images_en、images_cn_TW) 默认图片:a.png 中文繁体:images_zh_TW/a.png 英文:images_en/a.png
  • 字符串资源的适配 默认:strings/strings.xml 中文简体:strings/strings_zh_CN.xml 中文繁体:strings/strings_zh_TW.xml

看下面的示例:

<!-- strings.xml 内容 -->
<strings>
    <string name="musicName" value="Music player"/>
</strings>
<!-- strings_zh_CN.xml 内容 -->
<strings>
    <string name="musicName" value="打开音乐播放器"/>
</strings>
<!-- strings_zh_TW.xml 内容 -->
<strings>
    <string name="musicName" value="打開音樂播放器"/>
</strings>

manifest.xml 中可直接使用变量 @musicName

  • 自定义配置文件的适配(config 文件直接加后缀即可)
属性 释义
英文 config.xml
简体 config_zh_CN.xml
繁体 config_zh_TW.xml

适配多语言时,推荐 config.xml 为英文语言,再适配其他语言。


# 高级教程

动态帧率

  • 普通用法
<?xml version="1.0" encoding="utf-8"?>
<Lockscreen version="1" frameRate="30" screenWidth="1080">
</Lockscreen>
  • 普通用法二(充电有动画特效时可以在跟标签加入 充电/电量满/电量低 指定帧率,让指定电量状态帧率为 60-120,从而使动画更流畅)
<?xml version="1.0" encoding="utf-8"?>
<Lockscreen version="1" frameRate="30" frameRateCharging="60" frameRateBatteryLow="20" frameRateBatteryFull="30"  screenWidth="1080">
</Lockscreen>
属性 代码
frameRate 指定帧率,默认为 30。 注:目前小米手机屏幕刷新率最高为 120hz。
frameRateCharging 充电状态下 锁屏帧率
frameRateBatteryLow 电量低时 锁屏帧率
frameRateBatteryFull 电量满(100%)时 锁屏帧率
  • 高级用法

动态调整帧率,比如在播放动画或者滑动屏幕的时候把帧率提高到 60fps 或者 120fps,以保证动画和滑动不卡顿;其余时候可以降低到 30fps,在保证流畅度的前提下又可以兼顾省电。

注:frameRate="120"为目前高刷新率手机所支持的,在不支持 120 的手机上面还是以手机硬件所支持的最高刷新率为准。(比如写 120 时在 60hz 的机型上面最高只会到达 60hz)

<FramerateController name="framerateControllerAni" initPause="true" loop="false" >
    <ControlPoint frameRate="120" time="0"/>
    <ControlPoint frameRate="120" time="2000"/>
    <ControlPoint frameRate="30" time="2001"/>
</FramerateController>

<!-- 在需要提高帧率时播放;比如有开屏动画时,在开屏时播放 -->
<AnimationCommand target="framerateControllerAni" command="play"/>

<!-- 点击/滑动屏幕时提高帧率 -->
<Button x="0" y="0" w="#screen_width" h="#screen_height">
    <Triggers>
        <Trigger action="down,up,cancel">
            <AnimationCommand target="framerateControllerAni" command="play"/>
        </Trigger>
        <Trigger action="move">
            <AnimationCommand target="framerateControllerAni" command="play" condition="#frame_rate{55" />
        </Trigger>
    </Triggers>
</Button>

元素数组

就是界面元素(图形、文字…)以特定规律的形式展现,避免类似代码的重复。
例如:我要把 100 张 80x80 的矩形按 10 行 10 列(间距为 20 像素)的形式显示出来,如果是平常,我们至少得写 100 行代码,但现在用元素数组几行代码就行了。

示例:

<Array x="50" y="500" count="100" indexName="__i" >
    <!-- % 与 int 的用法请看 表达式 -->
    <Rectangle x="#__i%10*100" y="int(#__i/10)*100" w="80" h="80" fillColor="#99ffffff" />
    <Text textExp="#__i" size="36" align="center" x="#__i%10*100+40" y="int(#__i/10)*100+20"  color="#000000" />
</Array>
属性 释义
Array 元素数组的标签(就是对各元素进行排列)
indexName 指数名称,可理解为元素数组内,用来给各元素编号的变量名
count 规定这个元素数组内同类型元素的个数,也就是 indexName 中你自定义的变量名的范围,在 Array 中的 count 不支持表达

循环处理

主要与数组配合使用,可节省大量的代码,并提高效率。

示例:

<Var name="find" type="number[]" size="100" const="true" />
<Array x="100" y="300" count="100" indexName="__i" >
    <Text textExp="#find[#__i]" size="30" align="center" x="#__i%10*90" y="int(#__i/10)*90" color="#ffffffff" />
</Array>
<Button x="0" y="0" w="1080" h="1920">
    <Triggers>
        <Trigger action="up">
            <LoopCommand count="100" indexName="col">
                <VariableCommand name="find" type="number[]" index="#col" expression="#col"  />
            </LoopCommand>
        </Trigger>
    </Triggers>
</Button>
属性 释义
LoopCommand 循环标签
indexName 指数名称,用来标注循环计算次数的变量名(#col 等于 0 时,说明是第一次计算)
count 规定循环次数
begin indexName 指定的变量到达某个值时开始计算
end indexName 指定的变量到达某个值时终止计算
loopCondition 循环条件,可以用来中断循环

下面这段与前面 <LoopCommand/> 代码等效

<LoopCommand begin="0" end="99" indexName="col">
    <VariableCommand name="find" type="number[]" index="#col" expression="#col"  />
</LoopCommand>

贝塞尔曲线

重点:CanvasDrawer 是独立的,必须先计算在其它分辨率下的缩放比,

<?xml version="1.0" encoding="UTF-8"?>
<Lockscreen version="2" frameRate="60" screenWidth="1080">
    <Var name="orix" type="number" expression="400" const="true"/>
    <Var name="oriy" type="number" expression="400" const="true"/>
    <Var name="startx" type="number" expression="0" const="true"/>
    <Var name="starty" type="number" expression="0" const="true"/>
    <Var name="endx" type="number" expression="800" const="true"/>
    <Var name="endy" type="number" expression="0" const="true"/>
    <!-- 先初始化 -->
    <ExternalCommands>
        <Trigger action="init">
            <!-- scale_mum:CanvasDrawer是独立的,先计算在其它分辨率下的缩放比 -->
            <VariableCommand name="scale_mum"  expression="int(#raw_screen_width/1.08)/1000" />
            <MethodCommand targetType="ctor" class="android.graphics.Path" return="path" returnType="object"/>
            <MethodCommand target="path" targetType="var" method="moveTo" paramTypes="float,float" params="#startx,#starty"/>
            <MethodCommand target="path" targetType="var" method="quadTo" paramTypes="float,float,float,float" params="#orix,#oriy,#endx,#endy"/>
            <MethodCommand targetType="ctor" class="android.graphics.Paint" return="paint" returnType="object"/>
            <MethodCommand targetType="var" class="miui.maml.util.ReflectionHelper" method="getEnumConstant" paramTypes="String,String" params="'android.graphics.Paint$Style','STROKE'" return="style" returnType="object"/>
            <MethodCommand target="paint" targetType="var" method="setStyle" paramTypes="android.graphics.Paint$Style" params="'style'"/>
        </Trigger>
    </ExternalCommands>
    <Rectangle x="0" y="0" w="1080" h="1920" fillColor="argb(255,255,255,255)" touchable="true">
        <Triggers>
            <Trigger action="down" >
                <!-- 初始点 -->
                <VariableCommand name="x0" type="number" expression="#touch_x"/>
                <VariableCommand name="y0" type="number" expression="#touch_y"/>
            </Trigger>
            <!-- 滑动过程会刷新数据 -->
            <Trigger action="move" >
                <VariableCommand name="dx" type="number" expression="#touch_x-#x0"/>
                <VariableCommand name="dy" type="number" expression="#touch_y-#y0"/>
                <MethodCommand target="path" targetType="var" method="reset"/>
                <MethodCommand target="path" targetType="var" method="moveTo" paramTypes="float,float" params="#startx,#starty"/>
                <MethodCommand target="path" targetType="var" method="quadTo" paramTypes="float,float,float,float" params="#orix+#dx,#oriy+#dy,#endx,#endy"/>
            </Trigger>
            <Trigger action="up" >
                <VariableCommand name="orix" type="number" expression="#orix+#dx"/>
                <VariableCommand name="oriy" type="number" expression="#oriy+#dy"/>
            </Trigger>
        </Triggers>
    </Rectangle>
    <Group x="140" y="240" scale="#scale_mum">
        <!-- CanvasDrawer;画布哦!对这功能给跪了!大家好好用!谢谢MAML之父 -->
        <CanvasDrawer x="0" y="0">
            <Triggers>
                <Trigger action="draw" >
                    <MethodCommand target="paint" targetType="var" method="setStrokeWidth" paramTypes="float" params="8"/>
                    <MethodCommand target="paint" targetType="var" method="setColor" paramTypes="int" params="0xff00ff00"/>
                    <MethodCommand target="__objCanvas" targetType="var" method="drawPath" paramTypes="android.graphics.Path,android.graphics.Paint" params="'path','paint'"/>
                    <MethodCommand target="paint" targetType="var" method="setStrokeWidth" paramTypes="float" params="0"/>
                    <MethodCommand target="paint" targetType="var" method="setColor" paramTypes="int" params="0xff000000"/>
                    <MethodCommand target="paint" targetType="var" method="setTextSize" paramTypes="float" params="40"/>
                    <MethodCommand target="__objCanvas" targetType="var" method="drawTextOnPath" paramTypes="String,android.graphics.Path,float,float,android.graphics.Paint" params="'Hello World! This is a new awesome feature. 你好,这是很牛的新功哦!','path',0,-25,'paint'"/>
                </Trigger>
            </Triggers>
        </CanvasDrawer>
    </Group>
</Lockscreen>

# 其他相关

锁屏常亮

<!-- 初期测试锁屏时防止黑屏,可加入该代码,方便测试锁屏效果 -->
<!-- 注意:主题最终上线时该代码不可直接展示,不然锁屏会一直亮屏不会息屏;可以用条件控制什么时候可执行 -->
<Var expression="#second" threshold="5" >
    <Trigger>
        <ExternCommand command="pokewakelock"/>
    </Trigger>
</Var>

相当于每 5 秒(second)触发一次; pokewakelock 唤醒屏幕


全机型壁纸自适应算法

<Image name="i_bg" x="#screen_width/2" y="#screen_height/2" pivotX="#screen_width/2" pivotY="#screen_height/2" scale="max(#screen_width/#i_bg.bmp_width,#screen_height/#i_bg.bmp_height)" align="center" alignV="center" src="bg.jpg"/>

适用对象:任意尺寸图片。(自定义的 Image 非,因为 wallpaper 自带自适应缩放算法)
原理:宽不够拉宽,高不够拉高. 即:壁纸按照屏幕中心对齐平铺的情况下,未铺满区域的就把壁纸整体按照前两者的比值进行等比缩放以填充未充满区域。


# 全面屏机型锁屏元素适配

目前手机分辨率各有所异,那么我们在制作锁屏时书写代码就需要注意在锁屏中处于底部的元素就需要用相对坐标来写,而非绝对坐标。

解决方案:

  • 在全局变量中有一个screen_height,表示在你当前代码设定分辨率下相对应的屏幕高度
  • 屏幕底的按钮坐标,以屏幕高度(screen_height)为参照,通俗地讲就是从底部上去多少像素

示例(这里以系统默认锁屏底部两边的小图标为例):

<!-- 底部左右小图标 -->
<Group y="#screen_height-110">
    <Image x="115" y="0" align="center" alignV="center" src="icon_left.png"/>
    <Image x="#screen_width-115" y="0" align="center" alignV="center" src="icon_right.png"/>
</Group>

释义:y="#screen_height-110" 这里的坐标表示用屏幕高度减去 110 就是底部图标最终显示的位置,不管任何机型都是用机型的分辨率高度减去 110;这样就保障了在不同机型底部按钮都会在底部位置。


个性化设置中 文件管理和相册 选择图片不生效,用这行代码解决

<Var name="diy_img_var" type="string" expression="ifelse(strContains(@diy_img,'com.android.fileexplorer'),strReplaceAll(@diy_img,'content://com.android.fileexplorer.myprovider/external_files/','file:///sdcard/'),strContains(@diy_img,'com.miui.gallery'),strReplaceAll(@diy_img,'content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2F','file:///sdcard/'),@diy_img)"/>
<Image x="540" y="#screen_height/2" align="center" alignV="center" w="200" h="200" srcType="Uri" srcExp="@diy_img_var" />

记得把下面代码放到 config.xml 中

<?xml version="1.0" encoding="utf-8"?>
<Config>
    <Group text="自定义图片">
        <ImagePicker text="自定义图片" summary="图片最佳尺寸:200x200" id="diy_img"/>
    </Group>
</Config>

在百变壁纸为时钟插件添加大背景

放时钟插件内:

<ExternalCommands>
    <Trigger action="resume">
        <!-- 每次用不同的值发送一个广播(用于百变壁纸收到广播后触发计算) -->
        <IntentCommand action="initialization" broadcast="true">
            <Extra name="bg_number" type="number" expression="#time%1000000" />
        </IntentCommand>
    </Trigger>
</ExternalCommands>

放百变壁纸内:

<VariableBinders>
    <!-- 收到时钟插件发的广播后开始计算有时钟时wallpaper_offset_x的值,并用ui_num记录 -->
    <BroadcastBinder action="initialization">
        <Variable name="bg_num" type="number" extra="bg_number" />
        <Trigger condition="#bg_num">
            <VariableCommand name="ui_num" expression="int(#wallpaper_offset_x*10000000)" />
        </Trigger>
    </BroadcastBinder>
</VariableBinders>
<!-- 背景 -->
<Rectangle x="0" y="0" w="1080" h="2160" fillColor="#fffcfed8"   >
<!-- wallpaper_offset_step:wallpaper_offset_x等于ui_num时,透明度为255;单屏偏移量,wallpaper_offset_x当前值与ui_num的差值的绝对值等于wallpaper_offset_step时,透明度为0 -->
<Rectangle x="0" y="0" w="1080" h="1080" fillColor="#fffcfed8" alpha="255-int(255*abs(int(#wallpaper_offset_x*10000000)-#ui_num)/10000000/#wallpaper_offset_step)"  >
    <FillShaders>
        <LinearGradient x="0" y="0" x1="0" y1="1080" tile="clamp">
            <GradientStop color="#ff46e1c6" position="0"/>
            <GradientStop color="#fffcfed8" position="1"/>
        </LinearGradient>
    </FillShaders>
</Rectangle>

倒计时

倒计时的计算逻辑其实很简单,看下面之前, 先搞清楚两点 日历的规律(四年一闰、百年不闰、四百年又闰 闰年时 2 月份多一天) 下面的倒计时以 0000 年为参照年份,计算方法是:目标年份至 0000 年过了多少天,现在的时间过了多少天,求两值的差值。

<?xml version="1.0" encoding="UTF-8"?>
<Lockscreen frameRate="30" screenWidth="1080" version="1">
    <ExternalCommands>
        <Trigger action="resume">
            <VariableCommand name="djs_text" expression="ifelse(strIsEmpty(@djs_text),'距离过年',@djs_text)" type="string" persist="true" />
            <VariableCommand name="lya" expression="eq((#year%4),0)*ne((#year%100),0)+eq((#year%400),0)" />
            <VariableCommand name="da" expression="(ge(#month,1)*31+(28+#lya)*ge(#month,2)+ge(#month,3)*31+ge(#month,4)*30+ge(#month,5)*31+ge(#month,6)*30+ge(#month,7)*31+ge(#month,8)*31+ge(#month,9)*30+ge(#month,10)*31+ge(#month,11)*30+ge(#month,12)*31)+(365*#year+int(#year/4)+int(#year/400)-int(#year/100))+#date-1" />
            <VariableCommand name="fa" expression="#hour24*60+#minute" />
            <!-- 目标日期(2021/2/12 23:59)与计算 -->
            <VariableCommand name="y1" expression="ifelse(isnull(#y1),2021,#y1)" persist="true" />
            <VariableCommand name="m1" expression="ifelse(isnull(#m1),2,#m1)" persist="true" />
            <VariableCommand name="d1" expression="ifelse(isnull(#d1),12,#d1)" persist="true" />
            <VariableCommand name="h1" expression="ifelse(isnull(#h1),23,#h1)" persist="true" />
            <VariableCommand name="f1" expression="ifelse(isnull(#f1),59,#f1)" persist="true" />
            <VariableCommand name="lyz1" expression="eq((#y1%4),0)*ne((#y1%100),0)+eq((#y1%400),0)" />
            <VariableCommand name="dz1" expression="(gt(#m1,1)*31+(28+#lyz1)*gt(#m1,2)+gt(#m1,3)*31+gt(#m1,4)*30+gt(#m1,5)*31+gt(#m1,6)*30+gt(#m1,7)*31+gt(#m1,8)*31+gt(#m1,9)*30+gt(#m1,10)*31+gt(#m1,11)*30+gt(#m1,12)*31)+(365*#y1+int(#y1/4)+int(#y1/400)-int(#y1/100))+#d1-1" />
            <VariableCommand name="fz1" expression="#h1*60+#f1" />
        </Trigger>
    </ExternalCommands>

    <Var name="vvv1" expression="ge(#dz1*1440+#fz1,#da*1440+#fa)" />
    <Var name="date1" expression="ifelse(#vvv1,int(abs(#dz1-#da)-gt(#fa,#fz1)),int(abs(#dz1-#da)-gt(#fz1,#fa)))" />
    <Var name="hour1" expression="ifelse(#vvv1,int((gt(#fa,#fz1)*1440+#fz1-#fa)/60),int((gt(#fz1,#fa)*1440+#fa-#fz1)/60))" />
    <Var name="minute1" expression="ifelse(#vvv1,int((gt(#fa,#fz1)*1440+#fz1-#fa)%60),int((gt(#fz1,#fa)*1440+#fa-#fz1)%60))" />

    <!--
            lya     今年是否闰年
            da      计算现在至0000年有多少天
            dz1     计算目标日期至0000年有多少天
            fa      今天过了多少分
            vvv1    判断目标日期在现在之前还是之后
     -->
    <Wallpaper x="0" y="0"  />
    <Text x="540" y="500" align="center" color="#ffffff" size="35" textExp="@djs_text+ifelse(#to,'('+#y1+'/'+#m1+'/'+#d1+'/'+#h1+':'+#f1+')','')+ifelse(#vvv1,'还有','已经')+#date1+'天'+#hour1+'时'+#minute1+'分'" />
</Lockscreen>

将下面代码放入锁屏中的 config.xml 文件内可实现自定义倒计时目标时间:

<Config>
    <Group text="锁屏倒计时设置">
        <StringInput text="倒计时自定义文字" id="djs_text" default="" />
        <NumberInput text="目标 年" id="y1" default="2021" />
        <NumberInput text="目标 月" id="m1" default="2" />
        <NumberInput text="目标 日" id="d1" default="12" />
        <NumberInput text="目标 时" id="h1" default="23" />
        <NumberInput text="目标 分" id="f1" default="59" />
    </Group>
</Config>

摇一摇调出 NFC 界面

<VariableBinders>
    <SensorBinder type="linear_acceleration" rate="2">
        <Variable name="va_x" index="0" />
        <Variable name="va_y" index="1" />
        <Variable name="va_z" index="2" />
        <Trigger condition="abs(#va_x*10)}120">
            <!-- 线性加速度数据校正(取最大值),解决不同硬件灵敏度不同造成的问题 -->
            <VariableCommand name="va_num" expression="abs(#va_x*10)"  persist="true" condition="(abs(#va_x*10))}#va_num" />
            <!-- 如果va_x实时值大于va_num*0.8,则打开NFC界面 -->
            <IntentCommand action="com.miui.intent.action.DOUBLE_CLICK" package="com.miui.tsmclient" condition="abs(#va_x*10)}(0.8*#va_num)">
                <Extra name="event_source" type="string" expression="'key_volume_down'"/>
            </IntentCommand>
        </Trigger>
    </SensorBinder>
</VariableBinders>

# 性能优化

优化中需要注意的几点:

一个好的锁屏,不仅需要很好的视觉效果,还需要流畅的体验(用户手机的性能、玩机水平有高低,制作主题时,要考虑下如何用户最大化)。

  • 测试时尽量不要用顶配机,frameRate 写 60,实测效果:普通手机不要低于 30 帧/s,好一点的手机必需高于 40 帧/s(这里指的是能够达到的最高帧率,并非你在开头限定的帧率或是你在代码中写的动态帧率)。
  • 降低图片文件大小,减少缓存时读取的时间,节省运行内存:存储图片必需导出为 web 格式,或者后期压缩;能用 jpg 绝不用 png,webp 也是一个很好的选择!
  • 尽量用合适尺寸的图片,能用小尺寸绝不用大尺寸,有效减少计算量!
  • 代码逻辑上不要有冲突,必须精减。
  • 充分利用 visibility 控制各模块可见性,用 通过变量 #num 是否大于 0 来判断只显示当前需要显示的部分。

Layer 优化利器详解

Layer,这个部件是一个 Group,但是可以单独设置帧率,实现 Layer 内部元素独立于其它部分的单独渲染刷新控制。这个部件在 MAML 渲染引擎底层对应到 Android 中的单独一个 View ,于是这个元素就可以像 View 一样设置 layer 类型来实现 GPU 硬件缓冲,提高绘制性能。

<Layer name="layer" x="25" y="300" hardware="true" width="800" height="600" pivotX="400" pivotY="200" touchable="true" interceptTouch="true" frameRate="0">
</Layer>
属性 释义
hardware 是否使用硬件绘图缓冲,true 更快速但更占内存,false 相反
frameRate 指定 Layer 内部元素
updatePosition Layer 位置(x y)是否需要更新, true/false
updateSize Layer 大小(w h) 是否需要更新, true/false
updateTranslation Layer 的 pivot/rotation/scale/alpha 等属性是否需要更新

以上三个 updatePosition/updateSizeupdateTranslation 属性默认都为 true,如果某些属性值是固定的不需要更新(例如 x 不是表达式,或没有位移动画),设置成 false 会提高性能。

优化步骤:

  • 当界面某块部分(如一个日历面板或天气面板等)和其它界面部分刷新率不同时,可以考虑把这块界面元素放到 Layer 中,先尝试 hardware="false",给 Layer 单独指定一个合适的刷新帧率 frameRate,看一下性能有没有问题,
  • 如果还是有问题可以把 hardware 设成 true 再测试,这时会额外耗费内存,不是必要的话最好不用。
  • 如果确实需要 hardware="true",但是内存使用又超出预算的话, Layer 提供一个函数接口 setHardwareLayer(boolean) 通过 MethodCommand 调用,在动画开始前设为 true,动画结束后设为 false 即可
  • 可以减少界面重复代码编写的数组和循环类组件: 部件数组、变量数组、LoopCommand 等等

适用情景:

  • 整体更新频率较高,但是部分区域(比如常见的日历)不需要频繁更新,可以把这部分放到一个 Layer 中指定较低帧率
  • 整体更新频率较低,但是有部分区域有动画需要频繁更新的,可以把这部分放到一个 Layer 单独指定动画帧率
  • 较复杂的动画滑进滑出面板,提高动画流畅度,用 Layer 设置合适的 hardware 属性

注意,zorder 问题,Layer 内部元素在一个单独的图层中,类似如下:

<Lockscreen>
    <Wallpaper />
    <Image src="img1.jpg"/>
    <Layer name="layer1"/>
    <Image src="img2.jpg"/>
    <!-- layer1在最上的图层, img1和img2在原有默认的底图层,所以img2在layer1下面,如果必须要img2在Layer上面,则需要另建一个Layer -->
    <Image src="img1.jpg"/>
    <Layer name="layer1"/>
    <Layer name="layer2">
    <Image src="img2.jpg"/>
    </Layer>
</Lockscreen>

# Aod-息屏

主题包结构

image

制作须知:

  • 百变息屏基于 maml,可使用 maml 已有的全局变量;息屏效果仅做展示(时间、日期、通知 等),禁止使用 传感器/Button 跳转等 交互功能。
  • 息屏首次展示时,可播放动画,动画总时长不可超过 4000ms,超时的动画动会被强制停止,最高帧率建议使用 30 或 60,且无需使用动态帧率。
  • 息屏状态下,系统规定:1min/次 刷新时间;2min/次 刷新位置。(无法通过 maml 代码修改)
  • 限制大小;AOD 模块需保证 10M 之内,越小越好。
  • 含动画的效果,在初始化和动画结束 需规范代码书写方式,供系统获取动画状态。
  • 支持配置息屏通用开关(电量、通知、农历),支持用户关闭或开启。
  • 通知仅展示 icon,且与桌面图标一致,无法自行适配。
  • 尽量避免使用大量的 序列帧图片

AOD 设置页相关配置(aod_description.xml) 支持通过代码配置,可自定义支持用户控制开关项显示,且支持配置开关的默认状态。

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<MIUI_Theme_Values>
    <!-- 类型;百变框架 maml_style -->
    <theme_type>maml_style</theme_type>

    <!-- 是否支持显示 农历、电量、通知 开关;1 支持,0 不支持,默认为 0 -->
    <support_lunar_calendar>0</support_lunar_calendar>
    <support_battery>1</support_battery>
    <support_notification>1</support_notification>

    <!-- 开关初始状态;农历、电量、通知,1 开启,0 关闭,默认为 0 -->
    <lunar_calendar_enable>0</lunar_calendar_enable>
    <battery_enable>1</battery_enable>
    <notification_enable>1</notification_enable>
</MIUI_Theme_Values>

配置项 参数 说明
theme_type maml_style 类型:百变框架(必写)
support_lunar_calendar 1 支持,0 不支持(默认) 是否支持用户配置 农历 开关
support_battery 1 支持,0 不支持(默认) 是否支持用户配置 电量 开关
support_notification 1 支持,0 不支持(默认) 是否支持用户配置 通知 开关
全局变量 状态 说明
lunar_calendar_enable 1 显示,0 不显示(默认) 当支持 农历 开关时,开关状态
battery_enable 1 显示,0 不显示(默认) 当支持 电量 开关时,开关状态
notification_enable 1 显示,0 不显示(默认) 当支持 通知 开关时,开关状态
preview_mode 1 预览模式,0 息屏模式 支持通知开关时,
需使用此变量确保设置页面内支持预览效果

含动画的效果,在初始化和动画结束 需规范代码书写方式,供系统获取动画状态。

<ExternalCommands>
    <Trigger action="init">
        <!-- 初始化后,给系统一个状态;必须有 -->
        <ExternCommand command="animationState" strPara="'init'" />
    </Trigger>
    <!-- 首次展示的时候,播放的所有动画;action="play" 与 action="resume" 类似,但这里用 play -->
    <Trigger action="play">
        <AnimationCommand target="endAni" command="play"/>
    </Trigger>
</ExternalCommands>

<Var name="endAni">
    <VariableAnimation name="endAnimation" loop="false" initPause="true">
        <Item value="0" time="0" easeType="CubicEaseOut" />
        <Item value="1" time="1200"/>
        <Triggers>
            <!-- #endAnimation.current_frame==-1 动画结束 -->
            <Trigger action="end" condition="#endAnimation.current_frame==-1">
                <!-- 在最长的动画结束后,给系统发个命令;注意:此命令只能存在一个,请勿重复使用 -->
                <ExternCommand command="animationState" strPara="'finish'" />
            </Trigger>
        </Triggers>
    </VariableAnimation>
</Var>

AOD 通知消息 aod 通知与锁屏通知有所不同,aod 通知仅显示图标,不显示具体的内容信息。aod 会过滤 app 发出的重复通知,比如 同一应用只展示一个图标,不会重复。具体可参考下方代码:

<VariableBinders>
    <!-- aod通知;content://aod.notification/notifications -->
    <ContentProviderBinder name="data" uri="content://aod.notification/notifications" columns="pkg" countName="hasnotifications">
        <Variable name="noticePkg" type="string[]" column="pkg"/>
        <Trigger>
            <VariableCommand name="noticeCount" type="number" expression="min(#hasnotifications-1,3)"/>
        </Trigger>
    </ContentProviderBinder>
</VariableBinders>

<Var name="noticeApp" type="string[]" const="true" expression="" values="'com.android.contacts,com.android.contacts.activities.TwelveKeyDialer','com.android.mms','com.miui.securitycenter','com.android.thememanager'" />
<Array y="945+int((#timeAni-1)*140)" count="4" indexName="_i" alpha="#timeAni*255" visibility="#notification_enable">
    <!-- 预览通知图标 -->
    <Image x="540+#_i*132-132/2*3" w="72" h="72" align="center" srcType="ApplicationIcon" srcExp="@noticeApp[#_i]" visibility="#preview_mode" />
    <!-- 真实通知图标 -->
    <Image x="540+#_i*132-132/2*#noticeCount" w="72" h="72" align="center" srcType="ApplicationIcon" srcExp="@noticePkg[#_i]" visibility="#_i{=#noticeCount ** !#preview_mode" />
</Array>

点击下载附件


# 常用包名类名及获取方法

获取方法
1、下载并安装编辑器 (opens new window),提取密码:123a
2、在手机上打开需要跳转的软件
3、打开编辑器,并将手机用数据线连接至电脑
4、在编辑器中点击“当前界面跳转按钮”(点击后会自动复制包名类名)
5、在你的代码中粘贴使用即可

下列IntentCommand在锁屏中需要搭配解锁命令使用
若遇到无法正常跳转的问题,可能是因为app更改了相关包名类名,按照获取方法重新获取即可

拨号 ▽
<IntentCommand package="com.android.contacts" class="com.android.contacts.activities.TwelveKeyDialer"/>

联系人 ▽
<IntentCommand package="com.android.contacts" class="com.android.contacts.activities.PeopleActivity"/>

短信 ▽
<IntentCommand package="com.android.mms" class="com.android.mms.ui.MmsTabActivity"/>

相机 ▽
<IntentCommand package="com.android.camera" class="com.android.camera.Camera"/>

个性主题 ▽
<IntentCommand package="com.android.thememanager" class="com.android.thememanager.ThemeResourceTabActivity"/>

日历 ▽
<IntentCommand package="com.android.calendar" class="com.android.calendar.AllInOneActivity"/>

天气 ▽
<IntentCommand package="com.miui.weather2" class="com.miui.weather2.ActivityWeatherMain"/>

时钟 ▽
<IntentCommand package="com.android.deskclock" class="com.android.deskclock.DeskClockTabActivity"/>

便签 ▽
<IntentCommand package="com.miui.notes" class="com.miui.notes.ui.NotesListActivity"/>

图库 ▽
<IntentCommand package="com.miui.gallery" class="com.miui.gallery.app.Gallery"|/>

相册 ▽
<IntentCommand package="com.miui.gallery" class="com.miui.gallery.activity.HomePageActivity"/>

浏览器 ▽
<IntentCommand package="com.android.browser" class="com.android.browser.BrowserActivity"/>

计算器 ▽
<IntentCommand package="com.miui.calculator" class="com.miui.calculator.cal.CalculatorActivity"/>

指南针 ▽
<IntentCommand package="com.miui.compass" class="com.miui.compass.CompassActivity"/>

设置 ▽
<IntentCommand package="com.android.settings" class="com.android.settings.MiuiSettings"/>

米家 ▽
<IntentCommand package="com.xiaomi.smarthome" class="com.xiaomi.smarthome.SmartHomeMainActivity"/>

文件管理 ▽
<IntentCommand package="com.android.fileexplorer" class="com.android.fileexplorer.FileExplorerTabActivity"/>

小米视频 ▽
<IntentCommand package="com.miui.video" class="com.miui.video.HomeActivity"/>

应用商店 ▽
<IntentCommand package="com.xiaomi.market" class="com.xiaomi.market.ui.MarketTabActivity"/>

小爱同学、语音助手 ▽
<IntentCommand category="android.intent.category.LAUNCHER" package="com.miui.voiceassist" class="com.xiaomi.voiceassistant.CTAAlertActivity"/>

安全中心 ▽
<IntentCommand package="com.android.settings" class="com.miui.securitycenter.Main"/>

电子邮件 ▽
<IntentCommand package="com.android.email" class="com.android.email.activity.Welcome"/>

收音机 ▽
<IntentCommand package="com.miui.fmradio" class="com.miui.fmradio.FmRadioActivity"/>

录音机 ▽
<IntentCommand package="com.android.soundrecorder" class="com.android.soundrecorder.SoundRecorder"/>

下载管理 ▽
<IntentCommand package="com.android.providers.downloads.ui" class="com.android.providers.downloads.ui.DownloadList"/>

UC浏览器 ▽
<IntentCommand package="com.UCMobile" class="com.UCMobile.main.UCMobile"/>

小米商城 ▽
<IntentCommand package="com.xiaomi.shop" class="com.xiaomi.shop.activity.MainTabActivity"/>

小米音乐 ▽
<IntentCommand package="com.miui.player" class="com.miui.player.ui.MusicBrowserActivity"/>

QQ音乐 ▽
<IntentCommand package="com.tencent.qqmusic" class="com.tencent.qqmusic.activity.AppStarterActivity"/>

网易云音乐 ▽
<IntentCommand package="com.netease.cloudmusic" class="com.netease.cloudmusic.activity.LoadingActivity"/>

酷狗音乐 ▽
<IntentCommand package="com.kugou.android" class="com.kugou.android.app.splash.SplashActivity"/>

虾米音乐 ▽
<IntentCommand package="fm.xiami.main" class="fm.xiami.main.SplashActivity"/>

百度地图 ▽
<IntentCommand package="com.baidu.BaiduMap" class="com.baidu.baidumaps.WelcomeScreen"/>

今日头条 ▽
<IntentCommand package="com.ss.android.article.news" class="com.ss.android.article.news.activity.SplashActivity"/>

淘宝 ▽
<IntentCommand package="com.taobao.taobao" class="com.taobao.tao.welcome.Welcome"/>

新浪微博 ▽
<IntentCommand package="com.sina.weibo" class="com.sina.weibo.SplashActivity"/>

手机QQ ▽
<IntentCommand package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity"/>

微信 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.ui.LauncherUI"/>

微信 扫一扫 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.plugin.scanner.ui.BaseScanUI"/>

微信 付款码 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.plugin.offline.ui.WalletOfflineEntranceUI"/>

微信 收款码 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.plugin.collect.ui.CollectAdapterUI"/>

微信 朋友圈 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.plugin.sns.ui.SnsTimeLineUI"/>

微信 我的名片 ▽
<IntentCommand package="com.tencent.mm" class="com.tencent.mm.plugin.setting.ui.setting.SelfQRCodeUI"/>

支付宝 ▽
<IntentCommand package="com.eg.android.AlipayGphone" class="com.eg.android.AlipayGphone.AlipayLogin"/>

支付宝 扫一扫 ▽
<IntentCommand package="com.eg.android.AlipayGphone" class="com.alipay.mobile.scan.as.main.MainCaptureActivity"/>

支付宝 付款码 ▽
<IntentCommand package="com.eg.android.AlipayGphone" class="com.eg.android.AlipayGphone.FastStartActivity"/>

公交卡(小米钱包) ▽
<IntentCommand package="com.miui.tsmclient" class="com.miui.tsmclient.ui.quick.DoubleClickActivity"/>

不解锁使用

<!-- 录音机 -->
<IntentCommand action="android.intent.action.MAIN" package="com.android.soundrecorder" class="com.android.soundrecorder.SoundRecorder">
    <Extra name="StartActivityWhenLocked" type="boolean" expression="1"/>
    <Extra name="navigation_tab" expression="2" type="int" />
</IntentCommand>

<!-- 计算器 -->
<IntentCommand action="android.intent.action.MAIN" package="com.miui.calculator" class="com.miui.calculator.cal.CalculatorActivity">
    <Extra name="StartActivityWhenLocked" type="boolean" expression="1"/>
    <Extra name="navigation_tab" expression="2" type="int"/>
</IntentCommand>

<!-- 相机 -->
<IntentCommand action="android.intent.action.MAIN" package="com.android.camera" class="com.android.camera.Camera">
    <Extra name="ShowCameraWhenLocked" type="boolean" expression="1"/>
    <Extra name="StartActivityWhenLocked" type="boolean" expression="1"/>
</IntentCommand>

<!-- 手电筒 -->
<VariableCommand name="lightSwitch" expression="!(#lightSwitch)"/>
<IntentCommand action="miui.intent.action.TOGGLE_TORCH" broadcast="true">
    <Extra name="miui.intent.extra.IS_ENABLE" type="boolean" expression="ifelse(int(@__miui_version_code)}=8,#lightSwitch,1)"/>
</IntentCommand>