<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

HarmonyOS開發實例:【分布式手寫板】

jf_46214456 ? 來源:jf_46214456 ? 作者:jf_46214456 ? 2024-04-17 21:45 ? 次閱讀

介紹

本篇Codelab使用設備管理及分布式鍵值數據庫能力,實現多設備之間手寫板應用拉起及同步書寫內容的功能。操作流程:

  1. 設備連接同一無線網絡,安裝分布式手寫板應用。進入應用,點擊允許使用多設備協同,點擊主頁上查詢設備按鈕,顯示附近設備。
  2. 選擇設備確認,若已建立連接,啟動對方設備上的手寫板應用,否則提示建立連接。輸入PIN碼建立連接后再次點擊查詢設備按鈕,選擇設備提交,啟動對方設備應用。
  3. 建立連接前繪制的內容在啟動對方設備后同步,此時設備上繪制的內容會在另一端同步繪制。
  4. 點擊撤銷按鈕,兩側設備繪制內容同步撤銷。

相關概念

  • [設備管理]:模塊提供分布式設備管理能力。
  • [分布式鍵值數據庫]:分布式鍵值數據庫為應用程序提供不同設備間數據庫的分布式協同能力。

相關權限

本篇Codelab使用了設備管理及分布式鍵值數據庫能力,需要手動替換full-SDK,并在配置文件module.json5文件requestPermissions屬性中添加如下權限:

  • [分布式設備認證組網權限]:ohos.permission.ACCESS_SERVICE_DM。
  • [設備間的數據交換權限]:ohos.permission.DISTRIBUTED_DATASYNC。

約束與限制

  1. 本篇Codelab部分能力依賴于系統API,需下載full-SDK并替換DevEco Studio自動下載的public-SDK。
  2. 本篇Codelab使用的部分API僅系統應用可用,需要提升應用等級。

環境搭建

軟件要求

  • [DevEco Studio]版本:DevEco Studio 4.0 Beta2。
  • OpenHarmony SDK版本:API version 10。
  • 鴻蒙指導參考:[qr23.cn/AKFP8k]

搜狗高速瀏覽器截圖20240326151547.png

硬件要求

  • 開發板類型:[潤和RK3568開發板]。
  • OpenHarmony系統:4.0 Release。

環境搭建

完成本篇Codelab我們首先要完成開發環境的搭建,本示例以RK3568開發板為例,參照以下步驟進行:

  1. [獲取OpenHarmony系統版本]:標準系統解決方案(二進制)。以4.0 Release版本為例:
  2. 搭建燒錄環境。
    1. [完成DevEco Device Tool的安裝]
    2. [完成RK3568開發板的燒錄]
  3. 搭建開發環境。
    1. 開始前請參考[工具準備],完成DevEco Studio的安裝和開發環境配置。
    2. 開發環境配置完成后,請參考[使用工程向導]創建工程(模板選擇“Empty Ability”)。
    3. 工程創建完成后,選擇使用[真機進行調測]。

代碼結構解讀

本篇Codelab只對核心代碼進行講解,對于完整代碼,我們會在gitee中提供。

├──entry/src/main/ets                 // 代碼區
│  ├──common
│  │  ├──constants
│  │  │  └──CommonConstants.ets       // 公共常量類
│  │  └──utils
│  │     ├──Logger.ets                // 日志打印類
│  │     └──RemoteDeviceUtil.ets      // 設備管理類
│  ├──entryability
│  │  └──EntryAbility.ets             // 程序入口類
│  ├──pages
│  │  └──Index.ets                    // 主界面
│  ├──view
│  │  └──CustomDialogComponent.ets    // 自定義彈窗組件類
│  └──viewmodel
│     ├──KvStoreModel.ets             // 分布式鍵值數據庫管理類
│     └──Position.ets                 // 繪制位置信息
└──entry/src/main/resources           // 資源文件目錄

界面設計

主界面由導航欄及繪制區域組成,導航欄包含撤回按鈕及查詢設備按鈕。繪制區域使用Canvas畫布組件展示繪制效果。Index.ets文件完成界面實現,使用Column及Row容器組件進行布局。

// Index.ets
let storage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Index {
  ...
  build() {
    Column() {
      Row() {
        // 撤回按鈕
        Image($r('app.media.ic_back'))
          .width($r('app.float.ic_back_width'))
          .height($r('app.float.ic_back_height'))
          ...
        Blank()
        // 查找設備按鈕
        Image($r('app.media.ic_hop'))
          .width($r('app.float.ic_hop_width'))
          .height($r('app.float.ic_hop_height'))
          ...
      }
      .width(CommonConstants.FULL_PERCENT)
      .height(CommonConstants.TITLE_HEIGHT)

      Row() {
        // 繪制區域
        Canvas(this.canvasContext)
          .width(CommonConstants.FULL_PERCENT)
          .height(CommonConstants.FULL_PERCENT)
          ...
      }
      ...
      .width(CommonConstants.FULL_PERCENT)
      .layoutWeight(CommonConstants.NUMBER_ONE)
    }
    .height(CommonConstants.FULL_PERCENT)
    .width(CommonConstants.FULL_PERCENT)
  }
  ...
}

分布式組網

準備分布式環境

創建設備管理器。設備管理器創建完成后注冊設備上線離線監聽,信任設備上線離線時觸發。執行獲取本地設備信息,獲取信任設備列表,初始化展示設備列表等方法。其中deviceManager類需使用full-SDK。

// RemoteDeviceUtil.ets
import deviceManager from '@ohos.distributedHardware.deviceManager';

class RemoteDeviceUtil {
  ...
  async createDeviceManager() {
    ...
    await new Promise((resolve: (value: Object | PromiseLike< Object >) = > void, reject: ((reason?: RejectError) = > void)) = > {
      try {
        // 創建設備管理器
        deviceManager.createDeviceManager(CommonConstants.BUNDLE_NAME,
          (error, value: deviceManager.DeviceManager) = > {
            ...
            this.myDeviceManager = value;
            // 注冊信任設備上線離線監聽
            this.registerDeviceStateListener();
            // 獲取本地設備信息
            this.getLocalDeviceInfo();
            // 獲取信任設備列表
            this.getTrustedDeviceList();
            // 初始化展示設備列表
            this.initDeviceList();
            resolve(value);
        });
      } catch (error) {
        Logger.error('RemoteDeviceModel',
          `createDeviceManager failed, error=${JSON.stringify(error)}`);
      }
    });
  }
  ...
}

注冊設備狀態監聽。已驗證設備上線或有新設備驗證通過時狀態類型為ONLINE,將設備添加至信任設備列表。設備離線時狀態類型為OFFLINE,將設備從信任列表中移除。

// RemoteDeviceUtil.ets
class RemoteDeviceUtil {
  ...
  // 注冊設備狀態改變監聽
  registerDeviceStateListener(): void {
    ...
    try {
      // 注冊監聽
      this.myDeviceManager.on('deviceStateChange', (data) = > {
        ...
        switch (data.action) {
          // 設備上線
          case deviceManager.DeviceStateChangeAction.ONLINE: {
            this.deviceStateChangeActionOnline(data.device);
            break;
          }
          // 設備離線
          case deviceManager.DeviceStateChangeAction.OFFLINE: {
            this.deviceStateChangeActionOffline(data.device);
            break;
          }
          ...
        }
      });
    } catch (error) {
      Logger.error('RemoteDeviceModel',
        `registerDeviceStateListener on('deviceStateChange') failed, error=${JSON.stringify(error)}`);
    }
  }

  // 設備上線,加入信任列表及展示列表
  deviceStateChangeActionOnline(device: deviceManager.DeviceInfo): void {
    this.trustedDeviceList[this.trustedDeviceList.length] = device;
    this.addToDeviceList(device);
  }

  // 設備下線,將設備移出信任列表和展示列表
  deviceStateChangeActionOffline(device: deviceManager.DeviceInfo): void {
    let list: deviceManager.DeviceInfo[] = [];
    for (let i: number = 0; i < this.trustedDeviceList.length; i++) {
      if (this.trustedDeviceList[i].networkId !== device.networkId) {
        list.push(this.trustedDeviceList[i]);
        continue;
      }
    }
    this.deleteFromDeviceList(device);
    this.trustedDeviceList = list;
  }
  ...
}

建立分布式連接

點擊主界面的查詢設備按鈕,執行發現設備方法,注冊設備發現監聽任務,同時拉起彈窗展示設備列表。當彈窗關閉時,執行停止發現設備方法,注銷監聽任務。

// RemoteDeviceUtil.ets
class RemoteDeviceUtil {
  ...
  // 處理新發現的設備
  deviceFound(data: DeviceInfoInterface): void {
    for (let i: number = 0; i < this.discoverList.length; i++) {
      if (this.discoverList[i].deviceId === data.device.deviceId) {
        Logger.info('RemoteDeviceModel', `deviceFound device exist=${JSON.stringify(data)}`);
        return;
      }
    }
    this.discoverList[this.discoverList.length] = data.device;
    this.addToDeviceList(data.device);
  }

  startDeviceDiscovery(): void {
    ...
    try {
      // 注冊發現設備監聽
      this.myDeviceManager.on('deviceFound', (data) = > {
        ...
        // 處理發現的設備
        this.deviceFound(data);
      });
      ...
      let info: deviceManager.SubscribeInfo = {
        subscribeId: this.subscribeId,
        mode: CommonConstants.SUBSCRIBE_MODE,
        medium: CommonConstants.SUBSCRIBE_MEDIUM,
        freq: CommonConstants.SUBSCRIBE_FREQ,
        isSameAccount: false,
        isWakeRemote: true,
        capability: CommonConstants.SUBSCRIBE_CAPABILITY
      };
      // 發現周邊設備
      this.myDeviceManager.startDeviceDiscovery(info);
    } catch (error) {
      Logger.error('RemoteDeviceModel',
        `startDeviceDiscovery failed error=${JSON.stringify(error)}`);
    }
  }

  // 停止發現設備
  stopDeviceDiscovery(): void {
    ...
    try {
      // 停止發現設備
      this.myDeviceManager.stopDeviceDiscovery(this.subscribeId);
      // 注銷監聽任務
      this.myDeviceManager.off('deviceFound');
      this.myDeviceManager.off('discoverFail');
    } catch (error) {
      Logger.error('RemoteDeviceModel',
        `stopDeviceDiscovery failed error=${JSON.stringify(error)}`);
    }
  }
  ...
}

選擇彈窗內的設備項提交后,執行設備驗證。

  1. 若設備在信任設備列表,執行startAbility()方法啟動連接設備上的應用,將當前的繪制信息作為參數發送至連接設備。
  2. 若設備不是信任設備,執行authenticateDevice()方法啟動驗證。此時連接設備提示是否接受,接收連接后連接設備展示PIN碼,本地設備輸入PIN碼確認后連接成功。再次點擊查詢設備按鈕,選擇已連接設備,點擊確認啟動連接設備上的應用。
// RemoteDeviceUtil.ets
class RemoteDeviceUtil {
  ...
  // 設備驗證
  authenticateDevice(
    context: common.UIAbilityContext,
    device: deviceManager.DeviceInfo,
    positionList: Position[]
  ): void {
    // 設備為信任設備,啟動連接設備上的應用
    let tmpList = this.trustedDeviceList.filter((item: deviceManager.DeviceInfo) = > device.deviceId === item.deviceId);
    if (tmpList.length > 0) {
      this.startAbility(context, device, positionList);
      return;
    }
    ...
    try {
      // 執行設備認證,啟動驗證相關彈窗,接受信任,顯示PIN碼,輸入PIN碼等
      this.myDeviceManager.authenticateDevice(device, authParam, (err) = > {
        ...
      })
    } catch (error) {
      Logger.error('RemoteDeviceModel',
        `authenticateDevice failed error=${JSON.stringify(error)}`);
    }
  }

  // 啟動連接設備上的應用
  startAbility(context: common.UIAbilityContext, device: deviceManager.DeviceInfo, positionList: Position[]): void {
    ...
    // 啟動連接設備上的應用
    context.startAbility(wantValue).then(() = > {
      Logger.info('RemoteDeviceModel', `startAbility finished wantValue=${JSON.stringify(wantValue)}`);
    }).catch((error: Error) = > {
      Logger.error('RemoteDeviceModel', `startAbility failed, error=${JSON.stringify(error)}`);
    })
  }
  ...
}

資源釋放

程序關閉時,注銷設備狀態監聽任務,并釋放DeviceManager實例。

// RemoteDeviceUtil.ets
class RemoteDeviceUtil {
  ...
  // 注銷監聽任務
  unregisterDeviceListCallback(): void {
    ...
    try {
      // 注銷設備狀態監聽
      this.myDeviceManager.off('deviceStateChange');
      // 釋放DeviceManager實例
      this.myDeviceManager.release();
    } catch (err) {
      Logger.error('RemoteDeviceModel',
        `unregisterDeviceListCallback stopDeviceDiscovery failed, error=${JSON.stringify(err)}`);
    }
  }
  ...
}

繪制功能

Canvas組件區域監聽觸摸事件,按照按下、移動、抬起等觸摸事件,記錄繪制的起點、中間點以及終點。觸摸事件觸發時,使用CanvasRenderingContext2D對象的繪制方法根據位置信息進行繪制。繪制結束后,將當前位置信息列表存入分布式鍵值數據庫。

// Index.ets
let storage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Index {
  ...  
  build() {
    Column() {
      ...
      Row() {
        Canvas(this.canvasContext)
          ...
      }
      .onTouch((event: TouchEvent) = > {
        this.onTouchEvent(event);
      })
      ...
    }
    ...
  }

  // 繪制事件
  onTouchEvent(event: TouchEvent): void {
    let positionX: number = event.touches[0].x;
    let positionY: number = event.touches[0].y;
    switch (event.type) {
      // 手指按下
      case TouchType.Down: {
        this.canvasContext.beginPath();
        this.canvasContext.lineWidth = CommonConstants.CANVAS_LINE_WIDTH;
        this.canvasContext.lineJoin = CommonConstants.CANVAS_LINE_JOIN;
        this.canvasContext.moveTo(positionX, positionY);
        this.pushData(true, false, positionX, positionY);
        break;
      }
      // 手指移動
      case TouchType.Move: {
        this.canvasContext.lineTo(positionX, positionY);
        this.pushData(false, false, positionX, positionY);
        break;
      }
      // 手指抬起
      case TouchType.Up: {
        this.canvasContext.lineTo(positionX, positionY);
        this.canvasContext.stroke();
        this.pushData(false, true, positionX, positionY);
        break;
      }
      default: {
        break;
      }
    }
  }

  pushData(isFirstPosition: boolean, isEndPosition: boolean, positionX: number, positionY: number): void {
    let position = new Position(isFirstPosition, isEndPosition, positionX, positionY);
    // 存入位置信息列表
    this.positionList.push(position);
    if (position.isEndPosition) {
      // 當前位置為終點時,將位置信息列表存入分布式鍵值數據庫
      this.kvStoreModel.put(CommonConstants.CHANGE_POSITION, JSON.stringify(this.positionList));
    }
  }
  ...
}

點擊撤銷按鈕時,從位置列表中后序遍歷移除位置信息,直到找到軌跡的初始位置,完成移除上一次繪制的軌跡。移除完成后將位置信息列表存入分布式鍵值數據庫中。執行redraw()方法,清空畫板上的內容,遍歷位置信息列表,重新繪制。

// Index.ets
let storage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Index {
  ...
  @LocalStorageProp('positionList') positionList: Position[] = [];
  ...
  build() {
    Column() {
      Row() {
        // 撤銷按鈕
        Image($r('app.media.ic_back'))
          .width($r('app.float.ic_back_width'))
          .height($r('app.float.ic_back_height'))
          .margin({ left: CommonConstants.ICON_MARGIN_LEFT })
          .onClick(() = > {
            this.goBack();
          })
        ...
      }
      .width(CommonConstants.FULL_PERCENT)
      .height(CommonConstants.TITLE_HEIGHT)
      ...
  }

  ...
  redraw(): void {
    // 刪除畫布內的繪制內容
    this.canvasContext.clearRect(0, 0, this.canvasContext.width, this.canvasContext.height);
    // 使用當前記錄的位置信息,重新繪制
    this.positionList.forEach((position) = > {
      ...
      if (position.isFirstPosition) {
        this.canvasContext.beginPath();
        this.canvasContext.lineWidth = CommonConstants.CANVAS_LINE_WIDTH;
        this.canvasContext.lineJoin = CommonConstants.CANVAS_LINE_JOIN;
        this.canvasContext.moveTo(position.positionX, position.positionY);
      } else {
        this.canvasContext.lineTo(position.positionX, position.positionY);
        if (position.isEndPosition) {
          this.canvasContext.stroke();
        }
      }
    });
  }


  // 撤回上一筆繪制
  goBack(): void {
    if (this.positionList.length === 0) {
      return;
    }
    // 移除位置信息直到位置起始位置
    for (let i: number = this.positionList.length - 1; i >= 0; i--) {
      let position: Position | undefined = this.positionList.pop();
      if (position !== undefined && position.isFirstPosition) {
        break;
      }
    }
    this.redraw();
    this.kvStoreModel.put(CommonConstants.CHANGE_POSITION, JSON.stringify(this.positionList));
  }
  ...
}

分布式鍵值數據庫

使用分布式鍵值數據庫需申請數據交換權限:ohos.permission.DISTRIBUTED_DATASYNC。

應用啟動時創建分布式鍵值數據庫,設置數據庫數據改變監聽。數據改變時執行回調,獲取插入或更新數據列表,遍歷列表,匹配位置信息列表的設置key,更新位置列表后重新繪制。

// Index.ets
...
import KvStoreModel from '../viewmodel/KvStoreModel';
...
let storage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Index {
  ...
  private kvStoreModel: KvStoreModel = new KvStoreModel();
  ...
  aboutToAppear() {
    ...
    this.createKVStore();
  }

  ...
  createKVStore(): void {
    // 創建分布式鍵值數據庫
    this.kvStoreModel.createKvStore(this.context, (data: distributedKVStore.ChangeNotification) = > {
      // 使用分布式鍵值數據庫內的內容重置位置信息列表
      this.positionList = [];
      let entries: distributedKVStore.Entry[] = data.insertEntries.length > 0 ? data.insertEntries : data.updateEntries;
      entries.forEach((entry: distributedKVStore.Entry) = > {
        if (CommonConstants.CHANGE_POSITION === entry.key) {
          this.positionList = JSON.parse((entry.value.value) as string);
          // 位置信息列表更新后,重新繪制
          this.redraw();
        }
      });
    });
  }
  ...
}

創建分布式鍵值數據庫。設置數據庫類型為KVStoreType.SINGLE_VERSION單版本數據庫,其他配置參考[創建數據庫配置信息]。創建數據庫成功后,調用enableSync()方法開啟同步,調用setDataChangeListener()方法訂閱數據變更通知。

// KvStoreModel.ets
export default class KvStoreModel {
  ...
  kvStore?: distributedKVStore.SingleKVStore;
  ...
  createKvStore(
    context: common.UIAbilityContext,
    callback: (data: distributedKVStore.ChangeNotification) = > void
  ): void {
    ...
    try {
      // 創建一個KVManager對象實例,用于管理數據庫對象
      this.kvManager = distributedKVStore.createKVManager(config);
    } catch (error) {
      Logger.error('KvStoreModel',
        `createKvStore createKVManager failed, err=${JSON.stringify(error)}`);
      return;
    }

    // 創建數據庫的配置信息
    let options: distributedKVStore.Options = {
      ...
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION
      ...
    };

    // 獲取分布式鍵值數據庫
    this.kvManager.getKVStore(CommonConstants.KVSTORE_ID, options).then((store: distributedKVStore.SingleKVStore) = > {
      ...
      this.kvStore = store;
      // 開啟同步
      this.kvStore.enableSync(true).then(() = > {
        Logger.info('KvStoreModel', 'createKvStore enableSync success');
      }).catch((error: Error) = > {
        Logger.error('KvStoreModel',
          `createKvStore enableSync fail, error=${JSON.stringify(error)}`);
      });
      this.setDataChangeListener(callback);
    }).catch((error: Error) = > {
      Logger.error('getKVStore',
        `createKvStore getKVStore failed, error=${JSON.stringify(error)}`);
    })
  }
  ...
}

訂閱數據變更通知。創建分布式鍵值數據庫,設置數據變更訂閱,訂閱類型為全部,當更新數據集或插入數據集大于0時,執行傳入的callback()方法。

// KvStoreModel.ets
export default class KvStoreModel {
  ...
  kvStore?: distributedKVStore.SingleKVStore;
  ...
  setDataChangeListener(callback: (data: distributedKVStore.ChangeNotification) = > void): void {
    ...
    try {
      // 訂閱數據變更通知
      this.kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
        (data: distributedKVStore.ChangeNotification) = > {
          if ((data.updateEntries.length > 0) || (data.insertEntries.length > 0)) {
            callback(data);
          }
        });
    } catch (error) {
      Logger.error('KvStoreModel',
        `setDataChangeListener on('dataChange') failed, err=${JSON.stringify(error)}`);
    }
  }
  ...
}

應用退出時,分布式鍵值數據庫取消數據改變監聽。

// Index.ets
...
import KvStoreModel from '../viewmodel/KvStoreModel';
...
let storage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Index {
  ...
  private kvStoreModel: KvStoreModel = new KvStoreModel();
  ...
  aboutToDisappear() {
    this.kvStoreModel.removeDataChangeListener();
  }
  ...
}

// KvStoreModel.ets
export default class KvStoreModel {
  ...
  kvStore?: distributedKVStore.SingleKVStore;
  ...
  removeDataChangeListener(): void {
    ...
    try {
      // 取消數據改變監聽
      this.kvStore.off('dataChange');
    } catch (error) {
      Logger.error('KvStoreModel',
        `removeDataChangeListener off('dataChange') failed, err=${JSON.stringify(error)}`);
    }
  }
  ...
}

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 分布式
    +關注

    關注

    1

    文章

    780

    瀏覽量

    74217
  • HarmonyOS
    +關注

    關注

    79

    文章

    1912

    瀏覽量

    29449
  • OpenHarmony
    +關注

    關注

    24

    文章

    3442

    瀏覽量

    15289
  • 鴻蒙OS
    +關注

    關注

    0

    文章

    189

    瀏覽量

    4303
收藏 人收藏

    評論

    相關推薦

    手寫板

    手寫板不錯的資料,值得學習
    發表于 07-15 21:48

    HarmonyOS應用開發-分布式任務調度

    1. 介紹本篇CodeLab將實現的內容HarmonyOS是面向全場景多終端的分布式操作系統,使得應用程序的開發打破了智能終端互通的性能和數據壁壘,業務邏輯原子化開發,適配多端。通過一
    發表于 09-18 09:21

    HarmonyOS應用開發-分布式設計

    設計理念HarmonyOS 是面向未來全場景智慧生活方式的分布式操作系統。對消費者而言,HarmonyOS 將生活場景中的各類終端進行能力整合,形成“One Super Device”,以實現
    發表于 09-22 17:11

    HarmonyOS分布式數據庫,為啥這么牛?

    HarmonyOS 2.0 重要的三大核心技術底座之一:HarmonyOS 分布式數據管理平臺,也同步對開發者進行了細致的宣講,我作為開發
    發表于 11-19 15:38

    HarmonyOS分布式——跨設備遷移

    HarmonyOS分布式——跨設備遷移
    發表于 06-26 14:34

    HarmonyOS實戰—基于分布式能力,實現多設備同步書寫互動

    ,第三臺書寫筆跡是紅色(每臺設備的畫筆初始化時都會隨機一種顏色),每臺設備筆跡都會同步到連接的設備上顯示。圖3 分布式手寫板演示圖2. 搭建HarmonyOS環境我們首先需要完成Harmony
    發表于 09-03 17:14

    【福利加“碼”】鴻蒙線上Codelabs系列挑戰賽第三期:挑戰HarmonyOS分布式趣味應用

    DevEco studio下載地址為了幫助你更好的完成挑戰,你可以參考官方Codelabs案例:分布式鑒權、分布式手寫板、分布式親子早教系統官方Codelabs集合 活 動 預 告
    發表于 10-20 14:58

    HarmonyOS分布式應用框架深入解讀

    設備、分布式的能力及應用,二者具有無限能力。從開發者角度看,HarmonyOS上基本的組件分為3+1,其中3代表三個Ability,分別是:PageAbility:負責用戶界面的顯示
    發表于 11-22 15:15

    HDC2021技術分論壇:如何高效完成HarmonyOS分布式應用測試?

    作者:liuxun,HarmonyOS測試架構師HarmonyOS是新一代的智能終端操作系統,給開發者提供了設備發現、設備連接、跨設備調用等豐富的分布式API。隨著越來越多的
    發表于 12-13 14:55

    如何高效完成HarmonyOS分布式應用測試?

    作者:liuxun,HarmonyOS測試架構師HarmonyOS是新一代的智能終端操作系統,給開發者提供了設備發現、設備連接、跨設備調用等豐富的分布式API。隨著越來越多的
    發表于 12-13 18:07

    基于OpenHarmony3.1開發的一個分布式手寫板應用

    1.介紹基于TS擴展的聲明開發范式開發一個分布式手寫板應用。涉及的OS特性有分布式拉起和
    發表于 04-07 11:42

    HarmonyOS應用開發-EducationSystem分布式親子早教系統體驗

    HarmonyOS應用程序開發,多屏協作交互和分布式跨設備傳輸的經驗。 ? 從項目創建、代碼編寫到編譯、構造、部署和操作。二、效果圖:完整代碼地址:https://gitee.com/jltfcloudcn/jump_to/tr
    發表于 07-25 10:23

    HarmonyOS應用開發-分布式語音攝像頭體驗

    一、組件說明使用HarmonyOS分布式文件系統和AI語音識別功能開發了一個分布式語音攝像頭。使用此相機應用程序,同一分布式網絡下的不同設備
    發表于 08-24 15:06

    手寫板購買指南

    手寫板購買指南 手寫板簡介 如何
    發表于 07-28 08:21 ?1480次閱讀

    如何選購手寫板

    如何選購手寫板 家用或繪畫愛好者使用的手寫板價格從不足700元至1750元不等,而專業手寫板的價格則在1400元左右至5250元之間乃至更高。帶有筆感應式數位屏的產品價格區
    發表于 07-28 08:22 ?1461次閱讀
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>