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.
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.
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")
}
Add Permissions to AndroidManifest.xml: xml Copy Edit
@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)
}
}
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
)
}
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))
}
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)
}
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(null) }
var cameraControl by remember { mutableStateOf(null) }
var meteringFactory by remember { mutableStateOf(null) }
var focusRingOffset by remember { mutableStateOf(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)
}
}
}
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)
)
}
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.
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
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
M | T | W | T | F | S | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
We are a team of artists. We provide professional services in the field of Mobile Applications, Web Applications and everything related to IT services. Turning to us for help once – you can no longer refuse.
© 2019 – 2022 | Made with ❤️ by App Ringer
Recent Comments