Android Vistas y Layouts

“Desarrollo de Aplicaciones Móviles en Android”
Ejercicio Básico A: Vistas y Layouts
En el primer ejercicio, construimos una aplicación sencilla entre todos paso a paso. En este segundo ejercicio el objetivo es que empecemos a coger un poco de soltura escribiendo código para aplicaciones Android, tanto XML como Java.
A. Descripción
En este ejercicio nos concentraremos en modificar solamente tres archivos de un proyecto Android: la Actividad (Java), la interfaz (XML) y los textos (XML). Esto lo conseguiremos añadiendo vistas (views) a una aplicación por medio de XML, y luego nos conectaremos a ellas a través de código Java para poder actuar cuando se produzcan eventos,
como la pulsación de un botón En el camino, iremos descubriendo nuevas vistas, además de
nuevas propiedades y listeners.
B. Implementación
Comenzaremos este ejercicio creando un nuevo proyecto Android desde Eclipse. Le
asignamos un nombre, le damos una versión (recomendamos 2.2), un nombre a la aplicación, a
nuestro paquete (por ejemplo: com.[mi_nombre].android.apps.BasicoA) y a la Actividad que queremos que se nos cree por defecto; finalmente ponemos un “8” en el campo de versión mínima de SDK. Una vez le demos a Finalizar estaremos listos.
B.1. Primer layout
Vamos a comenzar editando el archivo de layout, el main.xml. En él tendremos ya la estructura creada por defecto, en la que destaca un TextView con un texto. Comenzaremos a trabajar añadiendo Views y ViewGroups1 bajo éste. Lo que queremos hacer es un clásico CheckBox para habilitar o no un ViewGroup, donde guardaremos una vista. Debido a que también queremos practicar los eventos de las Views, necesitamos también un TextView
que acompañe al ya mencionado CheckBox.
1 Las clases son en realidad View y ViewGroup, pero para hacer más fácil entender que hablamos de más de una, las mencionamos en plural y con la letra utilizada por el Eclipse.
El código XML para poder añadir un CheckBox seguido de un TextView
(main.xml) es el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView
android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello">




</TextView>
<CheckBox android:id="@+id/enableViews" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/enableViews" android:checked="false">
</CheckBox>
<TextView android:id="@+id/eventsTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:maxLines="2" android:minLines="2">
</TextView>

</LinearLayout>

Como vemos, hemos añadido dos Views, un CheckBox y un TextView. A ambas
les hemos dado un id, para poder referenciarlas desde el código Java, y les hemos puesto las
mismas características de anchura y altura.
El CheckBox viene acompañado de un TextView internamente, ahorrándonos la tarea de tener que añadirlo nosotros para que sea intuitivo de usar. Para poder asignarle un texto solamente tenemos que utilizar la propiedad text. Por último, hemos puesto por defecto que el CheckBox no esté marcado (checked=”false”) En el caso de que queramos tenerlo marcado por defecto solamente tenemos que ponerlo a true. El TextView
es muy sencillo. Las dos únicas propiedades que asignamos son número mínimo de líneas y
número máximo de líneas, de forma que tenga altura suficiente para mostrar dos líneas de texto. Es una forma para “forzar” al TextView a que tenga la altura que queremos, pero no
la mejor (veremos en otro ejercicio una forma mucho más correcta y elegante)
Con esto ya tenemos un interruptor y un lugar donde escribir. Ahora nos hace falta un
LinearLayout que nos guarde las vistas con las que vamos a practicar un poco. Realmente,
este Layout que vamos a añadir no es necesario, pero lo usamos para aprender. El código que sigue al último TextView (el eventsTextView) y que termina antes del cierre del LinearLayout principal (o raíz), es el siguiente:

<LinearLayout
           android:id="@+id/myViewsLayout"
          android:layout_width="match_parent"
     android:layout_height="wrap_content" 
     android:orientation="vertical"
     android:layout_margin="7dp"

     android:visibility="gone">

     <!-- Botóncircular -->

<RadioButton android:id="@+id/myRadioButton" 
android:layout_width="wrap_content
android:layout_height="wrap_content" 
android:text="@string/myRadioButton">
</RadioButton>
</LinearLayout>

A este LinearLayout le hemos dado un id, y le hemos asignado unas
características de anchura y altura similares a las Views que hemos visto antes. Le hemos dicho que queremos que nos coloque las Views una debajo de otra (orientation=”vertical”), y le hemos dado un valor de layout_margin. El layout_margin (como su nombre indica) es una distancia que separa una
View/ViewGroup cualquiera del marco en el que está situada, sean los límites de la pantalla
u otra View. layout_margin tiene una propiedad hermana, padding, que en este caso
representa un margen dentro de la propia View. Existen muchos casos en la práctica en los
que es muy fácil confundirlos porque sus resultados son iguales, pero en ningún caso
representan lo mismo. Para nuestro ejemplo, utilizamos un layout_margin de 7dp (“dp”
se explicará en un ejercicio posterior, por ahora interpretar que son píxeles), es decir, un margen de 7 píxeles respecto a las cuatro esquinas. El layout_margin (al igual que el padding) no tiene por qué ser respecto a las cuatro esquinas; también puede ser respecto a
una esquina o lado en concreto (ver la propiedad layout_marginLeft y similares) Para
terminar con el LinearLayout, tenemos la propiedad visibility puesta a gone. Como su nombre indica, la propiedad visibility determina si el LinearLayout es visible o no, y lo interesante es que si el LinearLayout no es visible, sus hijos no lo serán, aunque
éstos sean visibles. Una View tiene tres posibles estados de visibilidad: visible (por
defecto), invisible y gone. visible es autoexplicativo, y la diferencia entre invisible y gone la veremos en el último apartado. Para terminar aquí, tenemos el RadioButton, que es hijo de nuestro nuevo LinearLayout. Un RadioButton es un botón especial; deriva de la clase Button, y se parece mucho a un CheckBox, salvo por una
cosa: el CheckBox puede funcionar como interruptor (apagado / encendido o viceversa), pero el RadioButton no. El RadioButton, una vez marcado, no puede desmarcarse. Por lo demás, su declaración no contiene nada nuevo.
Ahora lo ideal sería marcharnos a modificar código Java, pero no podemos. Como
Eclipse ya os habrá avisado, existen errores que debemos solucionar. Si todo ha marchado
bien, los errores provienen de las propiedades text, que referencian a una cadena (String) del archivo strings.xml que aún no hemos añadido. Por tanto, vayámonos al archivo strings.xml y dejémoslo como está aquí:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, ViewsAndLayouts!</string>
<string name="app_name">Vistas y Layouts</string>





<string name="enableViews">Activar Layout</string>
<string name="myRadioButton">Botón circular</string>
</resources>

Con este código XML, los errores del main.xml deberían desaparecer. Ahora, por
fin, podemos empezar a jugar con Java.
B.2. Java y eventos
Abrimos nuestra Actividad, y lo primero que haremos es preparar las cosas como a nosotros nos gustan: nuestro método initConfig(). Comenzaremos dejando el código de nuestra Actividad (lo que va dentro de la definición de la clase) como vemos aquí:

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);





// llamamos a nuestro método de inicialización initConfig();
}

private void initConfig() {
setContentView(R.layout.main);
}

Como habréis adivinado,
el grueso de nuestro código de hoy va dentro del
método
initConfig(). Sin embargo, antes de empezar, necesitamos declarar las variables que
vamos a utilizar. Para ello, añadimos el siguiente código justo antes del método
onCreate():

private CheckBox enableViews; private TextView eventsTextView; private LinearLayout myViewsLayout; private RadioButton myRadioButton;


Éstas  son   las   cuatro   referencias  que   vamos  a   necesitar.  Lo  siguiente  es   inicializar  las
variables en el método   initConfig():

enableViews = (CheckBox) findViewById(R.id.enableViews); eventsTextView = (TextView) findViewById(R.id.eventsTextView); myViewsLayout = (LinearLayout) findViewById(R.id.myViewsLayout);

myRadioButton = (RadioButton) findViewById(R.id.myRadioButton);

Sin duda, éste es un buen momento para arrancar la aplicación. Al hacerlo, vemos nuestro CheckBox, y al tocarlo vemos que se marca y que se desmarca, pero no ocurre nada más. ¿Qué es lo que queremos que ocurra? Pues que el LinearLayout se vuelva visible si el CheckBox está marcado. ¿Cómo lo hacemos? Con un listener:

enableViews.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
myViewsLayout.setVisibility(View.VISIBLE);
}
else {
myViewsLayout.setVisibility(View.GONE);

}
}
});

Este listener nos da como parámetros el botón en cuestión, que no nos hace falta, y un boolean con la situación actual sobre si está marcado o no. Es importante tener presente que esta llamada se produce después de que el CheckBox cambie de estado, no antes. Lo único que debemos hacer es cambiar el estado de visibilidad según el booleano, a visible o
a gone. Con esto, ya deberíamos conseguir nuestro objetivo: que sea vea y que no sea vea el
RadioButton. ¿Qué nos queda? El propio RadioButton:

myRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
{
@Override

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
eventsTextView.setText("myRadioButton ha cambiado de estado.");

}

});

Probemos ahora. Vemos que funciona perfectamente, pero se nos formula otra
pregunta, y es, ¿cuántas veces se ejecuta este listener? Averiguarlo es muy fácil. Lo primero es
declarar una variable de tipo int, por ejemplo clickTimes; la declaramos como privada debajo de las demás variables, justo encima del método onCreate(). Luego, alteramos el código del listener, añadiendo una línea antes para inicializar clickTimes a cero:
clickTimes = 0;
myRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
{
@Override

public void onCheckedChanged(CompoundButton  buttonView, boolean isChecked) {
clickTimes++;
eventsTextView.setText("Clicked " + clickTimes + " times.");

}

});

Es importante señalar que la línea clickTimes = 0 se ejecuta una sola vez, pero el
código que está dentro del setOnCheckedChangeListener se ejecuta cada vez que el RadioButton cambie de estado, que es lo que nos promete este listener. Solamente tenemos que ejecutar este código para probarlo. Si de verdad quisiéramos contar cuántas veces hacemos click en el RadioButton, lo único que tendríamos que hacer es cambiar de listener, y utilizar el onClickListener, que se ejecuta una vez por cada click, independientemente de si se cambia de estado o no:

clickTimes = 0;
myRadioButton.setOnClickListener(new View.OnClickListener() {
@Override

public void onClick(View v) {
clickTimes++;
eventsTextView.setText("Clicked " + clickTimes + " times.");

}

});

B.3 Visibilidad
El último apartado de este ejercicio es también el más corto. Aquí, finalmente, descubrimos la diferencia entre que una View tenga visibilidad invisible o gone. Para ello, debemos volver al main.xml, y añadir una nueva View, justo donde lo dejamos antes,
es decir, tras el cierre () del Layout que guarda al RadioButton.
Éste es el código a añadir:

<ToggleButton 
android:id="@+id/toggleVisibility" 
android:layout_width="wrap_content
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textOn="@string/invisible"
android:textOff="@string/gone">
</ToggleButton>

El ToggleButton es una mezcla (a nivel de concepto) entre el CheckBox y el
Button convencional. Básicamente, es un botón clásico con estado, donde una luz nos indica
si está marcado o no. Visualmente, se parece mucho más a un interruptor que un CheckBox,
pero a nivel práctico (de programación, no así para el usuario) son lo mismo. La primera novedad es que no hemos declarado que ocupe todo el ancho de la pantalla, lo cual nos da pie
para la nueva propiedad que vemos allí, layout_gravity. Lo que nos permite layout_gravity es decirle al contenedor (padre) de una View dónde quiere que nos coloque en el caso de que no ocupemos todo el espacio que podemos ocupar. Por ejemplo en
este caso, ninguna otra View se colocará a la misma altura de este ToggleButton, por lo
que nosotros hemos decidido que queremos que esté en el centro. Después tenemos que la propiedad text está dividida en dos casos aquí, una para cuando el botón esté presionado o marcado (textOn), y otra para cuando no lo está (textOff).
Como siempre, si todo ha ido bien, los errores que tendremos serán porque nos falta
declarar las dos cadenas en el archivo strings.xml:


<string name="invisible">Invisible</string>
<string name="gone">Gone</string>

Ahora volvemos al código Java, que es donde se producen los cambios. ¿Qué queremos con el botón? Muy sencillo: siempre y cuando el CheckBox no esté activo, si el ToggleButton está apagado la visibilidad de myLinearLayout será gone, y si está activado será invisible. Los dos siguientes pasos os los dejamos a vosotros: declarar la
variable ToggleButton toggleVisibility y unirla con la View de la interfaz XML. Por último, y ya para terminar, añadimos este código al initConfig(), y habremos terminado:

toggleVisibility.setOnCheckedChangeListener(new
CompoundButton.OnCheckedChangeListener() {
@Override

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!enableViews.isChecked()) { if (isChecked) myViewsLayout.setVisibility(View.INVISIBLE)
else
myViewsLayout.setVisibility(View.GONE);

}

}

});


C. Conclusión
El objetivo de este ejercicio no era aburriros, ni mucho menos sino que os sintierais cómodos trabajando con la parte XML y la de Java que permite construir las interfaces. Hay muchas propiedades y muchos listeners que descubrir, ¡así que adelante!
D. Opcionales
Para aquellos alumnos a los que el ejercicio os ha sabido a poco, o que habéis
terminado antes, proponemos una serie de cambios o modificaciones:
--- ¿Qué ocurre si myViewsLayout, en vez de ser de tipo LinearLayout, lo pusiéramos de tipo ViewGroup, relizando los cambios pertinentes en nuestro código
Java? ¿Funcionaría? ¿Se comportaría el código de la misma forma? ¿Por qué?
--- El CheckBox enableViews lo estamos inicializando desde XML, dándole un texto y un estado de “no marcado”. En vez de hacerlo en XML, hagámoslo en Java. Primero habría que borrar las dos líneas pertinentes en el XML, y luego añadir otras dos de código Java.
--- Hemos visto que el RadioButton sólo puede ser marcado una vez. ¿Qué podemos
hacer para intentar que funcione como un botón normal? ¿Funciona?
--- La forma en la que utilizamos el ToggleButton no es del todo correcta, porque si cuando los dos están activados (ToggleButton y el CheckBox) desactivamos el CheckBox, myViewsLayout se queda en gone en vez de invisible (ToggleButton marcado) Solucionarlo. (Pista: ¿de qué tipo son View.VISIBLE, View.INVISIBLE y View.GONE?)