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