user15444913
user15444913

Reputation:

How can I use a PreviewView inside jetpack compose?

I was trying to use a CameraX PreviewView inside of a Composable via AndroidView, but the preview is stretched and the right half is clipped as you can see in the screenshot. Nevertheless the view seems to occupy the correct space. This problem only occurs in portrait mode. I was able to reproduce the problem on two cell phones and in the emulator so I doubt it is hardware specific.

I have tried different scale types, but that does not seem to affect the stretching. I would report a bug, but I am not sure whether this is a bug in compose or cameraX.

See the code I use below:

package com.example.camerapreview

import android.Manifest
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.LinearLayout
import androidx.activity.ComponentActivity
import androidx.activity.compose.registerForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.AspectRatio.RATIO_16_9
import androidx.camera.core.CameraSelector
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.example.pointergun.ui.theme.PointerGunTheme
import com.google.common.util.concurrent.ListenableFuture
import androidx.camera.core.Preview

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        setContent {
            PointerGunTheme {
                Surface(color = MaterialTheme.colors.background) {
                    var isPermissionGranted by remember {
                        mutableStateOf<Boolean?>(null)
                    }
                    val launcher =
                        registerForActivityResult(contract = ActivityResultContracts.RequestPermission()) { isGranted ->
                            isPermissionGranted = isGranted
                        }
                    when (isPermissionGranted) {
                        true -> CameraPreview(cameraProviderFuture)
                        false -> Text("permission pls")
                        null -> Button(onClick = { launcher.launch(Manifest.permission.CAMERA) }) {
                            Text(text = "Start!")
                        }
                    }
                }
            }
        }
    }
}

fun bindPreview(
    cameraProvider: ProcessCameraProvider,
    lifecycleOwner: LifecycleOwner,
    previewView: PreviewView,
) {
    val preview: Preview = Preview.Builder()
        .setTargetAspectRatio(RATIO_16_9)
        .build()

    val cameraSelector: CameraSelector = CameraSelector.Builder()
        .requireLensFacing(CameraSelector.LENS_FACING_BACK)
        .build()

    preview.setSurfaceProvider(previewView.surfaceProvider)

    var camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
}

@Composable
fun CameraPreview(cameraProviderFuture: ListenableFuture<ProcessCameraProvider>) {

    val lifecycleOwner = LocalLifecycleOwner.current
    AndroidView(
        factory = { context ->
            PreviewView(context).apply {
                setBackgroundColor(Color.GREEN)
                layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
                scaleType = PreviewView.ScaleType.FILL_START
                post {
                    cameraProviderFuture.addListener(Runnable {
                        val cameraProvider = cameraProviderFuture.get()
                        bindPreview(
                            cameraProvider,
                            lifecycleOwner,
                            this,
                        )
                    }, ContextCompat.getMainExecutor(context))
                }
            }
        }
    )
}

Upvotes: 12

Views: 4421

Answers (1)

Stefano Sansone
Stefano Sansone

Reputation: 2709

Need to set the implementation mode for the PreviewView

implementationMode = PreviewView.ImplementationMode.COMPATIBLE

In your case :

PreviewView(context).apply {
    setBackgroundColor(Color.GREEN)
    layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
    scaleType = PreviewView.ScaleType.FILL_START
    implementationMode = PreviewView.ImplementationMode.COMPATIBLE
    post {
        cameraProviderFuture.addListener(Runnable {
            val cameraProvider = cameraProviderFuture.get()
            bindPreview(
                cameraProvider,
                lifecycleOwner,
                this,
            )
        }, ContextCompat.getMainExecutor(context))
    }
}

Upvotes: 11

Related Questions