To provide you as simple example as possible, I will use CameraXBasic repository on Github. My goal is to implement tap to focus feature - when user taps on the preview, the camera should focus in the indicated point. I will skip the camera configuration, as the mentioned repository is an official sample and we can assume that it was implemented according to the documentation.
So in order to intercept user taps I added this part of code:
viewFinder.setOnTouchListener { v, event ->
if (event.action != MotionEvent.ACTION_UP) {
return@setOnTouchListener true
}
val factory = SurfaceOrientedMeteringPointFactory(
v.width.toFloat(),
v.height.toFloat()
)
val focusPoint = factory.createPoint(event.x, event.y)
val focusAction = FocusMeteringAction.Builder(focusPoint).build()
camera?.cameraControl?.startFocusAndMetering(focusAction)
return@setOnTouchListener true
}
It's worth mentioning, that above method is based on this blog. Everything works fine, except the camera focuses on wrong objects. E.g. we have two objects behind the camera:
-------
| | -----
| | | |
| A | | B |
| | | |
| | -----
-------
As you can imagine, object A is closer to the device than object B. When I tap on the A object, camera focuses on the B object. And conversely, when I tap on the B object, camera focuses on the A object. It looks like library was using not the event.x value, but v.width - event.x value.
To check this behavior, you can add code from this question to the provided sample in viewFinder.post { /* */ } method.
Update
As mentioned in this answer, the issue may be related to PreviewView which I was using. When creating the focus point we have to (at least for now) translate the point to PreviewView rotation. E.g. when device is in vertical position, just use this transformation: factory.createPoint(event.y, v.width - event.x).
The blog post above (which I should update to make this point clear, my bad) assumes the view finder doesn't use cameraX's PreviewView. Manually setting up the preview by using, for example, a TextureView and configuring it with a Matrix would probably work with the code snippet you provided, the reason it doesn't work with the current CameraXBasic sample is because it uses PreviewView.
PreviewView sets up the preview differently. First of all, it's worth noting that PreviewView is a custom View that extends FrameLayout. It internally adds (ViewGroup.addView()) a TextureView (or SurfaceView) to it, which it uses to handle the preview Surface. In order to correct/scale/rotate the frames it receives from the camera to adapt it to the display, it doesn't use a Matrix, but instead it uses actual View methods (like View.setScaleX(), View.setRotation(), etc).
So when you call SurfaceOrientedMeteringPointFactory.createPoint(x, y), the coordinates you're passing in do not map correctly to the preview (TextureView for example), because the TextureView has been modified (rotated, scaled up, translated).
To get an idea of the right way to implement touch to focus using PreviewView, you can take a look at this CL made in AOSP recently, especially this file. You can expect touch to focus to be coming soon (keep an eye out on camerax-view's next release).
Edit:
PreviewView now provides an API to create a metering point (PreviewView. createMeteringPointFactory(cameraSelector)), it will handle correctly converting a touch/tap point from PreviewView to the correct coordinates on the camera sensor.
To use this API for tap-to-focus on PreviewView, you can write:
final MeteringPointFactory factory = previewView.createMeteringPointFactory(cameraSelector);
final MeteringPoint point = factory.createPoint(event.getX(), motionEvent.getY());
final FocusMeteringAction action = new FocusMeteringAction.Builder(point).build();
cameraControl.startFocusAndMetering(action)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With