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