Interfaz gráfica GUI (Android)

TEORÍA

Crear interfaces gráficas en Android es similar a crear GUIS’s para HTML: existen archivos de lenguaje de marcas (que en este caso es XML) y cada etiqueta (nodo) tiene una serie de atributos con los que podemos definir tamaño de texto, color del fondo, colocación y comportamiento de elementos, etc.

A parte de ello también podemos definir estilos (como las clases en CSS) y asignarle dicho estilo al nodo. Los estilos también pueden heredar de otro estilo.

Cada pantalla (el símil al documento HTML) es una «Activity«, y cada Activity tiene una vista asociada: el documento XML almacenado en la carpeta «res/layout«. Este documento XML (conocido como el «ContentView» de la activity) tiene un nodo raíz de tipo «Layout» (símil del body de HTML).

NOTA: La existencia y comportamiento de las Activities se definen desde el código Android, y se le asigna el layout deseado de forma explícita en su creación.

Cada nodo raíz, del Layout de la Activity, puede ser de un tipo diferente, dependiendo de las características gráficas que queremos que tenga la pantalla (por ejemplo, si queremos una Activity con una barra de navegación lateral, el nodo será un «DrawerLayout«, pero para una Activity vacía podríamos emplear un «LinearLayout«, que posiciona los subnodos en orden de aparición en la pantalla). Así pues, un layout define la forma en la que se colocarán los elementos que va a contener dentro de sí.

Activity con menú de navegación lateral y barra de herramientas

El nodo raíz contendrá a su vez otros Layout’s si queremos combinar comportamientos en la misma Activity (por ejemplo, una pantalla con barra de navegación lateral y que además tenga una barra de herramientas en la parte superior, tendrá como nodo raíz el «DrawerLayout» y dentro de él un «AppBarLayout«).

Se pueden incluir archivos Layout dentro de otro usando la etiqueta «include» (<include layout="@layout/archivo_layout"/>), así podemos reutilizar el mismo código XML en varios sitios.

Dentro del Layout podemos tener «View«s, que son los nodos hoja (no contienen otros nodos, por ejemplo un botón o un input de texto) y «ViewGroup«s que son nodos View que pueden contener otros View, Layouts, ó ViewGroups a su vez (por ejemplo un scroll dentro de la pantalla es un ViewGroup de tipo «ScrollView» que contiene los nodos que se podrán desplazar verticalmente).

<LayoutRaiz>
...
<ScrollView ...>     <!-- viewgroup -->
  <LinearLayout ...> <!-- layout -->
    <Button ../>     <!-- view -->
    <TextView .../>  <!-- view -->
    ...
  </LinearLayout>
</ScrollView..>
...
</LayoutRaiz>

Cada nodo (View, ViewGroup, Layout) tiene una serie de propiedades que definen su tamaño en pantalla (menos el nodo raíz que debe ocupar toda la pantalla), la posición que ocupan dentro del nodo contenedor, su color, etc. Estas propiedades pueden definirse en un archivo de estilos si queremos reutilizar las mismas propiedades en otros nodos o bien se pueden definir en el mismo nodo.

Los estilos se almacenan en el archivo «res/values/styles.xml«; se declara cada estilo con un nombre (por ejemplo: <style name="NOMBRE_DE_TU_ESTILO"> atributos... </style> ) y se usan como una propiedad del nodo (por ejemplo <View style="@style/NOMBRE_DE_TU_ESTILO"/>).

Los iconos e imágenes importadas al proyecto que se van a usar en las Views, se almacenan en la carpeta «res/drawable» y se usan como una propiedad del nodo (por ejemplo <item android:icon="@drawable/NOMBRE_DE_IMAGEN"/>).

Menús: Algunos Layouts permiten la aparición de menús con una serie de opciones disponibles, como es el caso del menú de navegación lateral o el menú de opciones (situado habitualmente en la parte superior derecha representado por un icono de 3 puntos dispuestos verticalmente). Las opciones que puede tener un menú, se almacenan en la carpeta «res/menu«. Se declaran, por ejemplo:

<!-- res/menu/OPCIONES_DE_MI_MENU.xml -->
<menu ...>
    <item android:title="Opción 1"/>
    <item android:title="Opción 2"/>
</menu>

Y luego se utilizan indicando el archivo de «res/menu» dentro de la propiedad del nodo que contiene al menú <NavigationView menu="@menu/OPCIONES_DE_MI_MENU"/>. Hay otros menús que no permiten determinar sus opciones desde código XML, como es el caso del menú de opciones (OptionMenu), cuyas opciones deben indicarse desde código Android, mediante la clase «MenuInflater«:

public boolean onCreateOptionsMenu(Menu menu) {
  // R.menu.thisActivityOptions => 
  // accede al archivo res/menu/OPCIONES_DE_MI_MENU.xml
    getMenuInflater().inflate(R.menu.OPCIONES_DE_MI_MENU, menu);
    return true;
}

Los nodos también pueden ser creados dinámicamente desde código Android, así como modificar sus atributos o asignarles estilos, para ello será importante asignarles un ID cuando querámos manipularlos. Para asignar un ID a un nodo, se indica a través de una de sus propiedades siguiendo la sintaxis: android:id="@+id/MI_ID"

Resumen de localización

En resumen, la localización de cada elemento:

PRÁCTICA

Views

Para exponer las Views básicas, primero se necesita un documento XML con un Layout raíz donde alojarlas. Para empezar a ilustrar las Views, usaremos como Layout raíz el «LinearLayout«, el cual añade sus nodos uno tras otro vertical u horizontalmente (indicado en su propiedad «orientation«).

El nodo raíz requiere indicar el espacio de nombres (namespace) de aquellos atributos a utilizar en los nodos; por ejemplo, si vamos a indicar el ancho de nuestros nodos (cosa habitual), usaremos las propiedades definidas en el esquema:

http://schemas.android.com/apk/res/android

Y asignárselo como una propiedad del nodo raíz (el Layout) que llamaremos, por estandarizar, «xmlns:android«. Luego, para usar las propiedades definidas en ese esquema/namespace, se sigue la sintaxis

«android:<propiedad>=»<valor>» «.

<?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">

    <!-- Aquí nuestras Views, ViewGroups y Layouts -->

</LinearLayout>

Hay más namespaces a parte de este.

NOTA: Android necesita que todos los elementos definan su altura y su anchura con «layout_height» y «layout_width» respectivamente.

Button

Botón con un color y un texto interno. En código se le puede asociar un «EventListener» para que se lance cuando sea pulsado.

<Button
    android:text="Texto del botón"
    android:layout_height="..."
    android:layout_width="...">
</Button>

Textview

Etiqueta de texto predefinido.

<TextView
    android:layout_width="..."
    android:layout_height="..."
    android:text="Domiciliación bancaria: "
    android:textSize="XXXpt">
</TextView>

EditText

Input de texto donde el usuario puede introducir datos desde el teclado.

Se puede definir el tipo de dato que se va a introducir en el input en el atributo «android:inputType«, el cual puede adquirir varios valores, pero los más interesantes son:

  • number: sólo permite introducir números enteros positivos. El teclado del teléfono sólo mostrará digitos numéricos (para facilitar la introducción de los datos).
  • numberDecimal: sólo números y un punto para indicar la coma flotante. No tiene por qué haber un número antes del punto.
  • numberPassword: sólo números que se ocultan tras ser escritos (como un input de password que sustituye las entradas por asteriscos *).
  • numberSigned: sólo números con un signo menos (-) al principio y opcional (no se permite el signo más +).
  • phone: sólo números y caracteres «dialables» (como la almohadilla «#»).
  • text: texto de cualquier tipo.
  • textAutoComplete: input con auto-completar (si ya se introdujo un dato aquí, al seleccionar dicho campo volverá a aparecer como seleccionable, en un desplegable, alguno de los valores anteriormente introducidos).
  • textAutoCorrect: input de texto de cualquier tipo con el corrector ortográfico activado.
  • textCapCharacters: input de texto de transforma los caracteres en letras mayúsculas (no modifica los signos).
  • textCapSentences: textAutoCorrect que pone automáticamente la primera letra en mayúsculas donde corresponda (al inicio de cada frase y después de cada punto).
  • textCapWords: textAutoCorrect que pone automáticamente la primera letra de cada palabra en mayúsculas (para nombres propios).
  • textEmailAddress: prepara el teclado para que aparezca el símbolo arroba (@) cerca de los demás caracteres para facilitar la introducción de datos. No comprueba que el input esté bien formado.
  • textMultiLine: textAutoCorrect que permite saltos de línea dentro de él.
  • textPassword: texto que se oculta tras ser escrito algún caracter (como un input de texto que sustituye las entradas por asteriscos *).

También se puede añadir una pista de lo que el usuario debe introducir (un texto que aparecerá dentro del input y que desaparecerá tras introducir algún valor). El valor de dicha pista se indica en el atributo «android:hint«.

También se puede definir la cantidad máxima de carateres que pueden escribirse en el input con el atributo «android:maxLength«.

<EditText
    android:layout_width="..."
    android:layout_height="..."
    android:inputType="phone"
    android:hint="Teléfono"
    android:maxLength="9">
</EditText>
TextInputLayout

La View EditText se puede combinar con un Layout de tipo «TextInputLayout» para conseguir una estética mejorada del EditText y facilitar algunas tareas, como la de mostrar errores en el input cuando este contiene algún caracter ilegal ó crear una animación automática para que el hint (la pista) se desplace a la parte superior del input cuando el usuario situe ahí el cursor.

Puedes ver lo que se puede hacer con esta combinación aquí.

CheckBox

Input de check box, para valores binarios. Es la típica cajetilla con un tick que se marca o desmarca cuando pulsamos en dicho input.

Tiene un texto asociado a su derecha que se indica en el atributo «android:text» (pulsar en el texto también hará que se pulse la checkbox).

<CheckBox
    android:layout_height="..."
    android:layout_width="..."
    android:text="Texto checkbox">
</CheckBox>

Spinner (selector)

Spinner Dialog (cuadro de diálogo)

Input en el que se le ofrece al usuario una lista de opciones entre las que tiene que elegir una (similar al «select» de HTML).

Se puede indicar que aparezca como un desplegable bajo el input o como un cuadro de diálogo en el centro de la pantalla que desaparece al seleccionar un valor.

Spinner Dropdown (lista desplegable)

Los valores seleccionables del Spinner deben ser definidos en otro documento XML cuyo nodo raíz sea «resources«. Por convenio se usa el archivo «res/values/arrays.xml«; en este archivo se define un array de strings («string-array«) al que se le asigna un nombre que usaremos para enlazar el spinner con el array de valores:

<!--res/values/arrays.xml-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="misValores">
        <item>Opción 1</item>
        <item>Opción 2</item>
        ...
    </string-array>
</resources>

La view Spinner tiene un par de atributos a destacar:

entries: indica cuál es el string-array de valores seleccionables con la sintaxis android:entries="@array/misValores"

spinnerMode: indica el módo de mostrar el selector (un desplegable bajo el input o bien un cuadro de diálogo):

android:spinnerMode="dialog"
<!-- ó -->
android:spinnerMode="dropdown"

prompt: si el modo de mostrar el selector es un cuadro de diálogo, podemos definir el título de dicho cuadro siguiendo la sintaxis android:prompt="Título del selector"

<Spinner
    android:layout_height="wrap_content"
    android:layout_width="..."
    android:entries="@array/misValores"
    android:spinnerMode="dialog"
    android:prompt="Título del cuadro de diálogo">
</Spinner>

Para recuperar la opción seleccionada del selector, se usa el método «getSelectedPosition» de la View Spinner:

int posicion = ((Spinner) 
  context.findViewById(R.id.mySpinner)
  ).getSelectedItemPosition();

 

Date & time picker

Para crear un selector de fechas o un selector de hora se requiere definir un «EditText» como no seleccionable, crear una clase Java que herede de «DialogFragment» que cree un objeto de la clase «DatePickerDialog» y asignar un «OnclickListener» al «EditText» para que, al ser seleccionado, aparezca un «date picker» usando el objeto de la clase Java que hereda de «DialogFragment«.

Del mismo modo ocurre con el time picker, con la diferencia de que la clase que debe heredar de «DialogFragment» debe crear un «TimePickerDialog«.

Puedes ver todo el proceso de creación aquí.

Dibujo (firma)

Android no proporciona una View nativa para capturar un dibujo en un nodo de la pantalla y luego almacenarlo como imagen (Bitmap), para ello será necesario crear una View personalizada.

Esta View es útil para la captura de firmas en un formulario. Puedes ver el proceso de creación de la View y cómo manipularla aquí.

ToggleButton

Botón que tiene 2 estados (on y off), dependiendo de su estado se iluminará/apagará la pequeña raya que hay en su parte inferior (simulando un led endedido/apagado).

En su definición tiene 2 atributos que indica el texto que debe mostrar si está on y si está off:

  • textOn: android:textOn="Activo"
  • textOff: android:textOff="Inactivo"

Desde código se puede determinar el estado del botón con el método booleano «isChecked()«.

<ToggleButton
  android:layout_width="..."
  android:layout_height="..."
  android:textOn="Activo"
  android:textOff="Inactivo"/>

ProgressBar

Una barra de progreso de algún proceso. Puede ser un proceso de tiempo indeterminado (normalmente se utiliza un círculo que da vueltas indefinidamente hasta que termina la tarea) o un proceso en el cual podemos saber cuánto porcentaje lleva y cuánto le queda (normalmente es una barra horizontal que se va rellenando o bien un círculo como el anterior que, en lugar de dar vueltas se va rellenando).

Esta View se diferencia del diálogo de progreso (ProgressDialog) en que la primera se usa para indicar al usuario que puede seguir navegando y usando la aplicación mientras se realiza un proceso en segundo plano; y el segundo interrumpe la interacción con la GUI (debe terminar antes de continuar usando la aplicación).

Proceso NO acotado:

<ProgressBar
        android:layout_width="..."
        android:layout_height="..."/>

Esto mostrará un circulo girando sobre sí mismo constantemente.

Proceso acotado:

<ProgressBar
        android:layout_width="..."
        android:layout_height="..."
        style="?android:progressBarStyleHorizontal"
        android:progress="0"/>

Se indica que el estilo (style) de la barra será horizontal («?android:progressBarStyleHorizontal«) y se le pone un progreso inicial del 0% (android:progress=»0″) que deberá actualizarse manualmente desde código a la vez que el proceso avance.

Proceso acotado custom

El atributo que determina que el proceso está definido (o sea, que va a rellenarse la barra desde el 0% al 100%) es el style. Por defecto la figura será una barra horizontal pero se puede definir otra figura para que vaya rellenándose (dibujándose) desde el 0% al 100%.

Para ello se indica en el atributo «android:progressDrawable» la figura que queremos que tenga la barra, y para ello debemos definir previamente un documento XML cuyo nodo raíz sea «shape«; y se guarda, por convenio, en la carpeta «res/drawable«.

Por ejemplo, si queremos que la barra de progreso determinado sea un círculo, definimos primero la figura (shape) y lo guardamos en el archivo XML «circulo.xml«:

<!-- res/drawable/circulo.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:innerRadiusRatio="2.5"
    android:thickness="4dp"
    android:useLevel="true">
    <solid android:color="@color/colorAccent"/>
</shape>

Y luego referenciamos a dicho archivo XML en el atributo «android:progressDrawable» del nodo «ProgressBar«:

<ProgressBar
    android:layout_width="150dp"
    android:layout_height="150dp"
    style="?android:progressBarStyleHorizontal"
    android:progress="0"
    android:progressDrawable="@drawable/circulo"/>

Switch

Botón similar al toggle button que presenta dos estados: on y off a los cuales se les puede poner un texto personalizado en la posición de ON y en la de OFF.

La diferencia con el toggle button es que este botón tiene visualmente similitud con un interruptor que se desplaza para encender/apagar el circuito en lugar de tener similitud visual con un pulsador.

Este botón tiene un texto asociado situado a la izquierda del mismo. Se indica en el atributo «android:text«.

<Switch
  android:layout_width="..."
  android:layout_height="..."
  android:textOn="Activo"
  android:textOff="Inactivo"
  android:text="Texto lateral"/>

RadioButton

Los radio button representan una única opción a elegir dentro de un conjunto de opciones y viene representada habitualmente por un círculo vacío si no ha sido seleccionada dicha opción o un círculo relleno si ha sido seleccionada.

Para determinar a qué grupo de opciones pertenecen los Radio button, deben estar dentro de un nodo de tipo «RadioGroup» (un ViewGroup contenedor de RadioButton’s).

Cada radio button tiene un texto asociado para que el usuario sepa qué valor estamos seleccionando; se asigna con el atributo «android:text«.

NOTA: Todos los RadioButton deben tener un identificador (android:id=»@+id/mi_id») o no se producirá una selección excluyente (marcará todos los RadioButton en lugar de alternar de uno a otro).

<RadioGroup
  android:layout_width="..."
  android:layout_height="...">

  <RadioButton
    android:id="@+id/mi_id_1"
    android:layout_width="..."
    android:layout_height="..."
    android:text="Opción 1"/>

  <RadioButton
    android:id="@+id/mi_id_2"
    android:layout_width="..."
    android:layout_height="..."
    android:text="Opción 2"/>

    <!-- ... -->

</RadioGroup>

ImageView

View que contiene una imagen. La imagen a la que hace refencia debe estar en la carpeta «res/drawable» y se le hace referencia a través del atributo «android:src«.

<ImageView
  android:layout_width="..."
  android:layout_height="..."
  android:src="@drawable/miImagen"/>

ImageButton

Una View de tipo Button que en lugar de tener texto tiene una imagen. La imagen a utilizar debe estar almacenada en la carpeta «res/drawable» y se referencia usando el atributo «android:src»

<ImageButton
    android:layout_width="..."
    android:layout_height="..."
    android:src="@drawable/ic_menu_camera"/>

VideoView

La etiqueta VideoView permite reproducir dentro de ella un vídeo que se asigne a través de código Android. La ruta de acceso al vídeo debe ser convertida a URI y asignarle dicha URI al VideoView.

También se pueden añadir controles de vídeo con la clase Java «MediaController«.

Puedes ver todo el proceso de creación aquí.

NumberPicker

Esta etiqueta simula una rueda con números dentro de sí que el usuario debe hacer girar (deslizando el dedo verticalmente sobre dicha View) para seleccionar el valor deseado.

Esta etiqueta muestra un rango de valores que se especifica desde código Android. No hay saltos entre los valores ya que se trata de una lista ordenada de números enteros.

<NumberPicker
    android:id="@+id/numberPicker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Desde código Android indicamos el valor mínimo, el máximo y podemos recoger el valor actualmente seleccionado.

NumberPicker np = (NumberPicker) findViewById(R.id.numberPicker);
np.setMinValue(2);
np.setMaxValue(20);
Log.d("NumberPicker", "Initial value: " +np.getValue());

Podemos asignar a la View un EventListener del tipo «OnValueChangedListener» para saber cuándo ha cambiado el valor seleccionado del NumberPicker.

np.setOnValueChangedListener(new NumberPicker.OnValueChangeListener(){
  @Override
  public void onValueChange(NumberPicker numberPicker, int i, int i1) {
      Log.d("NumberPicker", "Selected number: " + numberPicker.getValue());
});

FloatingActionButton

Botón flotante abajo a la derecha de color magenta

Botón flotante que se mantiene en una capa superior sobre sus nodos hermanos (y sus respectivos hijos). Haciendo el símil con HTML es un elemento «fixed» (fijado en un lugar concreto de la pantalla).

Se indica la posición que ocupa dentro de la pantalla con el atributo «android:layout_gravity» (habitualmente abajo a la derecha, adquiriendo así el valor «bottom | end«).

Normalmente no se desea que esté pegado a los laterales de la pantalla, así que se le añade un margen (normalmente de unos 16dp) con el atributo «android:layout_margin«.

No es un botón con texto, sino que está pensado para que tenga un icono en su interior, el cual se asigna usando el atributo «android:src«.

<LayoutX>
  <ViewA/>

  <ViewB/>

  <!-- FloatingActionButton estará sobre las ViewA, B y C
  y sus respectivos hijos -->
  <android.support.design.widget.FloatingActionButton
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom|end"
      android:layout_margin="16dp"
      android:src="@drawable/mi_icono"/>

  <ViewC/>
</LayoutX>

ViewGroups (contenedores)

ScrollView

Es un contenedor de nodos «scrollable», es decir, «ScrollView» define un rectángulo dentro de la pantalla con un scroll y los nodos que haya dentro de dicho rectángulo se desplazarán verticalmente al deslizar el dedo sobre dicho rectángulo (igual que ocurre cuando un usuario sube o baja en una página de un documento).

Para usar la «ScrollView» se debe definir dentro de ella un Layout de tipo «LinearLayout» y dentro de dicho Layout introducir las Views deseadas.

Puede indicarse si se desea mostrar la barra de scroll vertical con el atributo «android:scrollbars«:

  • vertical: muestra la barra de scroll vertical (útil si el scroll es vertical como en ScrollView).
  • horizontal: muestra la barra de scroll horizontal (útil si el scroll es horizontal como en HorizontalScrollView).
  • none: no muestra ninguna barra de scroll.
<ScrollView
  android:layout_width="..."
  android:layout_height="..."
  android:scrollbars="vertical">
  <LinearLayout
    android:layout_width="..."
    android:layout_height="..."
    android:orientation="vertical">

    <!-- Tus Views aquí -->

  </LinearLayout>
</ScrollView>

HorizontalScrollView

Igual que el ScrollView pero el scroll será lateral (de izquierda a derecha). El LinearLayout utilizado aquí tendrá que tener orientación horizontal (android:orientation=»horizontal») y es conveniente que el nodo de scroll muestre la scrollbar horizontal (android:scrollbars=»horizontal»). El resto es igual que la ScrollView.

<HorizontalScrollView
  android:layout_width="..."
  android:layout_height="..."
  android:scrollbars="horizontal">
  <LinearLayout
    android:layout_width="..."
    android:layout_height="..."
    android:orientation="horizontal">

    <!-- Tus Views aquí -->

  </LinearLayout>
</ScrollView>

Horizontal & Verticall ScrollView

Para crear un scroll vertical y lateral a la vez, creamos un HorizontalScrollView que contenga a su vez un ScrollView y este a su vez que contenga un Layout.

<HorizontalScrollView
  android:layout_width="wrap_content"
  android:layout_height="fill_parent">
  <ScrollView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout ... >

      <!-- aquí tus views que sobrepasen la pantalla 
       horizontal y verticalmente -->

    </LinearLayout>
    
  </ScrollView>
</HorizontalScrollView>

Layouts

Los Layouts son rectángulos dentro de la pantalla pensados para agrupar y dividir Views. Siguiendo con el símil a HTML, estos serían los «divs», pero con propiedades especiales según el tipo.

LinearLayout

WIP

RelativeLayout

WIP

AppbarLayout & Toolbar

La AppBarLayout es la típica barra superior de una aplicación Android en la que habitualmente se muestra el nombre de la app, el botón de opciones (representado habitualmente por tres puntos dispuestos verticalmente), el botón para abrir el menú de navegación, etc.

Puedes ver cómo se crea y se utiliza aquí.

Estilos (Style)

En esta entrada se explica cómo aplicar estilos a nodos y los atributos visuales de cada etiqueta (Próximamente).

 

Deja una respuesta