Diseños   Parte de Android Jetpack.

Un diseño define la estructura de una interfaz de usuario en tu aplicación, por ejemplo, en una actividad. Todos los elementos del diseño se crean usando una jerarquía de objetos View y ViewGroup. Una View suele mostrar un elemento que el usuario puede ver y con el que puede interactuar. En cambio, un ViewGroup es un contenedor invisible que define la estructura de diseño de View y otros objetos ViewGroup, como se muestra en la Figura 1.

Figura 1: Ilustración de una jerarquía de vista, que define un diseño de IU

Los objetos View se denominan "widgets" y pueden ser una de muchas subclases, como Button o TextView. Los objetos ViewGroup se denominan "diseños" y pueden ser uno de los muchos tipos que proporcionan una estructura de diseño diferente, comoLinearLayout oConstraintLayout .

Puedes declarar un diseño de dos maneras:

  • Declarar elementos de la IU en XML. Android proporciona un vocabulario XML simple que coincide con las clases y subclases de vistas, como las que se usan para widgets y diseños.

    También puedes utilizar la función editor de diseño de Android Studio para crear tu diseño XML mediante una interfaz de arrastrar y soltar.

  • Crear una instancia de elementos de diseño durante el tiempo de ejecución. Tu aplicación puede crear objetos View y ViewGroup (y manipular sus propiedades) de forma programática.

Declarar tu IU en XML te permite separar la presentación de tu app del código que controla su comportamiento. El uso de archivos XML también facilita la creación de distintos diseños para diferentes tamaños de pantalla y orientaciones (se detalla más adelante enla guía Cómo admitir diferentes tamaños de pantalla).

El framework de Android te ofrece la flexibilidad de usar uno o ambos métodos para crear la IU de tu app. Por ejemplo, puedes declarar los diseños predeterminados de la app XML y, luego, modificar el diseño durante el tiempo de ejecución.

Sugerencia: Para depurar tu diseño durante el tiempo de ejecución, usa la herramienta Inspector de diseño.

Escribe en XML

Al usar vocabulario XML de Android, puedes crear rápidamente diseños de IU y de los elementos de pantalla que contienen, de la misma manera que creas páginas web en HTML, con una serie de elementos anidados.

Cada archivo de diseño debe contener exactamente un elemento raíz, que debe ser un objeto View o ViewGroup. Una vez que hayas definido el elemento raíz, puedes agregar widgets u objetos de diseño adicionales como elementos secundarios para crear gradualmente una jerarquía de vistas que defina el diseño. Por ejemplo, a continuación se muestra un diseño XML que usa un LinearLayout vertical para incluir una TextView y un Button:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

Después de declarar tu diseño en XML, guarda el archivo con la extensión .xml en el directorio res/layout/ de tu proyecto de Android para que pueda compilarse correctamente.

Puedes obtener más información acerca de la sintaxis para un archivo XML de diseño en el documento Recursos de diseño.

Carga el recurso XML

Cuando compilas tu aplicación, cada archivo XML de diseño se compila en un recurso View. Debes cargar el recurso de diseño desde el código de tu aplicación, en la implementación de devolución de llamada Activity.onCreate(). Para eso, llama a setContentView() pasando la referencia a tu recurso de diseño en forma de R.layout.layout_file_name. Por ejemplo, si tu diseño XML se guarda como main_layout.xml, los cargarás para tu actividad de la siguiente manera:

Kotlin

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Java

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

El framework de Android llama al método de devolución de llamada onCreate() en tu actividad cuando la actividad se inicia. Consulta el artículo acerca de los ciclos de vida en el documento Actividades.

Atributos

Cada objeto View y ViewGroup admite su propia variedad de atributos XML. Algunos atributos son específicos de un objeto View (por ejemplo, TextView admite el atributo textSize), aunque estos atributos también son heredados por cualquier objeto View que pueda extender esta clase. Algunos son comunes para todos los objetos View, porque se heredan de la clase raíz View (como el atributo id). Además, otros atributos se consideran "parámetros de diseño", que son atributos que describen ciertas orientaciones de diseño de View, tal como lo define el objeto ViewGroup superior de ese objeto.

ID

Cualquier objeto View puede tener un ID entero asociado para identificarse de forma única dentro del árbol. Cuando se compila la aplicación, se hace referencia a este ID como un número entero, pero normalmente se asigna el ID en el archivo XML de diseño como una string del atributo id. Este es un atributo XML común para todos los objetos View (definido por la clase View) y lo utilizarás muy a menudo. La sintaxis de un ID dentro de una etiqueta XML es la siguiente:

android:id="@+id/my_button"

El símbolo arroba (@) al comienzo de la string indica que el analizador de XML debe analizar y expandir el resto de la string de ID, y luego identificarla como un recurso de ID. El símbolo más (+) significa que es un nuevo nombre de recurso que se debe crear y agregar a nuestros recursos (en el archivo R.java). El framework de Android ofrece otros recursos de ID. Al hacer referencia a un ID de recurso de Android, no necesitas el símbolo más, pero debes agregar el espacio de nombres de paquete android de la siguiente manera:

android:id="@android:id/empty"

Con el espacio de nombres de paquete android establecido, se hace referencia a un ID de la clase de recursos android.R, en lugar de la clase de recursos local.

Para crear vistas y hacer referencia a ellas desde la aplicación, puedes seguir este patrón común:

  1. Definir una vista o un widget en el archivo de diseño y asignarle un ID único:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. Luego, crear una instancia del objeto View y capturarla desde el diseño (generalmente en el método onCreate()):

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

Definir ID para objetos View es importante cuando se crea un RelativeLayout. En un diseño relativo, las vistas del mismo nivel pueden definir su diseño en función de otra vista del mismo nivel, que se identifica con un ID único.

No es necesario que un ID sea único en todo el árbol, pero debe ser único dentro de la parte del árbol en la que estás buscando (que a menudo puede ser el árbol completo, por lo que es mejor que, en lo posible, sea siempre único).

Nota: Con Android Studio 3.6 y versiones posteriores, la función de vinculación de vistas puede reemplazar las llamadas findViewById(), y proporciona seguridad relativa al tiempo de compilación para el código que interactúa con las vistas. Puedes usar la vinculación de vistas en lugar de findViewById().

Parámetros de diseño

Los atributos de diseño XML denominados layout_something definen parámetros de diseño para el objeto View que son adecuados para el objeto ViewGroup en el que reside.

Cada clase ViewGroup implementa una clase anidada que extiende ViewGroup.LayoutParams. Esta subclase contiene tipos de propiedad que definen el tamaño y la posición de cada vista secundaria, según resulte apropiado para el grupo de vistas. Como se muestra en la Figura 2, el grupo de vistas superior define parámetros de diseño para cada vista secundaria (incluido el grupo de vistas secundario).

Figura 2: Visualización de una jerarquía de vista con parámetros de diseño asociados con cada vista

Ten en cuenta que cada subclase LayoutParams tiene su propia sintaxis para configurar valores. Cada elemento secundario debe definir LayoutParams adecuados para su elemento superior, aunque también puede definir diferentes LayoutParams para sus propios elementos secundarios.

Todos los grupos de vistas incluyen un ancho y una altura (layout_width y layout_height), y cada vista debe definirlos. Muchos LayoutParams también incluyen márgenes y bordes opcionales.

Puedes especificar el ancho y la altura con medidas exactas, aunque no es recomendable hacerlo con mucha frecuencia. Generalmente, se usa una de estas constantes para establecer el ancho o la altura:

  • wrap_content le indica a tu vista que modifique su tamaño conforme a las dimensiones que requiere su contenido.
  • match_parent le indica a tu vista que se agrande tanto como lo permita su grupo de vistas superior.

En general, no se recomienda especificar el ancho ni la altura de un diseño con unidades absolutas como píxeles. En cambio, el uso de medidas relativas, como unidades de píxeles independientes de la densidad (dp), wrap_content o match_parent, es un mejor enfoque, ya que ayuda a garantizar que la app se visualice correctamente en distintos tamaños de pantalla de dispositivos. Los tipos de medidas aceptados se definen en el documento Recursos disponibles.

Posición del diseño

La geometría de una vista es la de un rectángulo. Una vista tiene una ubicación, expresada como un par de coordenadas izquierda y superior, y dos dimensiones, expresadas como un ancho y una altura. La unidad para la ubicación y las dimensiones es el píxel.

Es posible recuperar la ubicación de una vista al invocar los métodos getLeft() y getTop(). El primero muestra la coordenada izquierda, o X, del rectángulo que representa la vista. El segundo muestra la coordenada superior, o Y, del rectángulo que representa la vista. Ambos métodos muestran la ubicación de la vista respecto al elemento superior. Por ejemplo, cuando getLeft() muestra 20, significa que la vista se encuentra a 20 píxeles a la derecha del borde izquierdo de su elemento superior directo.

Además, se ofrecen varios métodos convenientes para evitar cálculos innecesarios: getRight() y getBottom(). Estos métodos muestran las coordenadas de los bordes inferior y derecho del rectángulo que representa la vista. Por ejemplo, llamar a getRight() es similar al siguiente cálculo: getLeft() + getWidth().

Tamaño, relleno y márgenes

El tamaño de una vista se expresa con un ancho y una altura. En realidad, una vista tiene dos pares de valores de ancho y altura.

El primer par se conoce como ancho medido y altura medida. Estas dimensiones definen cuán grande quiere ser una vista dentro de su elemento superior. Las dimensiones medidas se pueden obtener llamando a getMeasuredWidth() y a getMeasuredHeight().

El segundo par se conoce simplemente como ancho y altura, o, algunas veces, ancho de dibujo y altura de dibujo. Estas dimensiones definen el tamaño real de la vista en la pantalla en el momento de dibujarlas y después del diseño. Estos valores pueden ser diferentes del ancho y la altura medidos, pero no necesariamente. El ancho y la altura se pueden obtener llamando a getWidth() y getHeight().

Para medir estas dimensiones, una vista considera su relleno. El relleno se expresa en píxeles para las partes izquierda, superior, derecha e inferior de la vista. El relleno se puede usar para desplazar el contenido de la vista una determinada cantidad de píxeles. Por ejemplo, un relleno izquierdo de 2 empuja el contenido de la vista 2 píxeles hacia la derecha del borde izquierdo. El relleno se puede ajustar usando el método setPadding(int, int, int, int) y se puede consultar llamando a getPaddingLeft(), getPaddingTop(), getPaddingRight() y getPaddingBottom().

Si bien una vista puede definir un relleno, no proporciona ningún tipo de compatibilidad para márgenes. No obstante, los grupos de vistas sí la proporcionan. Consulta ViewGroup y ViewGroup.MarginLayoutParams para obtener más información.

Para obtener más información acerca de las dimensiones, lee Valores de dimensión.

Diseños comunes

Cada subclase de la clase ViewGroup proporciona una manera única de mostrar las vistas que anidas en ella. A continuación, se muestran algunos de los tipos de diseño más comunes integrados en la plataforma Android.

Nota: Si bien puedes anidar uno o más diseños dentro de otro diseño para crear el diseño de la IU, debes esforzarte por mantener tu jerarquía de diseño lo más sencilla posible. Tu diseño se procesa más rápido si tiene menos diseños anidados (una jerarquía de vistas ancha es mejor que una profunda).

Diseño lineal

Un diseño que organiza sus elementos secundarios en una sola fila horizontal o vertical. Si la longitud de la ventana supera la de la pantalla, crea una barra de desplazamiento.

Objeto RelativeLayout

Te permite especificar la ubicación de los objetos secundarios en función de ellos mismos (el objeto secundario A a la izquierda del objeto secundario B) o en función del elemento primario (alineado con la parte superior del elemento primario).

Vista web

Muestra páginas web.

Creación de diseños con un adaptador

Cuando el contenido de tu diseño sea dinámico o no sea predeterminado, puedes usar un diseño con la subclase AdapterView para completar el diseño con vistas durante el tiempo de ejecución. Una subclase de la clase AdapterView usa un Adapter para enlazar datos con su diseño. Adapter se comporta como intermediario entre la fuente de datos y el diseño AdapterView; Adapter recupera los datos (de una fuente como un arreglo o una consulta a la base de datos) y convierte cada entrada en una vista que puedes agregar al diseño de la AdapterView.

Los diseños comunes respaldados por un adaptador incluyen:

Vista de lista

Muestra una sola lista de columnas desplazable.

Vista de cuadrícula

Muestra una cuadrícula desplazable de columnas y filas.

Relleno de una vista del adaptador con datos

Puedes completar una AdapterView, como ListView o GridView, enlazando la instancia AdapterView con un Adapter, que recupera datos de una fuente externa y crea una View que representa cada entrada de datos.

Android proporciona varias subclases de Adapter que resultan útiles para recuperar diferentes tipos de datos y generar vistas para una AdapterView. Los dos adaptadores más comunes son los siguientes:

ArrayAdapter
Usa este adaptador cuando la fuente de datos sea un arreglo. De forma predeterminada, ArrayAdapter crea una vista para cada elemento de arreglo llamando a toString() en cada elemento y disponiendo los contenidos en una TextView.

Por ejemplo, si hay un arreglo de strings que deseas mostrar en una ListView, inicializa un nuevo ArrayAdapter usando un constructor para especificar el diseño de cada string y el arreglo de strings:

Kotlin

val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)

Java

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, myStringArray);

Los argumentos para este constructor son los siguientes:

  • El Context de tu app
  • El diseño que contiene una TextView para cada string del arreglo
  • El arreglo de strings

Luego, simplemente llama a setAdapter() en tu ListView:

Kotlin

val listView: ListView = findViewById(R.id.listview)
listView.adapter = adapter

Java

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

Para personalizar el aspecto de cada elemento, puedes anular el método toString() de los objetos de tu arreglo. Como alternativa, si deseas crear una vista para cada elemento que no sea una TextView (por ejemplo, si deseas una ImageView para cada elemento de arreglo), extiende la clase ArrayAdapter y anula getView() a fin de mostrar el tipo de vista que desees para cada elemento.

SimpleCursorAdapter
Usa este adaptador cuando tus datos provengan de un Cursor. Cuando uses SimpleCursorAdapter, debes especificar un diseño para cada fila en el Cursor y qué columnas del Cursor se deben insertar en qué vistas del diseño. Por ejemplo, si deseas crear una lista de nombres y números de teléfono de personas, puedes realizar una consulta que muestre un Cursor con una fila para cada persona y columnas para los nombres y los números. Luego, crea un arreglo de strings que especifique las columnas del Cursor que desees en el diseño para cada resultado y un arreglo de números enteros que especifique las vistas correspondientes en las que se debe colocar cada columna:

Kotlin

val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                          ContactsContract.CommonDataKinds.Phone.NUMBER)
val toViews = intArrayOf(R.id.display_name, R.id.phone_number)

Java

String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};

Cuando crees una instancia del SimpleCursorAdapter, pasa el diseño que se debe usar para cada resultado, el Cursor que contiene los resultados y estos dos arreglos:

Kotlin

val adapter = SimpleCursorAdapter(this,
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
val listView = getListView()
listView.adapter = adapter

Java

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);

Luego, el SimpleCursorAdapter crea una vista para cada fila en el Cursor usando el diseño proporcionado al insertar cada elemento fromColumns en la vista toViews correspondiente.

.

Si durante el ciclo de vida de tu app cambias los datos subyacentes que lee tu adaptador, debes llamar a notifyDataSetChanged(). Esto notificará a la View adjunta que los datos se modificaron y deben actualizarse.

Cómo manejar eventos de clic

Puedes responder a eventos de clic en cada elemento de una AdapterView al implementar la interfaz AdapterView.OnItemClickListener. Por ejemplo:

Kotlin

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click
}

Java

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click
    }
};

listView.setOnItemClickListener(messageClickedHandler);

Recursos adicionales

Los diseños se usan en la app de demostración de Sunflower.