用ArkUI实现碰一碰配网、设备控制 原创 精华
【本文正在参与优质创作者激励】
前言
eTS发布有段时间了,用它写UI不光是代码易读性,还是代码量都是相当优秀。用过以后发现再也不想用java写UI了。前段时间尝试调试了碰一碰配网,使用的是碰一碰(个人体验版)的。正式版的需要企业账号才可以,使用的是java+js。今天来尝试下eTS开发,但是ArkUI只有在API7上才能支持,目前绝大多数手机还都是API6,所以配网部分只能先代码模拟测试,界面效果如下图:
项目分为两个模块,碰一碰配网entry模块,设备控制control模块。不多说了上代码。
entry模块
这个模块主要是实现设备联网。界面很简单。
UI界面
配网界面就是3个组件,一个图片2个text
build() {
Row(){
Column() {
Image($r('app.media.test'))
.width(152)
.height(152)
.margin({ top: 16 })
Text(this.desc)
.fontSize(14)
.fontColor('#FF0000000')
.margin({ top: 16 })
Text(this.progress)
.fontSize(14)
.fontColor('#999999')
.margin({ top: 2,right: 24,bottom: 20,left: 24 })
}
.height('35%')
.width('80%')
.margin('10%')
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
.width('100%')
.height('100%')
.alignItems(VerticalAlign.Bottom) //这是Row的参数
.backgroundColor(0x000000)
}
配网流程
整个配网的流程就是手机碰触NFC贴纸,获取Product ID,然后通过Product ID去云端获取用户意图。说白了就是告诉手机要打开哪个包名,哪个模块。关于如何在华为开发者门户设置用户意图可以看我之前的帖子。这里就是启动了entry模块。
接下来分析用eTS如何实现这个过程:
1.导入hilink包
在build.gradle中进行配置,底层的实现都是通过调用API,并不需要自己写。
dependencies {
... ...
implementation(group: 'com.huawei.hilink', name: 'ailifeability', version: '1.0.0.1', ext: 'har')
}
2.调用PA能力。
使用JSCallJava调用API接口,这个可以参考OneHop模板,来实现一个default/common/fa-netconfig.ets
它的作用就是将JAVA API转换为eTS函数接口,主要用到的就是FeatureAbility.callAbility(action)
function callAbilityFunc(callCode, argsObj, callbackFunc) {
let action = { // 要调用java的信息存放在action
bundleName : CONSTANT.BUNDLE_NAME,
abilityName : CONSTANT.ABILITY_NAME,
messageCode : callCode, // callCode用来区分要调用哪一个API
abilityType : 1,
data : argsObj,
};
return FeatureAbility.callAbility(action);//调用PA能力
}
3.eTS生命周期回调函数
要在entry被拉起时自动执行配网,就要使用.eTS生命周期回调函数。
函数名 | 描述 |
---|---|
aboutToAppear | 函数在创建自定义组件的新实例后,在执行其build函数之前执行。允许在aboutToAppear函数中改变状态变量,这些更改将在后续执行build函数中生效。 |
aboutToDisappear | 函数在自定义组件析构消耗之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是**@Link**变量的修改可能会导致应用程序行为不稳定。 |
onPageShow | 当此页面显示时触发一次。包括路由过程、应用进入前后台等场景,仅@Entry修饰的自定义组件生效。 |
onPageHide | 当此页面消失时触发一次。包括路由过程、应用进入前后台等场景,仅@Entry修饰的自定义组件生效。 |
onBackPress | 当用户点击返回按钮时触发,,仅@Entry修饰的自定义组件生效。返回true表示页面自己处理返回逻辑, 不进行页面路由。返回false表示使用默认的返回逻辑。不返回值会作为false处理。 |
由于本篇主题是ArkUI,至于配网流程等专门写一篇帖子再来分析。
aboutToAppear(){
this.discoverDevice() // 执行配网流程
}
control模块
接下来和大家分享下在制作control模块时,学习到的ArkUI知识点和踩到的一些坑。其中部分内容官方文档也没有写,都是参考示例代码连蒙带猜。
1.设置窗口模式
在OneHop官方模板src/main/java/com/liangzili/myonehop/MainAbility.java
下给window_modal
设置了一个参数。在官方文档中好像没有关于这个参数的说明,也或许是我没有找到。
public void onStart(Intent intent) {
intent.setParam("window_modal", 3);
... ...
}
测试发现这个参数可以很方便的实现类似弹窗下拉这样的效果,省去了很多界面代码。其中("window_modal", 3)
就是配网entry页面的效果,("window_modal", 1)
的效果可以看下图。而且比较有意思的是有1有3,但是传递2好像没啥效果。不过可惜的是,JS范式下传递这个参数效果如下图,但eTS下会有bug,要不就是弹窗无法拖拽,要不就是全屏无法设置大小。
不传递参数 | (“window_modal”, 1) | (“window_modal”, 3) |
---|---|---|
界面高度:50% | 界面高度:100% | 界面高度:50% |
界面背景:灰色 | 界面背景:灰色 | 界面背景:灰色 |
2.app在线设计
在官方指导中有提到HiLink可以使用在线可视化的方式设置界面,效果如下图。
看着就很方便,在线设计完成之后会得到一个界面文件。类似下图这样的效果。界面的数据要统一放到了src/main/resources/rawfile
下,根据productName
参数进行区选择,格式为JSON。(PS:本想体验下这个app在线设计,但是打开没有内容,我只好使用的模板里带的FAN_zh.json)
control模块需要解析json文件的数据来绘制界面。
3.配置文件的解析
配置文件的解析也是java来完成的,所以我直接原文复制的OneHop模板的java代码,eTS部分,在src/main/ets/default/pages/index.ets
下
async onPageShow(){
utils.setActionParam('com.liangzili.myonehop', // 为action初始化参数
'com.liangzili.myonehop.DataHandlerAbility', ABILITY_TYPE_INTERNAL)
await this.requestTemplate()
}
... ...
async requestTemplate() {
let action = utils.makeAction(ACTION_MESSAGE_CODE_GET_TEMPLATE, {});
let result = await FeatureAbility.callAbility(action); // 同样的方法去调用PA的能力
let resultJson = JSON.parse(result); // 返回的结果保存在 result 中
if (resultJson.code == 0) { // 不等于0就调用失败了,可以通过失败码查找问题
let template = JSON.parse(resultJson.data);
this.parseJson(template.template);
}
}
async parseJson(deviceInfo) { ... ... } // 最后就是解析JSON来生成界面了
4.生成界面
其实如果用eTS范式单纯生成一个右侧这样的界面,可能只需要几十行代码。但是要解析在线生成的JSON界面文件,再兼容各种样式的控制界面来绘制UI,这个问题就会变得复杂的多。可能官方的意思是想通过在线设计降低程序的工作量,或者是为了统一UI风格,降低用户学习成本。
index.ets
主界面分为两个区域,DeviceInfo() 和 Control(),DeviceInfo就一个主图和名字
build() {
Stack({ alignContent: Alignment.Bottom }){ // 用来模拟一个上边圆角的效果
Column(){}.height(35).width('100%').backgroundColor(0xF6F6F6) // 用来覆盖下端边框圆角
Column() {
DeviceInfo() // 设备信息组件
Control() // 控制面板组件
}
.height('95%')
.width('100%')
.borderRadius(25)
.backgroundColor(0xF6F6F6)
}
.width('100%')
.height('100%')
.backgroundColor(0x000000)
}
Control.ets
界面中通过传递参数,来实现用一个组件,显示不同内容。
build(){
Column(){
Reversal(this.reversalData1) //开关组件
Enum({ enumDatas: this.enumDatas1 }) //枚举组件
Enum({ enumDatas: this.enumDatas2 }) //枚举组件
Reversal(this.reversalData2) //开关组件
}
}
5.参数传递
组件间传值是我遇到问题比较大的地方,我总结了以下几种情况。这些基本能解决大部分的传值问题了。
1.单个变量
// 调用端
Component1({a01:"a01"}) // 调用的时候参数用{}包裹
// 被调用端
@Component
export struct Component1{
private a01:string // 这里定义变量,用来接收
build(){
Text(this.a01).fontSize(50)
}
}
2.对象键值对
// 调用端
struct Index {
parameter:{} = {b01: "b01",b02: "b02",}
build() {
Column(){
Component2(this.parameter) // 调用的时候参数不用{}包裹
}
}
}
// 被调用端
@Component
export struct Component2{
private b01:string // 这里定义变量,用来接收
private b02:string
build(){
Column(){
Text(this.b01).fontSize(50)
Text(this.b02).fontSize(50)
}
}
}
3.对象数组
// 调用端
struct Index {
parameters:any[] = [
{
c01:"c01",
c02:"c02"
},
{
c01:"c11",
c02:"c22"
}
]
build() {
Column(){
Component3({ parameters: this.parameters }) // 传递参数是parameters,对象数组类型
}
}
}
// 被调用端
@Component
export struct Component3{
private parameters:any[] // 定义同样的参数,同样的类型
build(){
Column(){
ForEach(this.parameters,(item:any) => { //【重要:获取数组之后可以直接使用ForEach遍历数据】
Text(item.c01).fontSize(50)
Text(item.c02).fontSize(50)
},(item:any) => item.toString() // 文档说选填,但不填会失败
)
}
}
}
4.自定义类数组
// 类
class enumData {
image: Resource
text: string
constructor(image: Resource, text: string) {
this.image = image;
this.text = text;
}
}
// 调用端
@Entry
@Component
struct Index {
enumDatas:enumData[] = this.getenumDatas()
getenumDatas(){
let enumDatas: Array<enumData> = []
enumDatas.push(new enumData($r("app.media.icon"), "001"))
enumDatas.push(new enumData($r("app.media.icon"), "002"))
return enumDatas;
}
build() {
Column(){
Component4({ enumDatas: this.enumDatas })
}
}
}
// 被调用端
@Component
export struct Component4{
private enumDatas:enumData[] // 这里定义变量,用来接收
build(){
Column(){
ForEach(this.enumDatas,(item:any) => {
Image(item.image).width(50).height(50)
Text(item.text).fontSize(50)
},(item:any) => item.text.toString() // 文档说选填,但不填会失败
)
}
}
}
6.发送设备控制信息
由于目前没有API7的真机进行调试,所以发送控制设备信息这部分还没有实现。但是以防万一控制流程先记录下来,方便以后再来添加。
与entry模块类似,需要在build.gradle中进行配置,同样的API。
dependencies {
... ...
implementation(group: 'com.huawei.hilink', name: 'ailifeability', version: '1.0.0.1', ext: 'har')
}
eTS侧要实现这样的函数,来调用PA的能力
async setKeyValue(key, value) {
let data = {};
data[key] = value;
let action = utils.makeAction(ACTION_MESSAGE_CODE_DATACHANGED, data);
let that = this;
that.data.timer = setTimeout(function () {
that.notifyObservers('showMessage', {
'show': true
});
}, WAIT_TIME);
await FeatureAbility.callAbility(action);
}
src/main/java/com/liangzili/myonehop/DataHandlerAbility.java
java侧根据ACTION_MESSAGE_CODE_DATA_CHANGED
来确定要调用的方法
case ACTION_MESSAGE_CODE_DATA_CHANGED: {
String zsonStr = data.readString();
ZSONObject zsonObj = ZSONObject.stringToZSON(zsonStr);
for (Map.Entry<String, Object> entry : zsonObj.entrySet()) {
deviceDataHandler.modifyDeviceProperty(entry.getKey(), entry.getValue());
}
break;
}
以上就是我在使用ArkUI开发时,一些重要知识点的总结,希望对朋友们有所帮助。
很全面的教程!
直接码住!感谢亮哥
客气了,互相学习啊
重磅帖子,赞👍
啥时候用js开发一个教程呀,TS不会呀
我最近在搞这个
看了你的帖子,你偏重的设备端的,我是做应用端(前端),嵌入式的看不懂。。
哈哈,我是从app到设备端都做了解释,也有前端js那部分的。https://ost.51cto.com/posts/12758;https://ost.51cto.com/posts/12772