Faucet to focus: Mastering CameraX Transformations in Jetpack Compose | by Jolanda Verhoef | Android Builders | Jan, 2025

Faucet to focus: Mastering CameraX Transformations in Jetpack Compose | by Jolanda Verhoef | Android Builders | Jan, 2025


Welcome again! In the primary submit of this sequence, we constructed a fundamental digicam preview utilizing the brand new camera-compose artifact. We coated permission dealing with and fundamental integration, and now it’s time to get extra interactive!

  • 🧱 Half 1: Constructing a fundamental digicam preview utilizing the brand new camera-compose artifact. We’ll cowl permission dealing with and fundamental integration.
  • 👆 Half 2 (this submit): Utilizing the Compose gesture system, graphics, and coroutines to implement a visible tap-to-focus.
  • 🔎 Half 3: Exploring learn how to overlay Compose UI components on prime of your digicam preview for a richer consumer expertise.
  • 📂 Half 4: Utilizing adaptive APIs and the Compose animation framework to easily animate to and from tabletop mode on foldable telephones.

On this submit, we’ll dive into implementing the tap-to-focus characteristic. This includes understanding learn how to translate Compose contact occasions to digicam sensor coordinates, and including a visible indicator to point out the consumer the place the digicam is focusing.

There’s an open characteristic request for a better degree composable that may include extra out-of-the-box performance (like tap-to-focus and zooming). Please upvote the characteristic when you want this!

First, let’s modify the CameraPreviewViewModel to deal with tap-to-focus logic. We have to adapt our current code in two methods:

  • We maintain on to a SurfaceOrientedMeteringPointFactory, that is ready to translate the faucet coordinates coming from the UI right into a MeteringPoint.
  • We maintain on to a CameraControl, that can be utilized to work together with the digicam. As soon as we’ve the right MeteringPoint, we move it to that digicam management for use because the reference level for auto-focusing.
class CameraPreviewViewModel : ViewModel() {
..
personal var surfaceMeteringPointFactory: SurfaceOrientedMeteringPointFactory? = null
personal var cameraControl: CameraControl? = null

personal val cameraPreviewUseCase = Preview.Builder().construct().apply {
setSurfaceProvider { newSurfaceRequest ->
_surfaceRequest.replace { newSurfaceRequest }
surfaceMeteringPointFactory = SurfaceOrientedMeteringPointFactory(
newSurfaceRequest.decision.width.toFloat(),
newSurfaceRequest.decision.peak.toFloat()
)
}
}

droop enjoyable bindToCamera(appContext: Context, lifecycleOwner: LifecycleOwner) {
val processCameraProvider = ProcessCameraProvider.awaitInstance(appContext)
val digicam = processCameraProvider.bindToLifecycle(
lifecycleOwner, DEFAULT_BACK_CAMERA, cameraPreviewUseCase
)
cameraControl = digicam.cameraControl

// Cancellation indicators we're finished with the digicam
attempt { awaitCancellation() } lastly {
processCameraProvider.unbindAll()
cameraControl = null
}
}

enjoyable tapToFocus(tapCoords: Offset) {
val level = surfaceMeteringPointFactory?.createPoint(tapCoords.x, tapCoords.y)
if (level != null) {
val meteringAction = FocusMeteringAction.Builder(level).construct()
cameraControl?.startFocusAndMetering(meteringAction)
}
}
}

  • We create a SurfaceOrientedMeteringPointFactory when the SurfaceRequest is obtainable, utilizing the floor’s decision. This manufacturing facility interprets the tapped coordinates on the floor to a spotlight metering level.
  • We assign the cameraControl hooked up to the Digital camera after we bind to the digicam’s lifecycle. We then reset it to null when the lifecycle ends.
  • The tapToFocus perform takes an Offset representing the faucet location in sensor coordinates, interprets it to a MeteringPoint utilizing the manufacturing facility, after which makes use of the CameraX cameraControl to provoke the main focus and metering motion.

Word: We might enhance the interplay between UI and CameraControl considerably by utilizing a extra subtle coroutines setup, however that is exterior the scope of this weblog submit. Should you’re interested by studying extra about such an implementation, take a look at the Jetpack Digital camera App pattern, which implements digicam interactions by way of the CameraXCameraUseCase.

Now, let’s replace the CameraPreviewContent composable to deal with contact occasions and move these occasions to the view mannequin. To try this, we’ll use the pointerInput modifier and the detectTapGestures extension perform:

@Composable
enjoyable CameraPreviewContent(..) {
..

surfaceRequest?.let { request ->
val coordinateTransformer = bear in mind { MutableCoordinateTransformer() }
CameraXViewfinder(
surfaceRequest = request,
coordinateTransformer = coordinateTransformer,
modifier = modifier.pointerInput(Unit) {
detectTapGestures { tapCoords ->
with(coordinateTransformer) {
viewModel.tapToFocus(tapCoords.rework())
}
}
}
)
}
}

  • We use the pointerInput modifier and detectTapGestures to hear for faucet occasions on the CameraXViewfinder.
  • We create a MutableCoordinateTransformer, which is supplied by the camera-compose library, to remodel the faucet coordinates from the structure’s coordinate system to the sensor’s coordinate system. This transformation is non-trivial! The bodily sensor is commonly rotated relative to the display screen, and extra scaling and cropping is completed to make the picture match the container it’s in. We move the mutable transformer occasion into the CameraXViewfinder. Internally, the viewfinder units the transformation matrix of the transformer. This transformation matrix is able to reworking native window coordinates into sensor coordinates.
  • Contained in the detectTapGestures block, we use the coordinateTransformer to remodel the faucet coordinates earlier than passing them to the tapToFocus perform of our view mannequin.

As we’re utilizing typical Compose gesture dealing with, we unlock any kind of gesture recognition. So if you wish to focus after the consumer triple-taps, or swipes up and down, nothing is holding you again! That is an instance of the facility of the brand new CameraX Compose APIs. They’re constructed from the bottom up, in an open approach, in an effort to lengthen and construct no matter you want on prime of them. Examine this to the outdated CameraController that had tap-to-focus in-built — that’s nice if tap-to-focus is what you want, but it surely didn’t provide you with any alternative to customise the conduct.

To offer visible suggestions to the consumer, we’ll add a small white circle that briefly seems on the faucet location. We’ll use Compose animation APIs to fade it out and in:

@Composable
enjoyable CameraPreviewContent(
viewModel: CameraPreviewViewModel,
modifier: Modifier = Modifier,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.present
) {
val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle()
val context = LocalContext.present
LaunchedEffect(lifecycleOwner) {
viewModel.bindToCamera(context.applicationContext, lifecycleOwner)
}

var autofocusRequest by bear in mind { mutableStateOf(UUID.randomUUID() to Offset.Unspecified) }

val autofocusRequestId = autofocusRequest.first
// Present the autofocus indicator if the offset is specified
val showAutofocusIndicator = autofocusRequest.second.isSpecified
// Cache the preliminary coords for every autofocus request
val autofocusCoords = bear in mind(autofocusRequestId) { autofocusRequest.second }

// Queue hiding the request for every distinctive autofocus faucet
if (showAutofocusIndicator) {
LaunchedEffect(autofocusRequestId) {
delay(1000)
// Clear the offset to complete the request and conceal the indicator
autofocusRequest = autofocusRequestId to Offset.Unspecified
}
}

surfaceRequest?.let { request ->
val coordinateTransformer = bear in mind { MutableCoordinateTransformer() }
CameraXViewfinder(
surfaceRequest = request,
coordinateTransformer = coordinateTransformer,
modifier = modifier.pointerInput(viewModel, coordinateTransformer) {
detectTapGestures { tapCoords ->
with(coordinateTransformer) {
viewModel.tapToFocus(tapCoords.rework())
}
autofocusRequest = UUID.randomUUID() to tapCoords
}
}
)

AnimatedVisibility(
seen = showAutofocusIndicator,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.offset { autofocusCoords.takeOrElse { Offset.Zero } .spherical() }
.offset((-24).dp, (-24).dp)
) {
Spacer(Modifier.border(2.dp, Shade.White, CircleShape).dimension(48.dp))
}
}
}

  • We use the mutable state autofocusRequest to handle the visibility state of the main focus field and the faucet coordinates.
  • A LaunchedEffect is used to set off the animation. When the autofocusRequest is up to date, we briefly present the autofocus field and conceal it after a delay.
  • We use AnimatedVisibility to point out the main focus field with a fade-in and fade-out animation.
  • The main target field is an easy Spacer with a white border in a round form, positioned utilizing offset modifiers.

On this pattern, we selected a easy white circle fading out and in, however the sky is the restrict and you’ll create any UI utilizing the highly effective Compose parts and animation system. Confetti, anybody? 🎊

Our digicam preview now responds to the touch occasions! Tapping on the preview triggers a spotlight motion within the digicam and reveals a visible indicator the place you tapped. Yow will discover the complete code snippet right here and a model utilizing the Konfetti library right here.

Within the subsequent submit, we’ll discover learn how to overlay Compose UI components on prime of your digicam preview for a flowery highlight impact. Keep tuned!

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.