You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将CameraX与SurfaceView/TextureView配合使用?Camera API迁移至CameraX时的预览组件替代方案及显式配置方法

Using SurfaceView/TextureView Directly with CameraX (Instead of PreviewView)

Great question! It makes total sense to avoid relying on an alpha-stage component like PreviewView if you prefer more control over your preview surface. While PreviewView wraps SurfaceView/TextureView under the hood, you can absolutely use these views directly with CameraX by leveraging the SurfaceProvider interface. Let's break down how to do this for both SurfaceView and TextureView:

Using SurfaceView with CameraX

Step 1: Add SurfaceView to your layout

First, include a SurfaceView in your XML layout (or create it programmatically):

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/camera_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Step 2: Set up CameraX and SurfaceHolder Callback

In your Activity/Fragment, get a reference to the SurfaceView, then set up a SurfaceHolder.Callback to listen for when the surface is ready. Then you can bind the CameraX Preview to this surface:

class CameraActivity : AppCompatActivity() {
    private lateinit var surfaceView: SurfaceView
    private var cameraProvider: ProcessCameraProvider? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)
        surfaceView = findViewById(R.id.camera_surface_view)

        // Request camera permissions first (skip if already handled)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED) {
            startCameraSetup()
        } else {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
        }
    }

    private fun startCameraSetup() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            cameraProvider = cameraProviderFuture.get()
            bindPreview()
        }, ContextCompat.getMainExecutor(this))
    }

    private fun bindPreview() {
        cameraProvider?.let { provider ->
            // Unbind any existing use cases first
            provider.unbindAll()

            // Create Preview use case
            preview = Preview.Builder()
                // Optional: Set preferred resolution to match SurfaceView size
                .setResolutionSelector(ResolutionSelector.Builder()
                    .setPreferredAspectRatio(AspectRatio.RATIO_16_9)
                    .build())
                .build()

            // Configure SurfaceProvider to use our SurfaceView's surface
            preview?.setSurfaceProvider { request ->
                val surfaceHolder = surfaceView.holder
                // Check if the surface is valid before providing it
                if (surfaceHolder.surface.isValid) {
                    request.provideSurface(surfaceHolder.surface, ContextCompat.getMainExecutor(this)) {
                        // Handle surface release if needed
                    }
                } else {
                    // Listen for surface creation if it's not ready yet
                    surfaceHolder.addCallback(object : SurfaceHolder.Callback {
                        override fun surfaceCreated(holder: SurfaceHolder) {
                            if (holder.surface.isValid) {
                                request.provideSurface(holder.surface, ContextCompat.getMainExecutor(this)) {
                                    holder.removeCallback(this)
                                }
                            }
                        }

                        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
                        override fun surfaceDestroyed(holder: SurfaceHolder) {}
                    })
                }
            }

            // Select back camera as default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            // Bind the Preview use case to the camera
            try {
                provider.bindToLifecycle(this, cameraSelector, preview)
            } catch(exc: Exception) {
                Log.e("CameraX", "Use case binding failed", exc)
            }
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            startCameraSetup()
        }
    }
}

Using TextureView with CameraX

The process is very similar to SurfaceView, but we'll use TextureView.SurfaceTextureListener instead:

Step 1: Add TextureView to your layout

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/camera_texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Step 2: Set up CameraX and SurfaceTextureListener

class CameraActivity : AppCompatActivity() {
    private lateinit var textureView: TextureView
    private var cameraProvider: ProcessCameraProvider? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)
        textureView = findViewById(R.id.camera_texture_view)

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED) {
            startCameraSetup()
        } else {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
        }
    }

    private fun startCameraSetup() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            cameraProvider = cameraProviderFuture.get()
            bindPreview()
        }, ContextCompat.getMainExecutor(this))
    }

    private fun bindPreview() {
        cameraProvider?.let { provider ->
            provider.unbindAll()

            preview = Preview.Builder()
                .setResolutionSelector(ResolutionSelector.Builder()
                    .setPreferredAspectRatio(AspectRatio.RATIO_16_9)
                    .build())
                .build()

            preview?.setSurfaceProvider { request ->
                val surfaceTexture = textureView.surfaceTexture
                if (surfaceTexture != null) {
                    val surface = Surface(surfaceTexture)
                    request.provideSurface(surface, ContextCompat.getMainExecutor(this)) {
                        surface.release()
                    }
                } else {
                    textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
                        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
                            val cameraSurface = Surface(surface)
                            request.provideSurface(cameraSurface, ContextCompat.getMainExecutor(this)) {
                                cameraSurface.release()
                                textureView.surfaceTextureListener = null
                            }
                        }

                        override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
                        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = true
                        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
                    }
                }
            }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            try {
                provider.bindToLifecycle(this, cameraSelector, preview)
            } catch(exc: Exception) {
                Log.e("CameraX", "Use case binding failed", exc)
            }
        }
    }

    // Permission handling same as SurfaceView example
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            startCameraSetup()
        }
    }
}

Key Notes & Best Practices

  • Surface Lifecycle: Always make sure the surface is valid (created, not destroyed) before passing it to CameraX. The callbacks (SurfaceHolder.Callback/SurfaceTextureListener) help you handle this correctly.
  • Resolution Matching: Use ResolutionSelector in the Preview.Builder to align the camera's output resolution with your view's aspect ratio—this prevents stretching or cropping of the preview.
  • Resource Cleanup: When the surface is released, make sure to clean up any Surface objects you create (like in the TextureView example) to avoid memory leaks.
  • Lifecycle Binding: Binding the use case to your Activity/Fragment lifecycle ensures CameraX handles camera resource management automatically when your app goes into the background.

内容的提问来源于stack exchange,提问作者Deepu George Jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 05:57:47