• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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