Отображение превью с камеры — обычный вариант использования у любого приложения для съемки фото и видео. Однако сих пор это было довольно трудно сделать правильно, в основном из-за сложностей, связанных с граничными случаями 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