diff --git a/zh-cn/third-party-cases/distributed-canvas.md b/zh-cn/third-party-cases/distributed-canvas.md new file mode 100644 index 0000000000000000000000000000000000000000..911ba45da6ba3395f434e7f6a338d2ace302fdbd --- /dev/null +++ b/zh-cn/third-party-cases/distributed-canvas.md @@ -0,0 +1,417 @@ +# 分布式画布流转场景 + +## 场景说明 + +两台设备组网,当其中一个设备修改文件时,两个设备可以同步修改的结果。分布式场景可以在协同办公(如多人多设备编辑同一文件),设备文档更新(分布式设备更新文件内容,所有设备同步更新)中发挥重要作用,有助于加快工作效率,减少工作中的冗余。 + +本示例将为大家介绍如何实现上述功能。 + +## 效果呈现 + +本例效果如下: + +| 设置分布式权限 | 进行分布式连接 | 连接后状态显示 | +| -------------------------------------- | ------------------------------------------ | ----------------------------------------- | +| ![](figures/disributed_permission.png) | ![](figures/disributed_canvas_connect.png) | ![](figures/disributed_canvas-before.png) | + +| 点击rect和ellipse按钮后后本机显示 | 另外一台机器分布式应用显示 | +| ---------------------------------------- | ---------------------------------------- | +| ![](figures/disributed_canvas-after.png) | ![](figures/disributed_canvas-after.png) | + +## 运行环境 + +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 + +- IDE:DevEco Studio 4.0.0.201 Beta1 +- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) + +## 实现思路 + +在分布式文件场景中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。 +首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。 + +- 分布式设备搜索:通过SUBSCRIBE_ID搜索分布式组网内的设备。 + +- 分布式设备列表弹窗:使用@CustomDialog装饰器来装饰分布式设备列表弹窗。 + +- 远端设备拉起:通过startAbility(deviceId)方法拉起远端设备的包。 + +- 分布式数据管理:(1)管理分布式数据库:创建一个distributedObject分布式数据对象实例,用于管理分布式数据对象。 + + ​ (2)订阅分布式数据变化:通过this.distributedObject.on('status', this.statusCallback)监听分布式数据对象的变更。 + +## 开发步骤 + +1. 申请所需权限 + + 在model.json5中添加以下配置: + + ```json + "requestPermissions": [ + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换 + }, + { + "name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力 + } + ] + ``` + +2. 构建UI框架 + + indexCanvas页面: + + TitleBar组件呈现标题栏。通过数据懒加载的方式遍历绘制的图形。被划出可视区域外的资源会被回收。 + + 绘制ellipse图形、rect图形的按钮使用Button组件呈现。 + + 返回按钮、删除按钮也通过Button组件呈现。 + + ```typescript + 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方法通知数据已经准备就绪。 + + ```typescript + //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以扫描获得附近可以连接的设备。 + + ```typescript + 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接口,通过注册数据监听接口监听数据的变化。 + + ```typescript + 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示例链接:[分布式对象](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote) + +## 参考 + +[权限列表](../application-dev/security/permission-list.md#ohospermissiondistributed_datasync) + +[Path2D对象](../application-dev/reference/arkui-ts/ts-components-canvas-path2d.md) + +[分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md) diff --git a/zh-cn/third-party-cases/distributed-file.md b/zh-cn/third-party-cases/distributed-file.md new file mode 100644 index 0000000000000000000000000000000000000000..2cef7070e7aa4479d5b4c69639e91d06d2ad1f4f --- /dev/null +++ b/zh-cn/third-party-cases/distributed-file.md @@ -0,0 +1,482 @@ +# 分布式文件场景 + +## 场景说明 + +两台设备组网的分布式场景是工作中常常需要的。常见的如代码的同步编辑、文档的同步修改等。这样的分布式场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。 + +## 效果呈现 + +本例效果如下: + +| 设置分布式权限 | 进行分布式连接 | 连接后状态显示 | +| -------------------------------------- | ---------------------------------------- | --------------------------------------- | +| ![](figures/disributed_permission.png) | ![](figures/disributed_note_connect.png) | ![](figures/disributed_note-before.png) | + +| 点击添加进入编辑界面 | 保存后本机显示 | 另外一台机器分布式应用显示 | +| ------------------------------------- | -------------------------------------- | -------------------------------------- | +| ![](figures/disributed_note-edit.png) | ![](figures/disributed_note-after.png) | ![](figures/disributed_note-after.png) | + +## 运行环境 + +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 + +- IDE:DevEco Studio 4.0.0.201 Beta1 +- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) + +## 实现思路 + +在分布式文件场景中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。 +首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。 + +- 分布式设备搜索:通过SUBSCRIBE_ID搜索分布式组网内的设备。 + +- 分布式设备列表弹窗:使用@CustomDialog装饰器来装饰分布式设备列表弹窗。 + +- 远端设备拉起:通过startAbility(deviceId)方法拉起远端设备的包。 + +- 分布式数据管理:(1)管理分布式数据库:创建一个distributedObject分布式数据对象实例,用于管理分布式数据对象。 + + ​ (2)订阅分布式数据变化:通过this.distributedObject.on('status', this.statusCallback)监听分布式数据对象的变更。 + +## 开发步骤 + +1. 申请所需权限 + + 在model.json5中添加以下配置: + + ```json + "requestPermissions": [ + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换 + }, + { + "name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力 + } + ] + ``` + +2. 构建UI框架 + + index页面: + + TitleBar组件呈现标题栏。使用List组件呈现文件列表,ListItem由一个呈现文件类型标志的Image组件,一个呈现文件标题的Text组件,一个呈现文件内容的Text组件组成。 + + ```typescript + 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) + //通过数据懒加载的方式从数据源中每次迭代一个文件进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收 + List({ space: 10 }) { + LazyForEach(this.noteDataSource, (item: Note, index) => { + ListItem() { + NoteItem({ note: item, index: index })//NoteItem引入自common/NoteItem.ets,负责主页文件信息的呈现 + .id(`${item.title}`) + } + }, item => JSON.stringify(item)) + } + .width('95%') + .margin(10) + .layoutWeight(1) + + Row() { + Column() { + Image($r('app.media.clear'))//清除按钮 + .size({ width: 40, height: 40 }) + Text($r('app.string.clear')) + .fontColor(Color.Red) + .fontSize(20) + }.layoutWeight(1) + .id('clearNote') + .onClick(() => { + //点击清除按钮清除所有文件 + Logger.info(TAG, 'clear notes') + this.noteDataSource['dataArray'] = [] + this.noteDataSource.notifyDataReload() + this.globalObject.clear() + AppStorage.SetOrCreate('sessionId', this.sessionId) + }) + + Column() { + Image($r('app.media.add'))//添加按钮 + .size({ width: 40, height: 40 }) + Text($r('app.string.add')) + .fontColor(Color.Black) + .fontSize(20) + }.layoutWeight(1) + .id('addNote') + .onClick(() => { + //点击添加按钮跳转到编辑页面 + router.push({ + url: 'pages/Edit', + params: { + note: new Note('', '', -1), + isAdd: true + } + }) + }) + } + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + } + .width('100%') + .height('100%') + .backgroundColor('#F5F5F5') + } + } + ... + //common/NoteItem.ets + import router from '@ohos.router' + import { MARKS } from '../model/Const' + import Note from '../model/Note' + + @Component + export default struct NoteItem { + @State note: Note | undefined = undefined + private index: number = 0 + + build() { + Row() { + Image(this.note.mark >= 0 ? MARKS[this.note.mark] : $r('app.media.note'))//文件标志图片 + .size({ width: 30, height: 30 }) + .objectFit(ImageFit.Contain) + Column() { + Text(this.note.title)//文件标题 + .fontColor(Color.Black) + .fontSize(30) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + Text(this.note.content)//文件内容 + .fontColor(Color.Gray) + .margin({ top: 10 }) + .fontSize(25) + .maxLines(1)//在列表中最多展示一行 + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .alignItems(HorizontalAlign.Start) + .margin({ left: 20 }) + } + .padding(16) + .width('100%') + .borderRadius(16) + .backgroundColor(Color.White) + .onClick(() => { + //点击文件进入此文件编辑页面 + router.push({ + url: 'pages/Edit', + params: { + index: this.index, + note: this.note, + isAdd: false + } + }) + }) + } + } + ``` + + Edit页面: + + 使用TextInput组件呈现文件标题输入框,使用TextArea组件呈现文件内容的输入区域,使用Button组件呈现保存按钮并绑定点击事件以新建或更新文件内容。 + + ```typescript + build() { + Column() { + TitleBar({ title: this.note.title === '' ? $r('app.string.add_note') : this.note.title }) + Column() { + Row() { + Image(this.note.mark >= 0 ? MARKS[this.note.mark] : $r('app.media.mark')) + .width(30) + .aspectRatio(1) + .margin({ left: 16, top: 16 }) + .objectFit(ImageFit.Contain) + .alignSelf(ItemAlign.Start) + Select([{ value: ' ', icon: MARKS[0] }, + { value: ' ', icon: MARKS[1] }, + { value: ' ', icon: MARKS[2] }, + { value: ' ', icon: MARKS[3] }, + { value: ' ', icon: MARKS[4] }]) + .selected(this.note.mark) + .margin({ top: 5 }) + .onSelect((index: number) => { + this.note.mark = index + }) + } + .width('100%') + + TextInput({ placeholder: 'input the title', text: this.note.title })//文件标题输入框 + .id('titleInput') + .placeholderColor(Color.Gray) + .fontSize(30) + .margin({ left: 15, right: 15, top: 15 }) + .height(60) + .backgroundColor(Color.White) + .onChange((value: string) => { + this.note.title = value + }) + TextArea({ placeholder: 'input the content', text: this.note.content })//文件内容输入区域 + .id('contentInput') + .placeholderColor(Color.Gray) + .backgroundColor(Color.White) + .fontSize(30) + .height('35%') + .margin({ left: 16, right: 16, top: 16 }) + .textAlign(TextAlign.Start) + .onChange((value: string) => { + this.note.content = value + }) + + Button() { + //保存按钮 + Text($r('app.string.save')) + .fontColor(Color.White) + .fontSize(17) + } + .id('saveNote') + .backgroundColor('#0D9FFB') + .height(50) + .width(200) + .margin({ top: 20 }) + .onClick(() => { + //点击按钮时调用model/DistributedObjectModel.ts定义的类globalObject中的方法 + if (!this.isAdd) { + let index = router.getParams()['index'] + this.globalObject.update(index, this.note.title, this.note.content, this.note.mark)//编辑时更新内容 + } else { + this.globalObject.add(this.note.title, this.note.content, this.note.mark)//新建时添加内容 + } + router.back()//返回主页 + }) + } + } + .width('100%') + .height('100%') + .backgroundColor('#F5F5F5') + } + } + ``` + +3. 将两台设备组网 + + 使用自RemoteDeviceModel.ts中引入的类RemoteDeviceModel以扫描获得附近可以连接的设备。 + + ```typescript + 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}`) + } + + } + ``` + +4. 实现同步编辑 + + 通过AppStorage设置持久性数据,然后实现IDataSource接口,通过注册数据监听接口监听数据的变化。 + + ```typescript + 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) + } + } + //数据准备好了 + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded() + }) + } + ... + } + + 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示例链接:[分布式对象](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote) + +## 参考 + +- [权限列表](../application-dev/security/permission-list.md#ohospermissiondistributed_datasync) +- [分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md) diff --git a/zh-cn/third-party-cases/figures/bluetooth_delete.png b/zh-cn/third-party-cases/figures/bluetooth_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..60e73b525e27c0eb73e3f437e749bc96eff16943 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_delete.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_delete_en.png b/zh-cn/third-party-cases/figures/bluetooth_delete_en.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b040c7cd39ba4162feb5f4da81dbc0e64af474 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_delete_en.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_dialog.png b/zh-cn/third-party-cases/figures/bluetooth_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..38ab4a29a780b6baba25d353a170c0f74743b668 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_dialog.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_dialog_en.png b/zh-cn/third-party-cases/figures/bluetooth_dialog_en.png new file mode 100644 index 0000000000000000000000000000000000000000..63857c729802e00d4c3cc65ac236aa2ff34ce607 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_dialog_en.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_list.png b/zh-cn/third-party-cases/figures/bluetooth_list.png new file mode 100644 index 0000000000000000000000000000000000000000..6edbda56e6cf01a45d055f995169ccf6e9463cbb Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_list.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_list_en.png b/zh-cn/third-party-cases/figures/bluetooth_list_en.png new file mode 100644 index 0000000000000000000000000000000000000000..4df05c5c4aee9216e0a0b5d2c0d7164fb18949be Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_list_en.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_match.png b/zh-cn/third-party-cases/figures/bluetooth_match.png new file mode 100644 index 0000000000000000000000000000000000000000..1daa1043afbdc2648c73675aba365722dc3df2b1 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_match.png differ diff --git a/zh-cn/third-party-cases/figures/bluetooth_match_en.png b/zh-cn/third-party-cases/figures/bluetooth_match_en.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb0101c6dd18298dbd1f94c4c56af4bc6c5da12 Binary files /dev/null and b/zh-cn/third-party-cases/figures/bluetooth_match_en.png differ diff --git a/zh-cn/third-party-cases/figures/camera.png b/zh-cn/third-party-cases/figures/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..245a0585d523889ba1f4411a1593176160ff80be Binary files /dev/null and b/zh-cn/third-party-cases/figures/camera.png differ diff --git a/zh-cn/third-party-cases/figures/camerapreview.gif b/zh-cn/third-party-cases/figures/camerapreview.gif new file mode 100644 index 0000000000000000000000000000000000000000..1e1f06dac46a9386f941d4701e5143fd0388ab1c Binary files /dev/null and b/zh-cn/third-party-cases/figures/camerapreview.gif differ diff --git a/zh-cn/third-party-cases/figures/connect.png b/zh-cn/third-party-cases/figures/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..18be004d104f1672e42af11186eecc7586333a41 Binary files /dev/null and b/zh-cn/third-party-cases/figures/connect.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_canvas-after.png b/zh-cn/third-party-cases/figures/disributed_canvas-after.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc98d9f9f596af7150c173f9577a30f1f5ee1ff Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_canvas-after.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_canvas-before.png b/zh-cn/third-party-cases/figures/disributed_canvas-before.png new file mode 100644 index 0000000000000000000000000000000000000000..e6a17222c21405ee2a191946af81c5429fa821e7 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_canvas-before.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_canvas_connect.png b/zh-cn/third-party-cases/figures/disributed_canvas_connect.png new file mode 100644 index 0000000000000000000000000000000000000000..249fae29cd541d0bae0150448eed500d70fdaf06 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_canvas_connect.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_note-after.png b/zh-cn/third-party-cases/figures/disributed_note-after.png new file mode 100644 index 0000000000000000000000000000000000000000..9eef39c0351577a06a15be085bca058051bc8012 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_note-after.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_note-before.png b/zh-cn/third-party-cases/figures/disributed_note-before.png new file mode 100644 index 0000000000000000000000000000000000000000..5b863b7a491a6a3671815637c90b13830043cc7e Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_note-before.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_note-edit.png b/zh-cn/third-party-cases/figures/disributed_note-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..585a7084b4a95f63a08aaa2ac8a1a783c4727602 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_note-edit.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_note_connect.png b/zh-cn/third-party-cases/figures/disributed_note_connect.png new file mode 100644 index 0000000000000000000000000000000000000000..a983cf00ca81f7280c84bb1e05d03494a5b76df4 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_note_connect.png differ diff --git a/zh-cn/third-party-cases/figures/disributed_permission.png b/zh-cn/third-party-cases/figures/disributed_permission.png new file mode 100644 index 0000000000000000000000000000000000000000..8d86371911f97124694b2555b1dd6eee975a9147 Binary files /dev/null and b/zh-cn/third-party-cases/figures/disributed_permission.png differ diff --git a/zh-cn/third-party-cases/figures/distributed.png b/zh-cn/third-party-cases/figures/distributed.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fb2e9bc8958a06521bf2befe5377f4a195776b Binary files /dev/null and b/zh-cn/third-party-cases/figures/distributed.png differ diff --git a/zh-cn/third-party-cases/figures/wlanconnect.png b/zh-cn/third-party-cases/figures/wlanconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1a604a564ad61652d907311a68dc15ecbf0e29 Binary files /dev/null and b/zh-cn/third-party-cases/figures/wlanconnect.png differ diff --git a/zh-cn/third-party-cases/figures/wlandisconnect.png b/zh-cn/third-party-cases/figures/wlandisconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..5050ec683cd16c0fc8a224924584f1facccf2906 Binary files /dev/null and b/zh-cn/third-party-cases/figures/wlandisconnect.png differ diff --git a/zh-cn/third-party-cases/figures/wlanmain.png b/zh-cn/third-party-cases/figures/wlanmain.png new file mode 100644 index 0000000000000000000000000000000000000000..c8454fc71d01b013dbb68c0579773d12278782a6 Binary files /dev/null and b/zh-cn/third-party-cases/figures/wlanmain.png differ diff --git a/zh-cn/third-party-cases/figures/wlanscan.png b/zh-cn/third-party-cases/figures/wlanscan.png new file mode 100644 index 0000000000000000000000000000000000000000..366799135770c2bb5fa3d93f67b251f304d42641 Binary files /dev/null and b/zh-cn/third-party-cases/figures/wlanscan.png differ diff --git a/zh-cn/third-party-cases/how-to-connect-to-bluetooth.md b/zh-cn/third-party-cases/how-to-connect-to-bluetooth.md new file mode 100644 index 0000000000000000000000000000000000000000..45b22ac7716edab37e44b46b6ed54cc39b7e7fd6 --- /dev/null +++ b/zh-cn/third-party-cases/how-to-connect-to-bluetooth.md @@ -0,0 +1,413 @@ +# 如何进行蓝牙连接 + +## 场景说明 +蓝牙技术是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的连接。本示例通过[@ohos.bluetoothManager](../application-dev/reference/apis/js-apis-bluetoothManager.md)接口实现蓝牙设备发现、配对、取消配对功能。 + +## 效果呈现 + +本示例最终效果如下: + +| 发现设备 | 连接设备 | 断开连接 | +| ------------------------------- | --------------------------------- | --------------------------------- | +| ![](figures/bluetooth_list.png) | ![](figures/bluetooth_dialog.png) | ![](figures/bluetooth_delete.png) | + +## 运行环境 + +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 + +- IDE:DevEco Studio 3.1.1 Release +- SDK:Ohos_sdk_full 4.0.8.5(API Version 10 Beta1) + +## 实现思路 +本文涉及到蓝牙的设备发现、配对、取消配对三个功能特性,实现思路如下: + +- 启动和关闭蓝牙:在Index页面中通过Toggle组件的onChange函数控制蓝牙的开关,开关打开的情况下执行initBluetooth函数,关闭的情况下执行bluetooth.disableBluetooth()方法来断开蓝牙; +- 验证蓝牙是否处于连接状态:蓝牙打开的时候通过bluetooth.on('stateChange')方法监听蓝牙连接状态改变事件,如确认已打开,执行foundDevices()函数来查找设备接口,确认已关闭则执行bluetooth.stopBluetoothDiscovery()方法停止查询接口。 +- 发现设备:在foundDevices()函数中通过bluetooth.on('bluetoothDeviceFind')方法监听设备发现事件,通过bluetooth.getPairedDevices()方法更新已配对蓝牙地址,然后通过bluetooth.startBluetoothDiscovery()方法开启蓝牙扫描发现远端设备,并且通过bluetooth.setBluetoothScanMode()方法来被远端设备发现。 +- 蓝牙配对:通过bluetooth.on('pinRequired')方法监听远端蓝牙设备的配对请求事件,点击配对执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)方法,点击取消执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)方法。 +- 取消配对:使用bluetooth.cancelPairedDevice()断开指定的远端设备连接。 + +## 开发步骤 +1. 申请蓝牙权限。 + 使用蓝牙需要先申请对应的权限,在module.json5文件中添加以下配置: + + ```json + "requestPermissions": [ + { + //允许应用查看蓝牙的配置 + "name": "ohos.permission.USE_BLUETOOTH", + "reason": "$string:grant_use_bluetooth", + "usedScene": { + "abilities": [ + "MainAbility" + ], + "when": "inuse" + } + }, + { + //允许应用配置本地蓝牙,查找远端设备且与之配对连接 + "name": "ohos.permission.DISCOVER_BLUETOOTH", + "reason": "$string:grant_discovery_bluetooth", + "usedScene": { + "abilities": [ + "MainAbility" + ], + "when": "inuse" + } + }, + { + //允许应用获取设备位置信息 + "name": "ohos.permission.LOCATION", + "reason": "$string:grant_location", + "usedScene": { + "abilities": [ + "MainAbility" + ], + "when": "inuse" + } + }, + { + //允许应用获取设备模糊位置信息 + "name": "ohos.permission.APPROXIMATELY_LOCATION", + "reason": "$string:grant_location", + "usedScene": { + "abilities": [ + "MainAbility" + ], + "when": "inuse" + } + }, + { + //允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问 + "name": "ohos.permission.MANAGE_BLUETOOTH", + "reason": "$string:grant_manage_bluetooth", + "usedScene": { + "abilities": [ + "MainAbility" + ], + "when": "inuse" + } + } + ] + ``` + +2. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。 + + ```ts + //Common/TitleBar.ets + @Component + export struct TitleBar { + private handlerClickButton: () => void + + build() { + Row() { + Image($r('app.media.ic_back')) + .width(40) + .height(30) + .onClick(() => { + this.handlerClickButton() + }) + Text($r('app.string.bluetooth')) + .fontSize(30) + .width(150) + .height(50) + .margin({ left: 15 }) + .fontColor('#ffa2a3a4') + } + .width('100%') + .height(60) + .padding({ left: 20, top: 10 }) + .backgroundColor('#ff2d30cb') + .constraintSize({ minHeight: 50 }) + } + } + + //Common/PinDalog.ets + ... + aboutToAppear() { + this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。` + this.pinCode = JSON.stringify(this.data.pinCode) + } + build() { + //配对弹框描述文字 + Column({ space: 10 }) { + Text($r('app.string.match_request')) + .fontSize(30) + .alignSelf(ItemAlign.Start) + Text(this.titleText) + .alignSelf(ItemAlign.Start) + .margin({ top: 20 }) + .fontSize(21) + Text(this.pinCode) + .fontSize(40) + .fontWeight(FontWeight.Bold) + .margin({ top: 20 }) + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox' }) + .select(false) + .selectedColor('#ff3d6fb8') + .key('checkBox') + Text($r('app.string.grant_permission')) + .fontSize(15) + .margin({ left: 3, top: 6 }) + } + .alignSelf(ItemAlign.Start) + .width('95%') + .margin({ top: 5 }) + + Row() { + //配对选择UI,取消or配对 + this.choiceText($r('app.string.cancel'), () => { + bluetooth.setDevicePairingConfirmation(this.data.deviceId, false) + logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`) + this.controller.close() + }) + + Divider() + .vertical(true) + .height(32) + + this.choiceText($r('app.string.match'), () => { + bluetooth.setDevicePairingConfirmation(this.data.deviceId, true) + logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)}`) + this.controller.close() + }) + } + .margin({ top: 20 }) + } + .width('100%') + .padding(15) + } + ... + + //pages/index.ets + @Entry + @Component + struct Index { + build() { + Column() { + TitleBar({ handlerClickButton: this.handlerClickButton }) + Scroll() { + Column() { + Row() { + //蓝牙开关 + Column() { + Text($r('app.string.bluetooth')) + .fontSize(30) + .margin({ top: 20 }) + .alignSelf(ItemAlign.Start) + if (true === this.isOn) { + Text($r('app.string.discovery')) + .fontSize(20) + .alignSelf(ItemAlign.Start) + } + } + + Blank() + + Column() { + Toggle({ type: ToggleType.Switch, isOn: this.isOn }) + .selectedColor('#ff2982ea') + .onChange((isOn: boolean) => { + if (isOn) { + this.isOn = true + this.initBluetooth() + } else { + this.isOn = false + bluetooth.disableBluetooth() + this.deviceList = [] + this.discoveryList = [] + } + }) + } + .id('toggleBtn') + } + .width('90%') + + if (this.isOn) { + Divider() + .vertical(false) + .strokeWidth(10) + .color('#ffece7e7') + .lineCap(LineCapStyle.Butt) + .margin('1%') + //已配对的设备 + Text($r('app.string.paired_device')) + .fontSize(25) + .fontColor('#ff565555') + .margin({ left: '5%' }) + .alignSelf(ItemAlign.Start) + + ForEach(this.deviceList, (item, index) => { + Row() { + Text(item) + .fontSize(20) + } + .alignSelf(ItemAlign.Start) + .width('100%') + .height(50) + .margin({ left: '5%', top: '1%' }) + .id(`pairedDevice${index}`) + .onClick(() => { + AlertDialog.show({ + //取消配对 + title: $r('app.string.disconnect'), + message: '此操作将会断开您与以下设备的连接:' + item, + primaryButton: { + value: $r('app.string.cancel'), + action: () => { + } + }, + //确认取消 + secondaryButton: { + value: $r('app.string.confirm'), + action: () => { + try { + bluetooth.cancelPairedDevice(item); + this.deviceList = bluetooth.getPairedDevices() + this.discoveryList = [] + bluetooth.startBluetoothDiscovery() + } catch (err) { + console.error("errCode:" + err.code + ",errMessage:" + err.message); + } + } + } + }) + }) + }) + + Divider() + .vertical(false) + .strokeWidth(10) + .color('#ffece7e7') + .lineCap(LineCapStyle.Butt) + .margin('1%') + + Text($r('app.string.available_device')) + .fontSize(25) + .fontColor('#ff565555') + .margin({ left: '5%', bottom: '2%' }) + .alignSelf(ItemAlign.Start) + //可用设备列表 + ForEach(this.discoveryList, (item) => { + Row() { + Text(item) + .fontSize(20) + } + .alignSelf(ItemAlign.Start) + .width('100%') + .height(50) + .margin({ left: '5%', top: '1%' }) + //进行配对操作点击 + .onClick(() => { + logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`) + let pairStatus = bluetooth.pairDevice(item) + logger.info(TAG, `pairStatus = ${pairStatus}`) + }) + + Divider() + .vertical(false) + .color('#ffece7e7') + .lineCap(LineCapStyle.Butt) + .margin('1%') + }) + } + } + } + .constraintSize({ maxHeight: '85%' }) + } + } + } + ``` + +3. 蓝牙开关打开关闭操作,需要打开蓝牙开关才能进行后续操作: + ```ts + initBluetooth() { + bluetooth.on('stateChange', (data) => { + logger.info(TAG, `enter on stateChange`) + //判断蓝牙开关状态 + if (data === bluetooth.BluetoothState.STATE_ON) { + logger.info(TAG, `enter BluetoothState.STATE_ON`) + //蓝牙打开后的相关操作 + this.foundDevices() + } + if (data === bluetooth.BluetoothState.STATE_OFF) { + logger.info(TAG, `enter BluetoothState.STATE_OFF`) + bluetooth.off('bluetoothDeviceFind', (data) => { + logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`) + }) + //关闭 + bluetooth.stopBluetoothDiscovery() + this.discoveryList = [] + } + logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`) + }) + //开启蓝牙 + bluetooth.enableBluetooth() + } + ``` + +4. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表 + ```ts + foundDevices() { + //订阅蓝牙设备发现上报事件 + bluetooth.on('bluetoothDeviceFind', (data) => { + logger.info(TAG, `enter on bluetoothDeviceFind`) + if (data !== null && data.length > 0) { + if (this.discoveryList.indexOf(data[0]) === -1 && this.deviceList.indexOf(data[0]) === -1) { + this.discoveryList.push(data[0]) + } + logger.info(TAG, `discoveryList = ${JSON.stringify(this.discoveryList)}`) + } + let list = bluetooth.getPairedDevices() + if (list !== null && list.length > 0) { + this.deviceList = list + logger.info(TAG, `deviceList = ${JSON.stringify(this.deviceList)}`) + } + }) + //开启蓝牙扫描,可以发现远端设备 + bluetooth.startBluetoothDiscovery() + //设置蓝牙扫描模式,可以被远端设备发现 + bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME) + } + ``` + +5. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表 + + ```ts + //配对确定和取消代码在PinDialog.ets文件中 + //setDevicePairingConfirmation(device: string, accept: boolean): void + //device string 表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX + //accept boolean 接受配对请求设置为true,否则设置为false + + //订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表 + bluetooth.on('bondStateChange', (data) => { + logger.info(TAG, `enter bondStateChange`) + logger.info(TAG, `data = ${JSON.stringify(data)}`) + if (data.state === bluetooth.BondState.BOND_STATE_BONDED) { + logger.info(TAG, `BOND_STATE_BONDED`) + let index = this.discoveryList.indexOf(data.deviceId) + this.discoveryList.splice(index, 1) + this.deviceList = bluetooth.getPairedDevices() + } + if (data.state === bluetooth.BondState.BOND_STATE_INVALID) { + logger.info(TAG, `BOND_STATE_INVALID`) + this.deviceList = bluetooth.getPairedDevices() + } + logger.info(TAG, `bondStateChange,data = ${JSON.stringify(data)}`) + }) + ``` + +​ + +## 完整代码 + +本例完整代码sample示例链接:[蓝牙Sample](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Bluetooth) + + + +## 参考 +- [权限申请指导](../application-dev/security/accesstoken-guidelines.md) +- [@ohos.bluetooth](../application-dev/reference/apis/js-apis-bluetooth.md) + +​ \ No newline at end of file diff --git a/zh-cn/third-party-cases/take-picture-and-preview.md b/zh-cn/third-party-cases/take-picture-and-preview.md new file mode 100644 index 0000000000000000000000000000000000000000..f45e3fee25151ba9b519beb56fac2f5087ee4e2f --- /dev/null +++ b/zh-cn/third-party-cases/take-picture-and-preview.md @@ -0,0 +1,257 @@ +# 如何调用设备摄像头进行拍照、预览并将拍摄结果保存在媒体库中 + +## 场景说明 + +调用设备摄像头进行拍照、预览是许多应用开发过程中都需要的功能。在拍照完成时显示照片预览图可以确认拍摄的照片是否达到预期,本例将为大家介绍如何实现上述功能。 + +## 效果呈现 + +本例效果如下: + +| 拍照 | 预览 | +| :----------------------------------------------------------: | :----------------------------------------------------------: | +| contactlist | contactlist | + + + +## 运行环境 + +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 + +- IDE:DevEco Studio 4.0.0.201 Beta1 +- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) + +## 实现思路 + +本例使用@ohos.multimedia.camera接口实现相机示例的主要功能:拍照、预览; + +- 拍照:XComponent组件负责绘制摄像头画面呈现的窗口,其onload事件调用cameraModel.ts的initCamera方法初始化相机功能输出画面信息。拍照动作使用Image组件实现,其onclick事件调用cameraModel.ts的takepicture方法开始拍照。 + +- 预览:返回相机界面点击底部左侧预览图可进入相册应用,可以在其中查看照片和录制的视频。 + +## 开发步骤 + +1. 申请所需权限 + + 在model.json5中添加以下配置: + + ```json + "requestPermissions": [ + { + "name": "ohos.permission.CAMERA"//允许应用使用相机拍摄照片和录制视频 + }, + { + "name": "ohos.permission.MICROPHONE"//允许应用使用麦克风 + }, + { + "name": "ohos.permission.MEDIA_LOCATION"//允许应用访问用户媒体文件中的地理位置信息 + }, + { + "name": "ohos.permission.WRITE_MEDIA"//允许应用读写用户外部存储中的媒体文件信息 + }, + { + "name": "ohos.permission.READ_MEDIA"//允许应用读取用户外部存储中的媒体文件信息 + } + ] + ``` + +2. 创建绘制组件XComponent以输出摄像头获取的画面,其绑定的onload方法中设定了画幅的大小。 + + ```typescript + build() { + Column() { + Title() + .visibility(this.isTitleShow ? Visibility.Visible : Visibility.None) + Stack({ alignContent: Alignment.Bottom }) { + Stack({ alignContent: Alignment.TopStart }) { + XComponent({ + id: 'componentId', + type: 'surface', + controller: this.mXComponentController //将控制器绑定至XComponent组件 + }) + .onLoad(() => { + this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: 640, surfaceHeight: 480 });//设置surface大小 + this.surfaceId = this.mXComponentController.getXComponentSurfaceId(); + this.currentModel = CameraMode.modePhoto; + this.cameraModel.initCamera(this.surfaceId); //调用model/cameraModel.ts初始化相机功能 + }) + .width('100%') + .height('100%') + .margin({ bottom: 152 }) + Column() { + } + .width('97%') + .height('100%') + ``` + +3. 初始化相机功能 + + initCamera方法通过创建相机管理器实例cameraMgr来创建画面输出对象previewOutput。cameraMgr再通过创建CaptureSession实例来配置会话,完成相机功能的准备工作。 + + ```typescript + import image from '@ohos.multimedia.image';//自@ohos.multimedia.image引入image,提供图片处理效果 + ... + private receiver: image.ImageReceiver = undefined;//图像接收类,用于获取组件surface id,接收最新的图片和读取下一张图片 + ... + constructor() { + this.mediaModel = MediaModel.getMediaInstance();//通过调用model/MediaModel.ets中的方法创建mediaInstance类mediaModel + //创建ImageReceiver实例receiver + this.receiver = image.createImageReceiver( + cameraWH.width, + cameraWH.height, + FOUR, + EIGHT + ); + //接收图片时注册回调 + this.receiver.on('imageArrival', () => { + //从ImageReceiver读取下一张图片 + this.receiver.readNextImage((err, image) => { + if (err || image === undefined) { + return; + } + //根据图像的组件类型从图像中获取组件缓存 + image.getComponent(FOUR, (errMsg, img) => { + if (errMsg || img === undefined) { + return; + } + let buffer = new ArrayBuffer(FOUR_THOUSAND_AND_SIXTY_NINE); + if (img.byteBuffer) { + buffer = img.byteBuffer; + } + this.saveImage(buffer, image); + }); + }); + }); + } + + + async initCamera(surfaceId: string): Promise { + ... + try { + this.cameraMgr = camera.getCameraManager(globalThis.cameraContext);//获取相机管理器实例 + } + this.camerasArray = this.cameraMgr.getSupportedCameras();//获取支持指定的相机设备对象 + if (this.camerasArray.length === 0) { + return; + } + let mCamera = this.camerasArray[0]; + this.cameraInput = this.cameraMgr.createCameraInput(mCamera); + this.cameraInput.open(); + this.capability = this.cameraMgr.getSupportedOutputCapability(mCamera);//查询相机设备支持的输出能力 + let previewProfile = this.capability.previewProfiles[0]; + //通过相机管理器创建预览输出对象 + this.previewOutput = this.cameraMgr.createPreviewOutput( + previewProfile, + surfaceId //surfaceId从XComponent组件获取 + ); + let rSurfaceId = await this.receiver.getReceivingSurfaceId();//获取一个surface id供其他组件使用 + let photoProfile = this.capability.photoProfiles[0]; + //通过相机管理器创建照片输出对象 + this.photoOutPut = this.cameraMgr.createPhotoOutput( + photoProfile, + rSurfaceId //rSurfaceId通过构造函数中定义的图像接收类receiver获取 + ); + this.capSession = this.cameraMgr.createCaptureSession();//创建CaptureSession实例 + this.capSession.beginConfig();//开始配置会话 + this.capSession.addInput(this.cameraInput);//将cameraInput加入会话 + this.capSession.addOutput(this.previewOutput);//将预览输出加入会话 + this.capSession.addOutput(this.photoOutPut);//将照片输出加入会话 + await this.capSession.commitConfig();//提交配置信息 + await this.capSession.start();//开始输出 + } + + ``` + +4. 点击按钮进行拍照 + + 拍照按钮通过Image组件呈现,其绑定的onClick方法调用takePicture方法开始拍照。 + + ```typescript + Image(this.getCameraIcon()) + .size({ width: 64, height: 64 }) + .margin({ left: 10 }) + .id('camera') + .onClick(() => { + if (this.currentModel === CameraMode.modePhoto) { + prompt.showToast({ message: '拍照中...', duration: 200 }); + this.cameraModel.takePicture();//调用model/cameraModel.takePicture()开始拍照 + } + }) + ``` + +5. 拍照功能具体实现 + + - 拍照 + + ```typescript + async takePicture(): Promise { + //设置拍照相关参数 + let photoSettings = { + rotation: this.imageRotation, + quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM, + location: { + // 位置信息,经纬度 + latitude: 12.9698, + longitude: 77.75, + altitude: 1000, + }, + mirror: false, + }; + await this.photoOutPut.capture(photoSettings); + AppStorage.Set('isRefresh', true); + } + ``` + + - 保存图片 + + saveImage方法使用MediaModel中的createAndGetUri方法创建Image类型资源,将拍摄到的照片写入到这个资源中去。 + + ```typescript + //model/MediaModel.ts中定义的负责保存图片的相关方法 + async createAndGetUri(mediaType: mediaLibrary.MediaType): Promise { + let dateTimeUtil: DateTimeUtil = new DateTimeUtil(); + let info: FileInfo = this.getInfoFromMediaType(mediaType); + let name: string = `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`;//获取当前时间 + let displayName: string = `${info.prefix}${name}${info.suffix}`; + //获取公共目录路径。 + let publicPath: string = await this.mediaLibraryTest.getPublicDirectory( + info.directory + );//通过引用自@ohos.multimedia.mediaLibrary的mediaLibraryTest类创建媒体资源,其中定义了媒体类型、名称、路径。 + let fileAsset: mediaLibrary.FileAsset = await this.mediaLibraryTest.createAsset( + mediaType,//根据传入函数createAndGetUri的mediaType参数决定创建什么类型的媒体资源 + displayName, + publicPath + ); + return fileAsset; + } + async getFdPath(fileAsset: mediaLibrary.FileAsset): Promise { + let fd: number = await fileAsset.open('Rw');//打开当前文件 + return fd; + } + ... + + async saveImage(buffer: ArrayBuffer, img: image.Image): Promise { + this.fileAsset = await this.mediaModel.createAndGetUri(mediaLibrary.MediaType.IMAGE); + //通过调用MediaModel中的方法创建Image类型资源 + this.photoPath = this.fileAsset.uri; + this.fd = await this.mediaModel.getFdPath(this.fileAsset); + await fileIo.write(this.fd, buffer);//将拍摄的照片写入到Mediamodel传回的资源中去 + await this.fileAsset.close(this.fd);//释放open函数 + await img.release(); + if (this.takePictureHandle) { + this.takePictureHandle(this.photoPath); + } + } + ``` + +## 全部代码 + +本例完整代码sample示例链接:[相机](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Media/Camera) + +## 参考 + +- [权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync) +- [@ohos.multimedia.camera](../application-dev/reference/apis/js-apis-camera.md) + + + diff --git a/zh-cn/third-party-cases/wlan-search-connect-disconnect.md b/zh-cn/third-party-cases/wlan-search-connect-disconnect.md new file mode 100644 index 0000000000000000000000000000000000000000..8439befad413a6c6a9460f9b9eb5e60990935fb2 --- /dev/null +++ b/zh-cn/third-party-cases/wlan-search-connect-disconnect.md @@ -0,0 +1,448 @@ +# 如何实现WLAN网络扫描、连接、断开 + +## 场景说明 + +对可用的WLAN列表进行扫描,连接、断开WLAN网络是设备常见的功能,本例将为大家介绍如何实现上述功能。 + +## 效果呈现 + +本例效果如下: + +| 扫描WLAN | 连接WLAN | 断开WLAN | +| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | +| contactlist | contactlist | contactlist | + +## 运行环境 + +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。 + +- IDE:DevEco Studio 4.0.0.201 Beta1 +- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) + +## 实现思路 + +本例主要通过@ohos.wifiManager 相关API实现WLAN激活和关闭、扫描和连接WLAN等功能。 + +- WLAN功能的激活和关闭:点击首页的切换按钮,如果是打开,使用wifi.enableWifi()开启WLAN功能;如果是关闭,则使用wifi.disconnect()断开WLAN功能, 然后使用wifi.disableWifi()关闭WLAN。 +- WLAN的连接:点击WLAN列表中加密的WLAN,并在弹窗中输入密码后,会在AvailableWifi.ets中通过WifiModule.connectNetwork()调用wifi.connectToDevice()连接WLAN。 +- WLAN的扫描:进入Index.ets 后就会间歇性的调用wifi.scan()开启扫描,然后通过WifiModel模块中的getScanInfos()调用wifi.getScanResults()来获取扫描的结果. + +## 开发步骤 + +1. 申请所需权限 + + 在model.json5中添加以下配置: + + ```json + "requestPermissions": [ + { + "name": "ohos.permission.GET_WIFI_INFO"//允许应用获取WLAN信息 + }, + { + "name": "ohos.permission.GET_WIFI_INFO_INTERNAL"//允许应用获取WLAN信息 + }, + { + "name": "ohos.permission.SET_WIFI_INFO"//允许应用配置WLAN设备 + }, + { + "name": "ohos.permission.GET_WIFI_PEERS_MAC"//允许应用获取对端WLAN或者蓝牙设备的MAC地址 + }, + { + "name": "ohos.permission.GET_WIFI_LOCAL_MAC"//允许应用获取本机WLAN或者蓝牙设备的MAC地址 + }, + { + "name": "ohos.permission.GET_WIFI_CONFIG"//允许应用获取WLAN配置信息 + }, + { + "name": "ohos.permission.SET_WIFI_CONFIG"//允许应用配置WLAN信息 + }, + { + "name": "ohos.permission.MANAGE_WIFI_CONNECTION"//允许应用管理WLAN连接 + }, + { + "name": "ohos.permission.MANAGE_WIFI_HOTSPOT"//允许应用开启或者关闭WLAN热点 + }, + { + "name": "ohos.permission.LOCATION"//允许应用获取设备位置信息 + }, + { + "name": "ohos.permission.APPROXIMATELY_LOCATION"//允许应用获取设备模糊位置信息 + } + ] + ``` + +2. 准备工作 + + 定义boolean变量isSwitchOn作为WLAN开关是否打开的标志。 + + ```typescript + import wifi from '@ohos.wifiManager' + aboutToAppear() { + // 如果WLAN是开的,就记录下状态,然后扫描WLAN,并获取连接信息 + if (wifi.isWifiActive()) {//查询WLAN是否已使能 + Logger.log(TAG, 'wifi is active') + this.isSwitchOn = true//已使能说明WLAN开关已被打开,那么让isSwitchOn为true + wifi.scan()//@ohos.wifiManager的接口,对WLAN进行扫描 + this.scan()//自定义的scan函数,确保扫描的持续性 + this.getLinkedInfo() + } + // 启动监听 + this.addListener()//监听连接状态的变化 + } + ``` + +3. 持续地扫描可用WLAN + + scan方法不断地自我调用以实现对WLAN的不停扫描。 + + ```typescript + async getLinkedInfo() { + try { + //调用getLinkedInfo接口得到是否已连接WLAN的信息 + let wifiLinkedInfo = await wifi.getLinkedInfo() + if (wifiLinkedInfo === null || wifiLinkedInfo.bssid === '') { + //如果结果为空,则说明未连接 + this.isLinked = false + this.linkedInfo = null + return + } + this.isLinked = true + this.linkedInfo = wifiLinkedInfo + } catch (err) { + Logger.info(`getLinkedInfo failed err is ${JSON.stringify(err)}`) + } + } + async scan() { + // 获取有关WLAN连接的信息,存入linkedInfo + await this.getLinkedInfo() + // 不停地扫描WLAN + let result: Array = await this.wifiModel.getScanInfos()//调用model/WifiModel.ets中的getScanInfos得到扫描结果 + if (this.isSwitchOn) { + //如果WLAN功能已打开,则要进行不停地扫描 + AppStorage.SetOrCreate('wifiList', result)//将扫描结果存至全局 + //每3000毫秒调用一次scan(),实现不停扫描可用WLAN + setTimeout(async () => { + await this.scan() + }, 3000) + } + } + addListener() { + // WLAN连接状态改变事件发生时,修改连接信息 + wifi.on('wifiConnectionChange', async state => { + Logger.log(TAG, `wifiConnectionChange: ${state}`) + await this.getLinkedInfo()//WLAN连接状态改变,重新获取WLAN连接信息 + }) + // WLAN状态改变时,先清空WLAN列表,然后判断是否是开启状态,如果是就扫描 + wifi.on('wifiStateChange', state => { + Logger.log(TAG, `wifiStateLisener state: ${state}`) + AppStorage.SetOrCreate('wifiList', []) + if (state === 1) { // 1: wifi is enable, 0:wifi is disable + this.scan() + } + }) + } + ... + //model/WifiModel.ets + async getScanInfos(): Promise> { + Logger.log(TAG, 'scanWifi begin') + let wifiList: Array = [] + let result: Array = [] + try { + result = await wifi.getScanResults()//因为在abouttoappear()中执行了wifi.scan(),所以这里直接调用getScanResults接口得到扫描结果 + } catch (err) { + Logger.log(TAG, `scan info err: ${JSON.stringify(err)}`) + return wifiList + } + Logger.log(TAG, `scan info call back: ${result.length}`) + for (var i = 0; i < result.length; ++i) { + wifiList.push({ + ssid: result[i].ssid, + bssid: result[i].bssid, + securityType: result[i].securityType, + rssi: result[i].rssi, + band: result[i].band, + frequency: result[i].frequency, + timestamp: result[i].timestamp + }) + } + return wifiList + } + ``` + +4. 构建UI框架、通过关闭按钮实现断开WLAN连接。 + + TitleBar组件呈现了标题框。WLAN开关按钮由一个Toggle组件呈现,其绑定的onChange事件在按钮被打开时开始扫描可用WLAN,在被关闭时断开当前连接WLAN并且关闭WLAN功能。当前已连接的WLAN由两个Text组件分别显示其ssid与连接状态。 + + ```typescript + build() { + Column() { + TitleBar()//引入自component/TitleBar.ets,负责标题框的具体呈现 + Row() { + Text($r('app.string.wlan')) + .fontSize(22) + .fontWeight(FontWeight.Bold) + .height(40) + Column() { + Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })//切换按钮 + .id('switch') + .onChange((isOn: boolean) => { + Logger.log(`LSQ: wifi swtich is: ${isOn}`) + AppStorage.SetOrCreate('wifiList', []) + try { + // 如果按钮被打开了,记录状态,打开网络,开始扫描可用WLAN + if (isOn) { + this.isSwitchOn = true + wifi.enableWifi()//使WLAN功能可用 + return + } else { + // 如果按钮被关闭了记录状态,断开网络禁用网络 + this.isSwitchOn = false + this.isLinked = false + wifi.disconnect()//断开当前WLAN连接 + wifi.disableWifi()//使WLAN功能不可用 + } + } catch (error) { + Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`) + } + }) + } + } + .width('100%') + .padding({ left: 16, right: 16 }) + if (this.isLinked && this.isSwitchOn) { + //如果当前按钮处于打开状态,且WLAN是已连接状态 + Column() { + Text($r('app.string.connected')) + .fontSize(22) + .width('100%') + Row() { + Text(this.linkedInfo.ssid)//展示当前已连接的WLAN + .fontSize(20) + .fontColor(Color.Black) + .layoutWeight(1) + Text($r('app.string.connected')) + .fontSize(18) + .fontColor(Color.Black) + } + .width('100%') + .padding(10) + .margin({ left: 16, right: 16 }) + .border({ radius: 15, color: Color.Gray, width: 1 }) + .backgroundColor(Color.White) + } + .width('100%') + .padding({ left: 16, right: 16 }) + } + if (this.isSwitchOn) { + //如果按钮处于打开状态,展示所有可用WLAN + AvailableWifi({ linkedInfo: this.linkedInfo })//AvailableWifi引入自component/AvailableWifi.ets + } + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.index_bg')) + } + ``` + +5. 展示可用WLAN列表及WLAN的连接。 + + 使用List组件呈现可用WLAN列表,ListItem由一个呈现WLANssid的Text组件,一个表示其是否被加密的Text组件和一个展示其加密与否图像的Image组件组成。 + + ```typescript + //component/AvailableWifi.ets + build() { + List() { + ListItem() { + Row() { + Text($r('app.string.available_wlan')) + .fontSize(22) + .layoutWeight(1) + } + .id('validWlan') + .width('100%') + } + //通过数据懒加载的方式从数据源中每次迭代一个可用WLAN进行展示,可用列表被放置在滚动容器中,被划出可视区域外的资源会被回收 + LazyForEach(this.wifiDataResource, (item, index) => { + ListItem() { + WifiView({ wifi: item })//WifiView引入自Component/WifiView.ets,负责可用WLAN信息展示的具体实现 + } + .id(`Wifi${index}`) + //对可用WLAN点击时触发事件 + .onClick(() => { + Logger.info(TAG, 'wifi click') + this.scanInfo = item + if (this.linkedInfo !== null && item.ssid === this.linkedInfo.ssid) { + prompt.showToast({ message: 'this wifi is connected' })//如果当前已连接WLAN并且点击的就是这个WLAN,创建并显示文本提示框 + return + } + if (item.securityType === 0 || item.securityType === 1) { + //如果点击的这个WLAN的加密类型是无效加密类型或者开放加密类型,调用model/Wifimodel.ets中的connectNetwork方法连接此WLAN + this.wifiModel.connectNetwork(item, '') + return + } + //如果点击的这个WLAN的加密类型是其他加密类型,则需要输入密码,调用component/PswDialog.ets中的方法进行弹窗的生成 + this.pswDialogController.open() + }) + }, item => JSON.stringify(item)) + } + .width('100%') + .height('100%') + .padding({ left: 16, right: 16 }) + .layoutWeight(1) + .divider({ strokeWidth: 1, color: Color.Gray, startMargin: 10, endMargin: 10 }) + .margin({ top: 10 }) + } + //pswDialogController回调的action函数,将传回的WLAN信息与密码传入model/Wifimodel.ets中的connectNetwork方法中 + onAccept(scanInfo, psw) { + Logger.info(TAG, 'connect wifi') + self.wifiModel.connectNetwork(scanInfo, psw) + } + ... + //Component/WifiView.ets + aboutToAppear() { + Logger.debug(TAG, `aboutToAppear ${JSON.stringify(this.wifi)}`) + if (this.wifi) { + if (this.wifi.securityType) { + if (this.wifi.securityType === 0 || this.wifi.securityType === 1) { + //WLAN的加密类型是无效加密类型或者开放加密类型 + this.securityString = $r('app.string.open') + this.isLock = false + } + } + } + } + build() { + Row() { + Column() { + if (this.wifi) { + if (this.wifi.ssid) { + Text(this.wifi.ssid) + .fontSize(20) + .width('100%') + } + } + //如果WLAN的加密类型是无效加密类型或者开放加密类型,显示“开放”,反之显示“加密” + Text(this.securityString) + .fontSize(18) + .fontColor(Color.Gray) + .width('100%') + } + .layoutWeight(1) + + Stack({ alignContent: Alignment.BottomEnd }) { + //如果WLAN的加密类型是无效加密类型或者开放加密类型,显示不带锁的图标,反之显示带锁的图标 + Image($r('app.media.wifi')) + .height(30) + .width(30) + .objectFit(ImageFit.Contain) + if (this.isLock) { + Image($r('app.media.lock')) + .objectFit(ImageFit.Contain) + .width(15) + .height(15) + } + } + .width(40) + .height(40) + .margin({ right: 10 }) + } + .backgroundColor(Color.White) + .width('100%') + .padding(10) + } + } + ... + //model/Wifimodel.ets + connectNetwork(scanInfo: wifi.WifiScanInfo, psw) { + prompt.showToast({ message: 'connecting', duration: 5000 }) + Logger.debug(TAG, `connectNetwork bssid=${scanInfo.bssid}`) + // 这里因为api问题,需要声明为any,已提单 + let deviceConfig: any = { + ssid: scanInfo.ssid, + bssid: scanInfo.bssid, + preSharedKey: psw, + isHiddenSsid: false, + securityType: scanInfo.securityType + } + try { + wifi.connectToDevice(deviceConfig)//连接到指定网络 + Logger.debug(TAG, `connectToDevice success`) + } catch (err) { + Logger.debug(TAG, `connectToDevice fail err is ${JSON.stringify(err)}`) + } + try { + wifi.addDeviceConfig(deviceConfig)//添加网络配置 + } catch (err) { + Logger.debug(TAG, `addDeviceConfig fail err is ${JSON.stringify(err)}`) + } + } + ... + //component/PswDialog.ets + build() { + Column() { + Text(this.scanInfo.ssid) + .fontSize(20) + .width('95%') + + TextInput({ placeholder: 'input password' })//密码的输入框 + .id('input') + .type(InputType.Password) + .placeholderColor(Color.Gray) + .fontSize(19) + .margin({ top: 15 }) + .width('95%') + .height(36) + //每当输入框中的内容变化,它就赋值给psw + .onChange((value: string) => { + this.psw = value + }) + + Row() { + //确认按钮 + Button() { + Text($r('app.string.sure')) + .fontColor(Color.Blue) + .fontSize(17) + } + .id('sure') + .layoutWeight(7) + .backgroundColor(Color.White) + .margin(5) + .onClick(() => { + this.controller.close() + this.action(this.scanInfo, this.psw) + }) + + Text() + .width(1) + .height(35) + .backgroundColor($r('app.color.text_color')) + //取消按钮 + Button() { + Text($r('app.string.cancel')) + .fontColor(Color.Red) + .fontSize(17) + } + .layoutWeight(7) + .backgroundColor(Color.White) + .margin(5) + .onClick(() => { + this.controller.close() + }) + } + .width('100%') + .margin({ top: '3%' }) + } + .padding(15) + } + ``` + +## 全部代码 + +本例完整代码sample示例链接:[WLAN](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Wlan) + +## 参考 + +- [权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync) +- [@ohos.wifiManager](../application-dev/reference/apis/js-apis-wifiManager.md) +