How to auto-focus with Android CameraX

Just point out, to get the "Tap to focus" working with PreviewView, you need to use DisplayOrientedMeteringPointFactory. Otherwise you'll get messed up coordinates.

val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())

For the rest use the MatPag's answer.


There is an issue with some android devices where the camera's aren't auto-focusing with CameraX. The CameraX team is aware of it and are tracking it with an internal ticket and hopefully will have a fix soon.


With the current CameraX 1.0.0, you can proceed in this 2 ways:

  1. Auto-focus every X seconds:

     previewView.afterMeasured {
         val autoFocusPoint = SurfaceOrientedMeteringPointFactory(1f, 1f)
                 .createPoint(.5f, .5f)
         try {
             val autoFocusAction = FocusMeteringAction.Builder(
                 autoFocusPoint,
                 FocusMeteringAction.FLAG_AF
             ).apply {
                 //start auto-focusing after 2 seconds
                 setAutoCancelDuration(2, TimeUnit.SECONDS)
             }.build()
             camera.cameraControl.startFocusAndMetering(autoFocusAction)
         } catch (e: CameraInfoUnavailableException) {
             Log.d("ERROR", "cannot access camera", e)
         }
     }
    
  2. Focus on-tap:

     previewView.afterMeasured {
         previewView.setOnTouchListener { _, event ->
             return@setOnTouchListener when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     true
                 }
                 MotionEvent.ACTION_UP -> {
                     val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
                         previewView.width.toFloat(), previewView.height.toFloat()
                     )
                     val autoFocusPoint = factory.createPoint(event.x, event.y)
                     try {
                         camera.cameraControl.startFocusAndMetering(
                             FocusMeteringAction.Builder(
                                 autoFocusPoint,
                                 FocusMeteringAction.FLAG_AF
                             ).apply {
                                 //focus only when the user tap the preview
                                 disableAutoCancel()
                             }.build()
                         )
                     } catch (e: CameraInfoUnavailableException) {
                         Log.d("ERROR", "cannot access camera", e)
                     }
                     true
                 }
                 else -> false // Unhandled event.
             }
         }
     }
    

afterMeasured extension function is a simple utility: (thanks ch271828n for improving it)

inline fun View.afterMeasured(crossinline block: () -> Unit) {
    if (measuredWidth > 0 && measuredHeight > 0) {
        block()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.GlobalLayoutListener {
            override fun onGlobalLayout() {
                if (measuredWidth > 0 && measuredHeight > 0) {
                    viewTreeObserver.removeOnGlobalLayoutListener(this)
                    block()
                } 
            }
        })
    }
}

A Camera object can be obtained with

val camera = cameraProvider.bindToLifecycle(
    this@Activity, cameraSelector, previewView //this is a PreviewView
)