В этом разделе показано, как настроить варианты использования CameraX внутри вашего приложения для получения изображений с правильной информацией о повороте, будь то из варианта использования ImageAnalysis
или ImageCapture
. Так:
-
Analyzer
варианта использованияImageAnalysis
должен получать кадры с правильным поворотом. - Вариант использования
ImageCapture
должен делать снимки с правильным поворотом.
Терминология
В этом разделе используется следующая терминология, поэтому важно понимать, что означает каждый термин:
- Ориентация дисплея
- Это относится к тому, какая сторона устройства находится вверху, и может иметь одно из четырех значений: книжная, альбомная, перевернутая книжная или перевернутая альбомная.
- Поворот дисплея
- Это значение, возвращаемое
Display.getRotation()
, и представляет собой степень, на которую устройство поворачивается против часовой стрелки от его естественной ориентации. - Целевое вращение
- Это количество градусов, на которое нужно повернуть устройство по часовой стрелке, чтобы достичь его естественной ориентации.
Как определить целевую ротацию
В следующих примерах показано, как определить целевое вращение устройства на основе его естественной ориентации.
Пример 1. Портретная естественная ориентация.
Пример устройства: Pixel 3 XL | |
---|---|
Естественная ориентация = Портрет Поворот дисплея = 0 | |
Естественная ориентация = Портрет Поворот дисплея = 90 |
Пример 2: Пейзаж, естественная ориентация
Пример устройства: Pixel C | |
---|---|
Естественная ориентация = Пейзаж Поворот дисплея = 0 | |
Естественная ориентация = Пейзаж Поворот дисплея = 270 |
Поворот изображения
Какой конец вверх? Ориентация датчика определяется в Android как постоянное значение, которое представляет собой градусы (0, 90, 180, 270), на которые датчик поворачивается относительно верхней части устройства, когда устройство находится в естественном положении. Для всех случаев на диаграммах поворот изображения описывает, как данные следует повернуть по часовой стрелке, чтобы они выглядели вертикально.
Следующие примеры показывают, каким должен быть поворот изображения в зависимости от ориентации датчика камеры. Они также предполагают, что целевое вращение установлено на вращение дисплея.
Пример 1: Датчик повернут на 90 градусов.
Пример устройства: Pixel 3 XL | |
---|---|
Поворот дисплея = 0 | |
Поворот дисплея = 90 |
Пример 2: Датчик повернут на 270 градусов.
Пример устройства: Nexus 5X. | |
---|---|
Поворот дисплея = 0 | |
Поворот дисплея = 90 |
Пример 3: Датчик повернут на 0 градусов
Пример устройства: Pixel C (планшет) | |
---|---|
Поворот дисплея = 0 | |
Поворот дисплея = 270 |
Вычисление поворота изображения
Анализ изображений
Analyzer
ImageAnalysis
получает изображения с камеры в форме ImageProxy
. Каждое изображение содержит информацию о повороте, доступную через:
val rotation = imageProxy.imageInfo.rotationDegrees
Это значение представляет собой степень, на которую изображение необходимо повернуть по часовой стрелке, чтобы оно соответствовало целевому повороту ImageAnalysis
. В контексте приложения Android целевой поворот ImageAnalysis
обычно соответствует ориентации экрана.
Захват изображения
К экземпляру ImageCapture
присоединяется обратный вызов, сигнализирующий о готовности результата захвата. Результатом может быть либо захваченное изображение, либо ошибка.
При съемке изображения предоставляемый обратный вызов может быть одного из следующих типов:
-
OnImageCapturedCallback
: получает изображение с доступом в памяти в формеImageProxy
. -
OnImageSavedCallback
: вызывается, когда захваченное изображение было успешно сохранено в расположении, указанномImageCapture.OutputFileOptions
. Параметры могут указыватьFile
,OutputStream
или местоположение вMediaStore
.
Вращение захваченного изображения, независимо от его формата ( ImageProxy
, File
, OutputStream
, MediaStore Uri
), представляет собой степень вращения, на которую захваченное изображение необходимо повернуть по часовой стрелке, чтобы оно соответствовало целевому вращению ImageCapture
, что опять же в контексте приложение Android обычно соответствует ориентации экрана.
Получить поворот захваченного изображения можно одним из следующих способов:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
Проверьте поворот изображения
Сценарии использования ImageAnalysis
и ImageCapture
получают ImageProxy
от камеры после успешного запроса на захват. ImageProxy
оборачивает изображение и информацию о нем, включая его вращение. Эта информация о повороте представляет собой степень, на которую необходимо повернуть изображение, чтобы оно соответствовало целевому повороту варианта использования.
Рекомендации по ротации целевых объектов ImageCapture/ImageAnalysis
Поскольку многие устройства по умолчанию не поворачиваются в обратную книжную или альбомную ориентацию, некоторые приложения Android не поддерживают эти ориентации. Независимо от того, поддерживает ли приложение это или нет, способ обновления целевой ротации вариантов использования может быть изменен.
Ниже приведены две таблицы, определяющие, как синхронизировать целевое вращение вариантов использования с вращением дисплея. Первый показывает, как это сделать, поддерживая все четыре ориентации; второй обрабатывает только те ориентации, в которых устройство вращается по умолчанию.
Чтобы выбрать, каким рекомендациям следовать в своем приложении:
Убедитесь, что
Activity
камеры вашего приложения имеет заблокированную ориентацию, разблокированную ориентацию или переопределяет изменения конфигурации ориентации.Решите, должна ли
Activity
камеры вашего приложения обрабатывать все четыре ориентации устройства (книжная, обратная книжная, альбомная и обратная альбомная) или она должна обрабатывать только те ориентации, которые устройство, на котором оно работает, поддерживает по умолчанию.
Поддержка всех четырех ориентаций
В этой таблице приведены определенные рекомендации, которым следует следовать в случаях, когда устройство не поворачивается в обратную книжную ориентацию. То же самое можно применить к устройствам, которые не поворачиваются в обратном направлении.
Сценарий | Рекомендации | Режим одного окна | Многооконный режим разделенного экрана |
---|---|---|---|
Разблокированная ориентация | Настраивайте варианты использования каждый раз при создании Activity , например, в обратном вызове onCreate() Activity . | ||
Используйте OrientationEventListener onOrientationChanged() . Внутри обратного вызова обновите целевую ротацию вариантов использования. Это обрабатывает случаи, когда система не воссоздает Activity даже после изменения ориентации, например, когда устройство поворачивается на 180 градусов. | Также обрабатывается ситуация, когда дисплей находится в обратной книжной ориентации, а устройство по умолчанию не поворачивается в обратную книжную ориентацию. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. | |
Необязательно: установите для свойства screenOrientation Activity значение fullSensor в файле AndroidManifest . | Это позволяет размещать пользовательский интерфейс в вертикальном положении, когда устройство находится в перевернутом портретном положении, и позволяет системе воссоздавать Activity всякий раз, когда устройство поворачивается на 90 градусов. | Не оказывает никакого влияния на устройства, которые по умолчанию не поворачиваются в обратную портретную ориентацию. Многооконный режим не поддерживается, если дисплей находится в обратной книжной ориентации. | |
Заблокированная ориентация | Настраивайте варианты использования только один раз, при первом создании Activity , например, в обратном вызове onCreate() Activity . | ||
Используйте OrientationEventListener onOrientationChanged() . Внутри обратного вызова обновите целевую ротацию вариантов использования, кроме предварительного просмотра. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. | ||
Изменения конфигурации ориентации переопределены | Настраивайте варианты использования только один раз, при первом создании Activity , например, в обратном вызове onCreate() Activity . | ||
Используйте OrientationEventListener onOrientationChanged() . Внутри обратного вызова обновите целевую ротацию вариантов использования. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. | ||
Необязательно: установите для свойства screenOrientation действия значение fullSensor в файле AndroidManifest. | Позволяет размещать пользовательский интерфейс в вертикальном положении, когда устройство находится в перевернутой портретной ориентации. | Не влияет на устройства, которые по умолчанию не поворачиваются в обратную портретную ориентацию. Многооконный режим не поддерживается, если дисплей находится в обратной книжной ориентации. |
Поддержка только ориентаций, поддерживаемых устройством
Поддерживаются только те ориентации, которые устройство поддерживает по умолчанию (которые могут включать или не включать перевернутую книжную/перевернутую альбомную ориентацию).
Сценарий | Рекомендации | Многооконный режим разделенного экрана |
---|---|---|
Разблокированная ориентация | Настраивайте варианты использования каждый раз при создании Activity , например, в обратном вызове onCreate() Activity . | |
Используйте DisplayListener onDisplayChanged() . Внутри обратного вызова обновите целевой поворот вариантов использования, например, когда устройство поворачивается на 180 градусов. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. | |
Заблокированная ориентация | Настраивайте варианты использования только один раз, при первом создании Activity , например, в обратном вызове onCreate() Activity . | |
Используйте OrientationEventListener onOrientationChanged() . Внутри обратного вызова обновите целевую ротацию вариантов использования. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. | |
Изменения конфигурации ориентации переопределены | Настраивайте варианты использования только один раз, при первом создании Activity , например, в обратном вызове onCreate() Activity . | |
Используйте DisplayListener onDisplayChanged() . Внутри обратного вызова обновите целевой поворот вариантов использования, например, когда устройство поворачивается на 180 градусов. | Также обрабатывает случаи, когда Activity не воссоздается при повороте устройства (например, на 90 градусов). Это происходит на устройствах малого форм-фактора, когда приложение занимает половину экрана, и на устройствах большего размера, когда приложение занимает две трети экрана. |
Разблокированная ориентация
Activity
имеет разблокированную ориентацию, когда его ориентация отображения (например, книжная или альбомная) соответствует физической ориентации устройства, за исключением обратной книжной/альбомной ориентации, которую некоторые устройства не поддерживают по умолчанию. Чтобы заставить устройство вращаться во всех четырех ориентациях, установите для свойства screenOrientation
Activity
значение fullSensor
.
В многооконном режиме устройство, которое по умолчанию не поддерживает обратную книжную/альбомную ориентацию, не будет поворачиваться в обратную книжную/альбомную ориентацию, даже если для его свойства screenOrientation
установлено значение fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
Заблокированная ориентация
Ориентация дисплея заблокирована, если он остается в одной и той же ориентации (например, книжной или альбомной) независимо от физической ориентации устройства. Это можно сделать, указав свойство screenOrientation
Activity
внутри его объявления в файле AndroidManifest.xml
.
Если ориентация дисплея заблокирована, система не уничтожает и не воссоздает Activity
при повороте устройства.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Изменения конфигурации ориентации отменены
Когда Activity
переопределяет изменения конфигурации ориентации, система не уничтожает и не воссоздает его при изменении физической ориентации устройства. Однако система обновляет пользовательский интерфейс, чтобы он соответствовал физической ориентации устройства.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
Настройка вариантов использования камеры
В описанных выше сценариях варианты использования камеры можно настроить при первом создании Activity
.
В случае Activity
с разблокированной ориентацией эта настройка выполняется каждый раз при повороте устройства, поскольку система уничтожает и воссоздает Activity
при изменении ориентации. Это приводит к тому, что в вариантах использования каждый раз по умолчанию устанавливается целевое вращение, соответствующее ориентации дисплея.
В случае Activity
с заблокированной ориентацией или действия, которое переопределяет изменения конфигурации ориентации, эта настройка выполняется один раз при первом создании Activity
.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
Настройка прослушивателя событий ориентации
Использование OrientationEventListener
позволяет постоянно обновлять целевое вращение вариантов использования камеры при изменении ориентации устройства.
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
Настройка DisplayListener
Использование DisplayListener
позволяет обновлять целевое вращение вариантов использования камеры в определенных ситуациях, например, когда система не уничтожает и не воссоздает Activity
после поворота устройства на 180 градусов.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }