1.概述
目前家庭電視機主要通過其自帶的遙控器進行操控,實現的功能較為單一。例如,當我們要在TV端搜索節目時,電視機在遙控器的操控下往往只能完成一些字母或數字的輸入,而無法輸入其他復雜的內容。分布式遙控器將手機的輸入能力和電視遙控器的遙控能力結合為一體,從而快速便捷操控電視。
分布式遙控器的實現基于OpenHarmony的分布式能力和RPC通信能力,UI使用eTS進行開發。如下圖所示,分別用兩塊開發板模擬TV端和手機端。
- 分布式組網后可以通過TV端界面的Controller按鈕手動拉起手機端的遙控界面,在手機端輸入時會將輸入的內容同步顯示在TV端搜索框,點擊搜索按鈕會根據輸入的內容搜索相關節目。
- 還可以通過點擊方向鍵(上下左右)將焦點移動到我們想要的節目上,再點擊播放按鈕進行播放,按返回按鈕返回TV端主界面。
- 同時還可以通過手機遙控端關機按鈕同時關閉TV端和手機端界面。
UI效果圖如下:
圖1 TV端主頁默認頁面
圖2 手機端遙控頁面
- 圖3 TV端視頻播放頁面
2.搭建OpenHarmony環境
完成本篇Codelab我們首先要完成開發環境的搭建,本示例以RK3568開發板為例,參照以下步驟進行:
- [獲取OpenHarmony系統版本]:標準系統解決方案(二進制)。
以3.1版本為例: - 搭建燒錄環境。
- 搭建開發環境。
- 開始前請參考[工具準備],完成DevEco Studio的安裝和開發環境配置。
- 開發環境配置完成后,請參考[使用工程向導]創建工程(模板選擇“Empty Ability”),選擇JS或者eTS語言開發。
- 工程創建完成后,選擇使用[真機進行調測])。
3.分布式組網
本章節以系統自帶的音樂播放器為例(具體以實際的應用為準),介紹如何完成兩臺設備的分布式組網。
硬件準備:準備兩臺燒錄相同的版本系統的RK3568開發板A、B。
開發板A、B連接同一個WiFi網絡。
打開設置-->WLAN-->點擊右側WiFi開關-->點擊目標WiFi并輸入密碼。將設備A,B設置為互相信任的設備。
- 找到系統應用“音樂”。
- 設備A打開音樂,點擊左下角流轉按鈕,彈出列表框,在列表中會展示遠端設備的id。選擇遠端設備B的id,另一臺開發板(設備B)會彈出驗證的選項框。
- 設備B點擊允許,設備B將會彈出隨機PIN碼,將設備B的PIN碼輸入到設備A的PIN碼填入框中。
配網完畢。
- 找到系統應用“音樂”。
4.代碼結構解讀
本篇Codelab只對核心代碼進行講解,首先來介紹下整個工程的代碼結構:
- MainAbility:
- PhoneAbility:存放應用手機控制端主頁面。
- pages/PhoneIndex.ets:手機控制端主頁面。
- ServiceAbility:存放ServiceAbility相關文件。
- service.ts:service服務,用于跨設備連接后通訊。
- resources :存放工程使用到的資源文件。
- resources/rawfile:存放工程中使用的圖片資源文件。
- config.json:配置文件。
5.實現TV端界面
在本章節中,您將學會開發TV端默認界面和TV端視頻播放界面,示意圖參考第一章圖1和圖3所示。
建立數據模型,將圖片ID、圖片源、圖片名稱和視頻源綁定成一個數據模型。詳情代碼可以查看MainAbility/model/PicData.ets和MainAbility/model/PicDataModel.ets兩個文件。
- 實現TV端默認頁面布局和樣式。
- 在MainAbility/pages/TVIndex.ets 主界面文件中添加入口組件。頁面布局代碼如下:
// 入口組件 @Entry @Component struct Index { private letters: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] private source: string @State text: string = '' @State choose: number = -1 build() { Flex({ direction: FlexDirection.Column }) { TextInput({text: this.text, placeholder: 'Search' }) .onChange((value: string) = > { this.text = value }) Row({space: 30}) { Text('Clear') .fontSize(16) .backgroundColor('#ABB0BA') .textAlign(TextAlign.Center) .onClick(() = > { this.text = '' }) .clip(true) .borderRadius(10) Text('Backspace') .fontSize(16) .backgroundColor('#ABB0BA') .textAlign(TextAlign.Center) .onClick(() = > { this.text = this.text.substring(0, this.text.length - 1) }) .clip(true) .borderRadius(10) Text('Controller') .fontSize(16) .backgroundColor('#ABB0BA') .textAlign(TextAlign.Center) .onClick(() = > { ...... }) .clip(true) .borderRadius(10) } Grid() { ForEach(this.letters, (item) = > { GridItem() { Text(item) .fontSize(20) .backgroundColor('#FFFFFF') .textAlign(TextAlign.Center) .onClick(() = > { this.text += item }) .clip(true) .borderRadius(5) } }, item = > item) } .rowsTemplate('1fr 1fr 1fr 1fr') .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr') .columnsGap(8) .rowsGap(8) .width('75%') .height('25%') .margin(5) .backgroundColor('#D2D3D8') .clip(true) .borderRadius(10) Grid() { ForEach(this.picItems, (item: PicData) = > { GridItem() { PicGridItem({ picItem: item }) } }, (item: PicData) = > item.id.toString()) } .rowsTemplate('1fr 1fr 1fr') .columnsTemplate('1fr 1fr') .columnsGap(5) .rowsGap(8) .width('90%') .height('58%') .backgroundColor('#FFFFFF') .margin(5) } .width('98%') .backgroundColor('#FFFFFF') } }
- 其中PicGridItem將PicItem的圖片源和圖片名稱綁定,實現代碼如下:
// 九宮格拼圖組件 @Component struct PicGridItem { private picItem: PicData build() { Column() { Image(this.picItem.image) .objectFit(ImageFit.Contain) .height('85%') .width('100%') .onClick(() = > { ...... }) }) Text(this.picItem.name) .fontSize(20) .fontColor('#000000') } .height('100%') .width('90%') } }
- 在MainAbility/pages/TVIndex.ets 主界面文件中添加入口組件。頁面布局代碼如下:
- 實現TV端視頻播放界面。
- 在MainAbility/pages/VideoPlay.ets 文件中添加組件。頁面布局代碼如下:
import router from '@system.router' @Entry @Component struct Play { // 取到Index頁面跳轉來時攜帶的source對應的數據。 private source: string = router.getParams().source build() { Column() { Video({ src: this.source, }) .width('100%') .height('100%') .autoPlay(true) .controls(true) } } }
- 在MainAbility/pages/TVIndex.ets中,給PicGridItem的圖片添加點擊事件,點擊圖片即可播放PicItem的視頻源。實現代碼如下:
Image(this.picItem.image) ...... .onClick(() = > { router.push({ uri: 'pages/VideoPlay', params: { source: this.picItem.video } }) })
- 在MainAbility/pages/VideoPlay.ets 文件中添加組件。頁面布局代碼如下:
6.實現手機遙控端界面
在本章節中,您將學會開發手機遙控端默認界面,示意圖參考第一章圖2所示。
- PhoneAbility/pages/PhoneIndex.ets 主界面文件中添加入口組件。頁面布局代碼如下:
@Entry @Component struct Index { build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { Row() { Image($rawfile('TV.png')) .width(25) .height(25) Text('華為智慧屏').fontSize(20).margin(10) } // 文字搜索框 TextInput({ placeholder: 'Search' }) .margin(20) .onChange((value: string) = > { if (connectModel.mRemote){ ...... } }) Grid() { GridItem() { // 向上箭頭 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('up.png')).width(80).height(80) } .onClick(() = > { ...... }) .width(80) .height(80) .backgroundColor('#FFFFFF') } .columnStart(1) .columnEnd(5) GridItem() { // 向左箭頭 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('left.png')).width(80).height(80) } .onClick(() = > { ...... }) .width(80) .height(80) .backgroundColor('#FFFFFF') } GridItem() { // 播放鍵 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('play.png')).width(60).height(60) } .onClick(() = > { ...... }) .width(80) .height(80) .backgroundColor('#FFFFFF') } GridItem() { // 向右箭頭 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('right.png')).width(70).height(70) } .onClick(() = > { ...... }) .width(80) .height(80) .backgroundColor('#FFFFFF') } GridItem() { // 向下箭頭 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('down.png')).width(70).height(70) } .onClick(() = > { ...... }) .width(80) .height(80) .backgroundColor('#FFFFFF') } .columnStart(1) .columnEnd(5) } .rowsTemplate('1fr 1fr 1fr') .columnsTemplate('1fr 1fr 1fr') .backgroundColor('#FFFFFF') .margin(10) .clip(new Circle({ width: 325, height: 325 })) .width(350) .height(350) Row({ space:100 }) { // 返回鍵 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('return.png')).width(40).height(40) } .onClick(() = > { ...... }) .width(100) .height(100) .backgroundColor('#FFFFFF') // 關機鍵 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('off.png')).width(40).height(40) } .onClick(() = > { ...... }) .width(100) .height(100) .backgroundColor('#FFFFFF') // 搜索鍵 Button({ type: ButtonType.Circle, stateEffect: true }) { Image($rawfile('search.png')).width(40).height(40) } .onClick(() = > { ...... }) .width(100) .height(100) .backgroundColor('#FFFFFF') } .padding({ left:100 }) } .backgroundColor('#E3E3E3') } }
7.實現分布式拉起和RPC通信
在本章節中,您將學會如何拉起在同一組網內的設備上的FA,并且連接遠端Service服務。
首先通過TV端拉起手機端界面,并將本端的deviceId發送到手機端。
- 點擊TV端主頁上的"Controller"按鈕,增加.onClick()事件。調用RegisterDeviceListCallback()發現設備列表,并彈出設備列表選擇框CustomDialogExample,選擇設備后拉起遠端FA。CustomDialogExample()代碼如下:
// 設備列表彈出框 @CustomDialog struct CustomDialogExample { @State editFlag: boolean = false controller: CustomDialogController cancel: () = > void confirm: () = > void build() { Column() { List({ space: 10, initialIndex: 0 }) { ForEach(DeviceIdList, (item) = > { ListItem() { Row() { Text(item) .width('87%') .height(50) .fontSize(10) .textAlign(TextAlign.Center) .borderRadius(10) .backgroundColor(0xFFFFFF) .onClick(() = > { onStartRemoteAbility(item); this.controller.close(); }) } }.editable(this.editFlag) }, item = > item) } }.width('100%').height(200).backgroundColor(0xDCDCDC).padding({ top: 5 }) } }
- 點擊設備彈出框內的Text組件會調用onStartRemoteAbility()方法拉起遠端FA(手機端),將TV端的deviceId傳給手機端,并連接手機端的Service。因此在featureAbility.startAbility()成功的回調中也要調用onConnectRemoteService()方法。這里將連接遠端Service和發送消息抽象為ConnectModel,詳細代碼可查看MainAbility/model/ConnectModel.ets文件中onConnectRemoteService()方法。onStartRemoteAbility()方法的代碼如下:
function onStartRemoteAbility(deviceId) { AuthDevice(deviceId); let numDevices = remoteDeviceModel.deviceList.length; if (numDevices === 0) { prompt.showToast({ message: "onStartRemoteAbility no device found" }); return; } var params = { remoteDeviceId: localDeviceId } var wantValue = { bundleName: 'com.example.helloworld0218', abilityName: 'com.example.helloworld0218.PhoneAbility', deviceId: deviceId, parameters: params }; featureAbility.startAbility({ want: wantValue }).then((data) = > { // 拉起遠端后,連接遠端service connectModel.onConnectRemoteService(deviceId) }); }
- 需要注意的是,配置文件config.json中ServiceAbility的屬性visible要設置為true,代碼如下:
"abilities": [ ... { "visible": true, "srcPath": "ServiceAbility", "name": ".ServiceAbility", "icon": "$media:icon", "srcLanguage": "ets", "description": "$string:description_serviceability", "type": "service" } ],
- 點擊TV端主頁上的"Controller"按鈕,增加.onClick()事件。調用RegisterDeviceListCallback()發現設備列表,并彈出設備列表選擇框CustomDialogExample,選擇設備后拉起遠端FA。CustomDialogExample()代碼如下:
成功拉起手機端界面后,通過接收TV端傳過來的deviceId連接TV端的Service。在手機端的生命周期內增加aboutToAppear()事件,在界面被拉起的時候讀取對方的deviceId并調用onConnectRemoteService()方法,連接對方的Service,實現代碼如下:
async aboutToAppear() { await featureAbility.getWant((error, want) = > { // 遠端被拉起后,連接對端的service if (want.parameters.remoteDeviceId) { let remoteDeviceId = want.parameters.remoteDeviceId connectModel.onConnectRemoteService(remoteDeviceId) } }); }
建立一個ServiceAbility處理收到的消息并發布公共事件,詳細代碼請看ServiceAbility/service.ts文件。TV端訂閱本端Service的公共事件,并接受和處理消息。
- 創建SubscribeEvent(),實現代碼如下:
subscribeEvent() { let self = this; // 用于保存創建成功的訂閱者對象,后續使用其完成訂閱及退訂的動作 var subscriber; // 訂閱者信息 var subscribeInfo = { events: ["publish_change"], priority: 100 }; // 設置有序公共事件的結果代碼回調 function SetCodeCallBack() { } // 設置有序公共事件的結果數據回調 function SetDataCallBack() { } // 完成本次有序公共事件處理回調 function FinishCommonEventCallBack() { } // 訂閱公共事件回調 function SubscribeCallBack(err, data) { let msgData = data.data; let code = data.code; // 設置有序公共事件的結果代碼 subscriber.setCode(code, SetCodeCallBack); // 設置有序公共事件的結果數據 subscriber.setData(msgData, SetDataCallBack); // 完成本次有序公共事件處理 subscriber.finishCommonEvent(FinishCommonEventCallBack) // 處理接收到的數據data ...... // 創建訂閱者回調 function CreateSubscriberCallBack(err, data) { subscriber = data; // 訂閱公共事件 commonEvent.subscribe(subscriber, SubscribeCallBack); } // 創建訂閱者 commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack); } }
- 在TV端的生命周期內增加aboutToAppear()事件,訂閱公共事件,實現代碼如下:
async aboutToAppear() { this.subscribeEvent(); }
成功連接遠端Service服務后,在手機遙控器端進行按鈕或者輸入操作都會完成一次跨設備通訊,消息的傳遞是由手機遙控器端的FA傳遞到TV端的Service服務。這里將連接遠端Service和發送消息抽象為ConnectModel,詳細代碼可查看MainAbility/model/ConnectModel.ets文件中sendMessageToRemoteService()方法。
8.設置遙控器遠端事件
手機端應用對TV端能做出的控制有:向上移動、向下移動、向左移動、向右移動、確定、返回、關閉。在手機端按鍵上增加點擊事件,通過sendMessageToRemoteService()的方法發送到TV端Service。TV端根據發送code以及數據,進行數據處理,這里只展示TV端數據處理部分的核心代碼:
// code = 1時,將手機遙控端search框內數據同步到TV端
if (code == 1) {
self.text = data.parameters.dataList;
}
// code = 2時,增加選中圖片效果
if (code == 2) {
// 如果在圖片序號范圍內就選中圖片,否則不更改
var tmp: number = +data.parameters.dataList;
if ((self.choose + tmp <= 5) && (self.choose + tmp >= 0)) {
self.choose += tmp;
}
}
// code = 3時,播放選中圖片對應的視頻
if (code == 3) {
self.picItems.forEach(function (item) {
if (item.id == self.choose) {
router.push({
uri: 'pages/VideoPlay',
params: { source: item.video }
})
}
})
}
// code = 4時,回到TV端默認頁面
if (code == 4) {
router.push({
uri: 'pages/TVIndex',
})
}
// code = 5時,關閉程序
if (code == 5) {
featureAbility.terminateSelf()
}
// code = 6時,搜索圖片名稱并增加選中特效
if (code == 6) {
self.picItems.forEach(function (item) {
if (item.name == self.text) {
self.choose = Number(item.id)
}
})
}
審核編輯 黃宇
-
遙控器
+關注
關注
18文章
840瀏覽量
66586 -
分布式
+關注
關注
1文章
924瀏覽量
74611 -
鴻蒙
+關注
關注
57文章
2392瀏覽量
43051 -
HarmonyOS
+關注
關注
79文章
1982瀏覽量
30577 -
OpenHarmony
+關注
關注
25文章
3745瀏覽量
16581
發布評論請先 登錄
相關推薦
OpenHarmony分布式開發前奏
![<b class='flag-5'>OpenHarmony</b><b class='flag-5'>分布式</b><b class='flag-5'>開發</b>前奏](https://file.elecfans.com/web2/M00/7F/0C/poYBAGOIRe2AErYgAACikz4T4_0305.png)
OpenHarmony 2.2 Beta2 版本發布,具備典型的分布式能力和媒體類產品開發能力
基于潤和DAYU200開發套件的OpenHarmony分布式音樂播放器
OpenHarmony標準設備應用開發(三)——分布式數據管理
【學習打卡】OpenHarmony的分布式任務調度
【開發樣例】OpenHarmony分布式購物車
OpenHarmony 分布式硬件關鍵技術
OpenHarmony技術論壇:分布式相機和分布式圖庫功能
![<b class='flag-5'>OpenHarmony</b>技術論壇:<b class='flag-5'>分布式</b>相機和<b class='flag-5'>分布式</b>圖庫功能](https://file.elecfans.com/web2/M00/3F/1F/poYBAGJmSv-ASqk1AA8Dhi9OakI548.png)
OpenHarmony知識賦能No.29-DAYU200分布式應用開發
![<b class='flag-5'>OpenHarmony</b>知識賦能No.29-DAYU200<b class='flag-5'>分布式</b>應用<b class='flag-5'>開發</b>](https://file1.elecfans.com/web2/M00/82/6D/wKgaomRTEU-APtxOAABCfZQGNyk795.png)
評論