Diseños en Views

Prueba el estilo de Compose
Jetpack Compose es el kit de herramientas de IU recomendado para Android. Obtén información para trabajar con diseños en Compose.

Un diseño define la estructura de una interfaz de usuario en tu app, por ejemplo, en una actividad. Todos los elementos del diseño se compilan usando una jerarquía de objetos View y ViewGroup. Por lo general, un View dibuja algo que el usuario puede ver y con el que puede interactuar. 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 vistas, que define un diseño de IU.

Los objetos View suelen llamarse widgets y pueden ser una de varias subclases, como Button o TextView. Los objetos ViewGroup suelen llamarse diseños y pueden ser uno de muchos tipos que proporcionan una estructura de diseño diferente, como LinearLayout o ConstraintLayout.

Puedes declarar un diseño de dos maneras:

  • Declara los elementos de la IU en XML. Android proporciona un vocabulario XML simple que corresponde a las clases y subclases View, como las que se usan para widgets y diseños. También puedes usar el editor de diseño de Android Studio para compilar tu diseño XML usando una interfaz de arrastrar y soltar.

  • Crea una instancia de elementos de diseño durante el tiempo de ejecución. Tu app puede crear objetos View y ViewGroup, y manipular sus propiedades de manera 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 en formato XML también facilita la entrega de diferentes diseños para diferentes tamaños y orientaciones de pantalla. Esto se explica con más detalle en Cómo brindar compatibilidad con diferentes tamaños de pantalla.

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

Escribe en XML

Con el vocabulario XML de Android, puedes crear rápidamente diseños de IU y 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. Después de definir el elemento raíz, puedes agregar widgets o objetos de diseño adicionales como elementos secundarios para compilar gradualmente una jerarquía View que defina tu diseño. Por ejemplo, a continuación, se muestra un diseño XML que usa un LinearLayout vertical para contener un 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/ del proyecto de Android para que se compile correctamente.

Para obtener más información sobre la sintaxis de un archivo en formato XML de diseño, consulta Recurso de diseño.

Carga el recurso XML

Cuando compilas tu aplicación, cada archivo XML de diseño se compila en un recurso View. Carga el recurso de diseño en la implementación de devolución de llamada Activity.onCreate() de tu app. Para ello, llama a setContentView() y pásale la referencia a tu recurso de diseño en el formato R.layout.layout_file_name. Por ejemplo, si tu diseño XML se guarda como main_layout.xml, cárgalo en tu Activity 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 Activity cuando se inicia Activity. Para obtener más información sobre los ciclos de vida de las actividades, consulta Introducción a las 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. Sin embargo, cualquier objeto View que extienda esta clase también hereda estos atributos. Algunos son comunes a todos los objetos View, ya que se heredan de la clase raíz View, como el atributo id. Otros atributos se consideran parámetros de diseño, que son atributos que describen ciertas orientaciones de diseño del objeto View, según lo define el objeto superior ViewGroup de ese objeto.

ID

Cualquier objeto View puede tener un ID de número entero asociado para identificar de forma única la View dentro del árbol. Cuando se compila la app, se hace referencia a este ID como un número entero, pero, por lo general, el ID se asigna en el archivo en formato XML de diseño como una cadena en el atributo id. Se trata de un atributo XML común para todos los objetos View y lo define la clase View. Lo usas muy seguido. La sintaxis de un ID dentro de una etiqueta XML es la siguiente:

android:id="@+id/my_button"

El símbolo at (@) al comienzo de la cadena indica que el analizador XML analiza y expande el resto de la cadena de ID y la identifica como un recurso de ID. El símbolo más (+) significa que es un nombre de recurso nuevo que debes crear y agregar a tus recursos en el archivo R.java.

El framework de Android ofrece muchos otros recursos de ID. Cuando haces referencia a un ID de recurso de Android, no necesitas el símbolo más, pero debes agregar el espacio de nombres del paquete android de la siguiente manera:

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

El espacio de nombres del paquete android indica que haces 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 tu app, puedes usar un patrón común de la siguiente manera:

  1. Define una vista en el archivo de diseño y asígnale un ID único, como en el siguiente ejemplo:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. Crea una instancia del objeto View y captúrala desde el diseño, generalmente en el método onCreate(), como se muestra en el siguiente ejemplo:

    Kotlin

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

    Java

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

La definición de IDs para los objetos de vista es importante cuando se crea un RelativeLayout. En un diseño relativo, las vistas del mismo nivel pueden definir su diseño en relación con otra vista del mismo nivel, que se identifica con el 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 buscas. A menudo, puede ser el árbol completo, por lo que lo mejor es hacerlo único siempre que sea posible.

Parámetros de diseño

Los atributos de diseño XML llamados layout_something definen parámetros de diseño para el View que son apropiados para el 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 corresponda 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 vistas con parámetros de diseño asociados con cada vista.

Cada subclase LayoutParams tiene su propia sintaxis para configurar valores. Cada elemento secundario debe definir un LayoutParams que sea apropiado para su elemento superior, aunque también puede definir un LayoutParams diferente para sus propios elementos secundarios.

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

Puedes especificar el ancho y la altura con medidas exactas, pero es posible que no quieras hacerlo con frecuencia. En general, se usa una de estas constantes para establecer el ancho o la altura:

  • wrap_content: Indica a tu vista que modifique su tamaño conforme a los requisitos de su contenido.
  • match_parent: Indica a tu vista que se agrande tanto como lo permita su grupo de vistas superior.

En general, no recomendamos especificar el ancho y la altura de un diseño con unidades absolutas como píxeles. Un mejor enfoque es usar medidas relativas, como unidades de píxeles independientes de la densidad (dp), wrap_content o match_parent, ya que ayudan a que tu app se muestre correctamente en una variedad de tamaños de pantalla de dispositivos. Los tipos de medición aceptados se definen en Recurso de diseño.

Posición del diseño

Una vista tiene una geometría rectangular. 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.

Para recuperar la ubicación de una vista, invoca los métodos getLeft() y getTop(). El primero muestra la coordenada izquierda (x) del rectángulo que representa la vista. El segundo muestra la coordenada superior (y) del rectángulo que representa la vista. Estos métodos muestran la ubicación de la vista en relación con su elemento superior. Por ejemplo, cuando getLeft() muestra 20, significa que la vista se encuentra a 20 píxeles a la derecha del borde izquierdo del elemento superior directo.

Además, existen 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, padding y márgenes

El tamaño de una vista se expresa con un ancho y una altura. 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. Para obtener las dimensiones medidas, llama a getMeasuredWidth() y getMeasuredHeight().

El segundo par se conoce como ancho y altura, o, a veces, ancho de dibujo y altura de dibujo. Estas dimensiones definen el tamaño real de la vista en la pantalla, al momento de dibujarlas y después del diseño. Estos valores pueden diferir del ancho y la altura medidos, pero no es necesario. Para obtener el ancho y la altura, llama a getWidth() y getHeight().

Para medir estas dimensiones, una vista considera su relleno. El padding se expresa en píxeles para las partes izquierda, superior, derecha e inferior de la vista. Puedes usar padding para desplazar el contenido de la vista una cantidad específica de píxeles. Por ejemplo, un padding izquierdo de dos empuja el contenido de la vista dos píxeles hacia la derecha del borde izquierdo. Puedes configurar el padding con el método setPadding(int, int, int, int) y buscarlo llamando a getPaddingLeft(), getPaddingTop(), getPaddingRight() y getPaddingBottom().

Si bien una vista puede definir un relleno, no admite márgenes. Sin embargo, los grupos de vistas sí admiten márgenes. Consulta ViewGroup y ViewGroup.MarginLayoutParams para obtener más información.

Para obtener más información sobre las dimensiones, consulta Dimensión.

Además de configurar márgenes y padding de manera programática, también puedes establecerlos en tus diseños XML, como se muestra en el siguiente ejemplo:

  <?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:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

En el ejemplo anterior, se muestran el margen y el padding que se aplican. La TextView tiene márgenes y padding uniformes aplicados por todas partes, y el objeto Button muestra cómo puedes aplicarlos de forma independiente a diferentes bordes.

Diseños comunes

Cada subclase de la clase ViewGroup proporciona una forma única de mostrar las vistas que anidas en ella. El tipo de diseño más flexible y el que proporciona las mejores herramientas para mantener superficialmente la jerarquía de diseño es ConstraintLayout.

A continuación, se incluyen algunos de los tipos de diseño comunes integrados en la plataforma de Android.

Crea un diseño lineal

Organiza sus elementos secundarios en una sola fila horizontal o vertical y crea una barra de desplazamiento si la longitud de la ventana supera la de la pantalla.

Crea apps web en WebView

Muestra páginas web.

Cómo crear listas dinámicas

Cuando el contenido de tu diseño es dinámico o no está predeterminado, puedes usar RecyclerView o una subclase de AdapterView. Por lo general, RecyclerView es la mejor opción porque usa la memoria de manera más eficiente que AdapterView.

Entre los diseños comunes posibles con RecyclerView y AdapterView, se incluyen los siguientes:

Lista

Muestra una sola lista de columnas desplazable.

Cuadrícula

Muestra una cuadrícula desplazable de columnas y filas.

RecyclerView ofrece más posibilidades y la opción de crear un administrador de diseño personalizado.

Cómo rellenar una vista del adaptador con datos

Puedes propagar una AdapterView, como ListView o GridView, si vinculas la instancia AdapterView a un Adapter, que recupera datos de una fuente externa y crea un View que representa cada entrada de datos.

Android proporciona varias subclases de Adapter que sirven para recuperar diferentes tipos de datos y compilar 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 del array llamando a toString() en cada elemento y colocando el contenido en una TextView.

Por ejemplo, si tienes un array de cadenas que quieres mostrar en una ListView, inicializa un nuevo ArrayAdapter usando un constructor para especificar el diseño de cada cadena y el array de cadenas:

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:

  • Tu app Context
  • El diseño que contiene un TextView para cada cadena del array
  • El arreglo de strings

Luego, 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 array. O bien, si deseas crear una vista para cada elemento que no sea una TextView (por ejemplo, si quieres una ImageView para cada elemento del array), extiende la clase ArrayAdapter y anula getView() para mostrar el tipo de vista que deseas para cada elemento.

SimpleCursorAdapter
Usa este adaptador cuando tus datos provengan de un Cursor. Cuando uses SimpleCursorAdapter, especifica el diseño que usarás para cada fila en el Cursor y las columnas de Cursor que quieras insertar en las vistas del diseño que deseas. 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 números. Luego, crea un array de cadenas que especifique qué columnas de Cursor deseas en el diseño para cada resultado y un array de números enteros que especifique las vistas correspondientes que se deben colocar en 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 de SimpleCursorAdapter, pasa el diseño que se usará para cada resultado, el Cursor que contiene los resultados y estos dos arrays:

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, SimpleCursorAdapter crea una vista para cada fila en el Cursor usando el diseño proporcionado. Para ello, inserta 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, llama a notifyDataSetChanged(). Esto notifica a la vista adjunta que se modificaron los datos y se actualiza.

Cómo controlar eventos de clic

Puedes responder a eventos de clic en cada elemento de una AdapterView mediante la implementación de 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

Consulta cómo se usan los diseños en la app de demostración de Sunflower en GitHub.