返回

Android 悬浮窗预览:将摄像头视图悬浮在应用程序之上

Android

引言

在当今移动优先的世界中,摄像头已成为智能手机不可或缺的一部分。从捕捉珍贵的回忆到进行视频通话,摄像头功能已成为我们日常生活的重要组成部分。随着 Android 领域的不断发展,开发人员正在不断寻求创新方式来增强摄像头体验。悬浮窗预览就是其中一项令人兴奋的创新,它允许用户在使用其他应用程序时同时预览摄像头视图。

本教程将深入探讨使用 CameraX 和悬浮窗技术在 Android 中实现悬浮窗预览。我们将逐步介绍从创建摄像头预览到实现可拖动悬浮窗的所有步骤,并提供代码示例和最佳实践指南。

先决条件

在开始之前,确保您已满足以下先决条件:

  • Android Studio 4.0 或更高版本
  • Android 设备或模拟器,运行 Android 5.0 或更高版本
  • 基本 Android 开发知识

步骤 1:创建摄像头预览

首先,让我们创建一个基本的摄像头预览。为此,我们将使用 CameraX 库,该库是 Android 中用于相机开发的最新推荐库。

在您的项目中添加以下依赖项:

implementation "androidx.camera:camera-core:1.1.0-rc01"
implementation "androidx.camera:camera-view:1.1.0-rc01"

在您的活动中,创建 CameraView 对象并将其添加到布局中:

private lateinit var cameraView: CameraView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    cameraView = findViewById(R.id.camera_view)
    cameraView.bindToLifecycle(this)
}

这将创建一个摄像头预览,该预览将实时显示摄像头帧。

步骤 2:创建悬浮窗

接下来,让我们创建悬浮窗。我们将使用 WindowManager 类来创建和管理悬浮窗。

在您的活动中,创建 WindowManager 对象并设置悬浮窗的参数:

private val windowManager: WindowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }

private fun createFloatingWindow() {
    val windowLayoutParams = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        PixelFormat.TRANSLUCENT
    )

    val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
    val floatingWindowView = inflater.inflate(R.layout.floating_window, null)

    windowManager.addView(floatingWindowView, windowLayoutParams)
}

这将创建一个透明的悬浮窗,位于所有其他应用程序之上。

步骤 3:将摄像头预览添加到悬浮窗

现在,让我们将摄像头预览添加到悬浮窗。为此,我们需要创建一个 SurfaceTexture 并将其传递给悬浮窗视图。

private fun addCameraPreviewToFloatingWindow() {
    val surfaceTexture = SurfaceTexture(10)
    cameraView.implementationMode = CameraX.LensFacing.BACK
    cameraView.surfaceProvider = SurfaceTextureProvider.createSurfaceTextureProvider(surfaceTexture)
    
    val floatingWindowView = windowManager.findViewWithTag("floating_window")
    val surfaceView = floatingWindowView.findViewById<SurfaceView>(R.id.surface_view)
    surfaceView.holder.surfaceTexture = surfaceTexture
}

这将创建一个 SurfaceView,该 SurfaceView 将显示摄像头预览,并将其添加到悬浮窗视图中。

步骤 4:实现可拖动悬浮窗

最后,让我们实现可拖动悬浮窗。为此,我们需要使用 TouchListener 并更新悬浮窗的参数。

private var initialX: Float = 0f
private var initialY: Float = 0f
private var isDragging = false

private fun makeFloatingWindowDraggable() {
    val floatingWindowView = windowManager.findViewWithTag("floating_window")

    floatingWindowView.setOnTouchListener { _, event ->
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                initialX = event.rawX
                initialY = event.rawY
                isDragging = true
            }
            MotionEvent.ACTION_MOVE -> {
                if (isDragging) {
                    val dx = event.rawX - initialX
                    val dy = event.rawY - initialY
                    val windowLayoutParams = floatingWindowView.layoutParams as WindowManager.LayoutParams
                    windowLayoutParams.x = dx.toInt()
                    windowLayoutParams.y = dy.toInt()
                    windowManager.updateViewLayout(floatingWindowView, windowLayoutParams)
                }
            }
            MotionEvent.ACTION_UP -> {
                isDragging = false
            }
        }
        true
    }
}

这将允许用户拖动悬浮窗,使其在屏幕上的任何位置。

代码示例

以下是完整代码示例:

class MainActivity : AppCompatActivity() {

    private lateinit var cameraView: CameraView
    private val windowManager: WindowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        cameraView = findViewById(R.id.camera_view)
        cameraView.bindToLifecycle(this)

        createFloatingWindow()
        addCameraPreviewToFloatingWindow()
        makeFloatingWindowDraggable()
    }

    private fun createFloatingWindow() {
        val windowLayoutParams = WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )

        val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
        val floatingWindowView = inflater.inflate(R.layout.floating_window, null)

        windowManager.addView(floatingWindowView, windowLayoutParams)
    }

    private fun addCameraPreviewToFloatingWindow() {
        val surfaceTexture = SurfaceTexture(10)
        cameraView.implementationMode = CameraX.LensFacing.BACK
        cameraView.surfaceProvider = SurfaceTextureProvider.createSurfaceTextureProvider(surfaceTexture)
    
        val floatingWindowView = windowManager.findViewWithTag("floating_window")
        val surfaceView = floatingWindowView.findViewById<SurfaceView>(R.id.surface_view)
        surfaceView.holder.surfaceTexture = surfaceTexture
    }

    private fun makeFloatingWindowDraggable() {
        val floatingWindowView = windowManager.findViewWithTag("floating_window")

        floatingWindowView.setOnTouchListener { _, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    initialX = event.rawX
                    initialY = event.rawY
                    isDragging = true
                }
                MotionEvent.ACTION_MOVE -> {
                    if (isDragging) {
                        val dx = event.rawX - initialX
                        val dy = event.rawY - initialY
                        val windowLayoutParams = floatingWindowView.layoutParams as WindowManager.LayoutParams
                        windowLayoutParams.x = dx.toInt()
                        windowLayoutParams.y = dy.toInt()
                        windowManager.updateViewLayout(floatingWindowView, windowLayoutParams)
                    }
                }
                MotionEvent.ACTION_UP -> {
                    isDragging = false
                }
            }
            true
        }
    }
}

总结

通过结合 CameraX 和悬浮窗技术,我们已经创建了一个可拖动的悬浮窗,可以实时预览摄像头。这种创新方法为开发人员提供了新的可能性,让他们可以在应用程序中集成更高级的摄像头功能。

随着 Android 生态系统的不断发展,我们可以期待看到更多激动人心的创新,它们将进一步提升移动摄像头体验。通过利用这些强大的工具,开发人员可以创建引人入胜的应用程序,为用户提供前所未有的交互体验。

SEO优化