提交 8f9059c7 编写于 作者: W wangqingkaihong

检视修改

Signed-off-by: Nwangqingkaihong <wangqing@kaihong.com>
上级 0196fee3
......@@ -2,7 +2,9 @@
## 场景说明
两台设备组网,实现修改文件时两个设备同时绘制的分布式画布场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。
两台设备组网,当其中一个设备修改文件时,两个设备可以同步修改的结果。分布式场景可以在协同办公(如多人多设备编辑同一文件),设备文档更新(分布式设备更新文件内容,所有设备同步更新)中发挥重要作用,有助于加快工作效率,减少工作中的冗余。
本示例将为大家介绍如何实现上述功能。
## 效果呈现
......@@ -40,358 +42,371 @@
## 开发步骤
1.申请所需权限
​ 在model.json5中添加以下配置:
```json
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换
},
{
"name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力
}
]
```
2.构建UI框架
indexCanvas页面:
```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)
}
}
```
2.数据model
```
//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)
}
}
```
3.将两台设备组网
```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.实现同步编辑
```typescript
onPageShow() {
//每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
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)
}
```
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)
本例完整代码sample示例链接:[分布式对象](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote)
## 参考
......
......@@ -2,7 +2,7 @@
## 场景说明
两台设备组网,实现修改文件时两个设备同时修改的分布式文件场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。
两台设备组网的分布式场景是工作中常常需要的。常见的如代码的同步编辑、文档的同步修改等。这样的分布式场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。
## 效果呈现
......@@ -40,402 +40,443 @@
## 开发步骤
1.申请所需权限
1. 申请所需权限
在model.json5中添加以下配置:
```json
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"//允许不同设备间的数据交换
},
{
"name": "ohos.permission.ACCESS_SERVICE_DM"//允许系统应用获取分布式设备的认证组网能力
}
]
```
2.构建UI框架
indexNote页面:
```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页面:
```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.将两台设备组网
```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.实现同步编辑
```typescript
onPageShow() {
//每当完成编辑或者新建文件,就会回到主页,此时就会执行onPageShow()
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)
}
```
```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)
本例完整代码sample示例链接:[分布式对象](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/DistributedNote)
## 参考
[权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
[分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md)
- [权限列表](../application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
- [分布式数据对象](../application-dev/reference/apis/js-apis-data-distributedobject.md)
......@@ -8,10 +8,16 @@
本例效果如下:
![contactlist](figures/camera.png)
| 拍照 | 预览 |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| <img src="figures/camera.png" alt="contactlist" style="zoom: 45%;" /> | <img src="figures/camerapreview.gif" alt="contactlist" style="zoom: 50%;" /> |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。
- IDE:DevEco Studio 4.0.0.201 Beta1
- SDK:Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
......@@ -19,222 +25,224 @@
本例使用@ohos.multimedia.camera接口实现相机示例的主要功能:拍照、预览;
使用@ohos.multimedia.image接口实现图片接收;
使用@ohos.multimedia.mediaLibrary接口实现对媒体文件的存储。
- 拍照:XComponent组件负责绘制摄像头画面呈现的窗口,其onload事件调用cameraModel.ts的initCamera方法初始化相机功能输出画面信息。拍照动作使用Image组件实现,其onclick事件调用cameraModel.ts的takepicture方法开始拍照。
- XComponent组件负责绘制摄像头画面呈现的窗口,其onload事件调用cameraModel.ts的initCamera方法初始化相机功能输出预览信息。
- 拍照按钮:使用Image组件实现,其onclick事件调用cameraModel.ts的takepicture方法开始拍照。
- 照片存储:MediaModel.ts中的createAndGetUri方法通过引用自@ohos.multimedia.mediaLibrary的mediaLibraryTest类创建媒体资源,saveImage方法将拍摄的照片写入到Mediamodel传回的资源中去。
- 预览:返回相机界面点击底部左侧预览图可进入相册应用,可以在其中查看照片和录制的视频。
## 开发步骤
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
```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.初始化相机功能
```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<void> {
..................
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.点击按钮进行拍照
```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<void> {
//设置拍照相关参数
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);
}
```
- 保存图片
```typescript
..................//model/MediaModel.ts中定义的负责保存图片的相关方法
async createAndGetUri(mediaType: mediaLibrary.MediaType): Promise<mediaLibrary.FileAsset> {
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<number> {
let fd: number = await fileAsset.open('Rw');//打开当前文件
return fd;
}
..................
async saveImage(buffer: ArrayBuffer, img: image.Image): Promise<void> {
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);
}
}
```
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<void> {
...
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<void> {
//设置拍照相关参数
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<mediaLibrary.FileAsset> {
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<number> {
let fd: number = await fileAsset.open('Rw');//打开当前文件
return fd;
}
...
async saveImage(buffer: ArrayBuffer, img: image.Image): Promise<void> {
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);
}
}
```
## 全部代码
......@@ -242,7 +250,8 @@ Image(this.getCameraIcon())
## 参考
[权限列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissiondistributed_datasync)
- [权限列表](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)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册