Отображение превью с камеры — обычный вариант использования у любого приложения для съемки фото и видео. Однако сих пор это было довольно трудно сделать правильно, в основном из-за сложностей, связанных с граничными случаями API camera2 и поведением конкретных устройств. PreviewView, часть библиотеки CameraX Jetpack, упрощает отображение превью камеры, предоставляя удобный разработчикам, последовательный и стабильный API для широкого спектра устройств Android.
Знакомство с PreviewView
PreviewView — это кастомный View, который позволяет отображать ленту камеры. Он был спроектирован, чтобы уменьшить нагрузку по настройке и обработке поверхности превью, которая используется камерой.
Если вам нужно отобразить базовое превью камеры в приложении, рекомендуется использовать PreviewView, так как он:
- Проще в использовании:
PreviewView— этоView. Он осуществляет всю работу, необходимую для отображения того, что видит камера в вашем макете, управляя поверхностьюSurface, применяемой в варианте использованияPreview. - Мало весит:
PreviewViewфокусируется исключительно на превью. Все его внутренние ресурсы направлены на то, чтобы настроить превью камеры и управлять поверхностью превью во время ее использования. Такое разделение ответственности позволяет кодуPreviewViewоставаться чистым. - Исчерпывающий:
PreviewViewобрабатывает все сложные детали отображения ленты камеры на экране, включая соотношение сторон, масштабирование и вращение. Он также содержит исправления совместимости и обходные пути, которые обеспечивают плавное взаимодействие со множеством различных устройств, разрешений экрана, уровней аппаратной поддержки камеры и конфигураций дисплея, таких как режим разделенного экрана, фиксированная ориентация и мульти-окно свободной формы.
PreviewView — режимы реализации
PreviewView — это подкласс FrameLayout. Для отображения ленты камеры он использует либо SurfaceView, либо TextureView, предоставляет камере готовую поверхность превью, пытается сохранить ее в актуальном состоянии, пока камера использует ее, и при преждевременном освобождении предоставляет новую поверхность, если камера все еще используется.
SurfaceView, как правило, лучше TextureView, когда речь заходит о некоторых ключевых метриках, включающих заряд и время ожидания, поэтому PreviewView пытается использовать SurfaceView по умолчанию. Однако некоторые устройства (в основном устаревшие) аварийно завершают работу, когда поверхность превью освобождается преждевременно. К сожалению, при использовании SurfaceView невозможно контролировать, когда поверхность освобождается, так как это контролируется иерархией View. На таких устройствах PreviewView вынужденно возвращается к использованию TextureView. Вы также должны настроить PreviewView на использование TextureView в тех случаях, когда требуется вращение превью, прозрачность или анимация.
Вы можете в явном виде задать ту реализацию, которую хотите использовать в PreviewView, вызвав PreviewView.setPreferredImplementationMode(ImplementationMode), где ImplementationMode — это либо SURFACE_VIEW, либо TEXTURE_VIEW. PreviewView старается уважать ваш выбор и гарантирует использование указанного режима.
⚠️Перед запуском превью обязательно удостоверьтесь, что установили предпочтительный режим реализации, вызвав Preview.setSurfaceProvider(PreviewView.createSurfaceProvider()).
Ниже показано, как установить предпочтительный режим реализации PreviewView:
// Установите предпочтительный режим реализации, прежде чем запускать превью
previewView.preferredImplementationMode = ImplementationMode.SURFACE_VIEW
// Присоедините вариант использования превью и previewView, чтобы запустить превью
preview.setSurfaceProvider(previewView.createSurfaceProvider(cameraInfo))
PreviewView — превью
PreviewView обрабатывает “винтики и шестеренки” создания SurfaceProvider, который необходим в варианте использования Preview для запуска потока превью. SurfaceProvider подготавливает поверхность, которая будет предоставлена камере для отображения потока превью, и при необходимости заботится о ее воссоздании. Preview.createSurfaceProvider(CameraInfo) принимает нуллифицированный экземпляр CameraInfo. PreviewView использует его наряду с предпочтительным режимом реализации и возможностями камеры, чтобы определить реализацию для внутреннего использования. Если вы передаете null CameraInfo, PreviewView использует реализацию TextureView, так как он не может определить, будет ли выбранная камера работать с SurfaceView.
Как только вы сконструируете вариант использования превью и любые другие необходимые вам варианты использования, свяжите их с LifecycleOwner, используйте CameraInfo привязанной камеры для создания SurfaceProvider, а затем прикрепите к варианту использования Preview для запуска потока превью, вызвав Preview.setSurfaceProvider(SurfaceProvider).
Ниже показано, как прикрепить PreviewView к Preview, чтобы запустить поток предварительного просмотра:
// Создайте экземпляр варианта использования Preview
val preview = Preview.Builder().build()
// Привяжите вариант использования preview и другие необходимые варианты использования к lifecycle
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis, imageCapture)
// Создайте surfaceProvider, используя cameraInfo привязанной камеры
val surfaceProvider = previewView.createSurfaceProvider(camera.cameraInfo)
// Прикрепите surfaceProvider к варианту использования preview, чтобы запустить превью
preview.setSurfaceProvider(surfaceProvider)
PreviewView — способы масштабирования
PreviewView предоставляет API, который позволяет управлять тем, как должно выглядеть превью и где оно должно располагается в контейнере.
- Параметр How определяет, должно ли превью вмещаться внутрь своего контейнера (FIT) или заполнять (FILL) его.
- Параметр where определяет, должно ли превью находиться в верхнем левом углу (START), в центре (CENTER) или в нижнем правом углу (END) своего контейнера.
Доступные значения способов масштабирования, поддерживаемые PreviewView, представляют собой комбинации из “how” и “where”: FIT_START, FIT_CENTER, FIT_END, FILL_START, FILL_CENTER и FILL_END. Чаще всего используются FIT_CENTER, который эквивалентен леттербоксингу превью, и FILL_CENTER, который центрирует превью в контейнере.
Установить способ масштабирования можно двумя путями.
В XML-макете с помощью атрибута scaleType в PreviewView, как показано в следующем примере:
<androidx.camera.view.PreviewView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scaleType="fitEnd" />
Программно через вызов PreviewView.setScaleType(ScaleType), как показано ниже:
previewView.setScaleType(ScaleType.FIT_CENTER)
Чтобы узнать текущий способ масштабирования, используемый PreviewView, вызовите PreviewView.getScaleType().
PreviewView — управление камерой
В зависимости от ориентации датчика камеры, поворота устройства, режима отображения и типа шкалы предварительного просмотра PreviewView может масштабировать, поворачивать и преобразовывать превью-кадры, полученные от камеры, чтобы правильно отображать поток превью в пользовательском интерфейсе. Вот почему так важно иметь возможность конвертировать координаты пользовательского интерфейса в координаты датчика камеры. В CameraX такое преобразование выполняется с помощью MeteringPointFactory. PreviewView предоставляет API для его создания: PreviewView.createMeteringPointFactory(cameraSelector), где CameraSelector выступает в качестве камеры, транслирующей превью.
MeteringPointFactory в PreviewView очень удобен, когда нужно реализовать tap-to-focus. Несмотря на то, что в превью камеры автофокус включен по умолчанию (если камера поддерживает его), вы также можете управлять тем, какой объект будет выбран целью фокусировки при нажатии на PreviewView. MeteringPointFactory преобразует координаты объекта фокусировки в координаты датчика камеры, что затем позволяет камере фокусироваться на нужной области.
В следующем примере показано, как реализовать tap-to-focus с помощью тач-слушателя, установленного в PreviewView:
fun onTouch(x: Float, y: Float) {
// Создайте factory для создания MeteringPoint
val factory = previewView.createMeteringPointFactory(cameraSelector)
// Преобразуйте UI-координаты в координаты датчиков камеры
val point = factory.createPoint(x, y)
// Подготовьте действие фокусировки для запуска
val action = FocusMeteringAction.Builder(point).build()
// Выполните действие фокусировки
cameraControl.startFocusAndMetering(action)
}
Еще одна распространенная функция превью камеры — это pinch-to-zoom, что позволяет камере приближать и отдалять изображение, когда вы делаете щипок по превью. Чтобы реализовать эту функцию с помощью PreviewView, добавьте тач-слушатель в PreviewView и прикрепите его к слушателю жестов масштабирования. Это позволит перехватывать жесты щипка и соответствующим образом обновлять коэффициент увеличения камеры.
В следующем примере показано, как реализовать pinch-to-zoom с помощью PreviewView.
// Создайте слушатель с обратным вызовом, который запускается, когда происходит событие жеста
val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
// Получите текущий коэффициент масштабирования камеры
val currentZoomRatio: Float = cameraInfo.zoomRatio.value ?: 1F
// Посмотрите, насколько сильно изменился масштаб из-за жеста щипка пользователя
val delta = detector.scaleFactor
// Обновите коэффициент увеличения камеры
cameraControl.setZoomRatio(currentZoomRatio * delta)
return true
}
}
// Прикрепите тач-слушатель событий PreviewView к слушателю жестов масштабирования
val scaleGestureDetector = ScaleGestureDetector(context, listener)
// Передайте тач-события от PreviewView слушателю жестов масштабирования
previewView.setOnTouchListener { _, event ->
scaleGestureDetector.onTouchEvent(event)
return@setOnTouchListener true
}
PreviewView — как он тестируется
PreviewView обеспечивает согласованное поведение камеры на широком диапазоне устройств Android. Это стало возможным благодаря инвестициям CameraX в тестирование PreviewView и других API-интерфейсов в лаборатории автоматизированного тестирования. Эти тесты делятся на две основные категории:
- Модульные тесты, которые проверяют поведение
PreviewViewв отношении режимов реализации, способов масштабирования иMeteringPointFactory. Они также помогают удостоверится, чтоPreviewViewправильно подстраивает превью, когда это необходимо, например, если изменяется размер его контейнера, если обновляется макет дисплея, или если он прикрепляется (первично или повторно) кWindow. - Интеграционные тесты, которые гарантируют, что
PreviewViewведет себя правильно, когда является частью приложения, и соответственно отображает или останавливает поток превью. Эти тесты включают в себя проверку состояния превью во время работы приложения, после его многократного закрытия и повторного открытия, после переключения объектива камеры вперед и назад, а также после уничтожения и перезапуска жизненного цикла приложения. В настоящее время эти тесты в основном охватывают реализациюPreviewViewсTextureView, поскольку получение отSurfaceViewсигнала о том, когда превью запущено или остановлено, оказалось сложной задачей.
Заключение
Подводя итог:
PreviewView— это пользовательскийView, который упрощает отображение превью камеры.PreviewViewобрабатывает поверхность предварительного просмотра с помощьюSurfaceViewпо умолчанию, но при необходимости или по запросу может вернуться к использованиюTextureView.- Свяжите другие ваши варианты использования, такие как
ImageCaptureиImageAnalysis, сLifecycleOwner, а затем запустите превью камеры, прикрепивSurfaceProviderизPreviewViewк связанному варианту использованияPreview. - Управление отображением предварительного просмотра осуществляется в
PreviewViewпутем определения способа масштабирования. - Реализуйте tap-to-focus в вашем превью, получив
MeteringPointFactoryизPreviewView. - Реализуйте pinch-to-zoom в вашем превью, настроив в
PreviewViewслушатель жестов.
Хотите узнать больше о CameraX? Загляните сюда:
- официальная документация CameraX;
- codelab по введению в CameraX;
- сообщество разработчиков CameraX;
- пример приложения камеры, созданного с использованием CameraX.
Спасибо за чтение!
Читайте также:
Читайте нас в Telegram, VK и Дзен
Перевод статьи Husayn Hakeem: Display a camera preview with PreviewView





