Los dispositivos Android no solo vienen con pantallas de diferentes tamaños (teléfonos celulares, tablets, TVs, etc.), sino que sus pantallas también tienen píxeles de distintos tamaños. Es decir, hay dispositivos que tienen 160 píxeles por pulgada cuadrada y otros que adaptan 480 píxeles en el mismo espacio. Si no tienes en cuenta estas variaciones de densidad de píxeles, es posible que el sistema escale tus imágenes (y se vean borrosas) o que estas se muestren en el tamaño incorrecto.
En esta página, se explica cómo diseñar tu app para que admita diferentes densidades de píxeles mediante las unidades de medida independientes de la resolución y los recursos de mapa de bits alternativos para cada densidad de píxeles.
Mira el siguiente video para obtener una descripción general de estas técnicas.
Para obtener más información sobre cómo diseñar elementos de íconos reales, consulta los lineamientos sobre íconos de material design.
Cómo utilizar píxeles independientes de la densidad
Lo primero que debes evitar es usar píxeles para definir distancias o tamaños. Definir dimensiones con píxeles es un problema, ya que las diferentes pantallas tienen distintas densidades de píxeles. Por lo tanto, la misma cantidad de píxeles puede corresponder a diferentes tamaños físicos en distintos dispositivos.
Figura 1: Dos pantallas del mismo tamaño pueden tener diferentes cantidades de píxeles
Para que el tamaño de tu IU se mantenga visible en pantallas con distintas densidades, debes diseñar tu IU con píxeles independientes de la densidad (dp) como unidad de medida. Un dp es una unidad de píxel virtual que prácticamente equivale a un píxel en una pantalla de densidad media (160 dpi; la densidad "de referencia"). Android traduce este valor a la cantidad apropiada de píxeles reales para cada densidad.
Por ejemplo, observa los dos dispositivos de la figura 1. Si definieras una vista de "100 px" de ancho, parecerá mucho más grande en el dispositivo de la izquierda. Por lo tanto, debes usar "100 dp" para asegurarte de que aparezca en ambas pantallas.
Sin embargo, cuando definas tamaños de texto, deberás usar píxeles escalables (sp) como unidades (pero nunca para los tamaños del diseño). De manera predeterminada, la unidad de sp tiene el mismo tamaño que la de dp, pero se modifica en función del tamaño de texto que prefiere el usuario.
Por ejemplo, cuando especifiques el espaciado entre dos vistas, usa dp
:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/clickme" android:layout_marginTop="20dp" />
Para especificar el tamaño de texto, siempre utiliza sp
:
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" />
Cómo convertir unidades de dp a unidades de píxeles
En algunos casos, necesitarás expresar las dimensiones en dp
y convertirlas a píxeles. La conversión de unidades dp a píxeles de pantalla es simple:
px = dp * (dpi / 160)
Imagina una app en la que un gesto de desplazamiento o lanzamiento se reconozca después de que el dedo del usuario haya recorrido al menos 16 píxeles. En una pantalla de referencia, un desplazamiento obligatorio del usuario de 16 pixels
/ 160 dpi
, que equivale a un décimo de pulgada (o 2.5 mm) antes de que se reconozca el gesto. En un dispositivo con una pantalla de alta densidad (240 dpi), el dedo del usuario debe desplazarse 16 pixels / 240 dpi
, que equivale a un quinceavo de pulgada (o 1.7 mm). La distancia es mucho más corta y, por lo tanto, la app resulta más sensible para el usuario.
Para solucionar este problema, el umbral del gesto debe expresarse en dp
en el código, y luego convertirse a píxeles reales. Por ejemplo:
Kotlin
// The gesture threshold expressed in dp private const val GESTURE_THRESHOLD_DP = 16.0f ... private var mGestureThreshold: Int = 0 ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Get the screen's density scale val scale: Float = resources.displayMetrics.density // Convert the dps to pixels, based on density scale mGestureThreshold = (GESTURE_THRESHOLD_DP * scale + 0.5f).toInt() // Use mGestureThreshold as a distance in pixels... }
Java
// The gesture threshold expressed in dp private static final float GESTURE_THRESHOLD_DP = 16.0f; // Get the screen's density scale final float scale = getResources().getDisplayMetrics().density; // Convert the dps to pixels, based on density scale mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f); // Use mGestureThreshold as a distance in pixels...
El campo DisplayMetrics.density
especifica el factor de escala que debes usar para convertir unidades de dp
a píxeles, según la densidad de píxeles actual. En una pantalla de densidad media, DisplayMetrics.density
equivale a 1.0; en una de densidad alta, a 1.5; en una de densidad extraalta, a 2.0; y en una de densidad baja, a 0.75. Esta cifra es el factor por el que debes multiplicar las unidades de dp
para obtener el recuento de píxeles real de la pantalla actual.
Cómo usar valores de configuración escalados previamente
Puedes usar la clase ViewConfiguration
para acceder a distancias, velocidades y tiempos normales que usa el sistema Android. Por ejemplo, la distancia en píxeles que usa el framework como límite de desplazamiento se puede obtener con getScaledTouchSlop()
:
Kotlin
private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop
Java
private static final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();
Los métodos de ViewConfiguration
que comienzan con el prefijo getScaled
garantizarán un valor en píxeles que se mostrará de forma apropiada, independientemente de la densidad de píxeles actual.
Cómo proporcionar mapas de bits alternativos
Para proporcionar gráficos de buena calidad en dispositivos con diferentes densidades de píxeles, debes brindar varias versiones de cada mapa de bits en tu app (una para cada intervalo de densidad) en la resolución correspondiente. De lo contrario, Android deberá escalar tu mapa de bits para que ocupe el mismo espacio visible en cada pantalla, lo que generará artefactos de escalamiento como imágenes borrosas.
Figura 2: Tamaños relativos de mapas de bits en diferentes tamaños de densidad
Hay muchos intervalos de densidad disponibles para usar en tus apps. En la tabla 1, se describen los diferentes calificadores de configuración disponibles y a qué tipos de pantallas se aplican.
Tabla 1: Calificadores de configuración para diferentes densidades de píxeles
Calificador de densidad | Descripción |
---|---|
ldpi |
Recursos para pantallas de densidad baja (ldpi) (120 dpi) |
mdpi |
Recursos para pantallas de densidad media (mdpi) (~160 dpi; esta es la densidad de referencia) |
hdpi |
Recursos para pantallas de densidad alta (hdpi) (~240 dpi) |
xhdpi |
Recursos para pantallas de densidad muy alta (xhdpi) (~320 dpi) |
xxhdpi |
Recursos para pantallas de densidad muy, muy alta (xxhdpi) (~480 dpi) |
xxxhdpi |
Recursos para usos de densidad extremadamente alta (xxxhdpi) (~640 dpi) |
nodpi |
Recursos para todas las densidades. Estos son recursos independientes de la densidad. El sistema no escala recursos etiquetados con este calificador, independientemente de la densidad de la pantalla actual. |
tvdpi |
Recursos para pantallas entre mdpi y hdpi; de aproximadamente 213 dpi. Este no se considera un grupo de densidad "principal". Se usa más que nada para televisiones, y la mayoría de las apps no lo necesitarán ya que con recursos mdpi y hdpi será suficiente; el sistema los escalará según corresponda. Si crees que es necesario proporcionar recursos tvdpi, debes ajustar su tamaño con un factor de 1.33*mdpi. Por ejemplo, una imagen de 100 x 100 píxeles para pantallas mdpi debe ser de 133 x 133 píxeles para tvdpi. |
Si deseas crear elementos de diseño alternativos de mapa de bits para diferentes densidades, debes seguir la proporción de escalamiento 3:4:6:8:12:16 entre las seis densidades principales. Por ejemplo, si tienes un elemento de diseño de mapa de bits de 48 x 48 píxeles para pantallas de densidad media, los tamaños diferentes deben ser los siguientes:
- 36 x 36 (0.75x) para densidad baja (ldpi)
- 48 x 48 (referencia de 1.0x) para densidad media (mdpi)
- 72 x 72 (1.5x) para densidad alta (hdpi)
- 96 x 96 (2.0x) para densidad muy alta (xhdpi)
- 144 x 144 (3.0x) para densidad muy, muy alta (xxhdpi)
- 192 x 192 (4.0x) para densidad extremadamente alta (xxxhdpi)
Luego, coloca los archivos de imagen generados en el subdirectorio correspondiente en res/
, y el sistema elegirá el correcto automáticamente, en función de la densidad de píxeles del dispositivo en el que se ejecuta tu app:
res/ drawable-xxxhdpi/ awesome-image.png drawable-xxhdpi/ awesome-image.png drawable-xhdpi/ awesome-image.png drawable-hdpi/ awesome-image.png drawable-mdpi/ awesome-image.png
De esta forma, cada vez que hagas referencia a @drawable/awesomeimage
, el sistema seleccionará el mapa de bits adecuado en función de los dpi de la pantalla. Si no proporcionas un recurso específico de densidad para ese valor, el sistema seleccionará la mejor opción y la escalará para que se ajuste a la pantalla.
Sugerencia: si tienes algunos recursos de elementos de diseño que el sistema no debe escalar nunca (tal vez porque tú mismo realizas algunos ajustes en la imagen durante el tiempo de ejecución), debes ubicarlos en un directorio con el calificador de configuración nodpi
. Los recursos con este calificador se consideran independientes de la densidad, y el sistema nos los escalará.
Para obtener más información sobre otros calificadores de configuración y la manera en que Android selecciona los recursos apropiados para la configuración de pantalla actual, consulta Cómo proporcionar recursos.
Cómo agregar íconos de apps en los directorios mipmap
Al igual que todos los otros elementos de mapas de bits, debes proporcionar versiones específicas de densidad del ícono de tu app. Sin embargo, algunos selectores de apps muestran el ícono de la app hasta un 25% más grande que lo que requiere el intervalo de densidad del dispositivo.
Por ejemplo, si el intervalo de densidad de un dispositivo es xxhdpi y el ícono de app más grande que proporcionas está en drawable-xxhdpi
, la app del selector aumentará la escala de este ícono, y hará que se vea menos nítido. Por lo tanto, debes incluir un ícono de selector de densidad aún mayor en el directorio mipmap-xxxhdpi
. De esta manera, el selector podrá usar el elemento xxxhdpi en su lugar.
Debido a que es posible que se amplíe la escala del ícono de tu app de esta manera, debes colocar todos los íconos de apps en directorios mipmap
en lugar de hacerlo en directorios drawable
. A diferencia del directorio drawable
, se retienen todos los directorios mipmap
en el APK, incluso si compilas APK específicos de densidad. Esto permite elegir el ícono de las apps del selector con mejor resolución para que se muestre en la pantalla principal.
res/ mipmap-xxxhdpi/ launcher-icon.png mipmap-xxhdpi/ launcher-icon.png mipmap-xhdpi/ launcher-icon.png mipmap-hdpi/ launcher-icon.png mipmap-mdpi/ launcher-icon.png
A fin de obtener los lineamientos de diseño de íconos, consulta la guía del material para íconos.
Para obtener información sobre cómo crear íconos de apps, consulta Cómo crear íconos de apps con Image Asset Studio.
Cómo usar gráficos vectoriales en su lugar
Como alternativa a la creación de varias versiones específicas de densidad de una imagen, puedes crear un gráfico vectorial. Estos crean una imagen mediante XML para definir rutas y colores, en lugar de usar mapas de bits de píxeles. Los gráficos vectoriales pueden escalarse a cualquier tamaño sin artefactos de escalamiento, aunque suelen funcionar mejor para ilustraciones como íconos, no así para fotos.
Los gráficos vectoriales suelen proporcionarse como un archivo SVG (gráficos vectoriales escalables), pero Android no admite este formato, por lo que debes convertirlos al formato de elemento de diseño vectorial de Android.
Puedes convertir un archivo SVG a un elemento de diseño vectorial de Android Studio fácilmente con Vector Asset Studio de la siguiente manera:
- En la ventana Proyecto, haz clic con el botón derecho en el directorio res y selecciona Nuevo > Elemento vectorial.
- Selecciona Archivo local (SVG, PSD).
Ubica el archivo que quieras importar y realiza las modificaciones necesarias.
Figura 3: Se importa un archivo SVG con Android Studio
Es posible que aparezcan algunos errores en la ventana de Asset Studio que indiquen que algunas propiedades del archivo no son compatibles con los elementos de diseño vectoriales. Sin embargo, estos no impedirán que realices la importación, ya que las propiedades no compatibles simplemente se ignoran.
Haz clic en Next.
En la pantalla siguiente, confirma el conjunto de orígenes en donde quieras ubicar el archivo de tu proyecto y haz clic en Finish.
Como un elemento de diseño vectorial puede usarse en todas las densidades de píxeles, este archivo se ubica en el directorio de elementos de diseño predeterminado (no necesitas usar directorios específicos de densidad):
res/ drawable/ ic_android_launcher.xml
Para obtener más información sobre cómo crear gráficos vectoriales, consulta la documentación sobre elementos de diseño vectoriales.
Sugerencias para problemas de densidad poco comunes
En esta sección, se proporciona una descripción adicional sobre cómo Android realiza el escalamiento para mapas de bits en diferentes densidades de píxeles y cómo puedes controlar la manera en que los mapas de bits se dibujan en diferentes densidades. A menos que tu app manipule gráficos o que tengas problemas para ejecutarla en diferentes densidades de píxeles, puedes ignorar esta sección.
Para entender mejor la manera de admitir varias densidades cuando manipulas gráficos durante el tiempo de ejecución, debes tener claro que el sistema ayuda a garantizar la escala apropiada para los mapas de bits de las siguientes maneras:
- Escalamiento previo de los recursos (como los elementos de diseño del mapa de bits)
En función de la densidad de la pantalla actual, el sistema usa cualquier recurso específico de densidad de tu app. Si los recursos no están disponibles en la densidad correcta, el sistema carga los recursos predeterminados y aumenta o disminuye la escala según sea necesario. El sistema da por sentado que los recursos predeterminados (los de un directorio sin calificadores de configuración) están diseñados para la densidad de píxeles de referencia (mdpi) y ajustará esos mapas de bits al tamaño adecuado para la densidad de píxeles actual.
Si solicitas las dimensiones de un recurso escalado previamente, el sistema muestra valores que representan las dimensiones después del escalamiento. Por ejemplo, un mapa de bits con un diseño de 50 x 50 píxeles para una pantalla mdpi se escala a 75 x 75 píxeles en una pantalla hdpi (si no hay un recurso alternativo para hdpi) y el sistema informa el tamaño como tal.
Hay algunas situaciones en las que quizá no te convenga que Android escale un recurso previamente. La manera más sencilla de evitar el ajuste previo es ubicar el recurso en un directorio de recursos con el calificador de configuración
nodpi
. Por ejemplo:res/drawable-nodpi/icon.png
Cuando el sistema usa el mapa de bits
icon.png
de esta carpeta, no lo escala según la densidad del dispositivo actual. - Escalamiento automático de dimensiones y coordenadas de píxeles
Puedes inhabilitar las dimensiones y las imágenes con ajuste de escala previo si configuras
android:anyDensity
en"false"
en el manifiesto, o de manera programática paraBitmap
, si configurasinScaled
en"false"
. En este caso, el sistema escala automáticamente cualquier coordenada absoluta de píxeles y valor de dimensión de píxeles al momento de la representación gráfica. De esta manera, garantiza que los elementos de pantalla definidos por píxeles se muestren aproximadamente en el mismo tamaño físico que tendrían en la densidad de píxeles de referencia (mdpi). El sistema controla este escalamiento de forma transparente en la app y, luego, informa las dimensiones de píxeles escalados a la app, en lugar de las dimensiones de píxeles físicos.Por ejemplo, supongamos que un dispositivo tiene una pantalla de densidad alta WVGA, con una resolución de 480 x 800, y aproximadamente el mismo tamaño que una pantalla HVGA tradicional, pero ejecuta una app con escalamiento previo inhabilitado. En este caso, el sistema le "mentirá" a la app cuando esta consulte las dimensiones de la pantalla y le informará que es de 320 x 533 (la traducción de mdpi aproximada para la densidad de píxeles). Entonces, cuando la app realice las operaciones de dibujo, como la invalidación del rectángulo de (10, 10) a (100, 100), el sistema transformará las coordenadas al realizar el escalamiento correspondiente y, luego, invalidará la región (15, 15) a (150, 150). Esta discrepancia puede generar un comportamiento inesperado si tu app manipula de manera directa el mapa de bits escalado, pero se considera como un intercambio razonable para que el rendimiento de las apps sea el máximo posible. Si tienes este problema, consulta Cómo convertir unidades de dp a unidades de píxeles.
Por lo general, no se debe inhabilitar el escalamiento previo. La mejor manera para admitir varias pantallas es seguir las técnicas básicas que se describen en este documento.
Si tu app manipula mapas de bits o interactúa directamente con píxeles en la pantalla de alguna otra forma, tal vez debas realizar pasos adicionales para admitir diferentes densidades de píxeles. Por ejemplo, si respondes a gestos táctiles al contar la cantidad de píxeles que recorre un dedo, debes usar los valores de píxeles independientes de la densidad adecuados, en lugar de píxeles reales. Puedes convertir valores de dp a px fácilmente.
Cómo probar la app en todas las densidades de píxeles
Es importante que pruebes tu app en varios dispositivos con diferentes densidades de píxeles, para asegurarte de que tu IU se escale correctamente. Es fácil probarla en un dispositivo físico, pero también puedes hacerlo en Android Emulator si no tienes acceso a dispositivos físicos de todas las densidades de píxeles.
Si prefieres probarla en dispositivos físicos, pero no quieres comprarlos, puedes usar Firebase Test Lab para acceder a dispositivos en el centro de datos de Google.