Let’s build an Android camera app! CameraX + ComposeВ данной статье автор рассказывает как можно создать просто приложение камера на Compose, используя библиотеку CameraX.
🔸 CameraX имеет 4 конкретных юз кейса:
1️⃣ Предварительный просмотр
2️⃣ Захват изображения для фотосъемки
3️⃣ Видеозахват для захвата движущихся изображений и звука
4️⃣ Анализ изображения, который позволяет делать умные вещи с выходными данными камеры в реальном времени
🔸 Первым шагом добавляем зависимость библиотеки
🔸 Вторым шагом добавляем PreviewView на UI:
@Composablefun CameraPreview(
modifier: Modifier = Modifier
) {
AndroidView(modifier = modifier,
factory = { context ->
PreviewView(context)
}
)
}
🔸 Запустив данный код получим белый экран, потому что
PreviewView по сути является просто холстом для рисования
🔸 Далее нужно создать вариант использования Preview:
@Composablefun CameraPreview( ... ) {
val previewUseCase = remember { androidx.camera.core.Preview.Builder().build() }
...
}
🔸 Затем нужно сказать Preview рисовать на холсте, предоставленном PreviewView:
AndroidView(
...
factory = { context ->
PreviewView(context).also {
previewUseCase.surfaceProvider = it.surfaceProvider
}
}
)
🔸 CameraProvider используется для привязки вариантов использования к камере и для того, чтобы делать это с учетом жизненного цикла:
@Composablefun CameraPreview( … ) {
val localContext = LocalContext.current
LaunchedEffect(Unit) {
cameraProvider = ProcessCameraProvider.awaitInstance(localContext)
}
…
}
🔸 Теперь сможем связать вариант использования Preview с камерой и жизненным циклом:
@Composablefun CameraPreview( … ) {
…
fun rebindCameraProvider() {
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build()
cameraProvider?.bindToLifecycle(
localLifecycleOwner,
cameraSelector,
previewUseCase
)
}
…
}
🔸 Функцию rebindCameraProvider() нужно вызывать только один раз, когда доступны и PreviewView, и ProcessCameraProvider:
AndroidView(modifier = modifier,
factory = { context ->
PreviewView(context).also {
previewUseCase.surfaceProvider = it.surfaceProvider
rebindCameraProvider()
}
}
)
🔸 Добавим параметр в наш Composable, чтобы можно было контролировать какую камеру использовать. Поскольку выбор камеры указан как часть привязки CameraProvider, нужно будет выполнить повторную привязку, когда она изменится:
@Composablefun CameraPreview(
lensFacing: Int = CameraSelector.LENS_FACING_BACK,
…
) {
…
fun rebindCameraProvider() {
cameraProvider.unbindAll()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build()
…
}
LaunchedEffect(lensFacing) {
rebindCameraProvider()
}
…
}
🔸 В итоге результат получается
такой🔸 Еще можно добавить зум:
@Composablefun CameraPreview(
zoomLevel: Float,
...
) {
...
LaunchedEffect(zoomLevel) {
cameraControl?.setLinearZoom(zoomLevel)
}
…
}
🔸 Чтобы захватить изображение с камеры в полном разрешении, нужно использовать вариант использования ImageCapture:
@Composablefun CameraPreview(
...
imageCaptureUseCase: ImageCapture
) {
...
fun rebindCameraProvider() {
cameraProvider?.let { cameraProvider ->
val camera = cameraProvider.bindToLifecycle(
localContext as LifecycleOwner,
cameraSelector,
previewUseCase, imageCaptureUseCase
)
…
}
}
…
}