Display a camera preview with PreviewView

Android Jetpack CameraX: PreviewView

Husayn Hakeem
Android Developers

--

A common use case for any camera app is to display a preview from the camera. So far, this has been quite difficult to get right, mostly due to the complexities that exist around the camera2 API edge cases and varying device behaviors. PreviewView, part of the CameraX Jetpack library, makes displaying a camera preview easier for developers by providing a developer-friendly, consistent, and stable API across a wide range of Android devices.

Introducing PreviewView

PreviewView is a custom View that enables the display of a camera feed. It was built to offload the burden of setting up and handling the preview surface used by the camera.

If you need to display a basic camera preview in your app, PreviewView is the recommended way to do so, as it is:

  • Easier to use: PreviewView is a View. It implements all the work necessary to display what the camera sees in your layout by managing the Surface used by the Preview use case.
  • Light-weight: PreviewView focuses only on preview. All its internal resources are geared towards getting a camera preview set up and managing the preview surface while the camera’s using it. This separation of concerns allows for PreviewView code to stay clean.
  • Exhaustive: PreviewView handles the hardest parts of displaying the camera feed on the screen, including aspect ratio, scaling, and rotation. It also contains compatibility fixes and workarounds in order to provide a seamless experience on a multitude of devices, screen sizes, camera hardware support levels, and display configurations such as split-screen mode, locked orientation, and free-form multi-window.

PreviewView — Implementation modes

PreviewView is a subclass of FrameLayout. To display the camera feed, it uses either a SurfaceView or TextureView, provides a preview surface to the camera when it’s ready, tries to keep it valid as long as the camera is using it, and when released prematurely, provides a new surface if the camera is still in use.

SurfaceView is generally better than TextureView when it comes to certain key metrics, including power and latency, which is why PreviewView tries to use a SurfaceView by default. However, some devices (mainly legacy devices) crash when the preview surface is released prematurely. With SurfaceView, unfortunately, it isn’t possible to control when the surface is released, as this is controlled by the View hierarchy. On these devices, PreviewView falls back to using a TextureView instead. You should also force PreviewView to use a TextureView in cases where preview rotation, transparency, or animation are needed.

You can explicitly set the implementation you want PreviewView to use by calling PreviewView.setPreferredImplementationMode(ImplementationMode), where ImplementationMode is either SURFACE_VIEW or TEXTURE_VIEW. PreviewView tries to honor your choice when the preferred mode is SURFACE_VIEW, and guarantees it when it’s TEXTURE_VIEW.

⚠️ Make sure to set your preferred implementation mode before starting preview by calling Preview.setSurfaceProvider(PreviewView.createSurfaceProvider()).

The following shows how to set the preferred implementation mode of PreviewView.

PreviewView — Preview

PreviewView handles the nuts and bolts of creating a SurfaceProvider needed by the Preview use case to start a preview stream. The SurfaceProvider prepares the surface that will be provided to the camera in order to display a camera preview stream, and takes care of recreating the Surface when necessary. PreviewView.createSurfaceProvider(CameraInfo) accepts a nullable CameraInfo instance. PreviewView uses it, along with your preferred implementation mode and the camera’s capabilities, to determine the implementation to use internally. If you pass in a null CameraInfo, PreviewView uses a TextureView implementation, since it can’t tell whether the chosen camera will work with SurfaceView.

Once you build the Preview use case and any other use cases you need, bind them to a LifecycleOwner, use the CameraInfo from the bound camera to create a SurfaceProvider, and then attach it to the Preview use case to start the preview stream by calling Preview.setSurfaceProvider(SurfaceProvider).

The following shows how to attach PreviewView to Preview to start a preview stream.

PreviewView — Scale types

PreviewView provides an API that allows you to control how the preview should look and where it should be positioned within its container.

  • The how defines whether the preview should fit inside (FIT) or fill (FILL) its container.
  • The where defines whether the preview should be at the top left (START), center (CENTER) or bottom right (END) or its container.

The combinations created from the “how” and “where” represent the available scale type values PreviewView supports: FIT_START, FIT_CENTER, FIT_END, FILL_START, FILL_CENTER and FILL_END. The most commonly used are FIT_CENTER, which translates to a letterboxed preview, and FILL_CENTER which center-crops the preview in its container.

Setting the scale type can be done in one of two ways:

  • In the XML layout by using PreviewView’s scaleType attribute as shown in the following sample.

To retrieve the current scale type that a PreviewView is using, call PreviewView.getScaleType().

PreviewView — Camera Controls

Depending on the camera sensor orientation, the device’s rotation, the display mode, and the preview scale type, PreviewView may scale, rotate, and translate the preview frames it receives from the camera in order to correctly display the preview stream in the UI. This is why being able to convert UI coordinates to camera sensor coordinates is important. In CameraX, this conversion is done by a MeteringPointFactory. PreviewView provides an API to create one: PreviewView.createMeteringPointFactory(cameraSelector), where the CameraSelector portrays the camera streaming the preview.

PreviewView’s MeteringPointFactory comes in handy when you need to implement tap-to-focus. Even though autofocus is enabled by default on the camera preview (when the camera supports it), you may also control the focus target when tapping on PreviewView. The MeteringPointFactory will convert the coordinates of the focus target to the camera sensor coordinates, which then enables the camera to focus on that region.

The following sample shows how to implement tap-to-focus using a touch listener set on PreviewView.

Another common camera preview feature is pinch-to-zoom, which enables the camera to zoom in and out as you pinch the preview. To implement it with PreviewView, add a touch listener to PreviewView and attach it to a scale gesture listener. This will intercept pinch gestures and update the camera’s zoom ratio accordingly.

The following sample shows how to implement pinch-to-zoom with PreviewView.

PreviewView — How it’s tested

PreviewView provides a consistent camera behavior across a wide range of Android devices. This is thanks to the investment CameraX has made in testing PreviewView and its other APIs in its automated testing lab. These tests are divided into two main categories:

  • Unit tests that verify PreviewView’s behavior with regard to its implementation modes, scale types, and MeteringPointFactory. They also make sure PreviewView correctly adjusts the preview when it should, such as when the size of its container changes, when the display layout is updated, and when it is attached or reattached to a Window.
  • Integration tests that ensure PreviewView behaves correctly when it’s part of an app, and displays or stops a preview stream accordingly. These tests include verifying the preview state while the app is running, after it’s closed and reopened multiple times, after the camera lens is toggled back and forth, and after the app’s lifecycle is destroyed and recreated. Currently, these tests mostly cover the TextureView implementation of PreviewView, as getting a signal on when the preview starts and stops from a SurfaceView has proved to be challenging.

Conclusion

In summary:

  • PreviewView is a custom View that makes displaying a camera preview easier.
  • PreviewView handles the preview surface using a SurfaceView by default, but may fall back to using a TextureView when needed or requested.
  • Bind your other use cases, like ImageCapture and ImageAnalysis, to a LifecycleOwner, and then start the camera preview by attaching a SurfaceProvider from PreviewView to the bound Preview use case.
  • Control how the preview is displayed by defining PreviewView’s scale type.
  • Implement tap-to-focus on your preview by getting a MeteringPointFactory from PreviewView.
  • Implement pinch-to-zoom on your preview by setting up a gesture listener on PreviewView.

Want more CameraX goodness? Check out:

Questions about anything related to PreviewView or preview? Leave a comment. Thanks for reading!

--

--