Android para todos los dispositivos

Parte 1 – Recursos

Introducción

Durante el Google IO 2010, una de las charlas que más me interesó fue la impartida por Justin Mattson llamada “Casting a wide net: how to target all Android devices” que trató de como hacer aplicaciones, que puedan ser usadas exitosamente, en todos los dispositivos Android. Puede que suene un poco audaz, pero sí es posible. Tenemos que tener claro que no para todas las aplicaciones es posible, pero se pueden tomar algunos consejos para poder permitir a la aplicación ser ejecutada, al menos, en la mayor cantidad de dispositivos posible.

Lamentablemente la charla fue corta, para la importancia del contenido por lo que quiero hacer esta serie de entradas, para explicar un poco más a detalle lo explicado en la presentación, basado tanto en el contenido de la misma, como en mi experiencia. Para ello, les explicaré con un ejemplo llamado Blue Monkey Eyes, una aplicación que hice para poder entender bien estos conceptos. La aplicación es bastante absurda, ya que no hace más que hacer ruidos, pero lo que veremos es el cómo se hizo.

Para esta primera parte discutiremos lo siguiente:

Densidad de Pantallas

En pocas palabras, cuantos puntos por pulgada hay en una superficie (dpi). No hay que confundir pixeles por pulgada (ppi). La importancia de la densidad es que esto permite que al crear una  imagen o texto tenga el mismo tamaño real, sin importar la resolución o tamaño físico de la pantalla. Veamos la siguiente imagen:

Ejemplo de independencia de densidad en WVGA de alta densidad (izq), HVGA media densidad (centro), y QVGA baja densidad (der). Tomado de Android Support Screens

Es importante que a la hora de diseñar los recursos de la aplicación, se defina bien los tamaños que se soportarán para así determinar la densidad o densidades y hacer un conjunto de recursos por cada uno. La plataforma Android nos permite definir carpetas por densidad de pantalla usando “calificadores” o “sufijos”. Para la densidad, están definidos 4 calificadores para la carpeta drawable:”-hdpi”, “-mdpi”, “-ldpi” y “nodpi”. Simplemente creamos las carpetas “drawable-[hpdi|mdpi|ldpi]” según las densidades que se soportarán. Es importante que los recursos mantengan el nombre a travéz de los folders, para que así la plataforma pueda hacer la selección sin problemas.

Estructura de carpetas

Estructura de carpetas

Existe una “pulga” o “bug” en la version 1.5 (SDK 3), ya que esta estructura de carpetas fue introducida hasta la versión 1.6. El problema con 1.5 es que puede escoger inadecuadamente el recurso, aunque éste se encuentre en la carpeta “drawable”. Para resolver esto, se debe agregar otro sufijo (después de -[hdpi|mdpi|ldpi]) a las carpetas que serán utilizadas para soportar diferentes densidades en dispositivos mayores a la version 3 del SDK. El sufijo a utilizar es “-v[4…n]” donde n es el número de SDK mínimo que la aplicación soportará para multi densidad y se deja “drawable” para que el SDK 3 tome las imágenes. Creo que esta mejor explicado en http://developer.android.com/guide/practices/screens_support.html#qualifiers.

Resolución de Pantallas

Actualmente, diferentes compañías han adoptado Android para la fabricación de dispositivos móviles, algunos de ellos son HTC (que sacó el primer dispositivo Android), Samsumg, Motorola entre otros. Debido a que no existe una estandariación en cuanto al hardware que soporta Android, tenemos a disposición diferentes modelos para diferentes necesidades. La visión de Google en cuanto dejar abierto a la innovación queda muy claro a este punto.  Tenemos los dispositivos tipo tablets a la vuelta de la esquina y no muy lejos Google TV que también se basará en android.

En la documentación oficial de Android se encuentra disponible una guía para poder soportar múltiples pantallas (http://developer.android.com/guide/practices/screens_support.html). Ahí definen “cajones” de como se dividen las pantallas y como se distribuye las densidades en ellas formando la siguiente matriz:

Baja densidad  (120), ldpi Densidad Media (160), mdpi Alta densidad (240), hdpi
Pantalla Pequeña
  • QVGA (240×320), 2.6″-3.0″ diagonal
Pantalla Normal
  • WQVGA (240×400), 3.2″-3.5″ diagonal
  • FWQVGA (240×432), 3.5″-3.8″ diagonal
  • HVGA (320×480), 3.0″-3.5″ diagonal
  • WVGA (480×800), 3.3″-4.0″ diagonal
  • FWVGA (480×854), 3.5″-4.0″ diagonal
Pantalla Grande
  • WVGA (480×800), 4.8″-5.5″ diagonal
  • FWVGA (480×854), 5.0″-5.8″ diagonal

Android ofrece los calificadores “small”, “normal” y “large” para poder separar los recursos de acuerdo al tamaño de la pantalla.

Orientación Horizontal y Vertical

Tal vez una de las cualidades más discutidas en la comunidad Android, es la capacidad de cambiar el aspecto cuando se tiene el dispositivo de forma vertical o horizontal. Dentro de las preguntas más frecuentes que se puede leer en el grupo de usuarios de Android es como desactivar esta facilidad. Lo que recomienda Google es no pelear con él sino aprender a manejarlo.

Hay que tener bien claro que estoy no aplica para todas las aplicaciones. Por ejemplo, algunos juegos ocupan que a pesar de que se cambie la orientación del dispositivo, se mantenga todo como está, para evitar una experiencia de usuario pobre y por que el game play lo requiere así.

Si su aplicación requiere que se maneje tanto una orientación vertical (portrait) como horizontal (landscape), puede utilizar los calificadores “-port” y “-land” para la carpeta de layout.

La carpeta debiera verse así:

Folders Layout con calificador -land

Dando como resultado que la aplicación se vea así en ambas posiciones:

Posición por defecto, Vertical

Horizontal

Aqui el XML de ambos layouts.

Layout por defecto de Obey the Monkey

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res/com.fr4gus.android.blueeyesmonkey"
	android:orientation="vertical" 
        android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<com.admob.android.ads.AdView android:id="@+id/ad"
		android:layout_width="fill_parent" 
		android:layout_height="wrap_content"
		app:backgroundColor="#000000" 
		app:primaryTextColor="#FFFFFF"
		app:secondaryTextColor="#CCCCCC" 
		app:refreshInterval="15"
		app:keywords="Android Silly Application" />

	<ImageView android:id="@+id/ImageMonkey"
		android:layout_width="wrap_content" 
		android:layout_height="wrap_content"
		android:src="@drawable/monkey_inactive" 
		android:baselineAlignBottom="true"
		android:layout_gravity="center_vertical|center_horizontal" />

	<LinearLayout android:id="@+id/ButtonsLayout"
		android:layout_width="fill_parent" 
		android:orientation="vertical" 
		android:layout_height="wrap_content" 
		android:layout_gravity="bottom|center_horizontal">
		<Button android:id="@+id/ObeyButton" android:layout_width="fill_parent"
			android:layout_height="wrap_content" 
			android:text="@string/obeyButtonLabel"
			android:gravity="center_horizontal|center_vertical" 
			android:background="@drawable/button_states" 
			android:textColor="#FFFFFF" />
		<Button android:id="@+id/ListenButton" 
			android:layout_width="fill_parent"
			android:layout_height="wrap_content" 
			android:text="@string/listenButtonLabel"
			android:typeface="normal" android:textColor="#FFFFFF" 
			android:background="@drawable/button_states" />
	</LinearLayout>
</LinearLayout>

Layout en modo landscape de Obey the Monkey

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res/com.fr4gus.android.blueeyesmonkey"
	android:orientation="vertical" android:layout_gravity="center_vertical|center_horizontal"
	android:layout_height="fill_parent" android:layout_width="fill_parent">
	<com.admob.android.ads.AdView android:id="@+id/ad"
		android:layout_width="fill_parent" android:layout_height="wrap_content"
		app:backgroundColor="#000000" app:primaryTextColor="#FFFFFF"
		app:secondaryTextColor="#CCCCCC" app:refreshInterval="15"
		app:keywords="Android Silly Application" />
	<LinearLayout android:id="@+id/contentLayout"
		android:layout_width="wrap_content" android:layout_height="wrap_content"
		android:orientation="horizontal" android:layout_gravity="right|center_vertical">
		<ImageView android:id="@+id/ImageMonkey"
			android:layout_width="wrap_content" android:layout_height="wrap_content"
			android:src="@drawable/monkey_inactive" android:layout_gravity="center_horizontal|center_vertical"></ImageView>
		<LinearLayout android:id="@+id/ButtonsLayout"
			android:layout_width="wrap_content" android:layout_height="wrap_content"
			android:orientation="vertical" android:layout_gravity="right|center_vertical">
			<Button android:id="@+id/ObeyButton" android:layout_width="fill_parent"
				android:layout_height="wrap_content" android:text="@string/obeyButtonLabel"
				android:gravity="center_horizontal|center_vertical"
				android:textColor="#FFFFFF" android:layout_gravity="center_vertical|center_horizontal"
				android:background="@drawable/button_states"></Button>
			<Button android:id="@+id/ListenButton" android:layout_width="fill_parent"
				android:layout_height="wrap_content" android:text="@string/listenButtonLabel"
				android:textColor="#FFFFFF" android:typeface="normal"
				android:layout_gravity="center_vertical|center_horizontal"
				android:background="@drawable/button_states"></Button>
		</LinearLayout>

	</LinearLayout>
</LinearLayout>

Resumen

La importancia de poder soportar diferentes pantallas, radica principalmente en el mercado meta de su aplicación. Entre más dispositivos soporte, a más usuarios podra llegar. Pero debe tomar en cuenta los requerimientos de la aplicación así como velar que la experiencia de usuario sea grata también.

Para la segunda parte revisaremos como manejar las diferentes versiones de SDKs y como podemos afrontar el problema de objetos o métodos disponibles en uno e innexistentes en otros.

Calificadores vistos:

Pantalla Calificador o Sufijo Descripción
Tamaño small Recursos para pantallas pequeñas, como QVGA en baja densidad
normal Recursos para pantallas normales, como las pantallas del  T-Mobile G1/HTC Magic o equivalente
large Recursos para pantResources for large screens. Typical example is a tablet like device.
Densidad ldpi Recursos de baja densidad (entre  100 y140 dpi).
mdpi Recursos de mediana densidad  (entre 140 y 180 dpi).
hdpi Recursos de alta densidad (entre 190 y 250 dpi)
nodpi Recursos independientes de densidad. La plataforma no intentara escalar automáticamente recursos bajo este calificador sin importar la densidad de la pantalla actual.
Relación de Aspecto long Recursos para pantalla de cualquier tamaño y densidad, donde la relación de acpecto tanto en alto (modo portrait)  o ancho (modo landscape) sea significativamente mayor que la configuración base de la pantalla.
notlong Recursos para pantallas donde la relación de aspecto es similar a la configuración base de la pantalla.
Versión de Plataforma v<api-level> Recursos que solo se usarán para una versión específica del API o mayor. Por ejemnplo, si su aplicación corre en Android 1.5 (API Level 3) y Android 1.5 (API Level 4 omayor) usted puede usar el calificador -v4 para excluir aquellos recursos cuando la aplicación va a correr Android  1.5 (API Level 3).
Vertical port Recursos que se utilizarán para la pantalla en modo portrait
Horizontal land Recursos que se utilizarán para la pantalla en modo landscape

Links importantes