[文章]HarmonyOS实战—基于分布式能力,实现多设备同步书写互动

阅读量0
0
4
1. 介绍      分布式手写板利用分布式数据库和分布式设备启动与连接实现。每台设备在书写的时候,连接的多台设备都能实时同步笔迹。为了让您快速了解本篇Codelab所实现的功能,我们先对分布式手写板进行展示,效果图如下:
图1 设备拉起界面
设备选择弹框,点击设备图标(可以多选)勾选完成,点击确认按钮可拉起设备。
34.png

图2 图形绘制界面
图中拉起三台设备,从左到右第一台设备书写笔迹是黑色,第二台设备书写笔迹是蓝色,第三台书写笔迹是红色(每台设备的画笔初始化时都会随机一种颜色),每台设备笔迹都会同步到连接的设备上显示。

图3 分布式手写板演示图


2. 搭建HarmonyOS环境     
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
  • 安装DevEco Studio,详情请参考下载和安装软件。
  • 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
    • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
    • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
  • 开发者可以参考以下链接,完成设备调试的相关配置:
    • 使用真机进行调试
    • 使用模拟器进行调试
                  您可以利用如下设备完成Codelab:开启了开发者模式的HarmonyOS真机或DevEco Studio自带模拟器。

说明
本篇文章使用的DevEco Studio版本为DevEco Studio 2.1 Beta4,使用的SDK版本为API Version 5。
   

3. 代码结构解读      
本篇Codelab只是对核心代码进行讲解,可以在后面章节中下载完整代码,以下介绍整个工程的代码结构。

  • bean:DeviceData设备适配器实体类;MyPoint存放绘制点的坐标和基本信息。
  • listcomponent:根据每个设备处理每个设备组件。
  • component:DeviceSelectDialog展示设备列表对话框,用户选择设备(可以是多个设备)进行连接;DrawPoint存放所有绘制点的基本信息和绘图。
  • slice:MainAbilitySlice画板的主界面。
  • utils:LogUtil主要作用打印日志信息;GsonUtils用于将绘制的多个点转成字符串。
  • resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件,resourcesbasemedia下存放图片资源。
  • config.json:应用的配置文件。
   

4. 相关权限      
本程序开发需要申请以下多设备协同相关的四个权限,应用权限的申请可以参考权限章节。
权限名
说明
ohos.permission.DISTRIBUTED_DATASYNC
必选(分布式数据管理权限,允许不同设备间的数据交换)
ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE
必选(允许获取分布式组网内设备的状态变化)
ohos.permission.GET_DISTRIBUTED_DEVICE_INFO
必选(允许获取分布式组网内的设备列表和设备信息)
ohos.permission.GET_BUNDLE_INFO
必选(查询其他应用信息的权限)

说明
其中多设备协同数据同步权限"ohos.permission.DISTRIBUTED_DATASYNC",需要按照动态申请流程向用户申请授权。
   

5. 设备连接      
  • 进入应用,点击设备选择,会弹出设备选择列表对话框,实现代码如下:
    1. DeviceSelectDialog dialog = new DeviceSelectDialog1(MainAbilitySlice.this);
    2. dialog.setListener(deviceIds -> {
    3.     // 启动远程页面
    4.     startRemoteFas(deviceIds);
    5.     // 同步远程数据库
    6.     singleKvStore.sync(deviceIds, SyncMode.PUSH_ONLY);
    7.     dialog.hide();
    8. });
    9. dialog.show();
    复制代码

    说明
    以上代码除数据同步外,还有部分场景业务代码。
  • 这个类DeviceSelectDialog 是一个封装好的对话框,可以动态设置设备信息,主要获取在线可同步的设备。遍历可同步设备,用onBindViewHolder方法,绑定设备的基本信息和多选框,返回到页面上,关键的实现代码如下:
    1. private void setAdapter() {
    2.     List deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
    3.     deviceList.clear();
    4.     for (DeviceInfo deviceInfo : deviceInfoList) {
    5.         deviceList.add(new DeviceData(false, deviceInfo));
    6.     }
    7.     listContainer.setItemProvider(listComponentAdapter =
    8.         new ListComponentAdapter<DeviceData>(context, deviceList, ResourceTable.Layout_dialog_device_item) {
    9.             [url=home.php?mod=space&uid=2735960]@Override[/url]
    10.             public void onBindViewHolder(CommentViewHolder commonViewHolder, DeviceData item, int position) {
    11.                commonViewHolder.getTextView(ResourceTable.Id_item_desc)
    12.                         .setText(item.getDeviceInfo().getDeviceName());
    13.                     switch (item.getDeviceInfo().getDeviceType()) {
    14.                         case SMART_PHONE:
    15.                             commonViewHolder.getImageView(ResourceTable.Id_item_type)
    16.                                 .setPixelMap(ResourceTable.Media_dv_phone);
    17.                             break;
    18.                         case SMART_PAD:
    19.                             commonViewHolder.getImageView(ResourceTable.Id_item_type)
    20.                                 .setPixelMap(ResourceTable.Media_dv_pad);
    21.                             break;
    22.                         case SMART_WATCH:
    23.                             commonViewHolder.getImageView(ResourceTable.Id_item_type)
    24.                                 .setPixelMap(ResourceTable.Media_dv_watch);
    25.                             break;
    26.                         }
    27.                         commonViewHolder.getImageView(ResourceTable.Id_item_check).setPixelMap(item.isChecked()
    28.                                 ? ResourceTable.Media_checked_point : ResourceTable.Media_uncheck_point);
    29.             }
    30.             @Override
    31.             public void onItemClick(Component component, DeviceData item, int position) {
    32.                 super.onItemClick(component, item, position);
    33.                 deviceList.get(position).setChecked(!item.isChecked());
    34.                 listComponentAdapter.notifyDataChanged();
    35.             }
    36.     });
    37. }
    复制代码

  • 遍历选中的设备,根据设备ID等其他信息,启动远程页面并拉起多台设备,实现代码如下:<
    1. private void startRemoteFas(List<String> deviceIds) {
    2.     if (deviceIds != null && !deviceIds.isEmpty()) {
    3.         Intent[] intents = new Intent[deviceIds.size()];
    4.         for (int i = 0; i < deviceIds.size(); i++) {
    5.             Intent intent = new Intent();
    6.             intent.setParam("isFormLocal", true);
    7.             Operation operation = new Intent.OperationBuilder()
    8.                 .withDeviceId(deviceIds.get(i))
    9.                 .withBundleName(getBundleName())
    10.                 .withAbilityName(MainAbility.class.getName())
    11.                 .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
    12.                 .build();
    13.                 intent.setOperation(operation);
    14.                 intents[i] = intent;
    15.         }
    16.         startAbilities(intents);
    17.     }
    18. }
    复制代码


6. 画笔连线实现     
主要遍历所有点,判断该点是否为最后一个点,如果不是,借助Canvas将这个点与之前的点连接组成线条,如果是,跳过本次循环继续下次循环,也就是一条线的完成,实现代码如下:
  1. private void draw(List<MyPoint> points, Canvas canvas) {
  2.     if (points != null && points.size() > 1) {
  3.         Point first = null;
  4.         Point last = null;
  5.         for (MyPoint myPoint : points) {
  6.             paint.setColor(myPoint.getPaintColor());
  7.             float finalX = myPoint.getPositionX();
  8.             float finalY = myPoint.getPositionY();
  9.             Point finalPoint = new Point(finalX, finalY);
  10.             if (myPoint.isLastPoint()) {
  11.                 first = null;
  12.                 last = null;
  13.                 continue;
  14.             }
  15.             if (first == null) {
  16.                 first = finalPoint;
  17.             } else {
  18.                 if (last != null) {
  19.                     first = last;
  20.                 }
  21.                 last = finalPoint;
  22.                 canvas.drawLine(first, last, paint);
  23.             }
  24.         }     
  25.     }
  26. }
复制代码
  

7. 分布式数据服务      
分布式数据库有专门的文档讲解,详细讲解请参照文档,这里仅对关键代码供参考,代码如下:
  1. private void initDatabase() {
  2.         // 创建分布式数据库管理对象
  3.         KvManagerConfig config = new KvManagerConfig(this);
  4.         kvManager = KvManagerFactory.getInstance().createKvManager(config);
  5.         // 创建分布式数据库
  6.         Options options = new Options();
  7.         options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
  8.         singleKvStore = kvManager.getKvStore(options, STORE_ID);
  9.         // 订阅分布式数据变化
  10.         KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
  11.         singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
  12. }
复制代码

8. 数据存储写入      
在setTouchEventListener监听事件开始时加入定时任务scheduledTask(),每200ms往数据库写一次数据(这个time常量可根据需求自定义),当TouchEvent.PRIMARY_POINT_UP事件触发,也就是最后一跟手指离开屏幕时取消定时任务,代码如下:
  1. private static final int TIME = 200;
  2. private static final int EVENT_MSG_STORE = 0x1000002;

  3. private EventHandler handler = new EventHandler(EventRunner.current()) {
  4.     @Override
  5.     protected void processEvent(InnerEvent event) {
  6.         switch (event.eventId) {
  7.             case EVENT_MSG_STORE:
  8.                 callBack.callBack(points);
  9.                 break;
  10.             default:
  11.                break;
  12.         }
  13.     }
  14. };

  15. private void init(int colorIndex) {
  16.     paint = new Paint();
  17.     paint.setAntiAlias(true);
  18.     paint.setStyle(Paint.Style.STROKE_STYLE);
  19.     paint.setStrokeWidth(STROKE_WIDTH);
  20.     addDrawTask(this);
  21.     Color color = getRandomColor(colorIndex);

  22.     setTouchEventListener((component, touchEvent) -> {
  23.         scheduledTask();
  24.         int crtX = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
  25.         int crtY = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getY()-150;

  26.         MyPoint point = new MyPoint(crtX, crtY);
  27.         point.setPaintColor(color);

  28.         switch (touchEvent.getAction()) {
  29.             case TouchEvent.POINT_MOVE:
  30.                 points.add(point);
  31.                 break;
  32.             case TouchEvent.PRIMARY_POINT_UP:
  33.                 points.add(point);
  34.                 point.setLastPoint(true);
  35.                 callBack.callBack(points);
  36.                 onTimerFinish();
  37.                 break;
  38.         }
  39.         invalidate();
  40.         return true;
  41.     });
  42. }

  43. public void scheduledTask(){
  44.     if (timer == null && timerTask == null) {
  45.         timer = new Timer();
  46.         timerTask = new TimerTask() {
  47.             @Override
  48.             public void run() {
  49.                 handler.sendEvent(EVENT_MSG_STORE);
  50.             }
  51.         };
  52.         timer.schedule(timerTask, 0, TIME);
  53.     }
  54. }

  55. public void onTimerFinish() {
  56.     timer.cancel();
  57.     timer = null;
  58.     timerTask = null;
  59. }
复制代码

9. 笔迹撤回      
每个MyPoint都有一个属性isLastPoint用于标记此点是否为最后一个点,首先移除第一个isLastPoint=true的点,然后从后往前遍所有点,移除isLastPoin()=false直到遇到isLastPoint=true跳出循环,存入新数据,更新数据服务,就可实现同步撤回功能,代码如下:
  1. back.setClickedListener(component -> {
  2.     List<MyPoint> points = drawl.getPoints();
  3.     if (points != null && points.size() > 1) {
  4.         points.remove(points.size() - 1);
  5.         for (int i = points.size() - 1; i >= 0; i--) {
  6.             MyPoint mypoint = points.get(i);
  7.             if (mypoint.isLastPoint()) break;
  8.             points.remove(i);
  9.         }
  10.         drawl.setDrawParams(points);
  11.         String pointsString = GsonUtil.objectToString(points);
  12.         LogUtils.info(TAG, "pointsString::" + pointsString);
  13.         if (singleKvStore != null) {
  14.             singleKvStore.putString("points", pointsString);
  15.         }
  16.     }
  17. });
复制代码

10. 画笔颜色      
通过intent传值,为每台设备编号,每个数字对应paintColors数组的index,实现每台设备一种颜色(只定义了三种颜色),多的设备定义成红色,关键代码如下:
  1. intent.setParam("colorIndex",i+1);

  2. private Color[] paintColors = new Color[]{Color.RED, Color.BLUE, Color.BLACK};

  3. private Color getRandomColor(int n) {
  4.     return n>paintColors.length-1 ? paintColors[0]: paintColors[n];
  5. }
复制代码
  

11. 恭喜你     
通过本教程的学习,你已经学会了HarmonyOS分布式数据库和分布式设备启动与连接,利用canvas组件绘制图形。
   

12. 参考      

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友