Android Aplicación completa I : Interfaz gráfica

(Fuente: http://www.javacodegeeks.com/2010/10/android-full-app-part-1-main-activity.html)

Esta es la primera parte de una serie de artículos para construir una aplicación completa en Android. La aplicación que  se va a realizar  es un servicio de búsqueda fácil de actores y películas a través de internet. En esta primera parte vamos a configurar el proyecto Eclipse, prepararemos el interfaz de usuario para la principal actividad y finalmente lo probamos en un emulador de Android apropiado.

Configurando el proyecto Eclipse

Suponiendo que tenemos ya instalado todo para programar en Android , empecemos creando un nuevo proyecto Eclipse. Al proyecto lo llamaremos ‘AndroidSupermocoFinder y la aplicación se llamará “SupermocoFinderApp”. Observese que el Android 1.5 (API de nivel 3) se utiliza como la plataforma target, porque no utilizaremos ninguno de los últimos APIs.

Definiendo el interfaz de usuario

Nuestro interfaz de usuario será simple:

  • Una textbox donde el usuario  introduce la query de búsqueda
  • Dos radiobuttons indicando si es una búsqueda de peli o de actor
  • Una label para mostrar el tipo de búsqueda
  • Y un botón para realizar la búsqueda. Los resultados de la búsqueda serán presentados en otro activity (esto será discutido luego).

Como probablemente sabes, el interfaz se crea via un fichero XML para así tener independizados la vista de presentación de la lógica de la aplicación. El fichero correspondiente se llama “main.xml” y está en la carpeta “res/layout”. Lo abrimos e introducimos el siguiente contenido:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">

	<EditText android:id="@+id/search_edit_text"
		android:text="@string/search"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"	/>

	<RadioGroup
			android:id="@+id/search_radio_group"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:orientation="vertical">

			<RadioButton
			android:id="@+id/movie_search_radio_button"
			android:checked="true"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="@string/movies" />

			<RadioButton
			android:id="@+id/people_search_radio_button"
			android:checked="false"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="@string/people" />

	</RadioGroup>

	<TextView
		android:id="@+id/search_type_text_view"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:textColor="#ffffff"/>

	<Button
		android:id="@+id/search_button"
		android:text="@string/search"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"/>

</LinearLayout>

Observe que los elementos que muestran cadenas no están hard-coded, sino que sus valores son tomados de recursos externos y más específicamente de un fichero llamado “strings.xml” que reside en la carpeta “res/values”.Esta es una buena práctica para tener la internacionalización preparada para tu aplicación. El fichero es el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="hello">Hello World, MovieSearchAppActivity!</string>
	<string name="app_name">MovieSearchApp</string>
	<string name="search">Search</string>
	<string name="movies">Movies</string>
	<string name="people">People</string>
</resources>

Podemos arrancar ya la aplicación en un emulador apropiado para ver cómo va quedando:

Integrando el interfaz gráfico con el código

Localizar elementos del UI

El siguiente paso es enganchar estos elementos UI en nuestro código y manipularlos para que estén acordes con nuestra funcionalidad de búsqueda. Este enganche es posible a través del método findViewById , que recibe un parámetro integer que es un Id unívoco , ese Id se lo dimos en el main.xml.

Listeners de los UI

Un aspecto muy importante de los widgets UI de Android es que tiene listeners que permiten programarlos para que ser notificados cuando el usuario realiza alguna acción, tal como hacer click en un botón. Para manejar el evento de clicking, implementamos el interfaz OnClickListener, que define un callback que será invocado cuando una vista sea clicada. El interfaz contiene sólo un método llamado onClick, que es invocado cuando la vista es clicada.

Otro interfaz útil es el OnFocusChangeListener, que define un callback para ser invocado cuando el estado del foco de una view es cambiado. Su único método es onFocusChange, que es invocado cuando cambia el estado del foco del elemento o view.

Veamos cómo se aplican estos listeners en nuestro ejemplo. El código para nuestra clase main principal es la siguiente:

package com.jes;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

public class SMFinderActivity extends Activity {

	private static final String EMPTY_STRING = "";
	private EditText searchEditText;
	private RadioButton moviesSearchRadioButton;
	private RadioButton peopleSearchRadioButton;
	private RadioGroup searchRadioGroup;
	private TextView searchTypeTextView;
	private Button searchButton;

	@Override
	public void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		this.findAllViewsById(); 

		moviesSearchRadioButton.setOnClickListener(radioButtonListener);
		peopleSearchRadioButton.setOnClickListener(radioButtonListener);
		searchButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				String query = searchEditText.getText().toString();
				if (moviesSearchRadioButton.isChecked()) {
					longToast(moviesSearchRadioButton.getText() + " " + query);
				}
				else if (peopleSearchRadioButton.isChecked()) {
					longToast(peopleSearchRadioButton.getText() + " " + query);
				}
			}
		});

		searchEditText.setOnFocusChangeListener(new DftTextOnFocusListener(getString(R.string.search)));
		int id = searchRadioGroup.getCheckedRadioButtonId();
		RadioButton radioButton = (RadioButton) findViewById(id);

		searchTypeTextView.setText(radioButton.getText());
	}

	private void findAllViewsById() {
		searchEditText = (EditText) findViewById(R.id.search_edit_text);
		moviesSearchRadioButton = (RadioButton) findViewById(R.id.movie_search_radio_button);
		peopleSearchRadioButton = (RadioButton) findViewById(R.id.people_search_radio_button);
		searchRadioGroup = (RadioGroup) findViewById(R.id.search_radio_group);
		searchTypeTextView = (TextView) findViewById(R.id.search_type_text_view);
		searchButton = (Button) findViewById(R.id.search_button);
	}

	public void longToast(CharSequence message) {
		Toast.makeText(this, message, Toast.LENGTH_LONG).show();
	}

	private OnClickListener radioButtonListener = new OnClickListener() {
		public void onClick(View v) {
			RadioButton radioButton = (RadioButton) v;
			searchTypeTextView.setText(radioButton.getText());
		}

	};

	private class DftTextOnFocusListener implements OnFocusChangeListener {
		private String defaultText;
		public DftTextOnFocusListener(String defaultText) {
			this.defaultText = defaultText;
		}

		public void onFocusChange(View v, boolean hasFocus) {
			if (v instanceof EditText) {
				EditText focusedEditText = (EditText) v;
				// handle obtaining focus
				if (hasFocus) {
					if (focusedEditText.getText().toString().equals(defaultText)) {
						focusedEditText.setText(EMPTY_STRING);
					}
				}
				// handle losing focus
				else {
					if (focusedEditText.getText().toString().equals(EMPTY_STRING)) {
						focusedEditText.setText(defaultText);
					}
				}
			}
		}
	}
}

Empezamos configurando la View para nuestra actividad utilizando el método setContentView. El view usado es el definido por el main.xml.

super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Entonces, tomamos referencia de todos los elementos UI asi que podamos manipularlos via código

private void findAllViewsById() {
	searchEditText = (EditText) findViewById(R.id.search_edit_text);
	moviesSearchRadioButton = (RadioButton) findViewById(R.id.movie_search_radio_button);
	peopleSearchRadioButton = (RadioButton) findViewById(R.id.people_search_radio_button);
	searchRadioGroup = (RadioGroup) findViewById(R.id.search_radio_group);
	searchTypeTextView = (TextView) findViewById(R.id.search_type_text_view);
	searchButton = (Button) findViewById(R.id.search_button);
}

Creamos dos OnclickListeners, uno para manejar los clicks de los radio buttons y otro para la ejecución de la búsqueda cuando se hace click sobre el botón. Los listeners son enganchados a los elementos UI utilizando el método setOnclickListener.

moviesSearchRadioButton.setOnClickListener(radioButtonListener);
peopleSearchRadioButton.setOnClickListener(radioButtonListener);

searchButton.setOnClickListener(new OnClickListener() {
					@Override
					public void onClick(View v) {
						String query = searchEditText.getText().toString();
						if (moviesSearchRadioButton.isChecked()) {
							longToast(moviesSearchRadioButton.getText() + " " + query);
						} else if (peopleSearchRadioButton.isChecked()) {
							longToast(peopleSearchRadioButton.getText() + " " + query);
						}
					}
});

private OnClickListener radioButtonListener = new OnClickListener() {
						public void onClick(View v) {
							RadioButton radioButton = (RadioButton) v;
							searchTypeTextView.setText(radioButton.getText());
						}
};

Como los radiobuttons están definidos dentro de un componente RadioGroup, el framework de Android es el que se encarga de permitir que sólo esté seleccionado a la vez. En tiempo de ejecución , podemos encontrar qué radio button está seleccionado utilizando el método getCheckedRadioButtonId. Observese que este método retorna una ID único global de el radiobutton y puede ser utilizado como argumento del método findViewById para tener referencia del botón.

Finalmente, creemos un  OnFocusChangeListener y lo enganchamos al widget EditText utilizando el método setOnFocusChangeListener:

searchEditText.setOnFocusChangeListener(new DftTextOnFocusListener(getString(R.string.search)));

Con esta implementación , queremos algo como lo siguiente:

Cuando la text box tiene el foco y está vacía, estás preparado para teclear la query de búsqueda. La query permanece allí tanto si la caja de texto tiene el foco como si no lo tiene. Sin embargo, cuando la caja de texto está vacía y pierde el foco, aparece un mensaje predefinido (con un texto que sacamos del strings.xml).

Primera ejecución

Estamos ahora preparados para darle a nuestra aplicación una primera ejecución. Esto lo haremos utilizando un emulador apropiado delAndroid SDK. Lanzamos el AVD (Android Virtual Device) Manager desde el Eclipse y creamos un nuevo device. Le damos un nombre que sea distintivo, por ejemplo “Android1.5-SD” y elegimos la plataforma target, en nuestro caso Android 1.5. También hemos elegido soporte para la SD card, solo por si lo necesitamos.Así es como queda el setup:

Ahora creamos un nueva “Run configuration” de Eclipse, elige nuestro proyecto Android y la “SupermocoFinderAppActivity” para arrancar. Esto es lo que debería salirnos:

En la pestaña “Target” , elegimos el nuevo AVD creado , aplicamos y ejecutamos. Tarda un rato , desbloqueamos y la aplicación aparecerá. Juega un poco con la aplicación , en este momento no realiza nada avanzado, tan solo sale una ventana cuando se le da a buscar con los valores introducidos.

Archivos fuentes utilizados