OpenHarmony实战开发-如何实现分布式画布

654 篇文章 4 订阅
648 篇文章 6 订阅

场景说明

两台设备组网,当其中一个设备修改文件时,两个设备可以同步修改的结果。分布式场景可以在协同办公(如多人多设备编辑同一文件),设备文档更新(分布式设备更新文件内容,所有设备同步更新)中发挥重要作用,有助于加快工作效率,减少工作中的冗余。

本示例将为大家介绍如何实现上述功能。

效果呈现

本例效果如下:

设置分布式权限	进行分布式连接	连接后状态显示

点击rect和ellipse按钮后后本机显示	另外一台机器分布式应用显示

运行环境

本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。

  • IDE:DevEco Studio 4.0.0.201 Beta1
  • SDK:Ohos_sdk_public 4.0.7.5 (APIVersion 10 Beta1)

实现思路

在分布式文件场景中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。 首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。

  • 分布式设备搜索:通过SUBSCRIBE_ID搜索分布式组网内的设备。
  • 分布式设备列表弹窗:使用@CustomDialog装饰器来装饰分布式设备列表弹窗。
  • 远端设备拉起:通过startAbility(deviceId)方法拉起远端设备的包。
  • 分布式数据管理:(1)管理分布式数据库:创建一个distributedObject分布式数据对象实例,用于管理分布式数据对象。

​ (2)订阅分布式数据变化:通过this.distributedObject.on(‘status’, this.statusCallback)监听分布式数据对象的变更。

开发步骤

1.申请所需权限

在model.json5中添加以下配置:

"requestPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换
      },
      {
        "name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力
      }
    ]

2.构建UI框架

indexCanvas页面:

TitleBar组件呈现标题栏。通过数据懒加载的方式遍历绘制的图形。被划出可视区域外的资源会被回收。

绘制ellipse图形、rect图形的按钮使用Button组件呈现。

返回按钮、删除按钮也通过Button组件呈现。

build() {
    Column() {
      TitleBar({ rightBtn: $r('app.media.trans'), onRightBtnClicked: this.showDialog })
	//自/common/TitleBar.ets中引入标题栏相关。点击标题栏中的右侧按钮会调用showDialog()函数连接组网设备
      Row() {
        Text($r('app.string.state'))
          .fontSize(30)
        Image(this.isOnline ? $r('app.media.green') : $r('app.media.red'))
          .size({ width: 30, height: 30 })
          .objectFit(ImageFit.Contain)
      }
      .width('100%')
      .padding(16)
	  //通过懒加载模式遍历绘制的图形,将每个图形绘制在画布上
      LazyForEach(this.canvasDataSource, (item: CanvasPath, index) => {
        Canvas(this.context)
          .width('100%')
          .height(200)
          .backgroundColor('#00ffff')
          .onReady(() => {
            if (item.path === 'rect') {
              this.context.save();
              this.path2Df.rect(80, 80, 100, 100);
              this.context.stroke(this.path2Df);
              this.context.restore();
            }
            if (item.path === 'ellipse') {
              this.context.restore();
              this.path2De.ellipse(100, 100, 50, 100, Math.PI * 0.25, Math.PI * 0.5, Math.PI);
              this.context.stroke(this.path2De);
              this.context.save();
            }
          })
      }, item => JSON.stringify(item))

      Row() {
        Button('ellipse')//绘制ellipse图形的按钮
          .width(130)
          .height(45)
          .key('ellipse')
          .onClick(() => {
            if (this.globalObject.isContainString('ellipse') === -1) {
              this.globalObject.add('ellipse');  //将绘制信息保存在持久全局数据中
            }
            this.onPageShow();
          })
        Button('rect')//绘制rect图形的按钮
          .width(130)
          .height(45)
          .key('rect')
          .onClick(() => {
            if (this.globalObject.isContainString('rect') === -1) {
              this.globalObject.add('rect');
            }
            this.onPageShow();
          })
      }.margin({ top: 10 })
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)

      Row() {
        Button('back')
          .width(130)
          .height(45)
          .key('back')
          .backgroundColor(Color.Orange)
          .onClick(() => {
            router.back()
          })
        Button('delete')//删除图形
          .width(130)
          .height(45)
          .key('delete')
          .onClick(() => {
            this.globalObject.clear();
            this.canvasDataSource['pathArray'] = [];
            this.canvasDataSource.notifyDataReload();
            this.context.clearRect(0, 0, 950, 950)
          })
      }.margin({ top: 10 })
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

3.数据model

通过registerDataChangeListener进行对数据变动的监听,数据发生变化时,调用notifyDataReload方法通知数据已经准备就绪。

//BasicDataSource.ets
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = []

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  //注册数据变动的监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener')
      this.listeners.push(listener)
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener')
      this.listeners.splice(pos, 1)
    }
  }

//数据reloaded,分布式数据数值变化需要调用这个接口重载下
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index)
    })
  }

 ....

export class CanvasDataSource extends BasicDataSource {
  //监听的数据类型
  private pathArray: Canvas[] = []

  //重载接口
  public totalCount(): number {
    return this.pathArray.length
  }

  public getData(index: number): any {
    return this.pathArray[index]
  }

  public addData(index: number, data: Canvas): void {
    this.pathArray.splice(index, 0, data)
    this.notifyDataAdd(index)
  }

  public pushData(data: Canvas): void {
    this.pathArray.push(data)
    this.notifyDataAdd(this.pathArray.length - 1)
  }
}

4.将两台设备组网

使用自RemoteDeviceModel.ts中引入的类RemoteDeviceModel以扫描获得附近可以连接的设备。

showDialog = () => {
    //RemoteDeviceModel引入自model/RemoteDeviceModel.ts
    RemoteDeviceModel.registerDeviceListCallback(() => {
        //得到附近可信的设备列表
      Logger.info(TAG, 'registerDeviceListCallback, callback entered')
      this.devices = []
      this.devices = RemoteDeviceModel.discoverDevices.length > 0 ? RemoteDeviceModel.discoverDevices : RemoteDeviceModel.devices
      if (this.dialogController) {
        this.dialogController.close()
        this.dialogController = undefined
      }
      this.dialogController = new CustomDialogController({
        builder: DeviceDialog({
          devices: this.devices,
          onSelectedIndexChange: this.onSelectedDevice
        }),
        autoCancel: true
      })
      this.dialogController.open()
    })
  }
....................................
//model/RemoteDeviceModel.ts
import deviceManager from '@ohos.distributedHardware.deviceManager'
registerDeviceListCallback(stateChangeCallback: () => void) {
    if (typeof (this.deviceManager) !== 'undefined') {
      this.registerDeviceListCallbackImplement(stateChangeCallback)
      return
    }
    Logger.info(TAG, 'deviceManager.createDeviceManager begin')
    try {
      deviceManager.createDeviceManager(BUNDLE, (error, value) => {
        if (error) {
          Logger.error(TAG, 'createDeviceManager failed.')
          return
        }
        this.deviceManager = value
        this.registerDeviceListCallbackImplement(stateChangeCallback)
        Logger.info(TAG, `createDeviceManager callback returned,value=${value}`)
      })
    } catch (error) {
      Logger.error(TAG, `createDeviceManager throw error, code=${error.code} message=${error.message}`)
    }

    Logger.info(TAG, 'deviceManager.createDeviceManager end')
  }
registerDeviceListCallbackImplement(stateChangeCallback: () => void) {
    Logger.info(TAG, 'registerDeviceListCallback')
    this.stateChangeCallback = stateChangeCallback
    if (this.deviceManager === undefined) {
      Logger.error(TAG, 'deviceManager has not initialized')
      this.stateChangeCallback()
      return
    }
    Logger.info(TAG, 'getTrustedDeviceListSync begin')
    try {
      let list = this.deviceManager.getTrustedDeviceListSync()//同步获取所有可信设备列表
      Logger.info(TAG, `getTrustedDeviceListSync end, devices=${JSON.stringify(list)}`)
      if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
        this.devices = list
      }
    } catch (error) {
      Logger.error(TAG, `getLocalDeviceInfoSync throw error, code=${error.code} message=${error.message}`)
    }
    this.stateChangeCallback()
    Logger.info(TAG, 'callback finished')
    try {
      this.deviceManager.on('deviceStateChange', (data) => {
        if (data === null) {
          return
        }
        Logger.info(TAG, `deviceStateChange data = ${JSON.stringify(data)}`)
        switch (data.action) {
          case deviceManager.DeviceStateChangeAction.READY://即设备处于可用状态,表示设备间信息已在分布式数据中同步完成, 可以运行分布式业务
            this.discoverDevices = []
            this.devices.push(data.device)
            this.stateChangeCallback()
            try {
              let list = this.deviceManager.getTrustedDeviceListSync()
              if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
                this.devices = list
              }
            } catch (error) {
              Logger.error(TAG, `getTrustedDeviceListSync throw error, code=${error.code} message=${error.message}`)
            }
            this.stateChangeCallback()
            break
          default:
            break
        }
      })
      this.deviceManager.on('deviceFound', (data) => {
        if (data === null) {
          return
        }
        Logger.info(TAG, `deviceFound data=${JSON.stringify(data)}`)
        this.onDeviceFound(data)
      })
      this.deviceManager.on('discoverFail', (data) => {
        Logger.info(TAG, `discoverFail data=${JSON.stringify(data)}`)
      })
      this.deviceManager.on('serviceDie', () => {
        Logger.info(TAG, 'serviceDie')
      })
    } catch (error) {
      Logger.error(TAG, `on throw error, code=${error.code} message=${error.message}`)
    }
    this.startDeviceDiscovery()
  }
startDeviceDiscovery() {
    SUBSCRIBE_ID = Math.floor(65536 * Math.random())
    var info = {
      subscribeId: SUBSCRIBE_ID,
      mode: 0xAA,
      medium: 2,
      freq: 2,//高频率
      isSameAccount: false,
      isWakeRemote: true,
      capability: 0
    }
    Logger.info(TAG, `startDeviceDiscovery${SUBSCRIBE_ID}`)
    try {
      this.deviceManager.startDeviceDiscovery(info)//开始发现周边设备
    } catch (error) {
      Logger.error(TAG, `startDeviceDiscovery throw error, code=${error.code} message=${error.message}`)
    }

  }

5.实现同步编辑

通过AppStorage设置持久性数据,然后实现IDataSource接口,通过注册数据监听接口监听数据的变化。

onPageShow() {
    //每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
    //noteDataSource获取globalObject保存的分布式的持久性数据,并进行Reload操作传递。
    this.noteDataSource['dataArray'] = this.globalObject.distributedObject.documents
    this.noteDataSource.notifyDataReload()
    Logger.info(TAG, `this.sessionId = ${this.sessionId}`)
    Logger.info(TAG, `globalSessionId = ${this.globalSessionId}`)
    if (this.sessionId !== this.globalSessionId) {
      this.sessionId = this.globalSessionId
      this.share()
    }
  }
share() {
    //多个设备间的对象如果设置为同一个sessionId,数据自动同步
    Logger.info(TAG, `sessionId = ${this.sessionId}`)
    this.globalObject.setChangeCallback(() => {
      this.noteDataSource['dataArray'] = this.globalObject.distributedObject.documents
      this.noteDataSource.notifyDataReload()
    })
    this.globalObject.setStatusCallback((session, networkId, status) => {
      Logger.info(TAG, `StatusCallback,${status}`)
      if (status === 'online') {
        this.isOnline = true
      } else {
        this.isOnline = false
      }
    })
    this.globalObject.distributedObject.setSessionId(this.sessionId)
    AppStorage.SetOrCreate('objectModel', this.globalObject)
  }

全部代码

本例完整代码sample示例链接:分布式对象

我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI、实战开发视频教程》以及《鸿蒙生态应用开发白皮书V2.0PDF》《鸿蒙开发学习手册》(共计890页)鸿蒙开发资料等…希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVkRGRUd3pHSnFG

在这里插入图片描述

应用开发中级就业技术:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

应用开发中高级就业技术:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

南北双向高工技能基础:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

全网首发-工业级 南向设备开发就业技术:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

《鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值