Búsqueda personalizada

Blog personal para aprender a programar en Android desde cero.

martes, 23 de marzo de 2010

Notepad Tutorial - Ejercicio 1

En Android Labs nos proponen un tutorial bastante completo, Notepad Tutorial, un gestor de notas.

El tutorial se sigue en forma de ejercicios, 3 más uno extra. De cada ejercicio tenemos un código base y su solución por si tenemos algún problema.

Nos descargamos el código base y las soluciones, y nos preparamos el entorno para empezar con el ejercicio 1.


Ejercicio 1

En este ejercicio 1 construiremos una lista de notas a la que podremos añadir nuevas notas, pero no editaralas.


Paso 1

Abrimos el proyecto Notepadv1 en Eclipse. Este proyecto es el punto de inicio para el ejercicio 1.

  1. File > New > Android Project
  2. Seleccionamos Create project from existing source
  3. Seleccionamos el directorio Notepadv1 que nos hemos descargado antes
  4. La mayoría de propiedades del proyecto se rellenan automáticamente. Seleccionamos el Target, en Android Labs nos recomiendan seleccionar la versión del sdk menor para que sea compatible con la mayor cantidad de dispositivos.
  5. Pulsamos en Finish

Yo no he tenido problemas, pero si en vuestro caso tenéis algún problema con el AndroidManifest.xml, con el botón derecho en el proyecto seleccionamos Android Tools > Fix Project Properties, para que dejen de salirnos.


Paso 2

Echamos un vistazo a la clase NotesDbAdapter, que nos servira para acceder a la base de datos SQLite que utilizaremos para guardar las notas.

Al principio de la clase tenemos unas cuantas constantes declaradas:

    private static final String DATABASE_CREATE =
            "create table notes (_id integer primary key autoincrement, "
                    + "title text not null, body text not null);";

Esta es la consulta que crea la tabla de notas, que contiene 3 campos, _id, title y body, de los que también tenemos sus constantes:

  public static final String KEY_TITLE = "title";
    public static final String KEY_BODY = "body";
    public static final String KEY_ROWID = "_id";

También tenemos una constante que identifica la clase a la hora de escribir en log:

  private static final String TAG = "NotesDbAdapter";

El constructor de la clase NotesDbAdapter toma como parámetro un objeto de clase Context. Esto es para poder acceder a propiedades y métodos del sistema (Android).

En el método open() creamos un instancia de la clase DatabaseHelper, que es un clase local que implementa SQLiteOpenHelper. Con el método getWritableDatabase() obtenemos la base de datos.

Con el método close() simplemente cerramos la conexión con la base de datos.

Al método createNote() le pasamos un String title y otro body para crear una nueva nota en la base de datos. Si se crea bien, devolveremos un long que sera el _id del nuevo registro creado en la tabla.

Con deleteNote() podemos eliminar un registro de la tabla pasándole el _id.

Para obtener todos los registros de la tabla usaremos fetchAllNotes(). Este método nos retorna un Cursor (según Android Labs el sistema es más eficiente que con collections). Dentro del método llamamos a SQLiteDatabase.query() con los parámetros: nombre de la tabla (String), columnas que queremos recuperar (array de Strings, y otros campos que dejamos a null para obtener todos los datos sin agrupar, ni ordernar. (Ver detalles en SQLiteDatabase).

fetchNote() funciona de una forma similar que fetchAllNotes(), pero le pasamos un _id para obtener un registro determinado.

Para actualizar un nota utilizaremos updateNote(), al que le pasaremos _id de la nota que modificar, y los valores actualizados.


Paso 3

Abrimos el fichero res/layout/notepad_list.xml y vemos un LinearLayout vacío. Si alguien no identifica o conoce algo que se pase por HelloLinearLayout.


Paso 4
.
Copiamos el siguiente cógido en res/layout/notepad_list.xml para crear la vista del listado de notas

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_notes"/>
 </LinearLayout>


Es una vista bastante sencilla, en la que tenemos un ListView y un TextView. Suponiendo que ya conocemos un poco de vistas (si no, mirar HelloLinearLayout), lo importante son los id de estos dos elementos. Vemos que empiezan los dos por @android:id/, esto quiere decir que nos lo proporciona la plataforma Android. Pues usaremos 'automáticamente' @android:id/list cuando tengamos notas y @android:id/empty cuando no haya notas.


Paso 5

Para cada fila o item de la lista también deberemos generar una vista. Creamos un fichero nuevo en res/layout y lo llamamos notes_row.xml. El contenido será:

<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text1"  
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

Podemos observar que en el id tenemos los carácteres @+. El + indica que los elementos que usan esta vista se generan dinámicamente y el id se creará automáticamente si todavía no existe.


Paso 6

Abrimos la clase Notepadv1 y le cambiamos la clase padre Activity por ListActivity. Esta clase añade funcionalidad extra para trabajar con listas.

Si miramos el código vemos que tenemos un atributo mNoteNumber que todavía no usamos, pero que haremos servir para numerar las notas.

También vemos que sobreescribimos tres métodos:

  1. onCreate: llamado cuando la Activity empieza. Una especie de main
  2. onCreateOptionsMenu: llamado cuando el usuario pulsa en botón de menu
  3. onOptionsItemSelected: cuando pulsamos en el botón de menú, normalmente lanzamos una lista de acciones, como por ejemplo "Crear Nota". Pues este método es llamado cuando el usuario pulsa en una opción de estas.

Paso 7

El método onCreate() debe quedarnos así:

@Override
         public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.notepad_list);
                mDbHelper = new NotesDbAdapter(this);
                mDbHelper.open();
                fillData();
         }

Después de las dos líneas típicas de un onCreate() (super.onCreate() y setContenView()), creamos la instancia del NotesDbAdapter, al que le pasamos el context. Abrimos la conexión y rellenamos los datos con fillData(), método que todavía no hemos implementado.


Paso 8

En el método onCreateOptionsMenu() copiamos:

@Override
         public boolean onCreateOptionsMenu(Menu menu) {
              boolean result = super.onCreateOptionsMenu(menu);
              menu.add(0, INSERT_ID, 0, R.string.menu_insert);
              return result;
         }

Para que el código compile debemos añadir en el res/value/strings.xml:

Add Item

Y añadimos en la clase Notepadv1 la siguiente constante:

public static final int INSERT_ID = Menu.FIRST;


De esta manera hemos añadido un botón ("Add Item") que aparecerá cuando pulsemos en el botón de menú del dispositivo.


Paso 9

En onOptionsItemSelected() copiamos:

@Override
        public boolean onOptionsItemSelected(MenuItem item) {
              switch (item.getItemId()) {
                 case INSERT_ID:
                 createNote();
                 return true;
              }
    
              return super.onOptionsItemSelected(item);
         }

Este método se ejecutará cuando pulsemos en un opción del menú. Recibe como parámetros un MenuItem, el cuál comprobamos si es la primera opción ("Add  Note") para crear una nota nueva con createNote(). Finalmente llamaremos al método de la superclase con super.onOptionsItemSelected().


Paso 10

Para este ejercicio 1 implementaremos un versión poco útil del método createNote(). Simplemente crearemos una nota nueva vacía asignándole un titulo incluyendo un contador. La nota no se podrá modificar de momento.

private void createNote() {
             String noteName = "Note " + mNoteNumber++;
             mDbHelper.createNote(noteName, "");
             fillData();
        }

Paso 11

Por último tenemos que implementar el método fillData(). El código es:

private void fillData() {
              Cursor c = mDbHelper.fetchAllNotes();
              startManagingCursor(c);


              String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
              int[] to = new int[] { R.id.text1 };
      
              SimpleCursorAdapter notes =
                    new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
              setListAdapter(notes);
        }

Para rellenar los datos usamos un SimpleCursorAdapter. Cuando creamos un objeto SimpleCursorAdapter le pasamos los siguientes parámetros:

  • this --> Contexto
  • int layout --> identificador del layout que contiene la lista de elementos a rellenar
  • Cursor --> Cursor de la base de datos
  • String[] --> Array de las columnas a rellenar
  • int[] --> Array de las vistas/layouts que mostrarán los datos


Para poder trabajar con el cursor, la clase Activity nos da el método startManagingCursor(Cursor c).

Paso 12

Ejecutamos la aplicación




lunes, 15 de marzo de 2010

Hacer capturas con Nexus One en Ubuntu


Por Internet existen muchas páginas con las instrucciones que hay que seguir para poder realizar capturas de pantalla, o debuggar, con tu teléfono. Pero después de seguir las instrucciones de varias páginas, yo no lo conseguía para mi Nexus One y Ubuntu.
Pero por fin he encontrado el cómo y voy a explicarlo (fuente).


  • En un terminal linux ejecutamos lsusb y obtendremos un pantalla del estilo a ésta:



  • Conectamos el N1 al ordenador y lo ponemos en modo Depuración USB. Ajustes -- Aplicaciones -- Desarrollo -- Depuración USB


  • En un terminal de linux volvemos a ejecutar lsusb y podremos que se ha añadido un dispositivo a la lista:
  • Ahora necesitamos crear una regla para nuestro dispositivo. Tenemos que crear un fichero en /etc/udev/rules.d/. Por ejemplo:
  • El contenido del fichero tiene que ser en mi caso (en negrita los valores que vemos con lsusb):
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="4e12", MODE="0666"


  • Le damos permisos de acceso y ejecución a ese fichero:
sudo chmod a+rx /etc/udev/rules.d/51.android.rules
  • Recargamos el servicio udev:
sudo service udev reload
  • Ahora si arrancamos el servicio adb vemos que no tenemos permisos (imágen de la fuente):
  • Ahora reiniciamos el servidor. Primero hacemos el kill-server y después (imágen de la fuente):
  • Por último listamos los dispositivos y veremos algo al estilo:

A partir de ahora podremos hacer capturas y debuggar desde Eclipse


O desde Dalvik Debug Monitor que ejecutamos con /tools/ddms:



sábado, 13 de marzo de 2010

Enviar e-mail con datos predefinidos (con Intents)

Puede que desde nuestra aplicación queramos enviar un email, ya sea para que el usuario se ponga en contacto con nosotros, con algún negocio, etcétera. En este post voy explicar como hacerlo con Intents.

// Creamos el Intent pasando un String
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);


// Rellenamos los datos que queremos predefinir
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"sergi.bc@gmail.com"});
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "DeSBC");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Gracias SBC");


// Empezamos la acción con createChooser
context.startActivity(Intent.createChooser(emailIntent, "Send mail..."));


En la primera línea creamos un Intent al que le pasamos un String con la acción android.content.Intent.ACTION_SEND. Esta acción permite al usuario enviar información a otra persona, aunque todavía no está definido el como.

En las siguientes 4 líneas estamos completando la información que por defecto queremos enviar, para que cada vez que ejecutemos esta acción siempre sea la misma.
El método Intent.putExtra(String, Bundle) añade información extra al Intent. También es posible eliminar información con Intent.removeExtra(String) y recuperar todo el Bundle con Intent.getBundleExtra(String).

En la última línea, con Intent.createChooser(Intent, String), estamos creando un Intent de tipo ACTION_CHOOSER. Se utiliza este método por convención en vez de crearlo de la manera habitual (new Intent(...)). Lo que conseguimos es lanzar una pantalla en la que podemos elegir una Activity, que en nuestro caso será la Activity encarga de enviar el email. Esta puede ser la Activity de gmail o de cualquier otra que sea capaz de enviar emails.





viernes, 12 de marzo de 2010

Guardar preferencias

Cuando queremos que nuestra aplicación tenga unas preferencias o propiedades, tanto fijas o variables, y que se mantengan cada vez que la ejecutamos, podemos almacenar estos valores de varias formas ( Preferences, Files, Databases, y Network)

Quizás la forma más fácil y ligera es con Preferences, que es apropiada para guardar datos de tipo primitivo. La manera de guardar y recoger propiedades es del tipo clave-valor.

Incluso, es posible compartir las preferencias de nuestra aplicación con otras aplicaciones. Aunque si solamente tenemos una aplicación, o no queremos que las preferencias sean compartidas, todavía es más sencillo (el ejemplo que voy a explicar es de este tipo).

El objeto que contiene las propiedades clave-valor es del tipo SharedPreferences, que obtenemos con la llamada Activity.getPreferences(int mode).

Para guardar los cambios de nuestras propiedades usaremos un editor SharedPreferences.Editor, pero para leerlas lo haremos directamente del SharedPreferences.

Vamos con el código de ejemplo (el siguiente código no compila, es solo un trozo de código),

import android.app.Activity;
import android.content.SharedPreferences;

        public class MyActivity extends Activity {

. . .      
// En la primera llamada de la Activity recuperamos el valor de una propiedad llamada "welcomeMessage"
@Override
protected void onCreate(Bundle state){
super.onCreate(state);


                                 // Creamos el objeto SharedPreferences 
SharedPreferences settings = getSharedPreferences(MODE_PRIVATE);
// defaultMessage será el valor por defecto de nuestra propiedad
String defaultMessage = "Hola SBC";
                                // recuperamos el valor de nuestra propiedad 'welcomeMessage'
String welcomeMessage = settings.getString("welcomeMessage", defaultMessage );
myObject.setWelcomeMessage(welcomeMessage);
}


// Guardaremos las opciones en este evento, pero  podríamos hacerlo en cualquier momento
@Override
protected void onStop(){
super.onStop();

// Creamos el objecto SharedPreferences
SharedPreferences settings = getSharedPreferences(MODE_PRIVATE);
                                 // Recuperamos el editor para poder guardar
SharedPreferences.Editor editor = settings.edit();
// Recuperamos el mensaje que hemos introducido
EditText tv = (EditText) findViewById(R.id.message);
String message = tv.toString();
                                // Con el editor le asignamos un valor a nuestra propiedad 'welcomeMessage'
editor.putString("welcomeMessage", message);
      
// El commit IMPORTANTE sobre el editor
editor.commit();
}
}


Como se puede observar es bastante sencillo. Para poder compartir las preferencias con otras aplicaciones es muy similar, pero en vez de llamar a Activity.getPreferences(int mode), deberemos llamar a Context.getSharedPrefences(String my_preferences, int mode), donde mode my_preferences es el nombre identificativo.

jueves, 11 de marzo de 2010

Application Fundamentals I


Como dije hace unos días, voy intentar resumir el articulo Application Fundamentals de Android Labs. Lo haré en dos partes.

Supongo que a estas alturas ya sabéis que el lenguaje de programación utilizado es Java. Así que para los que estamos familiarizados con este lenguaje, en principio no tendremos grandes problemas para empezar a programar en Android. Para los que no, quizás es recomendable empezar con algún tutorial de Java.

El código java compilado y los recursos utilizados en nuestra aplicación se empaquetan en un fichero de extensión APK (Android Package).
Este fichero APK lo podemos considerar como una aplicación, ya que por cada aplicación tenemos un fichero APK. Para instalarnos una aplicación debemos obtener su fichero APK, ya sea a través del Android Market o de algún otro medio.(Sí, es posible y legal)
Aunque el lenguaje de programación sea Java, las aplicaciones Android no trabajan de la misma manera. Gracias a la DalvikVM una aplicación tiene estas características:
  • Cada aplicación corre en un único proceso Linux.
  • Cada proceso corre en una VM.
  • Cada proceso es asignado a un único User ID.
Nota: este es el comportamiento normal, pero es posible que dos aplicaciones compartan el mismo UserID para ver los ficheros entre ellas. Y también correr en el mismo proceso compartiendo la misma VM.

Application Components
  • Activities: una Activity es una IU que realiza una actividad o acción. Una aplicación puede tener una simple Activity o varias. Por ejemplo, podemos tener una Activity que liste mensajes, otra que nos permita escribirlos, etc.. Todas estas acciones son clases que heredan de la clase Activity.
  • Services: es una acción sin IU pero que corre en modo background durante un tiempo indefinido. Por ejemplo, en una aplicación para escuchar música, probablemente tendremos una IU para escoger la(s) canción(es) que queremos escuchar, esto sería una Activity. En cambio, lo que es la reproducción de música en sí será un servicio, ya que si hacemos ostras cosas, como navegar, consultar el correo, etcétera, queremos seguir escuchando la música. Todas las clases que implementan servicios heredan de Service.
  • Broadcast Receivers: es un componente que aparentemente no hace nada, pero que recibe y reacciona a algunos eventos. Por ejemplo, cuando la batería esta baja, cambiamos de zona horaria, recibimos un mensaje (como en la imagen), etc.. Los broadcast receivers no tienen IU, pero suelen lanzar una Activity o un notificación a través del NotificationManager .Cada clase hereda de BrodcastReceiver

  • Content Provieders: los Content Providers hacen posible que cierta información de nuestra aplicación, sea accesible desde otras aplicaciones. Los datos podemos almacenarlos en sistema de ficheros con SQLite u otros medios. Nuestras clases Content Providers heredan de ContentProvider, y tendremos un conjunto de métodos con los que conseguir los datos que nos interesen. Un ContentProvider se activa cuando lo reclama un ContentResolver.
Intents
Los componentes ActivityBrodcastReceiver y Service se activan a través de unos mensajes asíncronos llamados Intents. Nuestros Intents heredan de la clase Intent y contienen el mensaje. A cada tipo componente se le llama de una manera distinta, pasándole la URI (entre otros parámetros):
Manifest
Para que Android pueda iniciar un componente de una aplicación debemos definirlo en un archivo xml. Este archivo se llama AndroidManifest.xml, y estará incluido en el paquete APK junto con los demás recursos.
Además de declarar los componentes, en este fichero también tenemos que incluir las bibliotecas que necesita la aplicación, y también determinar los permisos que el usuario deberá otorgar a la aplicación para su correcto funcionamiento.
Un ejemplo:


                  <?xml version="1.0" encoding="utf-8"?>
                      <manifest . . . >
                          <application . . . >
                               <activity android:name="com.example.project.FreneticActivity"
                                             android:icon="@drawable/small_pic.png"
                                             android:label="@string/freneticLabel" 
                                            . . .  >
                               </activity>
                                . . .
                          </application>
                    </manifest>

En el atributo de name de la <activity> tenemos la subclase de Activity que implimenta la funcionalidad. El icon (icono) y la label (etiqueta) apuntan a los ficheros del directorio de recursos /res/drawable que contiene un icono y una etiqueta.
Los demás componentes (<service><receiver><provider>) se declaran de una forma similar. Los componentes que no se declaran en el AndroidManifest.xml nunca se podrán ejecutar porque no son visibles para la aplicación. Sin embargo, los <broadcastReceiver> pueden ser declarados tanto en el AndroidManifest como en hardcode. Para ello deberán registrarse con la llamada Context.registerReceiver().
Esta mini-introducción del AndroidManifest.xml se puede completar con The AndroidManifest.xml File.

Intent Filters
Un objecto Intent puede llamar explícitamente a un componente. Gracias al AndroidManifest.xml, Android lo puede encontrará y lo activará. Pero si la llamada no es explícita, Android debe localizar el componente que mejor corresponda a la llamada. Android consigue hacer esto gracias a los IntentFilters. Un IntentFilter le indica a Android cual es el mejor handler para ese tipo de componente. Cosa que también declararemos en el AndroidManifest.xml. Completando el ejemplo anterior:



                <?xml version="1.0" encoding="utf-8"?>
                       <manifest . . . >
                           <application . . . >
                                <activity android:name="com.example.project.FreneticActivity"
                                              android:icon="@drawable/small_pic.png"
                                              android:label="@string/freneticLabel" 
                                        . . .  >
                                     <intent-filter . . . >
                                            <action android:name="android.intent.action.MAIN" />
                                            <category android:name="android.intent.category.LAUNCHER" />
                                     </intent-filter>
                                     <intent-filter . . . >
                                           <action android:name="com.example.project.BOUNCE" />
                                           <data android:mimeType="image/jpeg" />
                                           <category android:name="android.intent.category.DEFAULT" />
                                     </intent-filter>
                              </activity>
                                  . . .
                           </application>
                       </manifest>

El primer <intent-filter> del ejemplo (la combinación de "android.intent.action.MAIN" y "android.intent.category.LAUNCHER") es muy común, ya que indica que esta Activity es el punto de entrada de la aplicación y que se mostrará en el menú de aplicaciones que puede ejecutar el usuario.
En el segundo <intent-filter> se declara una Activity que podrá manejar datos de tipo image/jpg. "android.intent.category.DEFAULT" es la category que pondremos cuando nuestra Activity no sea ninguna otra específica.
Un componente puede tener cualquier número de IntentFilter. Si no tienen ninguno puede ser activado solamente cuando se llame explícitamente desde el código.

Para completar esta info Intents and Intent Filters.



martes, 9 de marzo de 2010

Hello Views VI

List View

Para crear una lista de elementos scrollables utilizaremos ListView. Estos elementos se insertarán automáticamente en la lista usando un ListAdapter.

En el ejemplo que nos proponen en Android Labs, veremos una lista de países que leeremos de un array de strings. Cuando seleccionemos un elemento, un Toast se mostrará diciéndonos su posición en la lista.

Los pasos resumidos son:

  1. Crear el proyecto con Eclipse indicando la Activity HelloListView
  2. Crear un fichero llamado list_item.xml y copiar el contenido en el directorio res/layout/. Es el layout para cada elemento de la lista.
  3. Modificar la clase padre de HelloListView. Ahora tiene que heredar de ListActivity
  4. Rellenar el método onCreate()
  5. Copiar el array de paises (o lo que queramos listar)
  6. Ejecutar la aplicación




Ahora vamos comentar los puntos más importantes del ejemplo.

  • Podemos ver que list_item.xml simplemente nos dice que cada elemento de la lista será una TextView. Aquí podríamos añadir por ejemplo una imagen al lado del texto.
  • ¿Porqué heredamos de ListActivity? Pues porque un ListActivity nos permite mostrar una lista de elementos vinculados a un data source, como un array. Además permite eventos sobre los elementos de la lista.
  • En el método onCreate() vemos cosas interesantes. Por ejemplo, que no cargamos ningún fichero de layout. En vez de esto, indicamos un ListAdapter con setListAdapter(ListAdapter), que añade automáticamente un ListView que rellena toda la pantalla de la ListActivity.
  • Al setListAdapter(ListAdapter) le pasamos como parámetros un ArrayAdapter. Para construir este objeto tenemos que pasarle el contexto (en nuestro caso con el this), el layout que utilizaremos para mostrar los elements (list_item.xml), y el array o lista de objecto que queremos mostrar (los países).
  • El método setTextFilterEnabled(boolean) de la ListView hace posible que podamos filtrar el listado.
  • El método setOnItemClickListener(OnItemClickListener) define un onClick listener para cada elemento, como vimos en el HelloGridView.
  • La manera de definir el array de strings, en hardcode, no es la mejor. Para hacerlo de una manera más elegante y práctica es mejor referenciarlo desde un fichero externo. El array deberemos declararlo de la siguiente forma, y ubicándolo el fichero strings.xml en el directorio res/values:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="countries_array">
<item>Bahrain</item>
<item>Bangladesh</item>
<item>Barbados</item>
<item>Belarus</item>
<item>Belgium</item>
<item>Belize</item>
<item>Benin</item>
</string-array>
</resources>

  • Ahora para cargar el ListAdapter tenemos que pasar el array de strings de la siguiente forma:
                   String[] countries = getResources().getStringArray(R.array.countries_array);
                   setAdapter(new ArrayAdapter(this, R.layout.list_item, countries));


Dudas
Me surge la duda de si el método setTextFilterEnabled(boolean) sólo funciona cuando nuestros elementos son TextView, o siempre aunque sean de otro tipo y el filtro se haga por algún identificador o descripción.

Notas
Como nos indica Android Labs en el ejemplo, android ya proporciona unos layouts de serie, cosa que no conocía hasta ahora. Os dejo el enlace.




sábado, 6 de marzo de 2010

Hello Views V

TabLayout

Como nos dicen en Android Labs, para crear una interfaz con pestañas o tabs, necesitaremos usar un TabHost y un TabWidget. El TabHost será el nodo raíz de nuestro layout, que tendrá el TabWidget para mostrar las pestañas, y un FrameLayout para enseñar el contenido de las pestañas.

Existen dos formas de implementar un IU con pestañas: en una sola Activity o en una Activity por pestaña. Este último método es el más indicado ya que tendremos separadas las funcionalidades, y lo utilizaremos para este ejemplo.






Los pasos resumidos son:
  1. Crear el proyecto con Eclipse indicando la Activity HelloTabWidget
  2. Crear 3 Activity's más: ArtistsActivity, AlbumsActivity y SongsActivity. Poniendo en cada método onCreate de las 3 Activity un TextView identificativo
  3. Para cada pestaña necesitamos dos imágenes, una para cuando está activada y otra para cuando no (en la página de Android Labs sólo vemos la imagen del micro, podemos coger otras para realizar el ejemplo). Además necesitamos un fichero XML, que hace de selector para realizar precisamente esa acción. Tanto las imágenes como los ficheros XML se guardan en res/drawable
  4. Crear o copiar el contenido del fichero main.xml
  5. Modificar la Activity principal, HelloTabWidget, para que herede de TabActivity en vez de Activity
  6. Copiar todo el código del método onCreate
  7. Cambiar el tema de la aplicación
  8. Ejecutar aplicación
    Los puntos clave de este ejemplo (bajo mi punto de vista) son:
    • El selector: La manera de mostrar una imagen para cuando la pestaña está activa o no se realiza en un fichero XML,  y un fichero por Activity. El tag es <selector> y dentro muestra los items que puede mostrar. Con el atributo android:state_selected="true" indicamos cuál es la imagen/item a mostrar cuando la pestaña está activa. Este recurso (fichero XML), se lo tenemos que pasar al método setIndicator a cada Tab.
        
          <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
                    <!-- When selected, use grey -->
                    <item android:drawable="@drawable/ic_tab_artists_grey" android:state_selected="true" />
                    <!-- When not selected, use white-->        
                      <item android:drawable="@drawable/ic_tab_artists_white" />
    </selector>

    • main.xml: Vemos que la vista empieza y acaba por con el TabHost. Después de indicar el xmlns, id y forma de relleno, vemos un LinearLayout. Dentro de ésto vemos lo importante, un TabWidget y un FrameLayout. El TabWidget ocupa a lo ancho el mismo espacio que su parent, y a lo alto el espacio necesario para mostrarse entero, es decir, el tamaño de la imagen.
    •  onCreate: Conseguimos los recursos, el TabHost con getTabHost (método específico de la clase TabActivity). Creamos un TabHost.TabSpec, que sirve para indicar las propiedades de una pestaña. Esta variable la vamos reutilizando una vez añadimos una pestaña al TabHost con addTab(spec).
    • Themes: En el ejemplo lo único que hace este tema es no mostrar el título de la aplicación. Pero se pueden hacer muchísimas más cosas. Más info aquí.
    Notas

    • En el código del ejemplo hay un fallo, en el método onCreate indica mTabHost.addTab(spec); cuando quiere decir tabHost.addTab(spec);.
    • No os olvidéis de cada pestaña tiene dos imágenes y un XML (selector), y que desde el XML se hace referencia al recurso de la imagen (<item android:drawable="@drawable/nombre_imagen"/>). Y que todo va dentro del directorio res/drawable/.
    • Y tampoco os olvidéis de incluir las 3 Activity en el fichero main.xml.



    jueves, 4 de marzo de 2010

    Hello Views IV

    GridView

    Un GridView también es un ViewGroup, que muestra sus hijos en una grid scrollable de manera bidimensional. A diferencia de las otras vistas, los elementos no se van añadiendo en el main.xml, sino que lo hacen a través de un ListAdapter.

    En el ejemplo de AndroidLabs nos enseñan a crear una GridView con imágenes en miniatura. Los pasos que tenemos que seguir, y que luego explicaré, son los siguientes:

    1. Crear el proyecto con Eclipse indicando la Activity HelloGridView.
    2. Copiar las imágenes que queremos visualizar en res/drawable (las del ejemplo son estas
    3. Crear o copiar el contenido del fichero main.xml
    4. Crear y copiar el contenido de la clase ImageAdapter, que hereda de la clase BaseAdapter
    5. Copiar el siguiente texto en el método onCreate de la Activity:
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.main);

                 GridView gridview = (GridView) findViewById(R.id.gridview);
                 gridview.setAdapter(new ImageAdapter(this));

                 gridview.setOnItemClickListener(new OnItemClickListener() {
                        public void onItemClick(AdapterView parent, View v, int position, long id) {
                            Toast.makeText(HelloGridView.this, "" + position, Toast.LENGTH_SHORT).show();
                        }
                });

    En este caso lo más importante no es el main.xml, pero que también voy a comentar:

    • android:columnWidth: ancho de la columna
    • android:numColumns: número de columnas. (auto-fit, se ajusta automáticamente)
    • android:verticalSpacing: separación vertical entre elementos
    • android:horizontalSpacing: separación horizontal entre elementos
    • android:stretchMode: forma de estirar el contenido. columnWidth, a lo ancho de la columna 


    La 'chicha' empieza en el método onCreate de la Activity:

    GridView gridview = (GridView) findViewById(R.id.gridview); 
    gridview.setAdapter(new ImageAdapter(this));

    Obtenemos el id de nuestra vista, la grid. Hemos indicado ese id en el fichero main.xml, cuando hemos creado el GridView.

    gridview.setOnItemClickListener(new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
      Toast.makeText(HelloGridView.this, "" + position, Toast.LENGTH_SHORT).show();
    }
    });

    Añadimos un Listener para cuando hacemos click en el item, una imagen. Al pulsar mostramos un Toast (especie de popup) en el que vemos la posicion de la imagen.

    Por último encontramos la clase ImageAdapter, que hereda de la clase BaseAdapter. El método importante es getView(). Este método esta sobreescribiendo al método de la clase Adapter, de la que hereda BaseAdapter, y es llamado cada vez que se va a pintar un elemento de la GridView. En nuestro caso carga las imágenes que hemos descargado antes, y en el orden que le indicamos en el array mThumbIds.

    Dudas 
    Creo que el parámetro convertView viene a null la primera vez que cargamos la GridView, pero en las siguientes llamadas nos viene ya la ImageView. Por eso comprobamos si viene a null o no. ¿Me equivoco?