赞
踩
目前家庭电视机主要通过其自带的遥控器进行操控,实现的功能较为单一。例如,当我们要在TV端搜索节目时,电视机在遥控器的操控下往往只能完成一些字母或数字的输入,而无法输入其他复杂的内容。分布式遥控器将手机的输入能力和电视遥控器的遥控能力结合为一体,从而快速便捷操控电视。
分布式遥控器的实现基于OpenHarmony的分布式能力和RPC通信能力,UI使用eTS进行开发。如下图所示,分别用两块开发板模拟TV端和手机端。
UI效果图如下:
图1 TV端主页默认页面
图2 手机端遥控页面
说明: 本示例涉及使用系统接口,需要手动替换Full SDK才能编译通过,具体操作可参考替换指南。
完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:
获取OpenHarmony系统版本:标准系统解决方案(二进制)。
以3.1版本为例:
搭建烧录环境。
搭建开发环境。
本章节以系统自带的音乐播放器为例(具体以实际的应用为准),介绍如何完成两台设备的分布式组网。
硬件准备:准备两台烧录相同的版本系统的RK3568开发板A、B。
开发板A、B连接同一个WiFi网络。
打开设置-->WLAN-->点击右侧WiFi开关-->点击目标WiFi并输入密码。
将设备A,B设置为互相信任的设备。
找到系统应用“音乐”。
设备A打开音乐,点击左下角流转按钮,弹出列表框,在列表中会展示远端设备的id。选择远端设备B的id,另一台开发板(设备B)会弹出验证的选项框。
设备B点击允许,设备B将会弹出随机PIN码,将设备B的PIN码输入到设备A的PIN码填入框中。
配网完毕。
本篇Codelab只对核心代码进行讲解,首先来介绍下整个工程的代码结构:
MainAbility:
model:数据模型。
pages:存放TV端各个页面。
PhoneAbility:存放应用手机控制端主页面。
ServiceAbility:存放ServiceAbility相关文件。
resources :存放工程使用到的资源文件。
config.json:配置文件。
在本章节中,您将学会开发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%')
- }
- }
实现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 }
- })
- })
在本章节中,您将学会开发手机遥控端默认界面,示意图参考第一章图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')
- }
- }
在本章节中,您将学会如何拉起在同一组网内的设备上的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端传过来的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() {
- 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);
- }
- }
- async aboutToAppear() {
- this.subscribeEvent();
- }
成功连接远端Service服务后,在手机遥控器端进行按钮或者输入操作都会完成一次跨设备通讯,消息的传递是由手机遥控器端的FA传递到TV端的Service服务。这里将连接远端Service和发送消息抽象为ConnectModel,详细代码可查看MainAbility/model/ConnectModel.ets文件中sendMessageToRemoteService()方法。
手机端应用对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)
- }
- })
- }
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
HarmonOS基础技能
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
OpenHarmony北向、南向开发环境搭建
获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。