Si tu app para Android usa cámaras, hay algunas consideraciones especiales que debes tener en cuenta cuando controlas las orientaciones. En este documento, se supone que comprendes los conceptos básicos de la API de camera2 de Android. Puedes leer nuestra entrada de blog o resumen para obtener una descripción general de camera2. También te recomendamos que primero intentes escribir una app de cámara antes de consultar este documento.
Segundo plano
El control de las orientaciones en las apps de cámara para Android es complejo y debe tener en cuenta los siguientes factores:
- Orientación natural: Es la orientación de la pantalla cuando el dispositivo está en la posición "normal" para el diseño del dispositivo. Por lo general, es la orientación vertical para los teléfonos celulares y la orientación horizontal para las laptops.
- Orientación del sensor: Es la orientación del sensor montado físicamente en el dispositivo.
- Rotación de la pantalla: Indica cuánto se rota el dispositivo físicamente desde la orientación natural.
- Tamaño del visor: Es el tamaño del visor que se usa para mostrar la vista previa de la cámara.
- Tamaño de la imagen que genera la cámara.
La combinación de estos factores genera una gran cantidad de posibles configuraciones de IU y vista previa para las apps de cámara. El objetivo de este documento es demostrar cómo los desarrolladores pueden navegar por estos problemas y controlar correctamente las orientaciones de la cámara en las apps para Android.
Para simplificar un poco las cosas, supón que todos los ejemplos involucran una cámara orientada hacia atrás, a menos que se indique lo contrario. Además, todas las siguientes fotos se simularon para que las ilustraciones sean más claras visualmente.
Todo sobre las orientaciones
Orientación natural
La orientación natural se define como la orientación de la pantalla cuando el dispositivo se encuentra en la posición en la que normalmente se espera que esté. En el caso de los teléfonos, la orientación natural suele ser vertical. En otras palabras, los teléfonos tienen anchos más cortos y alturas más largas. En el caso de las laptops, su orientación natural es horizontal, lo que significa que tienen anchos más largos y alturas más cortas. Las tablets son un poco más complicadas que esto, ya que pueden estar en orientación horizontal o vertical.
Orientación del sensor
Formalmente, la orientación del sensor se mide por los grados en que se debe rotar una imagen de salida del sensor en el sentido de las manecillas del reloj para que coincida con la orientación natural del dispositivo. En otras palabras, la orientación del sensor es la cantidad de grados que se rota un sensor en sentido antihorario antes de montarlo en el dispositivo. Cuando miras la pantalla, la rotación parece ser en el sentido de las manecillas del reloj, ya que el sensor de la cámara trasera está instalado en la parte "posterior" del dispositivo.
Según la Definición de compatibilidad de Android 10, sección 7.5.5, Orientación de la cámara, las cámaras frontales y posteriores "SE DEBEN orientar de modo que la dimensión larga de la cámara se alinee con la dimensión larga de la pantalla".
Los búferes de salida de las cámaras tienen el tamaño de un paisaje. Dado que la orientación natural de los teléfonos suele ser vertical, la orientación del sensor suele ser de 90 o 270 grados con respecto a la orientación natural para que el lado largo del búfer de salida coincida con el lado largo de la pantalla. La orientación del sensor es diferente para los dispositivos cuya orientación natural es horizontal, como las Chromebooks. En estos dispositivos, los sensores de imagen se vuelven a colocar de modo que el lado largo del búfer de salida coincida con el lado largo de la pantalla. Como ambos tienen el tamaño de un paisaje, las orientaciones coinciden y la orientación del sensor es de 0 o 180 grados.
Las siguientes ilustraciones muestran cómo se ven las cosas desde el punto de vista de un observador que mira la pantalla del dispositivo:
Considera la siguiente escena:
| Teléfono | Laptop |
|---|---|
![]() |
![]() |
Dado que la orientación del sensor suele ser de 90 o 270 grados en los teléfonos, sin tener en cuenta la orientación del sensor, las imágenes que obtendrías se verían así:
| Teléfono | Laptop |
|---|---|
![]() |
![]() |
Supongamos que la orientación del sensor en sentido contrario a las agujas del reloj se almacena en la variable sensorOrientation. Para compensar la orientación del sensor, debes rotar los búferes de salida en `sensorOrientation` en el sentido de las manecillas del reloj para que la orientación vuelva a alinearse con la orientación natural del dispositivo.
En Android, las apps pueden usar TextureView o SurfaceView para mostrar la vista previa de la cámara. Ambos pueden controlar la orientación del sensor si las apps los usan correctamente. En las siguientes secciones, detallaremos cómo debes tener en cuenta la orientación del sensor.
Rotación de la pantalla
La rotación de la pantalla se define formalmente por la rotación de los gráficos dibujados en la pantalla, que es la dirección opuesta a la rotación física del dispositivo desde su orientación natural. En las siguientes secciones, se supone que todas las rotaciones de la pantalla son múltiplos de 90. Si recuperas la rotación de la pantalla por sus grados absolutos, redondea el valor al más cercano de {0, 90, 180, 270}.
En las siguientes secciones, "orientación de la pantalla" se refiere a si un dispositivo se sostiene físicamente en posición horizontal o vertical, y es diferente de la "rotación de la pantalla".
Supongamos que rotas los dispositivos 90 grados en sentido antihorario desde sus posiciones anteriores, como se muestra en la siguiente figura:
Suponiendo que los búferes de salida ya están rotados según la orientación del sensor, tendrías los siguientes búferes de salida:
| Teléfono | Laptop |
|---|---|
![]() |
![]() |
Si la rotación de la pantalla se almacena en la variable displayRotation, para obtener la imagen correcta, debes rotar los búferes de salida en sentido contrario a las manecillas del reloj según el valor de displayRotation.
En el caso de las cámaras frontales, la rotación de la pantalla actúa sobre los búferes de imagen en la dirección opuesta en relación con la pantalla. Si trabajas con una cámara frontal, debes rotar los búferes en el sentido de las agujas del reloj según displayRotation.
Advertencias
La rotación de la pantalla mide la rotación del dispositivo en sentido contrario a las manecillas del reloj. Esto no se aplica a todas las APIs de orientación o rotación.
Por ejemplo:
-
Si usas
Display#getRotation(), obtendrás la rotación en sentido contrario a las agujas del reloj, como se menciona en este documento. - Si usas OrientationEventListener#onOrientationChanged(int), obtendrás la rotación en el sentido de las agujas del reloj.
Lo importante que debes tener en cuenta aquí es que la rotación de la pantalla es relativa a la orientación natural. Por ejemplo, si rotas físicamente un teléfono 90 o 270 grados, obtendrás una pantalla con forma horizontal. En comparación, obtendrías una pantalla con forma de retrato si rotaras una laptop en la misma cantidad. Las apps siempre deben tener esto en cuenta y nunca hacer suposiciones sobre la orientación natural de un dispositivo.
Ejemplos
Usemos las cifras anteriores para ilustrar qué son las orientaciones y las rotaciones.
| Teléfono | Laptop |
|---|---|
| Orientación natural = vertical | Orientación natural = horizontal |
| Orientación del sensor = 90 | Orientación del sensor = 0 |
| Rotación de la pantalla = 0 | Rotación de la pantalla = 0 |
| Orientación de la pantalla = Vertical | Orientación de la pantalla = horizontal |
| Teléfono | Laptop |
|---|---|
| Orientación natural = vertical | Orientación natural = horizontal |
| Orientación del sensor = 90 | Orientación del sensor = 0 |
| Rotación de la pantalla = 90 | Rotación de la pantalla = 90 |
| Orientación de la pantalla = horizontal | Orientación de la pantalla = Vertical |
Tamaño del visor
Las apps siempre deben cambiar el tamaño del visor según la orientación, la rotación y la resolución de la pantalla. En general, las apps deben hacer que la orientación del visor sea idéntica a la orientación de la pantalla actual. En otras palabras, las apps deben alinear el borde largo del visor con el borde largo de la pantalla.
Tamaño de salida de la imagen por cámara
Cuando elijas el tamaño de salida de la imagen para la vista previa, debes elegir un tamaño que sea igual o un poco más grande que el tamaño del visor siempre que sea posible. Por lo general, no querrás que los búferes de salida se amplíen, ya que esto provocaría pixelación. Tampoco te conviene elegir un tamaño demasiado grande, ya que podría reducir el rendimiento y consumir más batería.
Orientación JPEG
Comencemos con una situación común: capturar una foto en formato JPEG. En la API de camera2, puedes pasar JPEG_ORIENTATION en la solicitud de captura para especificar cuánto deseas que se roten los archivos JPEG de salida en el sentido de las agujas del reloj.
A continuación, se incluye un breve resumen de lo que mencionamos:
-
Para controlar la orientación del sensor, debes rotar el búfer de imagen
sensorOrientationen el sentido de las manecillas del reloj. -
Para controlar la rotación de la pantalla, debes rotar un búfer en sentido contrario a las agujas del reloj en
displayRotationpara las cámaras posteriores y en sentido de las agujas del reloj para las cámaras frontales.
Si sumas los 2 factores, la cantidad que deseas rotar en el sentido de las agujas del reloj es
-
sensorOrientation - displayRotationpara cámaras posteriores -
sensorOrientation + displayRotationpara cámaras frontales.
Puedes ver el código de muestra para esta lógica en la documentación de JPEG_ORIENTATION. Ten en cuenta que deviceOrientation en el código de ejemplo de la documentación usa la rotación en el sentido de las agujas del reloj del dispositivo. Por lo tanto, los signos de rotación de la pantalla se invierten.
Vista previa
¿Qué sucede con la vista previa de la cámara? Existen 2 formas principales en que una app puede mostrar una vista previa de la cámara: SurfaceView y TextureView. Cada uno requiere enfoques diferentes para controlar la orientación correctamente.
SurfaceView
En general, se recomienda SurfaceView para las vistas previas de la cámara, siempre y cuando no necesites procesar ni animar los búferes de vista previa. Es más eficiente y requiere menos recursos que TextureView.
SurfaceView también es relativamente más fácil de diseñar. Solo debes preocuparte por la relación de aspecto del SurfaceView en el que se muestra la vista previa de la cámara.
Fuente
Debajo de SurfaceView, la plataforma de Android rota los búferes de salida para que coincidan con la orientación de la pantalla del dispositivo. En otras palabras, tiene en cuenta tanto la orientación del sensor como la rotación de la pantalla. Para simplificarlo aún más, cuando la pantalla está en horizontal, obtenemos una vista previa que también está en horizontal, y viceversa para la orientación vertical.
Esto se ilustra en la siguiente tabla. Lo importante que debes tener en cuenta aquí es que la rotación de la pantalla por sí sola no determina la orientación de la fuente.
| Rotación de la pantalla | Teléfono (orientación natural = vertical) | Laptop (orientación natural = horizontal) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Diseño
Como puedes ver, SurfaceView ya controla algunas de las tareas difíciles por nosotros. Sin embargo, ahora debes tener en cuenta el tamaño del visor o qué tan grande quieres que se vea la vista previa en la pantalla. SurfaceView ajusta automáticamente la escala del búfer de origen para que se ajuste a sus dimensiones. Debes asegurarte de que la relación de aspecto del visor sea idéntica a la del SourceBuffer. Por ejemplo, si intentas ajustar una vista previa con forma de retrato en un SurfaceView con forma de paisaje, obtendrás algo distorsionado como esto:
Por lo general, se recomienda que la relación de aspecto (es decir, ancho/alto) del visor sea idéntica a la de la fuente. Si no quieres recortar la imagen en el visor (es decir, cortar algunos píxeles para corregir la pantalla), debes tener en cuenta 2 casos: cuando aspectRatioActivity es mayor que aspectRatioSource y cuando es menor o igual que aspectRatioSource.
aspectRatioActivity > aspectRatioSource
Puedes pensar en el caso como si la actividad fuera "más amplia". A continuación, analizaremos un ejemplo en el que tienes una actividad de 16:9 y una fuente de 4:3.
aspectRatioActivity = 16/9 ≈ 1.78 aspectRatioSource = 4/3 ≈ 1.33
Primero, debes asegurarte de que el visor también sea de 4:3. Luego, debes ajustar la fuente y el visor en la actividad de la siguiente manera:
En este caso, debes hacer que la altura del visor coincida con la altura de la actividad y que la relación de aspecto del visor sea idéntica a la relación de aspecto de la fuente. El pseudocódigo es el siguiente:
viewfinderHeight = activityHeight; viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource
El otro caso es cuando la actividad es "más angosta" o "más alta". Podemos reutilizar el ejemplo anterior, excepto que, en el siguiente ejemplo, rotas el dispositivo 90 grados, lo que hace que la actividad sea de 9:16 y la fuente de 3:4.
aspectRatioActivity = 9/16 = 0.5625 aspectRatioSource = 3/4 = 0.75
En este caso, debes ajustar la fuente y el visor en la actividad de la siguiente manera:
Debes hacer que el ancho del visor coincida con el ancho de la actividad (en lugar de la altura, como en el caso anterior) y que la relación de aspecto del visor sea idéntica a la de la fuente. Pseudocódigo:
viewfinderWidth = activityWidth; viewfinderHeight = activityWidth / aspectRatioSource;
recortes
AutoFitSurfaceView.kt (github) de las muestras de Camera2 anula SurfaceView y controla las relaciones de aspecto no coincidentes con una imagen que es igual o "solo más grande" que la actividad en ambas dimensiones y, luego, recorta el contenido que se desborda. Esto es útil para las apps que desean que la vista previa cubra toda la actividad o llene por completo una vista de dimensiones fijas, sin distorsionar la imagen.
Advertencias
En el ejemplo anterior, se intenta maximizar el espacio de la pantalla haciendo que la vista previa sea un poco más grande que la actividad, de modo que no quede ningún espacio sin completar. Esto se basa en el hecho de que las partes desbordantes se recortan de forma predeterminada con el diseño principal (o ViewGroup). El comportamiento es coherente con RelativeLayout y LinearLayout, pero NO con ConstraintLayout. Un ConstraintLayout podría cambiar el tamaño de las vistas secundarias para que quepan dentro del diseño, lo que interrumpiría el efecto de "recorte centrado" previsto y provocaría vistas previas estiradas. Puedes consultar este commit como referencia.
TextureView
TextureView brinda el máximo control sobre el contenido de la vista previa de la cámara, pero tiene un costo de rendimiento. También requiere más trabajo para que la vista previa de la cámara se muestre correctamente.
Fuente
Debajo de TextureView, la plataforma de Android rota los búferes de salida según la orientación del sensor para que coincidan con la orientación natural del dispositivo. Si bien TextureView controla la orientación del sensor, no controla las rotaciones de la pantalla. Alinea los búferes de salida con la orientación natural del dispositivo, lo que significa que deberás controlar las rotaciones de la pantalla por tu cuenta.
Esto se ilustra en la siguiente tabla. Si intentas rotar las figuras según su rotación de pantalla correspondiente, obtendrás las mismas figuras en SurfaceView.
| Rotación de la pantalla | Teléfono (orientación natural = vertical) | Laptop (orientación natural = horizontal) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Diseño
El diseño es un poco complicado en el caso de TextureView. Anteriormente, se sugirió usar una matriz de transformación para TextureView, pero ese método no funciona en todos los dispositivos. Te sugerimos que sigas los pasos que se describen aquí.
El proceso de 3 pasos para diseñar correctamente las vistas previas en un TextureView es el siguiente:
- Establece el tamaño de TextureView para que sea idéntico al tamaño de vista previa elegido.
- Vuelve a escalar el TextureView que podría haberse estirado a las dimensiones originales de la vista previa.
-
Rota la TextureView en sentido antihorario en
displayRotation.
Supongamos que tienes un teléfono con una rotación de pantalla de 90 grados.
1. Establece el tamaño de TextureView para que sea idéntico al tamaño de vista previa elegido
Supongamos que el tamaño de vista previa que elegiste es previewWidth × previewHeight, donde previewWidth > previewHeight (la salida del sensor tiene forma horizontal por naturaleza). Cuando se configura una sesión de captura, se debe llamar a SurfaceTexture#setDefaultBufferSize(int width, height) para especificar el tamaño de vista previa (previewWidth × previewHeight).
Antes de llamar a setDefaultBufferSize, es importante que también establezcas el tamaño de TextureView en "previewWidth × previewHeight" con View#setLayoutParams(android.view.ViewGroup.LayoutParams). El motivo es que TextureView llama a SurfaceTexture#setDefaultBufferSize(int width, height) con su ancho y altura medidos. Si el tamaño de TextureView no se establece de forma explícita con anticipación, se puede producir una condición de carrera. Esto se mitiga configurando explícitamente el tamaño de TextureView primero.
Ahora es posible que TextureView no coincida con las dimensiones de la fuente. En el caso de los teléfonos, la fuente tiene forma vertical, pero el TextureView tiene forma horizontal debido a los layoutParams que acabas de configurar. Esto generaría vistas previas estiradas, como se ilustra aquí:
2. Ajusta la escala de la TextureView que podría estar estirada para que vuelva a las dimensiones originales de la vista previa.
Ten en cuenta lo siguiente para volver a escalar la vista previa estirada a las dimensiones de la fuente.
Las dimensiones de la fuente (sourceWidth × sourceHeight) son las siguientes:
-
previewHeight × previewWidth, si la orientación natural es vertical o vertical inversa (la orientación del sensor es de 90 o 270 grados) -
previewWidth × previewHeight, si la orientación natural es horizontal o horizontal inversa (la orientación del sensor es de 0 o 180 grados)
Corrige el estiramiento con View#setScaleX(float) y View#setScaleY(float)
-
setScaleX(
sourceWidth / previewWidth) -
setScaleY(
sourceHeight / previewHeight)
3. Rotar la vista previa en el sentido contrario a las manecillas del reloj según el valor de `displayRotation`
Como se mencionó anteriormente, debes rotar la vista previa displayRotation en sentido contrario a las manecillas del reloj para compensar la rotación de la pantalla.
Para ello, View#setRotation(float)
-
setRotation(
-displayRotation), ya que realiza una rotación en el sentido de las manecillas del reloj.
Muestra
-
PreviewViewde camerax en Jetpack controla el diseño de TextureView como se describió anteriormente. Configura la transformación con PreviewCorrector.
Nota: Si anteriormente usaste la matriz de transformación para TextureView en tu código, es posible que la vista previa no se vea bien en un dispositivo con orientación horizontal natural, como las Chromebooks. Es probable que tu matriz de transformación suponga incorrectamente que la orientación del sensor es de 90 o 270 grados. Puedes consultar esta confirmación en GitHub para encontrar una solución alternativa, pero te recomendamos que migres tu app para que use el método que se describe aquí.





















