Software Development

Tap to Focus: Using Jetpack Compose to Master CameraX Transformations

Prince Kumar
10 April 2025
Tap to Focus: Using Jetpack Compose to Master CameraX Transformations

In the world of modern Android development, Jetpack Compose is transforming how we design UIs — and CameraX is simplifying how we work with the camera. One powerful but often overlooked feature you can add to camera apps is “Tap to Focus.”

When paired with CameraX’s MeteringPointFactory and the flexibility of Compose UI elements, implementing this behavior becomes intuitive and interactive.

In this blog, we’ll guide you step-by-step through:

  • Setting up CameraX with Compose

  • Capturing tap gestures on a preview

  • Converting tap coordinates into focus points

  • Using CameraControl and MeteringPointFactory to initiate focus

  • Adding visual feedback (focus ring) using Compose

Let’s start mastering camera transformations and interactions with Jetpack Compose.

Why Tap to Focus?

The ability to tap on a preview and adjust the camera’s focus is an essential part of modern camera apps. Whether you’re building a photography app, barcode scanner, or even an AR experience, manual focus control improves both usability and professional feel.

Prerequisites

Make sure your Android project is set up with:

  • Kotlin 1.8+

  • Jetpack Compose 1.5.0+

  • CameraX 1.3.0+

  • Min SDK 21

				
					dependencies {
    implementation("androidx.camera:camera-core:1.3.0")
    implementation("androidx.camera:camera-camera2:1.3.0")
    implementation("androidx.camera:camera-lifecycle:1.3.0")
    implementation("androidx.camera:camera-view:1.3.0")
    implementation("androidx.compose.ui:ui:1.5.0")
    implementation("androidx.compose.material:material:1.5.0")
    implementation("androidx.activity:activity-compose:1.7.2")
}
				
			

Step 1: Permissions and Camera Preview Setup

Add Permissions to AndroidManifest.xml: xml Copy Edit

				
					<uses-permission android:name="android.permission.CAMERA" />
				
			

Camera Permission Request Composable:

				
					@Composable
fun RequestCameraPermission(onPermissionGranted: () -> Unit) {
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) onPermissionGranted()
    }

    LaunchedEffect(Unit) {
        launcher.launch(Manifest.permission.CAMERA)
    }
}
				
			

Step 2: Create a Camera Preview Using AndroidView

CameraX’s preview is not natively compatible with Compose, so we embed PreviewView using AndroidView.

				
					@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    onPreviewReady: (PreviewView) -> Unit
) {
    AndroidView(
        factory = { context ->
            val previewView = PreviewView(context).apply {
                scaleType = PreviewView.ScaleType.FILL_CENTER
            }
            onPreviewReady(previewView)
            previewView
        },
        modifier = modifier
    )
}

				
			

Step 3: Initialize CameraX with Focus

				
					fun startCamera(
    context: Context,
    lifecycleOwner: LifecycleOwner,
    previewView: PreviewView,
    onCameraControlReady: (CameraControl, MeteringPointFactory) -> Unit
) {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

    cameraProviderFuture.addListener({
        val cameraProvider = cameraProviderFuture.get()

        val preview = Preview.Builder().build().apply {
            setSurfaceProvider(previewView.surfaceProvider)
        }

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        val camera = cameraProvider.bindToLifecycle(
            lifecycleOwner,
            cameraSelector,
            preview
        )

        val cameraControl = camera.cameraControl
        val meteringPointFactory = previewView.meteringPointFactory

        onCameraControlReady(cameraControl, meteringPointFactory)

    }, ContextCompat.getMainExecutor(context))
}

				
			

Step 4: Detect Tap Coordinates and Focus

Jetpack Compose lets us capture gestures via Modifier.pointerInput. Combine this with CameraControl and MeteringPointFactory to translate the screen tap into a camera focus action.

				
					fun focusOnTap(
    x: Float,
    y: Float,
    previewView: PreviewView,
    cameraControl: CameraControl
) {
    val meteringPoint = previewView.meteringPointFactory.createPoint(x, y)
    val action = FocusMeteringAction.Builder(meteringPoint)
        .setAutoCancelDuration(3, TimeUnit.SECONDS)
        .build()

    cameraControl.startFocusAndMetering(action)
}

				
			

Step 5: Compose Integration with Tap and Focus Ring

We now wire up everything in Compose and add a focus ring animation for visual feedback.

				
					@Composable
fun TapToFocusCamera() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    var previewViewRef by remember { mutableStateOf<PreviewView?>(null) }
    var cameraControl by remember { mutableStateOf<CameraControl?>(null) }
    var meteringFactory by remember { mutableStateOf<MeteringPointFactory?>(null) }

    var focusRingOffset by remember { mutableStateOf<Offset?>(null) }

    Box(modifier = Modifier.fillMaxSize()) {
        CameraPreview(modifier = Modifier.fillMaxSize()) { previewView ->
            previewViewRef = previewView
            startCamera(
                context,
                lifecycleOwner,
                previewView
            ) { control, factory ->
                cameraControl = control
                meteringFactory = factory
            }
        }

        Box(modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures { offset ->
                    focusRingOffset = offset
                    val x = offset.x
                    val y = offset.y

                    previewViewRef?.let { previewView ->
                        cameraControl?.let { control ->
                            focusOnTap(x, y, previewView, control)
                        }
                    }

                    // Remove ring after delay
                    CoroutineScope(Dispatchers.Main).launch {
                        delay(1000)
                        focusRingOffset = null
                    }
                }
            }
        )

        focusRingOffset?.let { offset ->
            FocusRing(offset = offset)
        }
    }
}

				
			

Step 6: Create the Focus Ring Composable

Add a simple animated ring to visualize where the user tapped.

				
					@Composable
fun FocusRing(offset: Offset) {
    val size = 100.dp
    Box(
        modifier = Modifier
            .offset {
                IntOffset(
                    (offset.x - size.toPx() / 2).toInt(),
                    (offset.y - size.toPx() / 2).toInt()
                )
            }
            .size(size)
            .border(2.dp, Color.Yellow, CircleShape)
            .background(Color.Transparent, CircleShape)
    )
}

				
			

Use Cases of Tap to Focus

  • Photography & Camera Apps: Give users manual control over subject focus.

  • Barcode/QR Scanners: Focus accurately on codes in low-light or cluttered environments.

  • AR & Object Recognition Apps: Help machine learning models concentrate on the correct region.

  • Video Streaming: Maintain sharp focus on faces or objects while broadcasting.

Tips & Best Practices

  • Debounce Tap Events
    Add a cooldown or debounce to prevent multiple focus calls rapidly.

  • Use AutoCancel
    CameraX focus metering supports auto-cancel—use it to return to auto mode.

  • Add Feedback
    Always provide visual feedback (like the ring) to show that focus occurred.

  • Edge Cases
    Handle taps near the edge or corners carefully. Some devices can’t focus at corners due to sensor limits.

  • Permission Handling
    Always check and handle camera permissions gracefully to avoid crashes

Conclusion

Jetpack Compose paired with CameraX unlocks exciting UI and camera functionality for Android developers. Adding tap-to-focus creates a smoother and more interactive user experience — and now, it’s easier than ever.

With Compose handling UI gestures and CameraX enabling easy control over the camera system, you’ve got a dynamic, efficient, and intuitive solution for building modern camera-based features

Table of Contents

Recent Comments
    April 2025
    M T W T F S S
     123456
    78910111213
    14151617181920
    21222324252627
    282930  
    Tags:
    androidiOSmobilemobile app development
    6 likes
    Leave a Comment
    Share:
    Social