Android 使用 CameraX 实现自定义相机拍照(二)

3,204 阅读4分钟

前言

上一篇文章说了一些关于相机预览、拍照、镜头翻转、闪光灯、图片旋转、图片镜像、图片压缩等常见功能的实现,这一篇补上其余常见的功能,比如双指缩放视图、单击聚焦预览视图、设置图片纵横比为 4:3 或 16:9、预览视图的全屏显示等这些功能。

代码实现

双指缩放视图

camera?.cameraControl?.setZoomRatio(float ratio) 就是CameraX 提供的用于缩放的 Api,只要将 setZoomRatio 的参数和手势缩放结合到一起,就能实现双指缩放视图的功能。

@SuppressLint("ClickableViewAccessibility")
private fun setZoomByScaleGesture() {
    val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val currentZoomRatio = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 1f
            val delta = detector.scaleFactor
            camera?.cameraControl?.setZoomRatio(currentZoomRatio * delta)
            return true
        }
    }

    val scaleGestureDetector = ScaleGestureDetector(this, listener)
    viewBinding.viewFinder.setOnTouchListener { view, event ->
scaleGestureDetector.onTouchEvent(event)
        true
    }
}

单击聚焦视图

startFocusAndMetering 就是CameraX 提供的用于聚焦的 Api,只要在手指按下的时候,将按下位置作为聚焦的焦点就行了。项目运行以后,我们就能看到图片的聚焦点附近比较清晰,距离聚焦点较远的位置会略显模糊。

viewBinding.viewFinder.setOnTouchListener { view, event ->
scaleGestureDetector.onTouchEvent(event)
    if (event.action == MotionEvent.ACTION_DOWN) {
        val factory = viewBinding.viewFinder.meteringPointFactory
val point = factory.createPoint(event.x, event.y)
        val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
            .setAutoCancelDuration(2, TimeUnit.SECONDS).build()

        camera?.cameraControl?.startFocusAndMetering(action)

        view.performClick()
    }
    true
} 

系统的相机 App 在手指单击聚焦的时候一般都会显示一个聚焦态,用于显示你手指点击的位置,也就是聚焦的位置。如果我们也想实现这个功能的话,可以实现一个自定义 View,手指点击时,显示这个自定义 View,500ms 后再自动隐藏它,显示的时机和 startFocusAndMetering 方法执行时机一致。

viewBinding.viewFinder.setOnTouchListener { view, event ->
scaleGestureDetector.onTouchEvent(event)
    if (event.action == MotionEvent.ACTION_DOWN) {
        val factory = viewBinding.viewFinder.meteringPointFactory
        val point = factory.createPoint(event.x, event.y)
        val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
            .setAutoCancelDuration(2, TimeUnit.SECONDS).build()

        val x = event.x
        val y = event.y

        val focusCircle = RectF(x - 50, y - 50, x + 50, y + 50)

        viewBinding.focusCircleView.focusCircle = focusCircle
        viewBinding.focusCircleView.invalidate()

        camera?.cameraControl?.startFocusAndMetering(action)

        view.performClick()
    }
    true
} 

设置纵横比

如果想动态修改拍摄后的图片是 4:3 还是 16:9,需要在构建 ImageCapture 时设置 setResolutionSelector。

val resolutionSelector = ResolutionSelector.Builder()
    .setAspectRatioStrategy(
        AspectRatioStrategy(
            AspectRatio.RATIO_16_9,
            AspectRatioStrategy.FALLBACK_RULE_AUTO
        )
    ).build()
 
 ImageCapture.Builder().setResolutionSelector(resolutionSelector).build()

如果还想让预览视图也变成4:3 或 16:9,可以通过 layout_constraintDimensionRatio 属性配置,dimensionRatio 属性也可以通过 kotlin 代码动态修改。

<androidx.camera.view.PreviewView
    android:id="@+id/viewFinder"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintDimensionRatio="9:16"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

全屏显示

如果想要实现相机预览视图的全屏显示,可以看看我之前写的“edge to edge”文章。实现思路就是启用“edge to edge”特性,且不给预览视图设置边衬。将页面布局结构修改为外层 ConstraintLayout 嵌套内层 ConstraintLayout,内层 ConstraintLayout 的 id 设置为 main,在设置边衬区时只给 main 布局添加边衬。修改后的页面的布局如下所示。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    tools:context=".TakePhotoActivity">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.isuperred.camerax.FocusCircleView
        android:id="@+id/focusCircleView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="@+id/viewFinder"
        app:layout_constraintEnd_toEndOf="@+id/viewFinder"
        app:layout_constraintStart_toStartOf="@+id/viewFinder"
        app:layout_constraintTop_toTopOf="@+id/viewFinder" />


    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageButton
            android:id="@+id/cameraCaptureBtn"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_marginBottom="20dp"
            android:background="@drawable/take_photo_btn"
            android:scaleType="fitCenter"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            tools:ignore="ContentDescription" />

        <ImageButton
            android:id="@+id/flipCameraBtn"
            android:layout_width="28dp"
            android:layout_height="28dp"
            android:layout_margin="16dp"
            android:background="@drawable/flip_camera_btn"
            android:scaleType="fitCenter"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="ContentDescription" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

这里将设置边衬区的代码也给一下,如下所示。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    viewBinding = ActivityTakePhotoBinding.inflate(layoutInflater)
    setContentView(viewBinding.root)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        window.isNavigationBarContrastEnforced = false
    }
    ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
        insets
    }
}

这样修改以后再运行项目,就能看到相机预览视图是全屏显示了。

结论

在本篇文章中,我们了解了如何通过 CameraX 库实现更多实用的相机功能。这些功能包括:

双指缩放视图: 利用 ScaleGestureDetector 结合 cameraControl.setZoomRatio 来实现。

单击聚焦视图: 通过 startFocusAndMetering 设置点击位置为聚焦点,并使用自定义 View 来显示聚焦状态。

设置图片纵横比: 通过 ImageCapture.Builder 中的 setResolutionSelector 动态调整图片的纵横比,并通过layout_constraintDimensionRatio 属性动态调整预览视图的比例。

全屏显示预览视图: 通过启用“edge to edge”特性和使用适当的布局结构,实现预览视图的全屏显示。

这里将这些功能记录下来,之后再用到这些功能时,就省的再去查资料了,希望本文对你有所帮助。

如果你有任何问题或建议,欢迎随时提出!