Replace AndroidView with CameraXViewfinder for camera preview

This commit is contained in:
yet300
2026-01-03 22:36:43 +04:00
parent 4a04433283
commit e07b79faef
3 changed files with 48 additions and 31 deletions
+1 -1
View File
@@ -98,7 +98,7 @@ dependencies {
// CameraX // CameraX
implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view) implementation(libs.androidx.camera.compose)
// Cryptography // Cryptography
implementation(libs.bundles.cryptography) implementation(libs.bundles.cryptography)
@@ -4,13 +4,15 @@ import android.graphics.Bitmap
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.camera.compose.CameraXViewfinder
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview import androidx.camera.core.Preview
import androidx.camera.core.SurfaceRequest
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView import androidx.camera.viewfinder.core.ImplementationMode
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -41,6 +43,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -59,7 +62,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import androidx.core.graphics.set import androidx.core.graphics.set
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -73,6 +76,7 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.common.InputImage
import kotlinx.coroutines.flow.MutableStateFlow
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.google.zxing.common.BitMatrix import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.QRCodeWriter
@@ -358,7 +362,8 @@ private fun QRScannerPanel(
var lastValid by remember { mutableStateOf<String?>(null) } var lastValid by remember { mutableStateOf<String?>(null) }
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
val cameraExecutor: ExecutorService = remember { Executors.newSingleThreadExecutor() } val cameraExecutor: ExecutorService = remember { Executors.newSingleThreadExecutor() }
val barcodeView = remember { PreviewView(context) } val surfaceRequests = remember { MutableStateFlow<SurfaceRequest?>(null) }
val surfaceRequest by surfaceRequests.collectAsState(initial = null)
val mainHandler = remember { Handler(Looper.getMainLooper()) } val mainHandler = remember { Handler(Looper.getMainLooper()) }
val onCodeState = rememberUpdatedState(onScan) val onCodeState = rememberUpdatedState(onScan)
@@ -373,31 +378,40 @@ private fun QRScannerPanel(
} }
DisposableEffect(permissionState.status.isGranted) { DisposableEffect(permissionState.status.isGranted) {
var cameraProvider: ProcessCameraProvider? = null
if (permissionState.status.isGranted) { if (permissionState.status.isGranted) {
val cameraProvider = cameraProviderFuture.get() val executor = ContextCompat.getMainExecutor(context)
val preview = Preview.Builder() cameraProviderFuture.addListener(
.build() {
.also { it.surfaceProvider = barcodeView.surfaceProvider } val provider = cameraProviderFuture.get()
val analysis = ImageAnalysis.Builder() cameraProvider = provider
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) val preview = Preview.Builder()
.build() .build()
.also { it.setAnalyzer(cameraExecutor, analyzer) } .also { it.setSurfaceProvider { request -> surfaceRequests.value = request } }
val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { it.setAnalyzer(cameraExecutor, analyzer) }
runCatching { runCatching {
cameraProvider.unbindAll() provider.unbindAll()
cameraProvider.bindToLifecycle( provider.bindToLifecycle(
lifecycleOwner, lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA, CameraSelector.DEFAULT_BACK_CAMERA,
preview, preview,
analysis analysis
) )
}.onFailure { }.onFailure {
Log.w("VerificationSheet", "Failed to bind camera: ${it.message}") Log.w("VerificationSheet", "Failed to bind camera: ${it.message}")
} }
},
executor
)
} }
onDispose { onDispose {
runCatching { cameraProviderFuture.get().unbindAll() } surfaceRequests.value = null
runCatching { cameraProvider?.unbindAll() }
} }
} }
@@ -423,12 +437,15 @@ private fun QRScannerPanel(
) )
if (permissionState.status.isGranted) { if (permissionState.status.isGranted) {
AndroidView( surfaceRequest?.let { request ->
factory = { barcodeView }, CameraXViewfinder(
modifier = Modifier surfaceRequest = request,
.size(220.dp) implementationMode = ImplementationMode.EMBEDDED,
.clipToBounds() modifier = Modifier
) .size(220.dp)
.clipToBounds()
)
}
} else { } else {
Text( Text(
text = stringResource(R.string.verify_camera_permission), text = stringResource(R.string.verify_camera_permission),
+1 -1
View File
@@ -117,7 +117,7 @@ zxing-core = { module = "com.google.zxing:core", version.ref = "zxing-core" }
# CameraX / ML Kit # CameraX / ML Kit
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } androidx-camera-compose = { module = "androidx.camera:camera-compose", version.ref = "camerax" }
mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkit-barcode" } mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkit-barcode" }
# Testing # Testing