用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生命周期回調函數。
由于本篇主題是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,要不就是彈窗無法拖拽,要不就是全屏無法設置大小。
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開發時,一些重要知識點的總結,希望對朋友們有所幫助。