1

I want to use only square crop of the image from sensor, therefore I want to have square preview in UI, other UI elements are supposed to be placed outside of preview. UI is built using jetpack compose. But somehow I'm getting preview looking like 3:4 rectangle, and preview is overlapping with other UI elements, which are arranged as expected for square preview.

Project illustrating the problem on github: https://github.com/skitov/android_camera

Code that I have tried


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyApplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Content(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

    @Composable
    fun CameraPreview()
    {
        val previewH = (LocalConfiguration.current.screenWidthDp).dp
        fun startCamera(context: Context,
                        previewView: PreviewView) {
            val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

            cameraProviderFuture.addListener({
                val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

                // CameraSelector for back camera
                val cameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build()

                // Set up Preview use case
                val preview = Preview.Builder().build().also {
                    it.surfaceProvider = previewView.surfaceProvider
                }
                try {
                    // Unbind all use cases before rebinding
                    cameraProvider.unbindAll()

                    val viewPort = ViewPort.Builder(Rational(1,1), Surface.ROTATION_0).
                    setScaleType(ViewPort.FILL_CENTER).build()
                    val useGrp = UseCaseGroup.Builder().
                    addUseCase(preview).
                    addUseCase(ImageAnalysis.Builder().build()).
                    setViewPort(viewPort).build()
                    // Bind the camera to the lifecycle and the Preview use case
                    cameraProvider.bindToLifecycle(context as LifecycleOwner,
                        cameraSelector, useGrp)
                } catch (exc: Exception) {
                    Log.e("CameraXApp", "Use case binding failed", exc)
                }
            }, ContextCompat.getMainExecutor(context))
        }

        Box(modifier = Modifier.fillMaxWidth().height(previewH))
        {
            AndroidView(
                modifier = Modifier.fillMaxSize()
                    .onGloballyPositioned { c ->
                        Log.i("PREVIEW", "Androview size: ${c.size}")
                    }
                ,
                factory = { context ->
                    val screenWidth = Resources.getSystem().displayMetrics.widthPixels
                    val previewView = PreviewView(context).apply {
                        scaleType = PreviewView.ScaleType.FILL_CENTER
                        layoutParams = ViewGroup.LayoutParams(
                            screenWidth,
                            screenWidth
                        )
                    }
                    //focusFactory = previewView.meteringPointFactory
                    startCamera(context, previewView)
                    previewView
                }
            )
        }
    }

    @Composable
    fun Content(modifier: Modifier = Modifier)
    {
        val ctx = LocalContext.current
        val needPermission = remember {
            mutableStateOf(ContextCompat.checkSelfPermission(ctx, Manifest.permission.CAMERA) !=
                    PackageManager.PERMISSION_GRANTED)}
        val CAMERA_PERMISSION_REQUEST_CODE = 101
        @Composable
        fun PermissionRequestUI() {
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text("Camera permission is required to use this feature.")
                Button(onClick = { ActivityCompat.requestPermissions(
                    ctx as Activity,
                    arrayOf(Manifest.permission.CAMERA),
                    CAMERA_PERMISSION_REQUEST_CODE
                )
                    needPermission.value = false}) {
                    Text("Grant Permission")
                }
            }
        }

        if(needPermission.value)
        {
            PermissionRequestUI()
        }
        else {
            Column(modifier = modifier)
            {
                CameraPreview()
                Text("ABYR")
                Text("VALG")
                Text("qwert")
                Text("zxcv")
            }
        }
    }

1 Answer 1

1
@Composable
fun CameraPreview() {
    val screenWidthDp = LocalConfiguration.current.screenWidthDp
    val previewSize = screenWidthDp.dp

    Box(
        modifier = Modifier
            .size(previewSize) // Enforces square preview
            .background(Color.Black) // Optional: helps visualize the box
    ) {
        AndroidView(
            modifier = Modifier
                .fillMaxSize()
                .onGloballyPositioned {
                    Log.i("PREVIEW", "AndroidView size: ${it.size}")
                },
            factory = { context ->
                val previewView = PreviewView(context).apply {
                    scaleType = PreviewView.ScaleType.FILL_CENTER
                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                }
                startCamera(context, previewView)
                previewView
            }
        )
    }
}

The issue stems from PreviewView not being properly constrained to a square size, even though you're passing layoutParams with square dimensions inside AndroidView.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer, but this doesn't seem to change anything, actually I started from LayoutParams.MATCH_PARENT

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.