1# 适配不同折叠状态的摄像头变更(ArkTS) 2 3在开发相机应用时,需要先参考开发准备[申请相关权限](camera-preparation.md)。 4 5一台可折叠设备在不同折叠状态下,可使用不同的相机,应用可调用[CameraManager.on('foldStatusChange')](../../reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12)或[display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10)监听设备的折叠状态变化,并调用[CameraManager.getSupportedCameras](../../reference/apis-camera-kit/js-apis-camera.md#getsupportedcameras)获取当前状态下可用相机,完成相应适配,确保应用在折叠状态变更时的用户体验。 6 7详细的API说明请参考[Camera API参考](../../reference/apis-camera-kit/js-apis-camera.md)。 8 9Context获取方式请参考:[获取UIAbility的上下文信息](../../application-models/uiability-usage.md#获取uiability的上下文信息)。 10 11## 创建XComponent 12 使用两个[XComponent](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)分别展示折叠态和展开态,防止切换折叠屏状态亮屏的时候上一个相机还未关闭,残留上一个相机的画面。 13 14 ```ts 15 @Entry 16 @Component 17 struct Index { 18 @State reloadXComponentFlag: boolean = false; 19 @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0; 20 private mXComponentController: XComponentController = new XComponentController(); 21 private mXComponentOptions: XComponentOptions = { 22 type: XComponentType.SURFACE, 23 controller: this.mXComponentController 24 } 25 26 reloadXComponent() { 27 this.reloadXComponentFlag = !this.reloadXComponentFlag; 28 } 29 30 async loadXComponent() { 31 //初始化XComponent。 32 } 33 34 build() { 35 Stack() { 36 if (this.reloadXComponentFlag) { 37 XComponent(this.mXComponentOptions) 38 .onLoad(async () => { 39 await this.loadXComponent(); 40 }) 41 .width(this.getUIContext().px2vp(1080)) 42 .height(this.getUIContext().px2vp(1920)) 43 } else { 44 XComponent(this.mXComponentOptions) 45 .onLoad(async () => { 46 await this.loadXComponent(); 47 }) 48 .width(this.getUIContext().px2vp(1080)) 49 .height(this.getUIContext().px2vp(1920)) 50 } 51 } 52 .size({ width: '100%', height: '100%' }) 53 .backgroundColor(Color.Black) 54 } 55 } 56 ``` 57## 获取设备折叠状态 58 59此处提供两种方案供开发者选择。 60 61- **方案一:使用相机框架提供的[CameraManager.on('foldStatusChange')](../../../application-dev/reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12)监听设备折叠态变化。** 62 ```ts 63 import { camera } from '@kit.CameraKit'; 64 import { BusinessError } from '@kit.BasicServicesKit'; 65 66 function registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) { 67 // foldStatus 变量用来控制显示XComponent组件。 68 AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus); 69 } 70 71 function onFoldStatusChange(cameraManager: camera.CameraManager) { 72 cameraManager.on('foldStatusChange', registerFoldStatusChanged); 73 } 74 75 function offFoldStatusChange(cameraManager: camera.CameraManager) { 76 cameraManager.off('foldStatusChange', registerFoldStatusChanged); 77 } 78 ``` 79- **方案二:使用图形图像的[display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10)监听设备折叠态变化。** 80 ```ts 81 import { display } from '@kit.ArkUI'; 82 let preFoldStatus: display.FoldStatus = display.getFoldStatus(); 83 display.on('foldStatusChange', (foldStatus: display.FoldStatus) => { 84 // 从半折叠态(FOLD_STATUS_HALF_FOLDED)和展开态(FOLD_STATUS_EXPANDED),相机框架返回所支持的相机是一致的,所以从半折叠态到展开态不需要重新配流,从展开态到半折叠态也是一样的。 85 if ((preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED && 86 foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) || 87 (preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED && 88 foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) { 89 preFoldStatus = foldStatus; 90 return; 91 } 92 preFoldStatus = foldStatus; 93 // foldStatus 变量用来控制显示XComponent组件。 94 AppStorage.setOrCreate<number>('foldStatus', foldStatus); 95 }) 96 ``` 97 98## 完整示例 99```ts 100import { camera } from '@kit.CameraKit'; 101import { BusinessError } from '@kit.BasicServicesKit'; 102import { abilityAccessCtrl } from '@kit.AbilityKit'; 103import { display } from '@kit.ArkUI'; 104 105const TAG = 'FoldScreenCameraAdaptationDemo '; 106 107@Entry 108@Component 109struct Index { 110 @State isShow: boolean = false; 111 @State reloadXComponentFlag: boolean = false; 112 @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0; 113 private mXComponentController: XComponentController = new XComponentController(); 114 private mXComponentOptions: XComponentOptions = { 115 type: XComponentType.SURFACE, 116 controller: this.mXComponentController 117 } 118 private mSurfaceId: string = ''; 119 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 120 private mCameraManager: camera.CameraManager | undefined = undefined; 121 // surface宽高根据需要自行选择。 122 private surfaceRect: SurfaceRect = { 123 surfaceWidth: 1080, 124 surfaceHeight: 1920 125 }; 126 private curCameraDevice: camera.CameraDevice | undefined = undefined; 127 private mCameraInput: camera.CameraInput | undefined = undefined; 128 private mPreviewOutput: camera.PreviewOutput | undefined = undefined; 129 private mPhotoSession: camera.PhotoSession | undefined = undefined; 130 // One of the recommended preview resolutions. 131 private previewProfileObj: camera.Profile = { 132 format: 1003, 133 size: { 134 width: 1920, 135 height: 1080 136 } 137 }; 138 private mContext: Context | undefined = undefined; 139 140 private preFoldStatus: display.FoldStatus = display.getFoldStatus(); 141 // 监听折叠屏状态,可以使用cameraManager.on(type: 'foldStatusChange', callback: AsyncCallback<FoldStatusInfo>): void; 142 // 也可以使用display.on(type: 'foldStatusChange', callback: Callback<FoldStatus>): void; 143 private foldStatusCallback = 144 (err: BusinessError, info: camera.FoldStatusInfo): void => this.registerFoldStatusChanged(err, info); 145 private displayFoldStatusCallback = 146 (foldStatus: display.FoldStatus): void => this.onDisplayFoldStatusChange(foldStatus); 147 148 149 registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) { 150 console.info(TAG + 'foldStatusChanged foldStatus: ' + foldStatusInfo.foldStatus); 151 for (let i = 0; i < foldStatusInfo.supportedCameras.length; i++) { 152 console.info(TAG + 153 `foldStatusChanged camera[${i}]: ${foldStatusInfo.supportedCameras[i].cameraId},cameraPosition: ${foldStatusInfo.supportedCameras[i].cameraPosition}`); 154 } 155 AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus); 156 } 157 158 onDisplayFoldStatusChange(foldStatus: display.FoldStatus): void { 159 console.error(TAG + `onDisplayFoldStatusChange foldStatus: ${foldStatus}`); 160 if ((this.preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED && 161 foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) || 162 (this.preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED && 163 foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) { 164 this.preFoldStatus = foldStatus; 165 return; 166 } 167 this.preFoldStatus = foldStatus; 168 if (!this.curCameraDevice) { 169 return; 170 } 171 // foldStatus 变量用来控制显示XComponent组件。 172 AppStorage.setOrCreate<number>('foldStatus', foldStatus); 173 } 174 175 requestPermissionsFn(): void { 176 let atManager = abilityAccessCtrl.createAtManager(); 177 atManager.requestPermissionsFromUser(this.mContext, [ 178 'ohos.permission.CAMERA' 179 ]).then((): void => { 180 this.isShow = true; 181 }).catch((error: BusinessError): void => { 182 console.error(TAG + 'ohos.permission.CAMERA no permission.'); 183 }); 184 } 185 186 initContext(): void { 187 let uiContext = this.getUIContext(); 188 this.mContext = uiContext.getHostContext(); 189 } 190 191 initCameraManager(): void { 192 this.mCameraManager = camera.getCameraManager(this.mContext); 193 } 194 195 aboutToAppear(): void { 196 console.log(TAG + 'aboutToAppear is called'); 197 this.initContext(); 198 this.initCameraManager(); 199 this.requestPermissionsFn(); 200 this.onFoldStatusChange(); 201 } 202 203 async aboutToDisappear(): Promise<void> { 204 await this.releaseCamera(); 205 // 解注册。 206 this.offFoldStatusChange(); 207 } 208 209 async onPageShow(): Promise<void> { 210 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 211 } 212 213 async releaseCamera(): Promise<void> { 214 // 停止当前会话。 215 try { 216 await this.mPhotoSession?.stop(); 217 } catch (error) { 218 let err = error as BusinessError; 219 console.error(TAG + 'Failed to stop session, errorCode = ' + err.code); 220 } 221 222 // 释放相机输入流。 223 try { 224 await this.mCameraInput?.close(); 225 } catch (error) { 226 let err = error as BusinessError; 227 console.error(TAG + 'Failed to close device, errorCode = ' + err.code); 228 } 229 230 // 释放预览输出流。 231 try { 232 await this.mPreviewOutput?.release(); 233 } catch (error) { 234 let err = error as BusinessError; 235 console.error(TAG + 'Failed to release previewOutput, errorCode = ' + err.code); 236 } 237 238 this.mPreviewOutput = undefined; 239 240 // 释放会话。 241 try { 242 await this.mPhotoSession?.release(); 243 } catch (error) { 244 let err = error as BusinessError; 245 console.error(TAG + 'Failed to release photoSession, errorCode = ' + err.code); 246 } 247 248 // 会话置空。 249 this.mPhotoSession = undefined; 250 } 251 252 onFoldStatusChange(): void { 253 this.mCameraManager?.on('foldStatusChange', this.foldStatusCallback); 254 // display.on('foldStatusChange', this.displayFoldStatusCallback); 255 } 256 257 offFoldStatusChange(): void { 258 this.mCameraManager?.off('foldStatusChange', this.foldStatusCallback); 259 // display.off('foldStatusChange', this.displayFoldStatusCallback); 260 } 261 262 reloadXComponent(): void { 263 this.reloadXComponentFlag = !this.reloadXComponentFlag; 264 } 265 266 async loadXComponent(): Promise<void> { 267 this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId(); 268 this.mXComponentController.setXComponentSurfaceRect(this.surfaceRect); 269 console.info(TAG + `mCameraPosition: ${this.mCameraPosition}`) 270 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 271 } 272 273 getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined { 274 let previewProfiles = cameraOutputCapability.previewProfiles; 275 if (previewProfiles.length < 1) { 276 return undefined; 277 } 278 let index = previewProfiles.findIndex((previewProfile: camera.Profile) => { 279 return previewProfile.size.width === this.previewProfileObj.size.width && 280 previewProfile.size.height === this.previewProfileObj.size.height && 281 previewProfile.format === this.previewProfileObj.format; 282 }) 283 if (index === -1) { 284 return undefined; 285 } 286 return previewProfiles[index]; 287 } 288 289 async initCamera(surfaceId: string, cameraPosition: camera.CameraPosition): Promise<void> { 290 await this.releaseCamera(); 291 // 创建CameraManager对象。 292 if (!this.mCameraManager) { 293 console.error(TAG + 'camera.getCameraManager error'); 294 return; 295 } 296 297 // 获取相机列表。 298 let cameraArray: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras(); 299 if (cameraArray.length <= 0) { 300 console.error(TAG + 'cameraManager.getSupportedCameras error'); 301 return; 302 } 303 304 for (let index = 0; index < cameraArray.length; index++) { 305 console.info(TAG + 'cameraId : ' + cameraArray[index].cameraId); // 获取相机ID。 306 console.info(TAG + 'cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置。 307 console.info(TAG + 'cameraType : ' + cameraArray[index].cameraType); // 获取相机类型。 308 console.info(TAG + 'connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型。 309 } 310 311 let deviceIndex = cameraArray.findIndex((cameraDevice: camera.CameraDevice) => { 312 return cameraDevice.cameraPosition === cameraPosition; 313 }) 314 if (deviceIndex === -1) { 315 deviceIndex = 0; 316 console.error(TAG + 'not found camera'); 317 } 318 this.curCameraDevice = cameraArray[deviceIndex]; 319 320 // 创建相机输入流。 321 try { 322 this.mCameraInput = this.mCameraManager.createCameraInput(this.curCameraDevice); 323 } catch (error) { 324 let err = error as BusinessError; 325 console.error(TAG + 'Failed to createCameraInput errorCode = ' + err.code); 326 } 327 if (this.mCameraInput === undefined) { 328 return; 329 } 330 331 // 打开相机。 332 try { 333 await this.mCameraInput.open(); 334 } catch (error) { 335 let err = error as BusinessError; 336 console.error(TAG + 'Failed to open device, errorCode = ' + err.code); 337 } 338 339 // 获取支持的模式类型。 340 let sceneModes: Array<camera.SceneMode> = this.mCameraManager.getSupportedSceneModes(this.curCameraDevice); 341 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 342 if (!isSupportPhotoMode) { 343 console.error(TAG + 'photo mode not support'); 344 return; 345 } 346 347 // 获取相机设备支持的输出流能力。 348 let cameraOutputCapability: camera.CameraOutputCapability = 349 this.mCameraManager.getSupportedOutputCapability(this.curCameraDevice, camera.SceneMode.NORMAL_PHOTO); 350 if (!cameraOutputCapability) { 351 console.error(TAG + 'cameraManager.getSupportedOutputCapability error'); 352 return; 353 } 354 console.info(TAG + 'outputCapability: ' + JSON.stringify(cameraOutputCapability)); 355 let previewProfile = this.getPreviewProfile(cameraOutputCapability); 356 if (previewProfile === undefined) { 357 console.error(TAG + 'The resolution of the current preview stream is not supported.'); 358 return; 359 } 360 this.previewProfileObj = previewProfile; 361 362 // 创建预览输出流,其中参数 surfaceId 参考上文 XComponent 组件,预览流为XComponent组件提供的surface。 363 try { 364 this.mPreviewOutput = this.mCameraManager.createPreviewOutput(this.previewProfileObj, surfaceId); 365 } catch (error) { 366 let err = error as BusinessError; 367 console.error(TAG + `Failed to create the PreviewOutput instance. error code: ${err.code}`); 368 } 369 if (this.mPreviewOutput === undefined) { 370 return; 371 } 372 373 //创建会话。 374 try { 375 this.mPhotoSession = this.mCameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 376 } catch (error) { 377 let err = error as BusinessError; 378 console.error(TAG + 'Failed to create the session instance. errorCode = ' + err.code); 379 } 380 if (this.mPhotoSession === undefined) { 381 return; 382 } 383 384 // 开始配置会话。 385 try { 386 this.mPhotoSession.beginConfig(); 387 } catch (error) { 388 let err = error as BusinessError; 389 console.error(TAG + 'Failed to beginConfig. errorCode = ' + err.code); 390 } 391 392 // 向会话中添加相机输入流。 393 try { 394 this.mPhotoSession.addInput(this.mCameraInput); 395 } catch (error) { 396 let err = error as BusinessError; 397 console.error(TAG + 'Failed to addInput. errorCode = ' + err.code); 398 } 399 400 // 向会话中添加预览输出流。 401 try { 402 this.mPhotoSession.addOutput(this.mPreviewOutput); 403 } catch (error) { 404 let err = error as BusinessError; 405 console.error(TAG + 'Failed to addOutput(previewOutput). errorCode = ' + err.code); 406 } 407 408 // 提交会话配置。 409 try { 410 await this.mPhotoSession.commitConfig(); 411 } catch (error) { 412 let err = error as BusinessError; 413 console.error(TAG + 'Failed to commit session configuration, errorCode = ' + err.code); 414 } 415 416 // 启动会话。 417 try { 418 await this.mPhotoSession.start() 419 } catch (error) { 420 let err = error as BusinessError; 421 console.error(TAG + 'Failed to start session. errorCode = ' + err.code); 422 } 423 } 424 425 build() { 426 if (this.isShow) { 427 Stack() { 428 if (this.reloadXComponentFlag) { 429 XComponent(this.mXComponentOptions) 430 .onLoad(async () => { 431 await this.loadXComponent(); 432 }) 433 .width(this.getUIContext().px2vp(1080)) 434 .height(this.getUIContext().px2vp(1920)) 435 } else { 436 XComponent(this.mXComponentOptions) 437 .onLoad(async () => { 438 await this.loadXComponent(); 439 }) 440 .width(this.getUIContext().px2vp(1080)) 441 .height(this.getUIContext().px2vp(1920)) 442 } 443 Text('切换相机') 444 .size({ width: 80, height: 48 }) 445 .position({ x: 1, y: 1 }) 446 .backgroundColor(Color.White) 447 .textAlign(TextAlign.Center) 448 .borderRadius(24) 449 .onClick(async () => { 450 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 451 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 452 this.reloadXComponentFlag = !this.reloadXComponentFlag; 453 }) 454 } 455 .size({ width: '100%', height: '100%' }) 456 .backgroundColor(Color.Black) 457 } 458 } 459} 460``` 461