CameraX currently provides CameraView, a View that displays a preview of the camera, while also providing methods to take pictures, control the camera, and query camera information. CameraView clearly takes on more responsibilities than a View should, as it participates in the View hierarchy and displays content, while also owning camera resources that exist outside the scope and lifecycle of the View hierarchy.

CameraView violates the separation of concerns principle, making it harder to guarantee its robustness in all corner cases. As a result, it will be marked as deprecated and removed before CameraX’s view artifact reaches Beta. It has been split up into two parts that you should use instead: PreviewView which handles its view-related tasks, and CameraController which handles the camera operations.

PreviewView has been available in CameraX’s view artifact for a while. CameraController, however, was introduced recently in version alpha19. It’s a high-level, all-in-one API that provides a way to easily access and manipulate core camera features, including displaying a camera preview, taking a picture, and analyzing camera frames. It does so while matching the output of its use cases to the viewfinder’s preview, thus providing a “What You See Is What You Get” (WYSIWYG) experience, a highly requested feature by developers that makes CameraX’s usage very intuitive. It also takes care of initializing the camera and adapting its output to device rotation.

To offload the burden of starting, stopping, and closing the camera, CameraX also introduced LifecycleCameraController, which offers all the convenience of CameraController with the added benefit of binding the camera’s lifecycle to that of a LifecycleOwner, typically the lifecycle of a Fragment or Activity. This allows the lifecycle’s state to control when the camera is opened, stopped, and closed.

Using PreviewView with CameraController is fairly simple, and can be done as follows:

1
2
3
4
5
6
7
8
9
10
// Set up the CameraController
val cameraController = LifecycleCameraController(context)
cameraController.bindToLifecycle(lifecycleOwner)

// Attach the CameraController to PreviewView
val previewView = findViewById(R.id.preview_view)
previewView.setController(cameraController)

// Use the CameraController
cameraController.takePicture(…)

The following sections list the mappings from CameraView to CameraController. You can use them to migrate from CameraView to PreviewView and CameraController.

Camera Initialization

CameraView CameraController
- getInitializationFuture()

Unlike CameraView, CameraController provides a ListenableFuture to monitor its initialization. It’s an asynchronous operation during which CameraController initializes CameraX and binds its use cases. The benefit of using this ListenableFuture is twofold:

  • Once it successfully completes, you can allow your users to start interacting with the camera, for example by taking pictures and zooming in and out of the viewfinder.
  • In case it fails, you can gracefully handle the error and communicate it to your users.

In case you aren’t familiar with ListenableFuture: it wraps an asynchronous operation and allows you to attach a listener that’s invoked on its completion. In case the operation has already finished, the future returns immediately.

Camera Lifecycle

CameraView LifecycleCameraController
bindToLifecycle(LifecycleOwner) bindToLifecycle(LifecycleOwner)

Similar to CameraView, LifecycleCameraController ties control of the camera to a lifecycle. You must bind a valid LifecycleOwner to the controller for it to be operational.

LifecycleCameraController provides an additional unbind() method which allows you to prematurely close the camera before the lifecycle that it’s bound to comes to an end. This is useful in cases in which the camera isn’t integral to a screen and isn’t needed for its entire lifecycle. Alternatively, you can define your custom LifecycleOwner to control when to close the camera, but unbind() can be more convenient in some cases.

Camera Control

- Zoom

CameraView CameraController
isZoomSupported() -
isPinchToZoomEnabled() isPinchToZoomEnabled()
setPinchToZoomEnabled(Boolean) setPinchToZoomEnabled(Boolean)
getZoomRatio() getZoomState().getZoomRatio()
getMinZoomRatio() getZoomState().getMinZoomRatio()
getMaxZoomRatio() getZoomState().getMaxZoomRatio()
- getZoomState().getLinearZoom()
setZoomRatio(float) setZoomRatio(float)
- setLinearZoom(float)

CameraController allows you to observe the camera’s zoom state via a LiveData instance. This state becomes available once the camera is open, and holds both static information, like the maximum and minimum zoom ratios, and dynamic information, like the current zoom ratio. You can verify whether zoom is supported using the maximum zoom ratio as follows:

1
val isZoomSupported = getZoomState().getValue().getMaxZoomRatio() != 1

CameraController additionally provides methods to get and set linear zoom, which can vary between 0 (minimum zoom) and 1 (maximum zoom).

Finally, like CameraView, CameraController allows you to enable or disable pinch-to-zoom. When enabled, CameraController handles pinch gestures on its attached PreviewView to zoom the camera in and out.

CameraView CameraController
enableTorch(boolean) enableTorch(boolean)
isTorchOn() getTorchState()
getFlash() getImageCaptureFlashMode()
setFlash(int) setImageCaptureFlashMode(int)

CameraController provides similar methods as CameraView to set and query the image capture’s flash mode, but unlike CameraView, it allows observing changes in the camera’s torch state via a LiveData instance, which emits TorchState.OFF and TorchState.ON.

- Focus/Metering

CameraView CameraController
- isTapToFocusEnabled()
- setTapToFocusEnabled(boolean)

While CameraView automatically acquires focus on any region of the viewfinder that is tapped, CameraController provides more flexibility by allowing you to enable or disable tap-to-focus functionality. When enabled, CameraController handles touch events on its attached PreviewView by focusing the camera on tapped regions.

Camera Selection

CameraView LifecycleCameraController
hasCameraWithLensFacing(int) hasCamera(CameraSelector)
setCameraLensFacing(Integer) setCameraSelector(CameraSelector)
getCameraLensFacing() getCameraSelector()
toggleCamera() -

CameraController provides you with more control over which camera to use by allowing you to specify a CameraSelector to select the camera it uses. This allows you to rotate between various cameras when toggling cameras, instead of only the default front and back cameras, which was a limitation of CameraView.toggleCamera(). You can still implement this fairly easily, though, using CameraController as follows.

1
2
3
4
5
6
7
8
9
fun toggleCamera() {
if (cameraController.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA
&& cameraController.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) {
cameraController.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
} else if (cameraController.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA
&& cameraController.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) {
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
}

Camera Use Cases

CameraView CameraController
getCaptureMode() isImageCaptureEnabled()
- isImageAnalysisEnabled()
setCaptureMode(CaptureMode) setEnabledUseCases(int)

CameraController supports all of CameraX’s use cases: Preview, ImageAnalysis, and ImageCapture, thus making it a more robust camera solution compared to CameraView. It also matches the output of ImageAnalysis and ImageCapture to the preview’s display, thereby providing a WYSIWYG experience.

CameraController’s default state enables preview, image analysis, and image capture. The Preview use case is always enabled, so you can choose the remaining use cases to enable or disable depending on your camera usage needs.

1
2
3
4
5
6
7
// Enable image capture, equivalent to cameraView.setCaptureMode(IMAGE)
// CameraController's enabled use cases: Preview + ImageCapture
cameraController.setEnabledUseCases(IMAGE_CAPTURE)

// Enable image capture and image analysis
// CameraController's enabled use cases: Preview + ImageCapture + ImageAnalysis
cameraController.setEnabledUseCases(IMAGE_CAPTURE|IMAGE_ANALYSIS)

- Preview

CameraView CameraController
getPreviewStreamState() Call directly on associated PreviewView
getScaleType() Call directly on associated PreviewView
setScaleType(ScaleType) Call directly on associated PreviewView

Unlike CameraView which wraps (and thus hides) a PreviewView instance and forwards method calls to it, CameraController is decoupled from PreviewView. This means you have more control over the viewfinder, and can access and manipulate it directly.

- ImageAnalysis

CameraView CameraController
- setImageAnalysisAnalyzer(Executor, Analyzer)
- clearImageAnalysisAnalyzer()
- getImageAnalysisBackpressureStrategy()
- setImageAnalysisBackpressureStrategy(int)
- getImageAnalysisImageQueueDepth()
- setImageAnalysisImageQueueDepth(int)

Unlike CameraView, CameraController supports the ImageAnalysis use case. You can set and clear its Analyzer, while also configuring its image-processing pipeline.

- ImageCapture

CameraView CameraController
takePicture(Executor, OnImageCapturedCallback) takePicture(Executor, OnImageCapturedCallback)
takePicture(OutputFileOptions, Executor, OnImageSavedCallback) takePicture(OutputFileOptions, Executor, OnImageSavedCallback)

CameraController provides the same methods as CameraView to take a picture, specify the image save destination, and provide the image capture callbacks.

One thing to note is that *CameraController* mirrors an image captured with a front-facing camera, unless you disable this in the *OutputFileOptions*’s metadata by calling *Metadata.setReversedHorizontal(false)*.

Conclusion

In summary:

  • Because CameraView is handling the responsibilities of both a view and a controller in the MVC sense, CameraX is deprecating it and splitting it into PreviewView and the newly introduced CameraController.
  • CameraController handles camera initialization, as well as the creation and configuration of its use cases.
  • CameraController provides a WYSIWYG experience by matching the output of its use cases to PreviewView’s display.
  • CameraController listens to the device’s motion sensor to correctly rotate the output of its use cases.
  • CameraController adds support for the ImageAnalysis use case, making it a more robust camera solution that provides easy access to all of CameraX’s use cases.
  • CameraController supports all of CameraView’s features and more, such as enabling and disabling tap-to-focus, getting and setting linear zoom, and observing dynamic camera information like the zoom and torch states.
  • LifecycleCameraController is a CameraController that binds the camera’s lifecycle to that of a LifecycleOwner, typically the lifecycle of the UI.

Want more CameraX goodness? Check out:

转载至medium.com