Você está na página 1de 461

COLECCIN AULA MENTOR

SERIE PROGRAMACIN

CamSp

SGALV

Desarrollo de
aplicaciones para
Android II

Ministerio
de Educacin, Cultura
y Deporte

Desarrollo de Aplicaciones
para Android II
Programacin

Catlogo de publicaciones del Ministerio: www.educacion.gob.es


Catlogo general de publicaciones oficiales: www.publicacionesoficiales.boe.es

Autor
David Robledo Fernndez

Coordinacin pedaggica
Hugo Alvarez

Edicin y maquetacin de contenidos


Hugo Alvarez

Diseo grfico e imagen


Almudena Bretn

NIPO: 030-14-019-1
ISBN: 978-84-369-5541-5

NDICE

Pg.

Unidad 0. Introduccin  11
1. Por qu un curso avanzado de Android?................................................................11
2. Cambios en las ltimas versiones de Android .......................................................11
3. La simbiosis de Android y Linux..............................................................................13
4. Instalacin del Entorno de Desarrollo ....................................................................16
4.1 Qu es Eclipse? 16
4.2 Instalacin de Java Development Kit ( JDK) 16
4.3 Instalacin de Eclipse ADT 18
5. Aadir versiones y componentes de Android...........................................................23
6. Definicin del dispositivo virtual de Android..........................................................26
Unidad 1. Multimedia y Grficos en Android 33
1.Introduccin.............................................................................................................33
2. Android Multimedia.................................................................................................33
3. Libreras de reproduccin y grabacin de audio.....................................................36
3.1 Clase SoundPool 36
3.2 Clase MediaPlayer 37
3.3 Clase MediaRecorder 39
3.3.1 Ejemplo de reproduccin y grabacin de audio 40
3.4 Cmo habilitar USB Debugging en Android 4.2 y superior Jelly Bean 49
3.5 Librera de reproduccin de vdeo 50
3.5.1 Ejemplo de reproduccin de vdeo 50
4. Conceptos bsicos de grficos en Android...............................................................59
4.1 Definicin de colores en Android 59
4.2 Clases de dibujo en Android 60
4.2.1 Clase Paint 60
4.2.2 Clase Rectngulo 60
4.2.3 Clase Path 60
4.2.4 Clase Canvas 61
4.2.4.1 Obtener tamao delCanvas:61
4.2.4.2 Dibujar figuras geomtricas: 61
4.2.4.3 Dibujar lneas y arcos: 62
4.2.4.4 Dibujar texto: 62
4.2.4.5 Colorear todo ellienzo Canvas: 62
4.2.4.6 Dibujar imgenes: 62
4.2.4.7 Definir unClip (rea de seleccin): 62
4.2.4.8 Definir matriz de transformacin (Matrix): 63

4.2.5 Definicin de dibujables (Drawable) 66


4.2.5.1 Dibujable de tipo bitmap (BitmapDrawable) 67
4.2.5.2 GradientDrawable (Gradiente dibujable) 67
4.2.5.3 ShapeDrawable (Dibujable con forma) 68
4.2.5.4 AnimationDrawable (Dibujable animado) 68
5. Animaciones de Android..........................................................................................70
5.1 Animaciones Tween 70
5.1.1 Atributos de las transformaciones Tween  71
5.2 API de Animacin de Android 74
5.2.1 Clases principales de la API de animacin 74
5.2.1.1Animator 75
5.2.1.2ValueAnimator 75
5.2.1.3ObjectAnimator 76
5.2.1.4AnimatorSet 76
5.2.1.5AnimatorBuilder 77
5.2.1.6AnimationListener 77
5.2.1.7PropertyValuesHolder 78
5.2.1.8Keyframe 78
5.2.1.9TypeEvaluator 78
5.2.1.10ViewPropertyAnimator 79
5.2.1.11LayoutTransition 80
5.3 Animacin de Actividad 80
5.4 Interpolators (Interpoladores) 89
6. Vista de tipo Superficie (ViewSurface) ....................................................................92
6.1 Arquitectura de Grficos en Android 93
6.2 Qu es la clase ViewSurface? 93
7. Grficos en 3D en Android..................................................................................... 101
7.1 OpenGL  102
7.1.1 Conceptos bsicos de geometra 102
7.1.2 Conceptos bsicos de OpenGL 104
7.2 Grficos en 2D 107
7.3 Grficos en 3D con movimiento 117
7.4 Grficos en 3D con textura y movimiento 125
8.Resumen................................................................................................................. 134
Unidad 2. Interfaz de usuario avanzada 136
1.Introduccin........................................................................................................... 136
2. Estilos y Temas en las aplicaciones de Android..................................................... 136
2.1 Cmo crear un Tema 137
2.2 Atributos personalizados 138
2.3 Definicin de recursos dibujables (Drawable) 140
2.3.1 Recurso de color 140
2.3.2 Recurso de dimensin 141
2.3.3 Gradiente Drawable (Gradiente dibujable) 141
2.3.4 Selector Drawable (Selector dibujable) 142
2.3.5 Nine-patch drawable con botones 143
2.4 Atributos de los temas 144
2.5 Carga dinmica de Temas 145
3. Implementacin de Widgets en la pantalla principal............................................. 147
3.1 Tipos de Widgets y sus limitaciones 148
3.2 Ciclo de vida de un Widget 149

3.3 Ejemplo de Creacin de un Widget 150


3.4 Ejemplo de implementacin de un Widget 150
3.4.1 Fichero de configuracin del widget:  151
3.4.2 Clase que define el Widget:  152
3.4.3 Servicio que actualiza el Widget:  154
3.4.4 Interfaz de la Actividad de configuracin del Widget:  157
3.4.5 Actividad de configuracin de las preferencias:  158
3.4.6 Definicin de la aplicacin: 161
3.5 Colecciones de Vistas en Widgets  164
3.6 Activando Widgets en la pantalla de Bloqueo  165
4. Creacin de fondos de pantalla animados............................................................. 166
4.1 Ejemplo de Creacin de un fondo de pantalla animado 166
4.2 Ejemplo de implementacin de un fondo animado 167
4.2.1 Fichero de configuracin del fondo animado: 167
4.2.2 Servicio que implementa el fondo animado: 167
4.2.3 Interfaz de la Actividad de configuracin del fondo animado: 172
4.2.4 Actividad de configuracin de las preferencias: 173
4.2.5 Actividad principal del usuario:  174
4.2.6 Definicin de la aplicacin:  174
5.Fragmentos............................................................................................................ 179
5.1 Cmo se implementan los Fragmentos 180
5.2 Ciclo de vida de un Fragmento 192
5.2.1 Cmo guardar el estado de un Fragmento 193
5.2.2 Cmo mantener los Fragmentos cuando la Actividad se recrea automticamente193
5.2.3 Cmo buscar Fragmentos  194
5.2.4 Otras operaciones sobre Fragmentos (Transacciones) 194
5.2.5 Cmo Gestionar la pila (Back Stack) de Fragmentos  195
5.2.6 Cmo utilizar Fragmentos sin layout 197
5.2.6.1 Comunicacin entre Fragmentos y con la Actividad 197
5.2.7 Recomendaciones a la hora de programar Fragmentos 198
5.2.8 Implementar dilogos con Fragmentos  199
5.2.9 Otras clases de Fragmentos 202
5.3 Barra de Accin (Action Bar) 202
5.3.1 Cmo integrar pestaas en la Barra de accin 207
6. Nuevas Vistas: GridView, Interruptor (Switch) y Navigation Drawer..................... 211
6.1 Grid View 211
6.2 Interruptores (Switches) 215
7. Navigation Drawer (Men lateral deslizante)......................................................... 217
8.Resumen................................................................................................................. 229
Unidad 3. Sensores y dispositivos de Android  231
1.Introduccin........................................................................................................... 231
2. Introduccin a los sensores y dispositivos............................................................ 231
2.1 Gestin de Sensores de Android 232
2.1.1 Cmo se utilizan los Sensores 234
2.1.2 Sistema de Coordenadas de un evento de sensor 239
3. Simulador de sensores de Android........................................................................ 240
3.1 Instalacin del Simulador de Sensores 241
3.2 Cmo utilizar el Simulador de Sensores 243
3.2.1 Ejemplo de desarrollo de aplicacin con el Simulador de Sensores 247
3.2.2 Grabacin de escenario de simulacin con un dispositivo real 251

4. Dispositivos de Android......................................................................................... 253


4.1 Mdulo WIFI 253
4.2 Mdulo Bluetooth 261
4.3 Cmara de fotos 267
4.3.1 Ejemplo de cmara mediante un Intent 268
4.3.2 Ejemplo de cmara mediante API de Android 269
4.4 Mdulo GPS 281
5. Uso de sensores en un juego .............................................................................. 293
5.1 Desarrollo de un Juego en Android 293
6.Resumen................................................................................................................. 315
Unidad 4. Bibliotecas, APIs y Servicios de Android 317
1.Introduccin........................................................................................................... 317
2. Uso de Bibliotecas en Android............................................................................... 317
2.1 Ejemplo de Biblioteca de Android 318
3. APIs del telfono: llamadas y SMS......................................................................... 327
3.1TelephonyManager  327
3.2SMSManager 328
3.3 Ejemplo de utilizacin de la API de telefona 328
3.3.1 Clase Loader 339
4. Calendario de Android........................................................................................... 343
4.1 API Calendario de Android 343
4.2 Tabla Calendarios 345
4.3 Tabla Eventos/Citas 347
4.4 Tabla Invitados 350
4.5 Tabla Recordatorios 351
4.6 Tabla de instancias 351
4.7 Intenciones de Calendario de Android 352
4.8 Diferencias entre Intents y la API del Calendario 354
4.9 Ejemplo de uso de Intents de la API del Calendario 354
5. Gestor de descargas (Download manager)............................................................ 366
5.1 Ejemplo de utilizacin del Gestor de descargas 367
6. Cmo enviar un correo electrnico........................................................................ 371
6.1 OAuth 2.0 de Gmail 371
6.2 Intent del tipo message/rfc822 371
6.3 Biblioteca externa JavaMail API 371
6.4 Ejemplo sobre cmo envar un correo electrnico 372
7. Servicios avanzados de Android............................................................................. 382
7.1 Teora sobre servicios de Android 382
7.2 Servicios propios 383
7.3 Intent Service 385
7.4 Ejemplo de uso de IntentService 385
7.5 Comunicacin con servicios 392
7.6 Ejemplo de uso de AIDL 393
8. Servicios SOAP en Android.................................................................................... 398
8.1 Instalacin de bibliotecas SOAP en Eclipse ADT 399
8.2 Desarrollo de un servidor SOAP en Eclipse ADT 404
8.3 Ejemplo de uso de servidor SOAP en Android 412
8.4 Peticin / Respuesta compleja SOAP en Android 420
9.Resumen................................................................................................................. 423

Unidad 5. Utilidades avanzadas 425


1.Introduccin........................................................................................................... 425
2. Portapapeles de Android........................................................................................ 425
2.1 Ejemplo de portapapeles 426
3. Drag and Drop (Arrastrar y soltar)........................................................................ 431
3.1 Proceso de Arrastrar y soltar 431
3.2 Ejemplo de Arrastrar y soltar 432
4. Gestin del toque de pantalla................................................................................ 436
4.1 Ejemplo de gestin de toque de pantalla 438
5.Tamaos de pantalla de los dispositivos Android.................................................. 448
5.1 Android y tamaos de pantalla 449
5.2 Densidades de pantalla 450
5.3 Buenas prcticas de diseo de interfaces de usuario 452
6. Internacionalizacin de aplicaciones Android....................................................... 453
6.1 Ejemplo del uso de Internacionalizacin 454
7. Desarrollo rpido de cdigo Android.................................................................... 459
8.Resumen................................................................................................................. 461

U0 Introduccin

Unidad 0. Introduccin

1. Por qu un curso avanzado de Android?


Android es un sistema operativo multidispositivo, inicialmente diseado para telfonos
mviles. En la actualidad se puede encontrar tambin en mltiples dispositivos, como
ordenadores, tabletas, GPS, televisores, discos duros multimedia, mini ordenadores, cmaras de
fotos, etctera. Incluso se ha instalado en microondas y lavadoras.
Est basado en Linux, que es un ncleo de sistema operativo libre, gratuito y multiplataforma.
Este sistema operativo permite programar aplicaciones empleando una variacin de Java
llamada Dalvik, y proporciona todas las interfaces necesarias para desarrollar fcilmente aplicaciones que acceden a las funciones del telfono (como el GPS, las llamadas, la agenda, etctera)
utilizando el lenguaje de programacin Java.
Su sencillez, junto a la existencia de herramientas de programacin gratuitas, es principalmente la causa de que existan cientos de miles de aplicaciones disponibles, que amplan la
funcionalidad de los dispositivos y mejoran la experiencia del usuario.
Este sistema operativo est cobrando especial importancia debido a que est superando
al sistema operativo por excelencia: Windows. Los usuarios demandan cada vez interfaces ms
sencillas e intuitivas en su uso; por esto, entre otras cosas, Android se est convirtiendo en el
sistema operativo de referencia de facto. El tiempo dir si se confirman las perspectivas.

El objetivo de este curso avanzado es que el alumno o alumna perfeccione la programacin en este sistema operativo tratando materias no estudiadas en el curso
de iniciacin. As, podr desarrollar aplicaciones ms complejas utilizando contenidos multimedia, 3D, sensores del dispositivo, servicios, etctera.

2. Cambios en las ltimas versiones de Android

1.5 Cupcake

1.6 Donut

2.0/2.1 Eclair

2.2 Froyo

2.3 Gingerbread 3.0/3.1 Honeycomb

...IceCream
Sandwich

11

Aula Mentor

Quien est familiarizado con el sistema operativo Android ya sabr que los nombres de sus
diferentes versiones tienen el apodo de un postre.
A continuacin, vamos a comentar la evolucin de las diferentes versiones indicando las
mejoras y funcionalidades disponibles en cada una. Partiremos de la versin 3.0 ya que las versiones anteriores a sta se tratan en el curso de Iniciacin de Android de Mentor.
- Android 3.0 (API 15)
Esta versin se dise pensando en las tabletas, que disponen de un hardware mucho ms
potente. Entre sus nuevas funcionalidades podemos encontrar:
Soporte para grandes pantallas, como las tabletas.
Inclusin del concepto de Fragmento (en ingls, Fragment).
Nuevos elementos de interfaz como las barras de accin (action bars) y el arrastrar y
soltar (drag-and-drop).
Instalacin de un nuevo motor OpenGL 2.0 para animacin en 3D.
Esta versin de Android se dise exclusivamente para ser utilizada en tabletas. En otros
dispositivos, como los telfonos, era necesario seguir utilizando la versin 2.3.7 disponible
en ese momento.

12

- Android 4.0 (API 16)


A partir de esta versin se unifica el sistema operativo para que pueda utilizarse tanto en
tabletas como en otros dispositivos, como telfonos mviles. As, se unifica la experiencia de
usuario en todos los dispositivos. Entre sus nuevas funcionalidades podemos destacar:
Optimizacin en las notificaciones al usuario.
Permite al usuario cambiar el tamao de los widgets.
Aade diferentes formas de desbloquear la pantalla del dispositivo.
Corrector ortogrfico integrado.
NFC (Near Field Communication)
Wi-Fi Direct para compartir archivos entre dispositivos.
Encriptacin total del dispositivo.
Nuevos protocolos de Internet como RTP (Real-time Transport Protocol) para que el
dispositivo accede en tiempo real a contenidos de audio y vdeos.
MTP (Media Transfer Protocol) que permite conectar el dispositivo al ordenador por
USB de forma ms simple.
Gestin de derechos de autor mediante Digital Rights Management (DRM).
- Android 4.2 (API 17)
Esta versin no supone un salto en cuanto a las posibilidades que ofrece desde el punto
de vista del desarrollador. Sin embargo, es una versin estable y madura. Entre sus nuevas
funcionalidades podemos destacar:
Soporte multiusuario.
Posibilidad e inclusin de Widgets en la ventana de bloqueo.
Mejoras de interfaz y de cmara de fotos.
- Android 4.3 (API 18)
De igual forma que en la versin anterior, esta versin no supone un cambio radical en funcionalidades disponibles al desarrollador. Sin embargo, es una versin ms estable y madura
sin ninguna duda. Entre sus nuevas funcionalidades podemos destacar:
Bluetooth Low Energy (Smart Ready) y modo Wi-Fi scan-only que optimizan el consumo de batera de estos dispositivos.
Inclusin de la librera OpenGL ES 3.0 que permite mejorar en grficos 3D.

U0 Introduccin

Definicin de perfiles de usuario limitados que, desde el punto de vista del desarrollador, implican una gestin de las Intenciones implcitas (Implicit Intent) para comprobar si el usuario tiene permisos para acceder a ese tipo de Intencin.
Mejoras en la gestin multimedia y de codecs de archivos de vdeo. Adems, permite
crear un vdeo de una Superficie dinmica.
Nuevos tipos de sensores relacionados con juegos.
Nueva Vista ViewOverlay que permite aadir elementos visuales encima de otros ya
existentes sin necesidad de incluir en un Layout. til para crear animaciones sobre la
interfaz de usuario.
Nuevas opciones de desarrollo como revocar el acceso a la depuracin USB de todos
los ordenadores o mostrar informacin del uso de la GPU del dispositivo.
Notification Listener es un nuevo servicio que permite que las aplicaciones reciban
notificaciones del sistema operativo y sustituye al servicio Accessibility APIs.

Este curso est basado en la ltima versin de Android disponible que es la


4.3 y todos los ejemplos y aplicaciones son compatibles con sta. De todas formas,
no debe haber ningn problema en utilizar este cdigo fuente en versiones futuras
de Android.

Importante
Los contenidos de este curso estn diseados para alumnos que estn familiarizados con el entorno de desarrollo Eclipse / Android / Emulador de Android. Por ello,
los alumnos deben conocer y manejar con soltura Vistas bsicas, Actividades, Mens, Dilogos, Adaptadores, sistema de ficheros, Intenciones, Notificaciones, Content Providers y utilizacin de SQLite. Todos estos conceptos bsicos de desarrollo
en este sistema operativo se tratan en el curso de Iniciacin a Android de Mentor.

3. La simbiosis de Android y Linux

13

Aula Mentor

Como sabes, Android est basado en Linux para los servicios base del sistema, como seguridad,
gestin de memoria, procesos y controladores. El diagrama de la arquitectura de Android tiene
este aspecto:

14

Antes del ao 2005, Linux estaba disponible en servidores web, aplicaciones de escritorio de
algunas empresas y administraciones, as como en ordenadores de programadores y entusiastas.
Sin embargo, con el despegue de Android, Linux empieza a estar instalado en nuestros mviles
y tabletas de forma masiva. En este apartado vamos a ver por qu es tan importante la simbiosis
Android y Linux.
El desarrollo de Linux empez el ao 1991 de la mano del famoso estudiante finlands
Linus Torvalds que crea la primera versin de este sistema operativo con el fin de implementar
una versin libre de licencias (Open Source) de Unix que cualquier programador pudiera modificar o mejorar a su antojo.
Al poco tiempo, grandes compaas como Intel e IBM advirtieron su potencial frente a
Windows e invirtieron grandes cantidades de dinero. Su objetivo principal era no depender de
Microsoft y, de paso, obtener un sistema operativo sin tener que empezar de cero.
En la actualidad, los sistemas operativos basados en Linux son sinnimo de estabilidad,
seguridad, eficiencia y rendimiento.
Sin embargo, hasta la aparicin de Android, a Linux le faltaba el xito entre el gran p-

U0 Introduccin

blico quedando casi relegado a los servidores.


Desde entonces, cada nuevo proyecto basado en Linux ha tenido como objetivo el gran
pblico. Ubuntu, con una interfaz muy sencilla e intuitiva y teniendo en cuenta al usuario como
primera prioridad, es hasta ahora la distribucin de escritorio ms popular de la historia del sistema operativo, gracias a que sus desarrolladores crearon una instalacin automtica de drivers
y cdecs. Adems, su interfaz actual, llamada Unity, aplica conceptos del entorno mvil, y, de
hecho, ya hay una versin preliminar de Ubuntu para telfonos.
A pesar de todo esto, sin embargo, a Linux le faltan los programas comerciales ms importantes, por lo que nicamente el 1% de los PCs del mundo funcionan con Linux.
En el ao 2005 surge Android, que, debido a su carcter abierto, emple el kernel (ncleo) de Linux como base. Tcnicamente, Android no es una distribucin de Linux, ya que la
cantidad de modificaciones realizadas al cdigo hace que se considere un sistema operativo
independiente, aunque gran parte del cdigo se comparte con el Linux normal de escritorio.
Pero, por qu ha conseguido Google llegar a tal cantidad de dispositivos en todo el mundo? La
respuesta es simple: ha colaborado con los fabricantes.
Para que Android (o cualquier sistema operativo) pueda ejecutarse en un dispositivo mvil, son necesarios los drivers. Los drivers son programas integrados en una librera que indica
al sistema operativo cmo controlar las distintas partes de hardware. Por ejemplo, para poder
utilizar la red WiFi, Android necesita conocer cmo indicar al chip las instrucciones que necesita
mediante los drivers. Dado que los drivers incluyen informacin sobre cmo funciona el hardware fsicamente, los fabricantes son siempre reacios a publicar su informacin por temor a que
los competidores los copien. Google consigui garantizar a los fabricantes la independencia de
sus tecnologas al mismo tiempo que aprovechaba la filosofa abierta de Linux para fabricar un
sistema alrededor de ellos. Por esta razn, puedes descargarte Android de Internet pero realmente no puedes ejecutarlo en tu mvil sin obtener los drivers antes y compilarlos.
A lo largo de este tiempo, la relacin Android/Linux ha tenido unos cuantos altibajos ya
que Google ha exigido cambios en Linux para mejorar Android sin tener en cuenta que Linux
es un proyecto global.
Con todo, la historia de Android y Linux no ha terminado, ni mucho menos. De hecho,
se podra decir que acaba de empezar. Algunos analistas de Internet hablan de que al final Linux
s vencer al otrora omnipotente Windows, pero ser a travs de Android. El tiempo dir.

15

Aula Mentor

4. Instalacin del Entorno de Desarrollo


4.1 Qu es Eclipse?

16

Como sabes, Eclipse es un entorno de software multi-lenguaje de programacin que incluye un


entorno de desarrollo integrado (IDE). Inicialmente, se dise pensando principalmente en el
lenguaje de programacin Java y se puede utilizar para desarrollar aplicaciones en este lenguaje.
En la web oficial de Eclipse (www.eclipse.org), se define como An IDE for everything
and nothing in particular (un IDE para todo y para nada en particular). Eclipse es, en realidad, un armazn (workbench) sobre el que se pueden instalar herramientas de desarrollo para
cualquier lenguaje, mediante la implementacin de los plugins adecuados. El trmino plugin
procede del ingls to plug, que significa enchufar. Es un software que permite cambiar, mejorar
o agregar funcionalidades.
La arquitectura de plugins de Eclipse permite, adems de integrar diversos lenguajes sobre un mismo IDE, introducir otras aplicaciones accesorias que pueden resultar tiles durante
el proceso de desarrollo, tales como herramientas UML (modelado de objetos), editores visuales
de interfaces, ayuda en lnea para libreras, etctera.
Si has realizado el curso de Iniciacin de Android de Mentor habrs utilizado ya Eclipse
y tendrs soltura en su uso.

Google ha simplificado todo el proceso de instalacin del entorno de desarrollo


preparando en un nico archivo todos los archivos necesarios. Este entorno se
denomina ADT (Android Developer Tools) que denominaremos en el curso Eclipse
ADT. Adems, el nuevo entorno ya es compatible con Java 1.7.

4.2 Instalacin de Java Development Kit ( JDK)


Es muy importante tener en cuenta que, para poder ejecutar el entorno de desarrollo Eclipse
ADT, es necesario tener instaladas en el ordenador las libreras de desarrollo de Java. La ltima
versin 1.7 ya es compatible con Eclipse ADT.
Podemos descargar la versin correcta del JDK de Java en:
http://www.oracle.com/technetwork/es/java/javase/downloads/index.html

U0 Introduccin

17
Si haces clic en el enlace anterior indicado, puedes encontrar un listado con todos los JDK de
Java:

Aula Mentor

Nota: en el caso de Linux o Mac, es posible tambin instalar Java usando los programas habituales
del sistema operativo que permiten la actualizacin de paquetes.
Nota: si vas a instalar Eclipse ADT en Linux, lee las notas que se encuentran en Preguntas y
Respuestas de esta Introduccin en la mesa del curso.

4.3 Instalacin de Eclipse ADT


La instalacin es muy sencilla. Simplemente accedemos a la pgina web:
http://developer.android.com/intl/es/sdk/index.html

Si vamos a instalar Eclipse ADT en Windows, podemos hacer clic directamente en el enlace
Download the SDK. En caso contrario debemos hacer clic en el enlace DOWNLOAD FOR
OTHER PLATFORMS y seleccionar el sistema operativo correspondiente.

18

Hay que tener en cuenta que debemos descargar la versin 32 bits o 64 bits en funcin del
sistema operativo de que dispongamos.
En el caso de Windows podemos ver el tipo de sistema operativo haciendo clic con el
botn derecho del ratn en el icono Equipo o Mi PC del Escritorio y haciendo clic de nuevo
en Propiedades:

U0 Introduccin

En el caso de Linux, desde la lnea de comandos podemos ejecutar el siguiente comando para
saber si el sistema operativo es de 64bits:
$ uname -m
x86_64

En el caso de Apple Mac, desgraciadamente, slo est disponible Eclipse ADT si ests utilizando
un kernel de 64 bits. Para saber si tu Mac ejecuta el sistema operativo de 64 bits sigue estas
instrucciones:
- En el men Apple ( ), selecciona Acerca de este Mac y a continuacin, haz clic en Ms
informacin:

- En el panel Contenido, selecciona Software.


- Si Extensiones y kernel de 64 bits est configurada como S, ests utilizando un kernel de
64 bits.
Cuando hayamos descargado el fichero correspondiente, lo copiamos a un directorio o carpeta
del ordenador y descomprimimos este fichero.
Es recomendable usar un directorio sencillo que podamos recordar fcilmente, por ejemplo

19

Aula Mentor

C:\cursosMentor\adt. Adems, es muy importante que los nombres de los directorios no


contengan espacios, pues Eclipse ADT puede mostrar errores y no funcionar correctamente.
Una vez descomprimido el fichero, Eclipse ADT est listo para ser utilizado; no es necesario hacer ninguna operacin adicional.

Recomendamos que conviene hacer un acceso directo del archivo C:\cursosMentor\adt\eclipse\


eclipse.exe en el Escritorio del ordenador para arrancar rpidamente el entorno de programacin
Eclipse ADT.

20

Si arrancamos Eclipse ADT haciendo doble clic sobre el acceso directo que hemos creado
anteriormente, a continuacin, Eclipse pedir que seleccionemos el workspace, es decir, el
directorio donde queremos guardar los proyectos.

U0 Introduccin

Seleccionaremos un directorio sencillo y fcil de recordar.

Importante:
Recomendamos usar el directorio C:\cursosMentor\proyectos como carpeta personal.

Finalmente hacemos clic en OK para abrir Eclipse ADT:

21

Aula Mentor

Si cerramos la pestaa abierta, podemos ver ya el entorno de desarrollo que deberas conocer si
has hecho del curso de inciacin:

22

Ahora vamos a comprobar en las preferencias que la versin de Java en Eclipse ADT es correcta
para compilar proyectos de Android. Para ello, hacemos clic en la opcin del men Window->
Preferences..., hacemos clic en el panel izquierdo sobre Java->Installed JREs y seleccionamos
jre7 en el campo Installed JREs:

U0 Introduccin

Para finalizar, en esta ventana hay que seleccionar la versin de Java utilizada para compilar los
proyectos de Android. Para ello hacemos clic en Java->Compiler y elegimos 1.6 en el campo
Compiler compliance settings:

23

Es muy importante comprobar la versin de java de compilacin

5. Aadir versiones y componentes de Android


Aunque Eclipse ADT incluye ya la ltima versin del SDK Android, el ltimo paso de la
configuracin consiste en descargar e instalar los componentes restantes del SDK que utilizaremos
en este curso.
El SDK utiliza una estructura modular que separa las distintas versiones de Android,
complementos, herramientas, ejemplos y la documentacin en un nico paquete que se puede
instalar por separado. En este curso vamos a usar la versin 4.3, por ser la ltima en el

Aula Mentor

momento de redaccin de la documentacin. No obstante, vamos a emplear sentencias compatibles y recompilables en otras versiones.
Para aadir esta versin hay que hacer clic en la opcin Android SDK Manager del men
principal Window de Eclipse:

Eclipse ADT tambin dispone de un botn de acceso directo:

24

Si lo hacemos, se abrir la ventana siguiente:

U0 Introduccin

Para instalar la versin 4.3 (si no lo est ya), seleccionamos los paquetes que se muestran en la
siguiente ventana:

25

Nota: la revisin de las versiones de Android puede ser superior cuando al alumno o alumna
instale el SDK.
Una vez hemos pulsado el botn Install 4 packages, aparece esta ventana y seleccionamos la
opcin Accept All y, despus, hacemos clic en Install:

Aula Mentor

26

El instalador tarda un rato (10-20 minutos) en descargar e instalar los paquetes. Una vez acabado
se indicar que la instalacin ha finalizado correctamente.

6. Definicin del dispositivo virtual de Android


Para poder hacer pruebas de las aplicaciones Android que desarrollemos sin necesidad de
disponer de un telfono Android, el SDK incluye la posibilidad de definir un Dispositivo Virtual
de Android (en ingls, AVD, Android Virtual Device). Este dispositivo emula un terminal con
Android instalado.
Antes de crear el AVD, es recomendable instalar el acelerador por hardware de Intel del
AVD llamado Intel Hardware Accelerated Execution Manager. As, conseguiremos que el
AVD se ejecute con mayor rapidez y eficiencia. Si abres el explorador de ficheros en el directorio:
Debes ejecutar el archivo
C:\cursosMentor\adt\sdk\extras\intel\Hardware_Accelerated_Execution_
Manager\IntelHaxm.exe:

U0 Introduccin

Es recomendable dejar que el instalador elija la memoria por defecto utilizada:

27

A continuacin, pulsamos el botn Next:

Aula Mentor

Si pulsamos el botn Install, se instalar el la utilidad:


28

Atencin:
Este acelerador de hardware slo est disponible en algunos procesadores de
Intel que disponen de tecnologa de virtualizacin (VT=Virtualization Technology).
Adems, slo est disponible para el sistema operativo Windows.

U0 Introduccin

Independientemente de que hayas podido instalar el acelerador, continuamos definiendo el


AVD. A continuacin, hacemos clic en la opcin Android AVD Manager del men principal
Window de Eclipse:

Aparecer la siguiente ventana:


29

Aula Mentor

Hacemos clic en el botn New de la ventana anterior y la completamos como se muestra en


la siguiente ventana:

30

U0 Introduccin

Si no has podido instalar el acelerador del emulador, debes seleccionar el campo CPU/ABI
siguiente:

31

La opcin Snapshot-> Enabled permite guardar el estado del dispositivo de forma que todos
los cambios que hagamos, como cambiar la configuracin de Android o instalar aplicaciones,
queden guardados. As, la prxima vez que accedamos al emulador, se recupera automticamente
el ltimo estado.

Importante:
En el curso hemos creado un dispositivo virtual que no guarda el estado porque
puede producir problemas de ejecucin con Eclipse ADT. En todo caso, el alumno
o alumna puede usar la opcin Edit del AVD cuando crea necesario que los ltimos cambios sean almacenados para la siguiente sesin de trabajo.

Aula Mentor

Para acabar, basta con hacer clic en OK:

32

Puedes encontrar el vdeo Cmo instalar Eclipse ADT, que muestra de manera
visual los pasos seguidos en las explicaciones anteriores.

U1 Multimedia y Grficos en Android

Unidad 1. Multimedia y Grficos en Android

1. Introduccin
En esta Unidad vamos a explicar cmo disear aplicaciones multimedia Android para or
msica, grabar con el micrfono y cargar vdeos desde una tarjeta SD.
Algunas aplicaciones Android deben mostrar un aspecto dinmico o representar algn
dato en forma grfica para que el usuario visualice mejor la informacin que se le est ofreciendo. Como hemos comentado anteriormente en el curso, una aplicacin Android tiene xito si
est bien programada internamente y, adems, si tiene una apariencia atractiva exteriormente.
Para poder desarrollar aplicaciones que incluyan estas funcionalidades es necesario adquirir previamente los Conceptos bsicos de grficos en Android.
Los grficos 2D/3D y las animaciones suelen ser muy tiles para presentar visualmente
informacin al usuario.
Para adquirir estas destrezas como programador Android, aprenderemos a animar imgenes de forma sencilla utilizando la API de animaciones de Android.
Despus, veremos qu es una Vista de tipo Superficie (ViewSurface) y sus aplicaciones ms interesantes.
Finalmente, estudiaremos cmo aplicar a proyectos Android la conocidsima librera
OpenGL para crear grficos en 2D y 3D, aplicarles colores, animarlos y permitir que el usuario interaccione con ellos.

2. Android Multimedia
Hoy en da, los dispositivos mviles han sustituido a muchos antiguos aparatos que utilizbamos
para escuchar msica, grabar conversaciones, ver vdeos, etctera.
En este apartado vamos a ver cmo disear aplicaciones multimedia Android y reproducir este tipo de archivos de audio y vdeo.
Mediante ejemplos prcticos expondremos una explicacin detallada de las funciones
propias del SDK que permitirn implementar una aplicacin multimedia.
La integracin de contenido multimedia en aplicaciones Android resulta muy sencilla e
intuitiva gracias a la gran variedad de clases que proporciona su SDK.
En concreto, podemos reproducir audio y vdeo desde:
- Un fichero almacenado en el dispositivo, normalmente en la tarjeta externa SD.
- Un recurso que est embutido en el paquete de la aplicacin (fichero.apk).
- Medianteel streaming: distribucin de multimedia a travs de una red de manera que el
usuario accede al contenido al mismo tiempo que se descarga. Los protocolos admitidos son
dos: http://yrtp://.
Tambin es posible grabar audio y vdeo, siempre y cuando el hardware del dispositivo lo
permita.

33

Aula Mentor

A continuacin, se muestra un listado de las clases de Android que nos permiten acceder
a estos servicios Multimedia:
- MediaPlayer: reproduce audio y vdeo desde ficheros ode streamings.
- MediaController: representa los controles estndar para MediaPlayer (botones de reproducir, pausa, stop, etctera).
- VideoView: Vista que permite la reproduccin de vdeo.
- MediaRecorder: clase que permite grabar audio y vdeo.
- AsyncPlayer: reproduce una lista de archivos de tipo audio desde un hilo secundario.
- AudioManager: gestor del sonido del sistema operativo de varias propiedades como son el
volumen, los tonos de llamada/notificacin, etctera.
- AudioTrack: reproduce un archivo de audio PCM escribiendo un bfer directamente en
elhardware. PCM son las siglas de Pulse Code Modulation, que es un procedimiento de modulacin utilizado para transformar una seal analgica en una secuencia de bits.
- SoundPool: gestiona y reproduce una coleccin de recursos de audio de corta duracin.
- JetPlayer: reproduce audio y video interactivo creado con SONiVOX JetCreator.
- Camera:clase para tomar fotos y video con la cmara.
- FaceDetector: clase para identificar la cara de las personas en una imagen de tipo bitmap.
El sistema operativo Android soporta una gran cantidad de tipos de formatos multimedia,
la mayora de los cuales pueden ser tanto decodificados como codificados. A continuacin,
mostramos una tabla con los formatos nativos multimedia soportados por Android. Hay
que tener en cuenta que algunos modelos de dispositivos pueden incluir formatos adicionales
que no se incluyen en esta tabla, como DivX.
34

Tipo

Formato

Informacin

Extensin
fichero

3GPP (.3gp)
MPEG-4
(.mp4)

H.264 AVC

a partir
Android 3.0

Baseline Profile
(BP)

3GPP (.3gp)
MPEG-4
(.mp4)

MPEG-4 SP

3GPP (.3gp)

a partir
Android
2.3.3

Streaming a partir
de Android 4.0

WebM
(.webm)
Matroska
(.mkv)

WP8

Imagen

Decodifica

H.263

Video

Tipo

Codifica

Formato

Codifica

Decodifica

Informacin

Extensin
fichero

JPEG
GIF
PNG
BMP

S
S
S
S

Base + progresivo

JPEG (.jpg)
GIF (.gif)
PNG (.png)
BMP (.bmp)

WEBP

a partir
Android 4.0

a partir
Android 4.0

WebP
(.webp)

U1 Multimedia y Grficos en Android

Tipo

Formato

Codifica

Decodifica

Informacin

Extensin
fichero

AAC LC/LTP

HE-AACv1

a partir
Android 4.1

HE-AACv2

AAC ELD

a partir
Android 4.1

a partir
Android
4.1

Mono/estreo,
16-8kHz

AMR-NB

4.75 a 12.2 Kbps


muestreada a @ 8kHz

3GPP (.3gp)

9 ratios de 6.60 Kbps


a 23.85 Kbps a @
16kHz

3GPP (.3gp)

Mono/estreo de 8 a
320 Kbps, frecuencia
de muestreo
constante (CBR) o
variable (VBR)

MP3 (.mp3)

MIDI tipo 0 y 1. DLS


v1 y v2. XMF y XMF
mvil. Soporte para
tonos de llamada
RTTTL / RTX, OTA y
iMelody.

Tipo 0 y 1
(.mid, .xmf,
.mxmf).
RTTTL / RTX
(.rtttl, .rtx),
OTA (.ota)
iMelody
(.imy)
Ogg (.ogg)
Matroska
(.mkv
a partir 4.0)

AMR-WB

MP3

Audio

MIDI

Ogg Vorbis

Mono/estreo
con cualquier
combinacin estndar
de frecuencia >
160 Kbps y ratios
de muestreo de 8 a
48kHz

FLAC

a partir
Android
3.1

mono/estereo
(no multicanal)

PCM/WAVE

a partir
Android 4.1

8 y 16 bits PCM lineal


(frecuencias limitadas
por el hardware)

3GPP (.3gp)
MPEG-4(.
mp4)
No soporta
raw AAC
(.aac) ni
MPEG-TS (.ts)

35

FLAC (.flac)
WAVE (.wav)

Aunque el listado anterior pueda parecer muy complicado y amplio, te recomendamos que le
eches un vistazo a la Wikipedia donde se explica los distintos Formatos de archivo de audio.

Aula Mentor

3. Libreras de reproduccin y grabacin de audio


Android incluye distintos tipos de flujos de audio con sus respectivos volmenes de sonido
dependiendo del propsito de estos audios: msica, tono de notificacin, tono de llamada,
alarma, etctera.
Para obtener informacin sobre cmo ha configurado el usuario el volumen de los diferentes flujos de audio, debemos utilizar la clase AudioManager que permite acceder a la configuracin de sonidos del sistema. Mediante la llamada al mtodo getSystemService(AUDIO_
SERVICE) se obtiene una instancia a este gestor de Audio.
Entre sus mtodos, podemos destacar:
- getStreamVolume(int streamType): obtiene el volumen definido por el usuario para el

tipo de flujo indicado como parmetro.

- getStreamMaxVolume(int streamType): obtiene el volumen mximo que se puede defi-

nir para este tipo de flujo.

- isMusicActive(): indica si se est reproduciendo msica.


- getRingerMode(): devuelve el modo de sonidos del dispositivo; puede tomar las contantes
RINGER_MODE_NORMAL, RINGER_MODE_SILENT, o RINGER_MODE_VIBRATE.

36

Existen mtodos adicionales que permiten conocer si el audio se reproduce a travs de un


dispositivo Bluetooth, ajustar el volumen de un tipo de audio, etctera. Te recomendamos que
le eches un vistazo a la documentacin oficial.
Para establecer el volumen del audio que vamos a reproducir en esa actividad debemos utilizar el mtodo setVolumeControlStream() en el evento onCreate() de la Actividad
dependiendo del propsito:
- Volumen para msica o vdeo:
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

- Permite, adems, que el usuario utilice los botones del dispositivo para subir y bajar su volumen.
- Volumen para tono de llamada del telfono
this.setVolumeControlStream(AudioManager.STREAM_RING);

- Volumen de alarma

this.setVolumeControlStream(AudioManager.STREAM_ALARM);

- Volume de notificacin

this.setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);

- Volumen del sistema

this.setVolumeControlStream(AudioManager.STREAM_SYSTEM);

- Volumen de llamada por voz

this.setVolumeControlStream(AudioManager.STREAM_VOICECALL);

El SDK de Android dispone de dos APIs principales que permiten reproducir ficheros de tipo
audio: SoundPool y MediaPlayer.

3.1 Clase SoundPool


La clase SoundPool permite reproducir sonidos de forma rpida y simultneamente.
Es recomendable utilizar la primera API SoundPool para reproducir pequeos archivos de audio que no deben exceder 1 MB de tamao, por lo que es el mecanismo ideal para
reproducir efectos de sonido como en los juegos.
Con la clase SoundPool podemos crear una coleccin de sonidos que se cargan en la

U1 Multimedia y Grficos en Android

memoria desde un recurso (dentro de la APK) o desde el sistema de archivos. SoundPool utiliza
el servicio de la clase MediaPlayer, que estudiaremos a continuacin, para descodificar el audio en un formato crudo (PCM de 16 bits) y mantenerlo cargado en memoria; as, el hardware
lo reproduce rpidamente sin tener que decodificarlas cada vez.
La clase SoundPool realiza esta carga en memoria de los archivos multimedia de forma
asncrona, es decir, el sistema operativo lanzar el sonido con el listener OnLoadCompleteListener cuando se haya completado la carga de cada uno de los archivos.
Es posible repetir los sonidos en un bucle tantas veces como sea necesario, definiendo
un valor de repeticin al reproducirlo, o mantenerlo reproduciendo en un bucle infinito con el
valor -1. En este ltimo caso, es necesario detenerlo con el mtodo stop().
Tambin podemos establecer la velocidad de reproduccin del sonido, cuyo rango puede estar entre 0.5 y 2.0. Una velocidad de reproduccin de 1.0 indica que el sonido se reproduce
en su frecuencia original. Si definimos una velocidad de 2.0, el sonido se reproduce al doble de
su frecuencia original y, por el contrario, si fijamos una velocidad de 0.5, lo har lentamente a la
mitad de la frecuencia original.
Cuando se crea un objeto del tipo SoundPool hay que establecer mediante un parmetro
el nmero mximo de sonidos que se pueden reproducir simultneamente. Este parmetro no
tiene por qu coincidir con el nmero de sonidos cargados. Adems, cuando se reproduce un
sonido con su mtodo play(), hay que indicar su prioridad. As, cuando el nmero de reproducciones activas supere el valor mximo establecido en el constructor, esta prioridad permite
que el sistema detenga el flujo con la prioridad ms baja y, si todos tienen la misma prioridad, se
parar el ms antiguo. Sin embargo, en el caso de que el nuevo flujo sea el de menor prioridad,
ste no se reproducir.
En el ejemplo prctico vamos a estudiar los mtodos ms importantes de esta clase.
37

3.2 Clase MediaPlayer


La segunda API es la ms importante de Android y realiza la reproduccin multimedia mediante
la clase bsica MediaPlayer (reproductor multimedia) que permite reproducir audio de larga
duracin. A continuacin, estudiaremos las caractersticas ms importantes de esta clase y cmo
podemos sacarle partido.

La diferencia entre utilizar la clase SoundPool y MediaPlayer est en la duracin


y tamao del archivo de sonido. Para sonidos cortos, debemos utilizar la primera
clase, dejando la segunda para reproducciones largas como canciones de msica.

Un objeto MediaPlayer puede estar en uno de los siguientes estados:


- Initialized: ha inicializado sus recursos internos, es decir, se ha creado el objeto.
- Preparing: se encuentra preparando o cargando la reproduccin de un archivo multimedia.
- Prepared: preparado para reproducir un recurso.
- Started: reproduciendo un contenido.
- Paused: en pausa.
- Stopped: parado.
- Playback Completed: reproduccin completada.
- End: finalizado.
- Error: indica un error.

Aula Mentor

Es importante conocer en qu estado se encuentra el reproductor multimedia, ya que muchos


de sus mtodos nicamente se pueden invocar desde determinados estados.
Por ejemplo, no podemos cambiar al modo en reproduccin (con su mtodo start()) si
no se encuentra en el estado preparado. Lgicamente, tampoco podremos cambiar al modo en
pausa (con su mtodo pause()) si ya est parado. Ocurrir un error de ejecucin si invocamos
un mtodo no admitido para un determinado estado.
El siguiente esquema permite conocer los mtodos que podemos invocar desde cada uno
de sus estados y cul es el nuevo estado al que cambiar el objeto tras invocarlo:

38

U1 Multimedia y Grficos en Android

Existen dos tipos de mtodos:


- Asncronos: onPrepared(), onError(), onCompletion(). Los lanza el sistema cuando ha
acabado una tarea.
- Sncronos: el resto de mtodos que se ejecutan de forma continua cuando se invocan, es
decir, no hay que esperar.
Mediante un ejemplo prctico vamos a estudiar los mtodos ms importantes de esta clase.

3.3 Clase MediaRecorder


La API de Android ofrece tambin la posibilidad de capturar audio y vdeo, permitiendo su
codificacin en diferentes formatos. La clase MediaRecorder permite, de forma sencilla, integrar
esta funcionalidad a una aplicacin.
La mayora de los dispositivos Android disponen de un micrfono que puede capturar
audio.
La clase MediaRecorder dispone de varios mtodos que puedes utilizar para configurar
la grabacin:
- setAudioSource(int audio_source): dispositivo que se utilizar como fuente del sonido, es decir, el micrfono. Normalmente, indicaremos MediaRecorder.AudioSource.MIC.
Si bien, es posible utilizar otras constantes como DEFAULT (micrfono por defecto), CAMCORDER (micrfono que tiene la misma orientacin que la cmara), VOICE_CALL (micrfono
para llamadas), VOICE_COMUNICATION (micrfono para VoIP), etctera.
- setOutputFile (String fichero): permite indicar el nombre del fichero donde se guardar la informacin.
- setOutputFormat(int output_format): establece el formato del fichero de salida. Se
pueden utilizar las constantes siguientes de la clase MediaRecorder.OutputFormat: DEFAULT, AMR_NB, AMR_WB, RAW_AMR (ARM), MPEG_4 (MP4) y THREE_GPP (3GPP).
- setAudioEncoder(int audio_encoder): permite seleccionar la codificacin del audio.
Podemos indicar cuatro posibles constantes de la clase MediaRecorder.AudioEncoder:
AAC, AMR_NB, AMR_WB y DEFAULT.
- setAudioChannels(int numeroCanales): especifica el nmero de canales de la grabacin: 1 para mono y 2 para estreo.
- setAudioEncodingBitRate(int bitRate): indica los bits por segundo (bps) utilizados en
la codificacin (desde nivel de API 8).
- setAudioSamplingRate(int samplingRate): permite indicar el nmero de muestreo por
segundo empleados en la codificacin (desde nivel de API 8).
- setProfile(CamcorderProfile profile): permite elegir un perfil de grabacin de vdeo.
- setMaxDuration(int max_duration_ms): indica la duracin mxima de la grabacin.
Pasado este tiempo, sta se detendr.
- setMaxFileSize(long max_filesize_bytes): establece el tamao mximo para el fichero
de salida. Si se alcanza este tamao, la grabacin se detendr.
- prepare(): prepara la grabacin para la captura del audio o vdeo.
- start(): inicia la grabacin.
- stop(): finaliza la grabacin.
- reset(): reinicia el objeto como si lo acabramos de crear por lo que debemos configurarlo
de nuevo.
- release(): libera todos los recursos utilizados del objeto MediaRecorder. Si no invocas
este mtodo, los recursos se liberan automticamente cuando el objeto se destruya.

39

Aula Mentor

Adicionalmente, la clase MediaRecorder dispone de mtodos que puedes utilizar para configurar
la grabacin de video.
Tal y como ocurre con la clase MediaPlayer, para poder invocar los diferentes mtodos
de la clase MediaRecorder debemos estar en un estado determinado. El siguiente esquema
permite conocer los mtodos que podemos invocar desde cada uno de sus estados y cul es el
nuevo estado al que cambiar el objeto tras invocarlo:

40

En el ejemplo prctico vamos a aprender los mtodos ms importantes de esta clase.

3.3.1 Ejemplo de reproduccin y grabacin de audio


Es recomendable abrir el Ejemplo 1 de esta Unidad para seguir la explicacin siguiente.
La aplicacin de este ejemplo muestra tres botones: el primero permite reproducir un
tono utilizando la clase SoundPool, el segundo botn reproduce un archivo largo de audio y
el ltimo botn, graba una conversacin utilizando el micrfono del dispositivo. Para los dos
ltimos botones usamos la clase MediaPlayer. En la parte de debajo de la Actividad hemos
incluido una vista de tipo TextView desplazable que muestra las acciones del usuario cuando

U1 Multimedia y Grficos en Android

pulsa en un botn.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android

android:layout_width=fill_parent

android:layout_height=fill_parent>








<LinearLayout
android:id=@+id/linearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=vertical
android:gravity=top
android:layout_marginTop=6dp
android:layout_marginBottom=1dp>

<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignParentLeft=true
android:layout_alignParentTop=true
android:text=Haz clic en un botn
android:textAppearance=?android:attr/textAppearanceMedium/>

<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=65dp
android:layout_width=fill_parent
android:orientation=horizontal>
<Button
android:id=@+id/soundpool1
android:layout_width=wrap_content

android:layout_height=fill_parent

android:text=Tono SoundPool 1

android:tag=1 />
<Button
android:id=@+id/soundpool2
android:layout_width=wrap_content

android:layout_height=fill_parent

android:text=Tono SoundPool 2

android:tag=2 />

</LinearLayout>

<Button
android:id=@+id/mediaplayer

android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp

android:text=Reproducir Cancin con MediaPlayer />
<Button
android:id=@+id/mediaplayer_record

41

Aula Mentor

android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp
android:text=Grabar conversacin />

<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Log:/>
</ScrollView>
</LinearLayout>
</RelativeLayout>

42

Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
MainActivity.java:
public class MainActivity extends Activity {
// Objetos de las clases SoundPool y MediaPlayer
private SoundPool sPool;
private MediaPlayer mPlayer;
// Guarda los IDs de sonidos que se deben reproducir por SoundPool
private int soundID1=-1, soundID2=-1;
// Vistas de la Actividad
private TextView logTextView;
private ScrollView scrollview;
// Grabador de audio
private MediaRecorder recorder;
// Fichero donde guardamos el audio
private File audiofile = null;
// Botones de la Actividad
private Button boton_spool1, boton_spool2;
private Button boton_mplayer;
private Button boton_mrecorder;
@Override
protected void onCreate(Bundle savedInstanceState) {

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

// Localizamos las Vistas del layout

logTextView = (TextView) findViewById(R.id.Log);

U1 Multimedia y Grficos en Android

scrollview = ((ScrollView)findViewById(R.id.ScrollView));


// Establecemos el tipo de flujo de audio que deseamos
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

// Cargamos el tono con SoundPool indicando el tipo de flujo


// STREAM_MUSIC
sPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0);

// Cuando la carga del archivo con SoundPool se completa...


sPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
@Override

public void onLoadComplete(SoundPool soundPool, int sampleId,

int status) {

// Mostramos un log

log(Tono + sampleId + cargado con SoundPool);

}

});

// Cargamos los archivos para SoundPool y guardamos su ID para

// poder reproducirlo

soundID1 = sPool.load(this, R.raw.bigben, 1);

soundID2 = sPool.load(this, R.raw.alarma, 1);


// Definimos el mismo evento onClick de los botones SoundPool y


// los distinguimos por su propiedad Tag
View.OnClickListener click = new View.OnClickListener() {
public void onClick(View v) {

// Obtenemos acceso al gestor de Audio para obtener
// informacin
AudioManager audioManager =
(AudioManager)getSystemService(AUDIO_SERVICE);
// Buscamos el volumen establecido para el tipo
// STREAM_MUSIC
float volumenMusica = (float) audioManager
.getStreamVolume(AudioManager.STREAM_MUSIC);
// Obtenemos el vlumen mx para el tipo STREAM_MUSIC
float volumeMusicaMax = (float) audioManager
.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
// Vamos a reducir el volumen del sonido
float volumen = volumenMusica / volumeMusicaMax;
// Qu botn se ha pulsado? Se ha cargado el tono?
if (v.getTag().toString().equals(1) && soundID1>-1)
// Reproducimos el sonido 1
sPool.play(soundID1, volumen, volumen, 1, 0, 1f);
else
if (v.getTag().toString().equals(2) && soundID2>-1)
// Reproducimos el sonido 2
sPool.play(soundID2, volumen, volumen, 1, 0, 1f);
}
}; // end onClick botn

// Buscamos los botones de SoundPool y asociamos su evento


// onClick
boton_spool1 = (Button) findViewById(R.id.soundpool1);
boton_spool2 = (Button) findViewById(R.id.soundpool2);
boton_spool1.setOnClickListener(click);

43

Aula Mentor

boton_spool2.setOnClickListener(click);

// Buscamos el botn que reproduce MediaPlayer y definimos su


// evento onClick
boton_mplayer = (Button) findViewById(R.id.mediaplayer);
boton_mplayer.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {

// Si ya estamos reproduciendo un sonido, lo paramos

if (mPlayer!=null && mPlayer.isPlaying()) {

mPlayer.stop();

// Cambiamos los botones y mostramos log

boton_mplayer.setText(Reproducir Audio con Mediaplayer);
boton_spool.setEnabled(true);
boton_mrecorder.setEnabled(true);
log(Cancelada reproduccin MediaPlayer);
} else // Si no, iniciamos la reproduccin
{
// Cambiamos los botones y hacemos log
boton_mplayer.setText(Cancelar);
boton_spool.setEnabled(false);
boton_mrecorder.setEnabled(false);
log(Reproduciendo Audio con MediaPlayer);

// Creamos el objeto MediaPlayer asocindole la cancin


mPlayer = MediaPlayer.create(MainActivity.this,
R.raw.beethoven_para_elisa);
// Iniciamos la reproduccin
mPlayer.start();

44

// Definimos el listener que se lanza cuando la cancin


// acaba
mPlayer.setOnCompletionListener(new

OnCompletionListener() {

public void onCompletion(MediaPlayer arg0) {


// Hacemos log y cambiamos botones

log(Fin Reproduccin MediaPlayer);

boton_spool.setEnabled(true);

boton_mrecorder.setEnabled(true);

}

}); // end setOnCompletionListener
}
}
}
); // end onClick botn

// Buscamos el botn que graba con MediaRecorder y definimos su


// evento onClick

boton_mrecorder = (Button) findViewById(R.id.mediarecorder);

boton_mrecorder.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Si estamos grabando sonido
if (boton_mrecorder.getText().equals(Parar grabacin))

{

// Paramos la grabacin, liberamos los recursos y la

// aadimos

recorder.stop();

U1 Multimedia y Grficos en Android

recorder.release();
addRecordingToMediaLibrary();
// Refrescamos interfaz usuario
boton_mrecorder.setText(Grabar conversacin);
boton_spool.setEnabled(true);
boton_mplayer.setEnabled(true);
// Log de la accin
log(Parada grabacin MediaRecorder);
} else
{
// Cambiamos los botones y hacemos log
boton_mrecorder.setText(Parar grabacin);
boton_spool.setEnabled(false);
boton_mplayer.setEnabled(false);
log(Grabando conversacin);


// Obtenemos el directorio de tarjeta SD

File directorio =
Environment.getExternalStorageDirectory();
try {

// Definimos el archivo de salida

audiofile = File.createTempFile(sonido, .3gp,
directorio);

} catch (IOException e) {

Log.e(ERROR, No se puede acceder a la tarjeta
SD);

return;

}

// Creamos el objeto MediaRecorder

recorder = new MediaRecorder();

// Establecemos el micrfono


recorder.setAudioSource(MediaRecorder.AudioSource.MIC);

// Tipo de formato de salida

recorder.setOutputFormat(
MediaRecorder.OutputFormat.THREE_GPP);

// Codificacin de la salida
recorder.setAudioEncoder(
MediaRecorder.AudioEncoder.AMR_NB);
// Fichero de salida

recorder.setOutputFile(audiofile.getAbsolutePath());
try {

// Preparamos la grabacin
recorder.prepare();

} catch (IllegalStateException e) {
Log.e(ERROR, Estado incorrecto);


return;

} catch (IOException e) {

Log.e(ERROR, No se puede acceder a la tarjeta
SD);


return;
}

// Iniciamos la grabacin

recorder.start();
} // end else
}
}

45

Aula Mentor

); // end onClick botn


log();
}

46

// Mtodo que aade la nueva grabacin a la librera


// multimedia del dispositivo. Para ello, vamos a
// utilizar un Intent del sistema operativo
protected void addRecordingToMediaLibrary() {
// Valores que vamos a pasar al Intent
ContentValues values = new ContentValues(4);
// Obtenemos tiempo actual
long tiempoActual = System.currentTimeMillis();
// Indicamos que queremos buscar archivos de tipo audio
values.put(MediaStore.Audio.Media.TITLE, audio +

audiofile.getName());
// Indicamos la fecha sobre la que deseamos buscar
values.put(MediaStore.Audio.Media.DATE_ADDED, (int)(tiempoActual /
1000));
// Tipo de archivo
values.put(MediaStore.Audio.Media.MIME_TYPE, audio/3gpp);
// Directorio destino
values.put(MediaStore.Audio.Media.DATA,

audiofile.getAbsolutePath());
// Utilizamos un ContentResolver
ContentResolver contentResolver = getContentResolver();
// URI para buscar en la tarjeta SD
Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Uri newUri = contentResolver.insert(base, values);
// Enviamos un mensaje Broadcast para buscar el nuevo contenido de
// tipo audio
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
newUri));
Toast.makeText(this, Se ha aadido archivo + newUri +

a la librera multimedia., Toast.LENGTH_LONG).show();
} // end addRecordingToMediaLibrary
// Mtodo que aade a la etiqueta Log un nuevo evento
private void log(String s) {

logTextView.append(s + \n);

// Movemos el Scroll abajo del todo

scrollview.post(new Runnable() {

@Override

public void run() {

scrollview.fullScroll(ScrollView.FOCUS_DOWN);

}
});
} // end log
} // end clase

Repasemos ahora con cuidado el cdigo Java anterior.


Puedes ver que el constructor de la clase SoundPool es el siguiente:
SoundPool(int maxStreams , int streamType , int srcQuality)

U1 Multimedia y Grficos en Android

Donde sus parmetros son:


- maxStreams: indica el nmero de sonidos que puede reproducir al mismo tiempo.
- streamType: marca el tipo de flujo de audio que usaremos.
- srcQuality: indica la calidad. Este atributo no tiene uso en la API de Android.
La siguiente sentencia establece el tipo de flujo a msica, lo que permite que el usuario utilice
los botones de subida y bajada de volumen del dispositivo:
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

Por ltimo, debemos precargar con el objeto SoundPool los archivos de audio con el mtodo siguiente: SoundPool.load(Context context, int resId, int priority). Donde
resId es la Id de nuestro archivo de msica. El parmetro priority permite seleccionar la
prioridad de este sonido frente a otro en caso de que se llegue al mximo nmero de sonidos
simultneos establecidos en el constructor de la clase.
Mediante el listener OnLoadCompleteListener el sistema operativo avisar cada
vez que complete la carga de un archivo de sonido.
Para reproducir un sonido debemos usar el mtodo play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) cuyos parmetros
indican:
- soundID: ID del sonido que ha indicado el mtodo load() al cargarlo.
- leftVolume: volumen del altavoz izquierdo (rango de 0.0 a 1.0)
- rightVolume: volumen del altavoz derecho (rango de 0.0 a 1.0)
- priority: prioridad del sonido (0 es la ms baja)
- loop:
modo en bucle si establecemos el valor -1.
- rate: velocidad de reproduccin (1.0 = normal, rango de 0.5 a 2.0)
En el siguiente bloque de cdigo hemos utilizado la clase Mediaplayer para reproducir una
pista de audio mediante su mtodo start() y pararla con el mtodo stop().
Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en la carpeta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp3 se empaqueta
con la aplicacin y hace que sta ocupe mucho espacio. Si queremos reproducir una cancin
desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
- MediaPlayer mPlayer = new MediaPlayer();
- mPlayer.setDataSource(RUTA+NOMBRE_FICHERO);
- mPlayer.prepare();
- mPlayer.start();
Observa que, en este caso, hay que invocar previamente el mtodo prepare() para cargar el
archivo de audio. En el ejemplo del curso no es necesario hacerlo ya que esta llamada se hace
desde el constructor create().
El ltimo bloque de cdigo realiza una grabacin empleando la clase MediaRecorder. Hemos definido la variable audiofile para guardar la grabacin. Para iniciar la grabacin
utilizamos los mtodos setAudioSource() que establece el micrfono de entrada; setOutputFormat() selecciona el formato de salida; setAudioEncoder() indica la codificacin del
audio; setOutputFile() establece el fichero de salida y start() inicia la grabacin.
A la hora de parar la grabacin, simplemente debemos invocar los mtodos stop() y
release() que libera los recursos del sistema.
Para finalizar con el cdigo Java, hemos desarrollado el mtodo local addRecordingToMediaLibrary() que aade la nueva grabacin a la librera multimedia del dispositivo.
Para ello, vamos a utilizar un Intent del tipo ACTION_MEDIA_SCANNER_SCAN_FILE y enviar
un mensaje Broadcast al sistema operativo para buscar el nuevo contenido multimedia de tipo

47

Aula Mentor

audio con la orden sendBroadcast().


Por ltimo, para poder ejecutar esta aplicacin es necesario que tenga permisos de
grabar audio y acceso a la tarjeta SD del dispositivo. Para ello, hay que incluir en el fichero
AndroidManifest.xml las siguientes etiquetas:
<uses-permission
android:name=android.permission.WRITE_EXTERNAL_STORAGE/>
<uses-permission android:name=android.permission.RECORD_AUDIO />

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 1 (Audio) de la Unidad 1.


Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado la API de Audio de Android.

Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente aplicacin:

48

Para poder or en tu AVD los sonidos, debes encender los altavoces de tu ordenador. Prueba a ejecutar sonidos mediante SoundPool simultneamente, incluso si
se est reproduciendo msica con el MediaPlayer.
Sin embargo, la funcionalidad de grabacin de audio no est integrada en el AVD y, para poder

U1 Multimedia y Grficos en Android

probar esta funcionalidad del Ejemplo debes instalarlo en un dispositivo real.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar Ajustes del dispositivo en
las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

3.4 Cmo habilitar USB Debugging en Android 4.2 y superior Jelly Bean
A partir de la versin de Android Jelly Bean 4.2, Google esconde la opcin de Desarrollo
(Developer) en los Ajustes (Settings) del dispositivo. Para que aparezca esta opcin debes
dar los pasos siguientes:
- Abre Opciones->Informacin del telfono/Tablet.
- Haz clic repetidamente en la opcin Nmero de compilacin (Build Number) hasta 7
veces seguidas.
Eso es todo, aparecer un mensaje de que Ya eres un developer y vers que aparece la
nueva opcin Opciones de desarrollo (Developer) y dentro encontrars USB Debugging.
Fjate en las siguientes capturas de pantalla:
49

Aula Mentor

3.5 Librera de reproduccin de vdeo

50

La clase VideoView permite al programador incluir vdeos en las aplicaciones y abarca una gran
cantidad de mtodos para hacerlo. Los mtodos ms utilizados son los siguientes:
- setVideoPath(String path): especifica el directorio y archivo de vdeo que se reproduce. Podemos indicar tanto una URL para vdeos en Internet como un archive local en el
dispositivo.
- setVideoUri(Uri uri): de igual forma que el mtodo anterior, establece la fuente del
vdeo en formato URI.
- start(): inicia la reproduccin del vdeo.
- stopPlayback(): para la reproduccin del vdeo.
- pause(): pausa la reproduccin del vdeo.
- isPlaying(): devuelve true o false indicando as si se est reproduciendo un vdeo o no.
- setOnPreparedListener(MediaPlayer.OnPreparedListener): define un mtodo
callback que se invoca cuando el vdeo est preparado para reproducirse.
- setOnErrorListener(MediaPlayer.OnErrorListener): establece el mtodo callback
que el sistema invocar en caso de un error en reproduccin del vdeo. Este mtodo es muy
til cuando el vdeo est mal codificado u ocurre un error de conexin a Internet al reproducir un vdeo remoto.
- setOnCompletionListener(MediaPlayer.OnCompletionListener): permite definir un
mtodo callback para detectar que la reproduccin del vdeo ha terminado.
- getDuration(): indica la duracin del vdeo. Devuelve siempre -1 salvo que lo ejecutemos
dentro del evento OnPreparedListener().
- getCurrentPosition(): devuelve la posicin actual de reproduccin del vdeo.
- setMediaController(MediaController): establece el objeto MediaController que veremos a continuacin.
Si reproducimos un vdeo utilizando directamente la clase VideoView, el usuario no podr
controlar la reproduccin, que continuar hasta que finalice el vdeo. Para permitir que el usuario
gestione la visualizacin del vdeo podemos definir una interfaz ad hoc para la aplicacin o
emplear una instancia de la clase MediaController que asignaremos al VideoView.
La clase MediaController muestra un conjunto de controles que permiten al usuario
gestionar la reproduccin del vdeo, por ejemplo, hacer una pausa, buscar hacia atrs y hacia
adelante, etctera.
Estos controles aparecern brevemente cuando el usuario toca en la vista VideoView a
la que estn asignados.
Entre sus mtodos ms importantes podemos destacar:
- setAnchorView(View view): indica la Vista de la Actividad donde aparecern estos controles.
- show() : muestra los controles.
- show(int timeout): muestra los controles durante el tiempo indicado como parmetro en
milisegundos.
- hide(): oculta los controles.
- isShowing():devuelve true o false indicando as si se estn mostrando los controles o
no.

3.5.1 Ejemplo de reproduccin de vdeo


En el Ejemplo 2 de esta Unidad hemos desarrollado un reproductor sencillo de vdeo. Es recomendable abrirlo en Eclipse ADT para seguir la explicacin siguiente.

U1 Multimedia y Grficos en Android

La interfaz de la aplicacin muestra una barra de herramientas en la parte superior con botones
que permiten al usuario controlar la reproduccin del vdeo. En la parte central hemos incluido
un objeto heredado de la clase VideoView. En la parte de abajo de la Actividad hemos incluido
una vista de tipo TextView desplazable que muestra las acciones del usuario.
En cdigo del Layout activity_main.xml se incluye el diseo de la Actividad principal:
<RelativeLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_height=fill_parent
android:layout_width=fill_parent>
<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=wrap_content
android:layout_width=fill_parent
android:orientation=horizontal
android:layout_alignParentTop=true
android:gravity=center_horizontal>
<ImageButton
android:id=@+id/play
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/play/>
<ImageButton
android:id=@+id/pause
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/pause/>
<ImageButton
android:id=@+id/stop
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/stop/>
<ImageButton
android:id=@+id/controls
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/plus/>
<ImageButton
android:id=@+id/logButton
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/log/>
</LinearLayout>
<es.mentor.unidad3.eje2.video.CustomVideoView
android:id=@+id/videoView
android:layout_height=fill_parent
android:layout_width=fill_parent

51

Aula Mentor

android:layout_below=@+id/botonesLayout/>
<ScrollView
android:id=@+id/ScrollView
android:layout_height=150dp
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:text=Log:/>
</ScrollView>
</RelativeLayout>

Puedes observar que en lugar de utilizar directamente la clase VideoView hemos indicado la
clase heredada de sta es.mentor.unidad3.eje2.video.CustomVideoView. Esto se hace
as porque hemos redefinido los eventos onPlay(), onPause() y onTimeBarSeekChanged()
y, para hacerlo, es necesario extender la clase VideoView.
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero MainActivity.java:
52

public class MainActivity extends Activity


// Vistas de la interfaz de usuario de la Actividad

private ImageButton bPlay, bPause, bStop, bLog, bControls;

private TextView logTextView;

ScrollView scrollview;

// VideoView extendido y mejorado con nuevos eventos (listeners)

private CustomVideoView visorVideo;

// Control del vdeo por parte del usuario

private MediaController mediaC;

// Posicin actual de reproduccin del vdeo que usaremos

// cada vez que el usuario vuelva a la aplicacin

private int posActual=0;
@Override

protected void onCreate(Bundle estado) {
super.onCreate(estado);
setContentView(R.layout.main_layout);

// Buscamos las Vistas

scrollview = ((ScrollView)findViewById(R.id.ScrollView));

visorVideo = (CustomVideoView) findViewById(R.id.videoView);

// Definimos la URI del vdeo

Uri uri = Uri.parse(android.resource:// + getPackageName()

+ /+R.raw.video);
visorVideo.setVideoURI(uri);
// Si se encuentra en el sistema de ficheros deberamos

// haber escrito
//visorVideo.setVideoPath(sdcard/video.mp4);

// Creamos el controlador del vdeo y lo asignamos al visor

// de vdeo y viceversa

mediaC = new MediaController(this);

U1 Multimedia y Grficos en Android

mediaC.setMediaPlayer(visorVideo);
visorVideo.setMediaController(mediaC);
// Definimos el listener cuando el usuario reproduce, para o

// cambia la posicin del vdeo
visorVideo.setPlayPauseListener(new
CustomVideoView.PlayPauseListener() {

// Reproduce vdeo

@Override

public void onPlay() {

// Hacemos log y deshabilitamos botones

log(REPRODUCIENDO VIDEO);

bPause.setEnabled(true);

bStop.setEnabled(true);

bPlay.setEnabled(false);

}

@Override

public void onPause() {

// Hacemos log y deshabilitamos botones

log(VIDEO EN PAUSA);

bPause.setEnabled(false);

bPlay.setEnabled(true);

}

@Override

public void onTimeBarSeekChanged(int currentTime) {
// Hacemos log
log(CAMBIO POSICION VIDEO: +currentTime);

// Si se ha parado el vdeo lo indicamos

if (currentTime==0 && !visorVideo.isPlaying()) {
bStop.setEnabled(false);
log(PARADA VIDEO);
}
}
}); // end setPlayPauseListener

// Definimos el listener cuando finaliza la reproduccin del


// vdeo

visorVideo.setOnCompletionListener(new
MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {

// Hacemos log y deshabilitamos botones

log(FIN DE VIDEO);

bPause.setEnabled(false);

bStop.setEnabled(false);

bPlay.setEnabled(true);
}
});

// Definimos el listener cuando el visor ha preparado


// la reproduccin del vdeo

visorVideo.setOnPreparedListener(new OnPreparedListener() {
@Override

public void onPrepared(MediaPlayer mp) {

// Hacemos log. Slo se puede llamar a
// getDuration() desde este evento

53

Aula Mentor

log(VIDEO PREPARADO. Duracin: +


visorVideo.getDuration()+ msegundos);


});

// Definimos el listener cuando el usuario toca la pantalla del


// vdeo

visorVideo.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

log(CLIC VIDEOVIEW: MUESTRA CONTROLADOR);

return false;

}
});

// Definimos el listener cuando ocurre un error en la reproduccin

visorVideo.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override

public boolean onError(MediaPlayer mp, int what, int extra)

{

log(ERROR AL REPRODUCIR VIDEO);
return false;
}

});

54


logTextView = (TextView)findViewById(R.id.Log);

// Buscamos el botn Reproducir y definimos su evento onClick

bPlay = (ImageButton)findViewById(R.id.play);

bPlay.setOnClickListener(new OnClickListener() {

public void onClick(View view) {

// Pedimos el foco del vdeo y lo reproducimos
visorVideo.requestFocus();
visorVideo.start();
}
});

// Buscamos el botn Pausa y definimos su evento onClick

bPause = (ImageButton)findViewById(R.id.pause);

bPause.setOnClickListener(new OnClickListener() {

public void onClick(View view) {

// Pausamos la reproduccin
visorVideo.pause();
}
});

// Buscamos el botn Parada y definimos su evento onClick

bStop = (ImageButton)findViewById(R.id.stop);

bStop.setOnClickListener(new OnClickListener() {

public void onClick(View view) {
// Pausamos la reproduccin y vamos al principio del

// vdeo
visorVideo.pause();
visorVideo.seekTo(0);
}
});

// Buscamos el botn Controles y definimos su evento onClick

bControls = (ImageButton)findViewById(R.id.controls);

U1 Multimedia y Grficos en Android


bControls.setOnClickListener(new View.OnClickListener() {

public void onClick(View arg0) {

// Mostramos los controles de usuario del vdeo
mediaC.show();
}
});

// Buscamos el botn Log y definimos su evento onClick

bLog = (ImageButton)findViewById(R.id.logButton);

bLog.setOnClickListener(new OnClickListener() {

public void onClick(View view) {

if (scrollview.getVisibility()==TextView.VISIBLE) {


scrollview.setVisibility(TextView.INVISIBLE);

} else {


scrollview.setVisibility(TextView.VISIBLE);

}
}
});

// Inicializamos Vistas
log();
bPause.setEnabled(true);
bStop.setEnabled(true);
} // end onCreate
// Debemos guardar la posicin de reproduccin en el evento onPause()
// porque en el evento onSaveInstanceState ya est parado el vdeo y
// es tarde.
@Override
protected void onPause() {
super.onPause();

posActual = visorVideo.getCurrentPosition();
}
// Cuando vuelve a estar activa la ACtividad volvemos a reproducir
// donde lo dejamos
@Override
public void onResume() {
super.onResume();
Log.d(POS , onResume called);
// Volvemos a la posicin guardada
visorVideo.seekTo(posActual);
// Continuamos la reproduccin del vdeo
visorVideo.resume();
}
// Mtodo que aade a la etiqueta Log un nuevo evento
private void log(String s) {

logTextView.append(s + \n);

// Movemos el Scroll abajo del todo

scrollview.post(new Runnable() {

@Override

public void run() {

scrollview.fullScroll(ScrollView.FOCUS_DOWN);

}
});
} // end log
} // end clase

55

Aula Mentor

En el cdigo fuente anterior puedes ver que la aplicacin extiende la clase Activity. Adems,
implementa varias interfaces que corresponden a varios eventos. Despus, se sigue con la
declaracin de los diferentes elementos de la aplicacin. La variable posActual almacena la
posicin de reproduccin.
En lugar de utilizar directamente la clase VideoView, la hemos extendido en la nueva
clase CustomVideoView para poder definir los eventos onPlay(), onPause() y onTimeBarSeekChanged() ya que la clase base no los incluye y es necesario redefinir sus mtodos
start(), pause() y seekTo() respectivamente para poder detectar cundo el usuario realiza
una de estas acciones.
Con el mtodo setVideoURI() hemos indicando el fichero local del paquete de la
aplicacin. Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en
la carpeta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp4 se empaqueta con la aplicacin y hace que sta ocupe muchsimo espacio. Si queremos reproducir
vdeo desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
videoView.setVideoURI(Uri.parse(file:// +
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES) + /video.mp4));

56

A continuacin, invocamos el mtodo setMediaController() para que el usuario pueda


dirigir la reproduccin del vdeo mediante el objeto MediaController.
El cdigo contina asignando al objeto CustomVideoView varios escuchadores (listeners) de eventos. Veamos su funcionalidad:
- setPlayPauseListener(): mtodo de la clase extendida definido para controlar la reproduccin, parada y movimiento del vdeo. En estos casos, se realiza un log y se habilitan o
deshabilitan los botones correspondientes en la interfaz de usuario.
- OnCompletionListener(): implementa la interfaz onCompletion() que ser invocada
cuando el vdeo llegue al final. Igualmente, hacemos un log y gestionamos los botones.
- setOnPreparedListener(): implementa la interfaz onPrepared() que ser invocada
cuando el vdeo est preparado para su reproduccin. Igualmente, hacemos un log indicando la duracin del vdeo con el mtodo getDuration().
- setOnTouchListener(): implementa la interfaz onTouch() que ser invocada cuando el
usuario toca la pantalla del vdeo. Hacemos tambin un log de la accin.
- setOnErrorListener(): implementa la interfaz onError() que ser invocada cuando se
produce un error en la reproduccin del vdeo. Hacemos tambin un log de la accin.
Posteriormente, definimos los eventos onClick() de los botones de la interfaz de usuario. Es
sencillo y auto explicativo ver en el cdigo de arriba cmo hemos usado los distintos mtodos
de la clase VideoView para reproducir, parar y pausar el vdeo.
Hemos definido tambin los mtodos onPause() y onResume() de la Actividad que
se invocan cuando sta pasa a un segundo plano y cuando vuelve de nuevo a primer plano.
El vdeo debe parar la reproduccin y continuar en la posicin anterior en cada uno de estos
casos, por lo que se invocan a sus mtodos pause() y start() respectivamente.
El ltimo mtodo log() es utilizado por varios escuchadores de eventos para mostrar
informacin sobre lo que est pasando. Esta informacin puede visualizarse o no, utilizando el
botn correspondiente.
Una vez expuesto la lgica de la Actividad principal, veamos la implementacin de la
clase extendida de VideoView en el fichero CustomVideoView.java:
// Para poder definir los eventos onPlay, onPause y
// onTimeBarSeekChanged es necesario redefinir la clase
// VideoView extendiendo sus mtodos

U1 Multimedia y Grficos en Android

public class CustomVideoView extends VideoView {


private PlayPauseListener mListener;

// Definimos los constructores de la clase


public CustomVideoView(Context context) {
super(context);
}
public CustomVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomVideoView(Context context, AttributeSet attrs, int
defStyle) {
super(context, attrs, defStyle);
}

// Mtodo que establece el listener


public void setPlayPauseListener(PlayPauseListener listener) {
mListener = listener;
}

// Redefinimos los mtodo internos de la clase VideoView


@Override
public void pause() {

// Primero llamamos al mtodo de la clase original
super.pause();
if (mListener != null) {

// Si el listener est asignado, ejecutamos el mtodo
mListener.onPause();
}
}
@Override
public void start() {

// Primero llamamos al mtodo de la clase original
super.start();
if (mListener != null) {

// Si el listener est asignado, ejecutamos el mtodo
mListener.onPlay();
}
}
@Override
public void seekTo(int msec)
{

// Primero llamamos al mtodo de la clase original
super.seekTo(msec);
if (mListener != null) {

// Si el listener est asignado, ejecutamos el mtodo
mListener.onTimeBarSeekChanged(msec);
}
}

// Definimos las interfaces de la nueva clase que deben

57

Aula Mentor

// implementarse
interface PlayPauseListener {
void onPlay();
void onPause();
void onTimeBarSeekChanged(int currentTime);
}
} // end clase

Como puedes ver, la clase anterior es muy sencilla: hemos redefinimos los mtodo internos
start(), pause() y seekTo() de la clase VideoView para poder invocar dentro de sta los
mtodos que se definen en la interfaz PlayPauseListener: onPlay(), onPause() y onTimeBarSeekChanged() respectivamente. Mediante su mtodo setPlayPauseListener()
podemos establecer el listener correspondiente.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Vdeo) de la Unidad 1.


Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado la API de Vdeo de Android.

Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente aplicacin:
58

U1 Multimedia y Grficos en Android

La reproduccin de un vdeo en el AVD se realiza mediante software, por lo que


puedes observar saltos o paradas al visualizarlo. Es recomendable utilizar un dispositivo real Android que reproduzca el vdeo mediante el hardware.

4. Conceptos bsicos de grficos en Android


Antes de comenzar a trabajar con grficos conviene que el alumno o alumna se familiarice con
las clases bsica de Android que permiten crear y manipular grficos en Android.

4.1 Definicin de colores en Android


Android representa un color utilizando un nmero entero de 32 bits. Estos bits se dividen en 4
campos de 8 bits: alfa, rojo, verde y azul (ARGB, si usamos las iniciales en ingls). Dado que
cada componente de color consta de 8 bits, podr tomar 256 valores diferentes.
Las componentes rojo, verde y azul son utilizadas para definir un color y la componente
alfa define su grado de transparencia con respecto al fondo (capa inferior). Un valor de 255
significa un color opaco y, a medida que reduzcamos este valor, el color se ir haciendo ms
transparente.
Podemos definir un color de diferentes maneras.
- Definiendo un color estndar de Android:


59

int color = Color.RED;

- Indicando sus valores en el modo ARGB:

color = Color.argb(127, 255, 0, 0);

- Especificando sus valores en hexadecimal:


color = 0xFFFF0000;

// Rojo opaco
// Rojo transparente
// Rojo

- Utilizando un recurso de la aplicacin:



color = getResources().getColor(R.color.color_rojo);
Para conseguir una ptima separacin entre la programacin Java y el diseo de la interfaz de
usuario, se recomienda utilizar la ltima opcin, es decir, no definir directamente los colores
en el cdigo fuente, sino utilizar el fichero de recursos res/values/colors.xml del proyecto:
<?xml version=1.0 encoding=utf-8?>
<resources>
<color name=color_rojo>#ffff0000</color>
</resources>

As, si deseamos cambiar los colores de la aplicacin, nicamente debemos modificar este
archivo de recursos.

Aula Mentor

4.2 Clases de dibujo en Android


A continuacin, expondremos las clases ms importantes que se utilizan para dibujar en Android:

4.2.1 Clase Paint

60

La clase Paint se emplea para definir el pincel que utilizaremos para pintar. Podemos definir
su color, su tipo de trazo, transparencia, etctera. Veamos los mtodos de esta clase ms utilizados por el programador:
- setColor(int color): indica el color del pincel utilizando una de las definiciones anteriores de colores.
- setAlpha(int alfa): modifica el grado de transparencia del pincel.
- setStrokeWidth(float grosor): define el grosor del trazado.
- setStyle(Paint.Style estilo): marca el estilo de relleno con las constante FILL (relleno), FILL_AND_STROKE (relleno y borde), STROKE (slo dibuja borde).
- setShadowLayer(float radio, float x, float y, int color): realiza un segundo trazado a modo de sombra.
- setTextAlign(Paint.Align justif): justifica el texto segn las constantes CENTER, LEFT
y RIGHT.
- setTextSize(float size): establece el tamao de la fuente del texto.
- setTypeface (Typeface typeface): indica el tipo de fuente MONOSPACE, SERIF y SANS_
SERIF. Adems, podemos definir negrita e itlica.
- setTextScaleX(float escala): seala el factor de escalado horizontal. Un valor de 1.0
indica sin escalado.
- setTextSkewX(float inclinacion): indica el factor de inclinacin del texto. 0 denota sin
inclinacin.
- setUnderlineText(boolean subrayado): determina si un texto aparece subrayado o no.

4.2.2 Clase Rectngulo


La clase Rect permite dibujar un rectngulo que se representa mediante sus cuatro lados: izquierdo, derecho, alto y bajo. Su constructor tiene este aspecto:
Rect(int left, int top, int right, int bottom)
Podemos obtener su ancho y largo mediante los mtodos height() y width() respectivamente.

4.2.3 Clase Path


La clase Path (del ingls, camino) permite definir un trazado mediante segmentos de lnea y
curvas. Un Path tambin se puede utilizar para dibujar un texto sobre el trazado marcado y
para ocultar (tramas) o difuminar.
Entre sus mtodos ms importante podemos encontrar:
- moveTo(float x, float y): mueve el pincel de dibujo a la posicin x, y.
- lineTo(float x, float y): pinta un lnea desde la posicin actual hasta la posicin x, y.

U1 Multimedia y Grficos en Android

- addRect(float left, float top, float right, float bottom, Path.Direction dir):
aade un rectngulo del tamao indicado siguiendo el sentido dir que puede tomar los valores CW (giro de la agujas del reloj) y CCW (sentido contrario a las agujas del reloj).
- addCircle(float x, float y, float radio, Path.Direction dir): aade un crculo de
radio siguiendo el sentido dir que puede tomar los valores CW (giro de la agujas del reloj)
y CCW (sentido contrario a las agujas del reloj).
- offset (float dx, float dy): desplaza el trazado en dx y dy.
- reset(): limpia el trazado actual.
- close(): cierra el trazado actual, es decir, ya no se pueden aadir nuevos elementos al

mismo.

Fjate en el siguiente ejemplo que dibuja una lnea y un crculo:


Path trazado = new Path();
trazado.moveTo(10, 10);
trazado.lineTo(10, 50);
trazado.addCircle(50, 50, 60, Direction.CCW);

4.2.4 Clase Canvas


La clase Canvas (del ingls, lienzo) representa la superficie bsica donde podemos dibujar
grficos. Dispone de varios mtodos que permiten representar lneas, crculos, texto, etctera.
Para dibujar en un lienzo debemos utiliza un pincel (clase Paint que hemos visto)
donde indicamos el color, grosor de trazo, transparencia, etctera.
Tambin es posible definir una matriz de 3x3 (Matrix) que permite transformar coordenadas aplicando una translacin, escala o rotacin del lienzo.
Adems, podemos seleccionar un rea conocida como Clip para que los mtodos de
dibujo afecten solo a esta rea.
A continuacin, veamos los mtodos ms importantes de esta clase Canvas segn su
funcin. Como vers, no hemos incluido todos sus mtodos, por lo que recomendamos consultar la documentacin oficial para obtener informacin ms detallada.

4.2.4.1 Obtener tamao delCanvas:


- intgetHeight(): devuelve el ancho del lienzo.
- intgetWidth(): devuelve el largo del lienzo.
Puedes notar que el tamao del lienzo es un nmero entero aunque, para dibujar en l, debes
indicar posiciones con nmeros decimales (float) contenidos en ste.

4.2.4.2 Dibujar figuras geomtricas:


- drawCircle(floatx,floaty,floatr,Paintpaint): dibuja un crculo en la posicin
(x, y), de radio r y utilizando el pincel paint.
- drawOval(RectFrect,Paintpaint): dibuja una eclipse contenida en el rectngulo
rect y utilizando el pincel paint.
- drawRect(RectF rect, Paint paint): dibuja un rectngulo utilizando el pincel paint.
- drawPoint(float x, float y, Paint paint): pinta un punto en la posicin (x, y) empleando el pincel paint.

61

Aula Mentor

- drawPoints(float[] puntos, Paint paint): pinta los puntos de la matriz bidimensional


puntos empleando el pincel paint.

4.2.4.3 Dibujar lneas y arcos:


- drawLine(float iniX, float iniY, float finX, float finY, Paint paint): pinta un
lnea empezando en la posicin (iniX, iniY) y finalizando en (finX, finY) empleando el
pincel paint.
- drawLines(float[] puntos, Paint paint): pinta una lnea continua siguiendo los puntos de la matriz bidimensional puntos empleando el pincel paint.
- drawArc(RectF rect, float iniAngulo, float finAngulo, boolean usarCentro,
Paint paint): dibuja un arco en el rectngulo rect, de ngulo inicial iniAngulo y final
finAngulo, muestra el centro del valo si lo indicamos en el parmetro usarCentro y emplea el pincel paint.
- drawPath(Path trazo, Paint paint): dibuja un camino utilizando el pincel paint.

4.2.4.4 Dibujar texto:

62

- drawText(String texto, float x, float y, Paint paint): aade un texto en la posicin (x, y) utilizando el pincel paint.
- drawTextOnPath(String texto, Path trazo, float desplazamHor, float desplazamVert, Paint paint): aade un texto a lo largo del trazo con un desplazamiento
horizontal o vertical y utilizando el pincel paint.
- drawPosText(String texto, float[] posicion, Paint paint): aade un texto en la
posicin utilizando el pincel paint.

4.2.4.5 Colorear todo ellienzo Canvas:


- drawColor(intcolor): rellena el lienzo completo con el color.
- drawARGB(intalfa,introjo,intverde,intazul): rellena el lienzo completo

utilizando la terminologa ARGB.

- drawPaint(Paintpaint): rellena el lienzo completo con el pincel paint.

4.2.4.6 Dibujar imgenes:


- drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint): dibuja la imagen bitmap en el rectngulo dst del lienzo recortando la imagen original con el rectngulo
src y utilizando el pincel paint.
- drawBitmap(Bitmapbitmap,Matrixmatriz,Paintpincel): dibuja la imagen bitmap recortando la imagen original utilizando la matriz y el pincel paint.

4.2.4.7 Definir unClip (rea de seleccin):


- booleanclipRect(RectFrect): selecciona el rea mediante un rectngulo rect sobre
el que se aplicarn los dibujos.

U1 Multimedia y Grficos en Android

- booleanclipRegion(Regionregion): selecciona el rea mediante la region (superficie que se define con la clase Path) sobre la que se aplicarn los dibujos.
- booleanclipPath(Pathtrazo): selecciona el rea mediante el path sobre el que se
aplicarn los dibujos.

4.2.4.8 Definir matriz de transformacin (Matrix):


Una matriz de transformacin Matrix permite transformar las coordenadas de referencia
aplicando una translacin, escala o rotacin mediante una matriz 3x3. Veamos sus mtodos
bsicos:
- setMatrix(Matrixmatriz): establece la matriz en el lienzo.
- MatrixgetMatrix(): obtiene la matriz en el lienzo.
- concat(Matrixmatriz): aade una nueva matriz a la transformacin ya existente.
- translate(float despazX, float despazY): mueve el eje de coordenadas.
- scale(float escalaX, float escalaY): escala la matriz activa.
- rotate(float grados, float centroX, float centroY): rota la matriz activa.
- skew(float despazX, float despazY): inclina la matriz activa.
A continuacin, se muestra el Ejemplo 3 donde se crea una Vista que se dibuja mediante
cdigo Java empleando la clase Canvas, es decir, no se define un fichero layout xml en el
proyecto. Si abres el archivo MainActivity.java vers que contiene:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// No definimos Layout, el layout ser definido por CanvasView
setContentView(new CanvasView(this));
}
} // end clase

El cdigo anterior comienza con la creacin de una Activity en la que asociamos un objeto
CanvasView extendido de tipo View mediante el mtodo setContentView() que no est
definido mediante un layout XML.
Sin embargo, si accedes al fichero CanvasView.java vers que define:
zpublic class CanvasView extends View {
// Trazo del dibujo
Path trazo = new Path();
// Pincel del dibujo
Paint pincel = new Paint();
// Bitmap con el icono Mentor
Bitmap miBitmap = BitmapFactory.decodeResource(getResources(),

R.drawable.mentor);
// Variables para almacenar largo, ancho, centro_x y centro_y
float l, a, cx, cy;
// Constructor de la clase
public CanvasView(Context context) {

super(context);
}
@Override

63

Aula Mentor

protected void onDraw(Canvas canvas) {



// Buscamos el largo, ancho y centro del canvas

l = getWidth();

a = getHeight();

cx = l/2;

cy = a/2;

64


// Dibujamos un trazo de tipo rectngulo

trazo.addRect(l/5, a/4, l-l/5, a-a/4, Direction.CW);

// Rellenamos de color blanco todo el fondo del canvas
canvas.drawColor(Color.WHITE);

// Definimos pincel de color azul de ancho 8 y con borde
pincel.setColor(Color.BLUE);
pincel.setStrokeWidth(8);
pincel.setStyle(Style.STROKE);

// Dibujamos el rectngulo

canvas.drawPath(trazo, pincel);

// Limpiamos el trazo
trazo.reset();

// Movemos puntero a posicin

trazo.moveTo(0, a/7);

// Trazamos una lnea

trazo.lineTo(l, a/7);

// Indicamos estilo de relleno
pincel.setStyle(Style.FILL);

// Tamao del texto, tipo de fuente y alineamiento
pincel.setTextSize(25);
pincel.setTypeface(Typeface.SANS_SERIF);
pincel.setTextAlign(Paint.Align.CENTER);

// Imprimimos el texto

canvas.drawTextOnPath(Curso avanzado de Android de

Mentor., trazo, 0, 0, pincel);

// Imprimimos la imagen de Mentor

canvas.drawBitmap(miBitmap, cx-miBitmap.getWidth() / 2, cy miBitmap.getHeight() / 2, null);
} // end onDraw
// Evento que se lanza cada vez que cambia el tamao de la Vista
@Override
protected void onSizeChanged(int ancho, int alto,

int ancho_anterior, int alto_anterior){

// Mostramos un mensaje indicndolo

Toast.makeText(getContext(), Cambio de tamao de pantalla. Alto
nuevo: + alto + - Ancho nuevo: + ancho,
Toast.LENGTH_LONG).show();
}
} // end clase CanvasView

La clase CanvasView se extiende de View cambiando su mtodo onDraw() que es el responsable de dibujar la vista. Puedes ver que hemos utilizado los mtodos que hemos utilizado
anteriormente y vemos su funcin en los comentarios del cdigo fuente. Adems, hemos
implementado el mtodo onSizeChanged() que Android invoca cada vez que cambia el
tamao de la Vista.

U1 Multimedia y Grficos en Android

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (Canvas) de la Unidad 1.


Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado la clase Canvas.
Si ejecutas en Eclipse ADT este Ejemplo 3, vers que se muestra la siguiente aplicacin en
el Emulador:

65

Si cambias la orientacin del AVD con el atajo de teclado [CTRL+F12] vers que la imagen se
amolda al nuevo tamao de pantalla y muestra un mensaje indicndolo.

Aula Mentor

4.2.5 Definicin de dibujables (Drawable)


Un Dibujable (del ingls, Drawable) es un mecanismo para dibujar la interfaz de una aplicacin Android. Existen muchos tipos de recursos dibujables, tales como, archivos de imgenes,
colores, gradientes, formas geomtricas, etctera. A continuacin, vamos a estudiar los ms
importantes.
Podemos entender la clase Drawable como una abstraccin que representa algo que
se puede dibujar. Muchos de estos dibujables pueden ser definidos como recursos mediante
ficheros XML.

Aquellos Drawables que se pueden especificar mediante ficheros XML debemos


crearlos en la carpeta res/drawable del proyecto.

66

Entre ellos, podemos encontrar los siguientes:


- BitmapDrawable: imagen basada en un fichero de imagen PNG o JPG. En un recurso XML
se define con la etiqueta <bitmap>.
- ShapeDrawable: permite dibujar un grfico mediante primitivas vectoriales bsicas como
crculos, cuadrados, etctera, y trazados (Path). No puede ser definido mediante un fichero
XML.
- LayerDrawable: contiene una matriz de elementos de tipo Drawable que podemos utilizar
despus en la aplicacin. En un recurso XML se define con la etiqueta <layer-list>.
- StateListDrawable: recurso similar al anterior pero que permite utilizar una mscara de
bits para seleccionar los objetos visibles (por ejemplo, botn habilitado, deshabilitado, presionado, etctera). En un recurso XML se define con la etiqueta <selector>.
- GradientDrawable: permite definir un degradado de color que puede ser utilizado en botones o fondos.
- TransitionDrawable: extensin de LayerDrawables que permite crear efectos de fundido
entre capas. Para iniciar la transicin hay que invocar el mtodo startTransition(inttiempo).
Para visualizar la primera capa hay que invocar resetTransition(). En un recurso XML se
define con la etiqueta <transition>.
- AnimationDrawable: permite crear animaciones fotograma a fotograma utilizando una serie
de objetos Drawable. En un recurso XML se define con la etiqueta <animation-list>.
Notamos que es posible utilizar como base la clase Drawable o uno de sus descendientes para
crear tus propias clases grficas.
Al inicio de la Unidad 2 estudiaremos algunos tipos de Drawables ms.
Adems, esta clase Drawable proporciona una serie de mecanismos para indicar cmo
debe ser pintado el grfico (cada tipo de Drawable implementa algunos de ellos). Veamos los
ms importantes:
- setBounds(x1, y1, x2, y2): indica el rectngulo donde se debe dibujar el Drawable.
ste debe respetar el tamao indicando por el programador, es decir, el dibujo se escala. Podemos consultar el tamao de un Drawable mediante los mtodos getIntrinsicHeight()
y getIntrinsicWidth().
- getPadding(Rect): proporciona informacin sobre los mrgenes recomendados para representar contenidos. Por ejemplo, un Drawable destinado a ser el marco de un botn, debe
devolver los mrgenes correctos para localizar las etiquetas u otros contenidos en el interior
del botn.

U1 Multimedia y Grficos en Android

- setState(int[]): indica al Drawable en qu estado ha de ser dibujado. Por ejemplo con


foco, seleccionado, etctera. Algunos Drawable cambian su aspecto en funcin de este

estado.

Veamos con ms detalle algunas de las subclases deDrawable:

4.2.5.1 Dibujable de tipo bitmap (BitmapDrawable)


Como has visto, la forma ms sencilla de aadir imgenes a una aplicacin Android es incluirlas en la carpeta res/drawable del proyecto. El SDK de Android soporta los formatos PNG,
JPG y GIF. El formato recomendado es PNG, aunque tambin se puede utilizar JPG. Android
desaconseja el uso del formato GIF.
Cada imagen de esta carpeta se asocia automticamente a un ID de recurso. Por ejemplo, para
el archivo mentor.png crear el ID mentor que permite hacer referencia a la imagen desde el
cdigo o desde un archivo de recursos XML:
<bitmap xmlns:android=http://schemas.android.com/apk/res/android

android:src=@drawable/mentor/>

Si usamos sentencias Java, podemos escribir:

Drawable miImagen =
context.getResources().getDrawable(R.drawable.mi_imagen);

4.2.5.2 GradientDrawable (Gradiente dibujable)


Un Gradient Drawable permite al programador dibujar un gradiente de colores que consiste
en mostrar un degradado de dos colores en el fondo de cualquier Vista.
Por ejemplo, el siguiente archivo define un degradado desde el color blanco (FFFFFF)
a rojo (FF0000):
<shape xmlns:android=http://schemas.android.com/apk/res/android>
<gradient
android:startColor=#FFFFFF
android:endColor=#FF0000
android:angle=270 />
</shape>

Este tipo de objetos grficos se utiliza con frecuencia como fondo de botones o de pantalla.
El parmetro angle establece la direccin del degradado. nicamente se pueden definir los
ngulos 0, 90, 180 y 270.
Si guardamos el archivo anterior en res/drawable/degradado.xml entonces
podemos utilizarlo para establecer el fondo de una vista en su Layout en XML introduciendo
el siguiente atributo en el Layout main.xml de la aplicacin:
android:background=@drawable/degradado
Adems, es posible introducir la siguiente sentencia en el constructor de una Actividad
para que este drawable sea utilizado como degradado de fondo:
setBackgroundResource(R.drawable.degradado);

67

Aula Mentor

4.2.5.3 ShapeDrawable (Dibujable con forma)


Este drawable permite dibujar formas dinmicamente mediante primitivas vectoriales disponibles en Android: Veamos un ejemplo sencillo que pinta una forma ovalada y la rellena de
color rojo:
ShapeDrawable imagen = new ShapeDrawable(new OvalShape());
imagen.getPaint().setColor(0xffff0000);
imagen.setBounds(10, 10, 250, 75);
imagen.draw(canvas);

Con la orden setBounds() hemos establecido los lmites de la forma, es decir, su tamao.

4.2.5.4 AnimationDrawable (Dibujable animado)


Android proporciona varias formas de crear animaciones, tambin mediante Drawables, que
tienen la ventaja de que se pueden crear desde un fichero XML.
Estas animaciones se crean a partir de un grupo de fotogramas. Para ello, emplearemos
la clase AnimationDrawable.
Veamos en el Ejemplo 4 de esta Unidad una aplicacin sencilla que utiliza este tipo
de animacin.
Si abres el archivo res/drawable/animacion.xml advertirs que hemos definido los
fotogramas de la animacin utilizando la etiqueta animation-list y las imgenes contenidas
en esta carpeta:
68


<animation-list
xmlns:android= http://schemas.android.com/apk/res/android
android:oneshot= false>
<item android:drawable=@drawable/android1
android:duration=200 />
<item android:drawable=@drawable/android2
android:duration=200 />
<item android:drawable=@drawable/android3
android:duration=200 />
</animation-list>

Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin observars que contiene:

public class MainActivity extends Activity {


AnimationDrawable animacion;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las imgenes de la animacin
animacion = (AnimationDrawable)getResources().
getDrawable(R.drawable.animacion);
// Definimos la Vista donde vamos a mostrar la animacin
ImageView imagen = new ImageView(this);
// La coloremos de blanco

U1 Multimedia y Grficos en Android

imagen.setBackgroundColor(Color.WHITE);
// Establecemos la animacin en la imagen
imagen.setImageDrawable(animacion);
// Definimos el evento onClick de la imagen que inicia o para la

// animacin
imagen.setOnClickListener(new OnClickListener() {

public void onClick(View view) {

if (animacion.isRunning()) animacion.stop();

else animacion.start();

}
});
// El contenido de la actividad es la imagen
setContentView(imagen);
} // end onCreate
} // end clase

El cdigo fuente anterior comienza declarando el objeto animacion de la clase AnimationDrawable. ste se inicializa a partir del fichero XML anterior incluido en la carpeta de recursos. Despus, se crea una nueva vista del tipo ImageView que sirve de contenedor de esta
vista animacion. Finalmente, se crea un listener de evento onClick para que la animacin
se inicie o se pare utilizando los mtodos start() y stop() respectivamente.
De igual forma que el ejemplo anterior, se usa la Vista animacion para dibujar mediante
cdigo Java la interfaz del usuario, es decir, no se define fichero layout xml en el proyecto.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Animacin Drawable) de


la Unidad 1. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado la clase AnimationDrawable.

Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra la siguiente aplicacin en
el Emulador:

69

Aula Mentor

En el apartado siguiente de esta Unidad estudiaremos animaciones ms complejas.


En el manual oficial sobre DRAWABLES de Android puedes encontrar un listado completo
de todos los drawables disponibles, as como sus propiedades.

5. Animaciones de Android
Android dispone de tres mecanismos para crear animaciones en las aplicaciones:
- AnimationDrawable: mediante esta clase, ya vista en el apartado anterior, podemos crear
drawables que reproducen una animacin fotograma a fotograma (en ingls se denomina
Frame Animation).
- Animaciones Tween: crean efectos de translacin, rotacin, zoom y alfa a cualquier vista
de una aplicacin Android, cambiando su representacin en la pantalla.
- API de animacin de Android: anima cualquier propiedad de un objeto Java sea del tipo
Vista o no, modificando el objeto en s mismo.
A continuacin, mediante ejemplos aprenderemos a usar estos dos ltimos mecanismos para
crear animaciones en Android.

5.1 Animaciones Tween

70

Una animacin tween (del ingls between, que significa en medio o entre) consiste en
realizar una serie de transformaciones simples en las Vistas de una Actividad, como su posicin,
tamao, rotacin y transparencia.
Por ejemplo, es posible mover, rotar, modificar el tamao o cambiar la transparencia a un
objeto del tipo TextView.

Para componer la secuencia de instrucciones que definen esta animacin de tipo


tween podemos utilizar un archivo xml o cdigo Java. Es recomendable emplear
el archivo xml al ser ms legible y reutilizable.

La clase Animation de Android es la que permite crear animaciones en las Vistas de una
Actividad.
Las instrucciones que definen esta animacin son transformaciones donde indicamos
cundo ocurrirn y cunto tiempo tardarn en completarse. Estas transformaciones pueden ejecutarse de forma secuencial o simultnea. Cada tipo de transformacin posee unos parmetros
especficos, si bien existen unos parmetros comunes a todas ellas, como son el tiempo de duracin y de inicio.
Los ficheros XML que definen animaciones deben almacenarse en el directorio res/
anim/ del proyecto Android y deben contener un nico elemento raz que indique las transformaciones que deseamos ejecutar. Esta etiqueta raz debe ser una de las siguientes:
- <translate>: mueve la Vista.
- <rotate>: rota la Vista.
- <scale>: escala la Vista.
- <alpha>: modifica la opacidad de la Vista.
- <set>: conjunto de varias transformaciones anteriores.

U1 Multimedia y Grficos en Android

Por defecto, todas las instrucciones de una animacin ocurren a partir del instante inicial. Si es
necesario que una animacin comience ms tarde, hay que especificar su atributo startOffset.

5.1.1 Atributos de las transformaciones Tween


Los atributos siguientes se aplican a todas las transformaciones:
- startOffset: instante inicial de la transformacin en milisegundos.
- duration: duracin de la transformacin en milisegundos.
- repeatCount: nmero de repeticiones adicionales de la animacin.
- Interpolator: permite acelerar o desacelerar la animacin. Alguno de sus valores posibles
son:
accelerate_decelerate_interpolator
accelerate_interpolator
anticipate_interpolator
anticipate_overshoot_interpolator
bounce_interpolator
cycle_interpolator
decelerate_interpolator
linear_interpolator
overshoot_interpolator
Al final de este apartado veremos su descripcin.
En el manual oficial de Android sobre Tween puedes encontrar ms informacin sobre
estos aceleradores de la animacin.
Veamos ahora la lista de transformaciones que tiene atributos especficos:
Nombre
transformacin
<translate>

<rotate>

Atributo

Descripcin

fromXDelta
toXDelta

Valores inicial y final del desplazamiento en el


eje X.
Valores inicial y final del desplazamiento en el
eje Y.
Grado inicial y final de la rotacin. Para
realizar un giro completo en sentido
antihorario debemos establecer 0 y 360
respectivamente. Para sentido horario, de 360
a 0 o de 0 a -360. Para dos giros consecutivos
escribe 0 y 720.
Punto sobre el que se realiza el giro que
queda fijo en la pantalla.
Valor inicial y final para la escala del eje X
(0.5=50%, 1=100%)

fromYDelta
toYDelta

fromDegrees
toDegrees

pivotX
pivotY

<scale>

<alpha>

fromXScale
toXScale
fromYScale
toYScale
pivotX
pivotY
fromAlpha,
toAlpha

Valor inicial y final para la escala del eje Y


Punto sobre el que se realiza el giro que
queda fijo en la pantalla.
Valor inicial y final de la opacidad.

71

Aula Mentor

En el Ejemplo 5 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza este
tipo de animacin Tween.
Si abres el archivo res/anim/animacion.xml advertirs que hemos definido un conjunto de transformaciones de la animacin utilizando la etiqueta set:
<set xmlns:android=http://schemas.android.com/apk/res/android>

72

<alpha
android:startOffset=0
android:duration=3000
android:fromAlpha=0
android:toAlpha=1 />
<rotate
android:startOffset=4000
android:duration=2000
android:fromDegrees=0
android:toDegrees=360
android:pivotX=50%
android:pivotY=50%/>
<translate
android:startOffset=6000
android:duration=500
android:fromXDelta=0
android:fromYDelta=0
android:toXDelta=500
android:toYDelta=0 />
</set>

Es bastante sencillo entender las transformaciones que aplicamos secuencialmente en el archivo anterior.
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin,
observars que contiene las sentencias siguientes:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las imgenes de la animacin
setContentView(R.layout.main);
final TextView texto = (TextView)findViewById(R.id.textoAnimado);
// Definimos la animacin
final Animation animacion = AnimationUtils.loadAnimation(this,
R.anim.animacion);
// Evento que lanza Android cuando le ocurre algo a la animacin
animacion.setAnimationListener(new AnimationListener() {
@Override

public void onAnimationStart(Animation animation) {}
@Override

public void onAnimationRepeat(Animation animation) {}
@Override

public void onAnimationEnd(Animation animation) {

// Cuando la animacin acaba, volvemos a iniciarla

U1 Multimedia y Grficos en Android


// La animacin se repite de forma infinita
texto.startAnimation(animacion);
}
});
// Iniciamos la animacin al texto
texto.startAnimation(animacion);
} // end onCreate
} // end clase

El cdigo fuente anterior comienza declarando el objeto animacion de la clase Animation que
se inicializa mediante el mtodo loadAnimation() a partir del fichero XML anterior incluido en
la carpeta anim.
Despus, se inicia la animacin sobre una etiqueta TextView mediante el mtodo
startAnimation().

Podemos utilizar el mtodo startAnimation() sobre cualquier subclase de View,


es decir, podemos animar cualquier Vista con este mecanismo.

Finalmente, se crea un listener del tipo AnimationListener para detectar cundo se inicia
la animacin, se para o se repite. En este caso usamos este listener para que la animacin
comience de nuevo.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 5 (Animaciones Tween) de


la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado una Animacin Tween de Android.

Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente aplicacin:

73

Aula Mentor

5.2 API de Animacin de Android


En este apartado vamos a estudiar la API de Animacin de Android que permite cambiar las
propiedades de objetos en un intervalo de tiempo, dando as al usuario una sensacin de
movimiento lineal en la interfaz.
Esta API est disponible a partir de la versin 3.0 de Android (nivel de API 11). Sin embargo, existe una librera en Internet que permite utilizar animaciones en versiones anteriores.
En este curso slo utilizaremos la librera nativa de Android.
Mediante esta API podemos indicar el movimiento de una Vista o cualquier objeto Java
de una aplicacin indicando su nueva posicin y el tiempo en el que debe acabar la animacin.
A diferencia de las animaciones Tween, que nicamente son aplicables a Vistas, la API
de animacin puede aplicarse a cualquier tipo de objetos Java. Adems, es ms flexible poder
animar cualquier propiedad del objeto, es decir, no est restringida a las cuatro transformaciones
que hemos estudiado anteriormente. Por ejemplo, podemos crear una animacin que cambie
progresivamente el color de fondo de una Vista.
Las animaciones Tween slo modifican la forma en que Android representa una
Vista, pero sus propiedades internas no cambian. Por ejemplo, si aplicamos una animacin
Tween a una etiqueta para desplazarla por la pantalla, la animacin se visualizar correctamente
pero, al finalizar, la etiqueta seguir en la posicin inicial. Si hubiramos empleado la API de
Animacin, las propiedades de la etiqueta hubieran sido modificadas efectivamente.

74

Desventajas de las animaciones Tween:


- nicamente podemos animar objetos de la clase View.
- Est limitada a cuatro transformaciones estticas (no se puede cambiar el color de fondo).
- Modifica la representacin de la vista, pero no sus propiedades en s.
Desventajas de la API de Animacin:
- Solo disponible a partir de la versin 3.0 de Android.
- Requiere ms tiempo en cargarse en el dispositivo y el programador debe escribir ms cdigo Java.

La API de Animacin de Android se denomina tambin Animacin de Propiedades ya que, como hemos dicho, modifica las propiedades propiamente dichas del
objeto que se anima.

5.2.1 Clases principales de la API de animacin


- Animator: superclase de la que se extienden el resto de clases.
- ValueAnimator: permite modificar el valor de una variable o Vista.
- ObjectAnimator: permite establecer los atributos del movimiento de un objeto.
- AnimatorSet: permite ejecutar animaciones en secuencia o en paralelo.
- AnimatorBuilder: permite aadir animaciones a la clase AnimatorSet y relacionarlas entre s de forma compleja.
- AnimatorListener: permite definir listeners en funcin del estado de la animacin, por
ejemplo, cuando comienza o cuando finaliza.
- PropertyValuesHolder: permite animar mltiples valores durante el ciclo de animacin.
- Keyframe: permite crear animaciones con fotogramas.

U1 Multimedia y Grficos en Android

- TypeEvaluator: si la propiedad que queremos modificar en una animacin es un objeto en


s mismo, esta interface permite interactuar con l.
- ViewPropertyAnimator: permite animar de forma automtica y optimizada varias propiedades a la vez de una Vista exclusivamente.
- LayoutTransition: permite animar los Layout mediante transiciones.
Es posible definir animaciones en archivos XML dentro del directorio /res/anim del proyecto
empleando esta API.
Adems, cuando una aplicacin consta de varias Actividades, podemos tambin animar
los cambios que ocurren cuando el usuario cambia la Actividad activa.
A continuacin, vamos a estudiar en detalle esta API.

5.2.1.1 Animator
Superclase de la API de la que se extienden el resto de clases. Veamos los mtodos ms importantes de esta clase:
- start(): comienza la animacin.
- end(): termina la animacin. El objeto se queda en el ltimo estado marcado por esta animacin.
- cancel(): cancela la animacin y vuelve al estado inicial.
- setDuration(long duration): indica la duracin en milisegundos de la animacin.
- setTarget(): establece el objeto que va a animar.
- addListener(): aade el listener correspondiente a los cambios de estado de la animacin.
- removeAllListeners(): quita todos los listeners de la animacin.
- removeListener(listener): quita el listener indicado como parmetro de la animacin.
- isRunning(): seala si la animacin se est ejecutando.
- setInterpolator(): establece el interpolador que usaremos en la animacin.
- setStartDelay(long retraso): retrasa el inicio de la animacin despus de invocar el
mtodo start(). Como es habitual, el tiempo retraso se indica en milisegundos.
Hay que tener en cuenta que todas las clases siguientes, al extenderse de sta, heredan los
mtodos anteriores.

5.2.1.2 ValueAnimator
Una forma de entender mejor por qu se llama tambin a esta API Animacin de Propiedades es mediante el siguiente ejemplo que define una animacin mediante la clase ValueAnimator. En sta se modifica el valor de una variable de tipo entero durante un intervalo
de tiempo de 5000 milisegundos. Veamos cdigo fuente del ejemplo:
// Definimos el animator que cambia el valor entero de 10 a 200
ValueAnimator animacion = ValueAnimator.ofInt(10f, 200f);
// Indicamos la duracin de la animacin
animacion.setDuration(5000);
// Establecemos el listener para detectar el cambio del valor entero
animacion.addUpdateListener(

new ValueAnimator.AnimatorUpdateListener()

{

75

Aula Mentor


public void onAnimationUpdate(ValueAnimator animation)
{

Int valor = (Int)animation.getAnimatedValue();
// Durante 5 segundos la variable valor va cambiando

// desde 10 hasta 200. Por defecto, esta clase

// modifica el valor cada 10 milisegundos
}

}
);
// Iniciamos la animacin
animacion.start();

La clase ValueAnimator es un mecanismo para hacer algo cada 10 milisegundos (valor por
defecto).

5.2.1.3 ObjectAnimator

76

La clase ObjectAnimator (animador de objetos) es un mecanismo de animacin que consiste


en modificar un objeto y animarlo desde un estado inicial a un estado final en un perodo
de tiempo concreto. Este perodo de tiempo se define en milisegundos. Adems, podemos
indicar la rutina que marca cmo se comporta la animacin durante ese perodo de tiempo;
estas rutinas se denominan interpoladores (del ingls, interpolators).
El interpolador predeterminado es accelerate_decelerate que comienza y termina
la animacin con una aceleracin y desaceleracin suaves respectivamente. Un poco ms
adelante, trataremos los interpoladores con un ejemplo.
Veamos ahora, los mtodos ms importantes de esta clase:
- ofFloat(Object objeto, String nombrePropiedad, float... valores): establece
los nuevos valores de tipo float que debe tomar el objeto para la propiedad nombrePropiedad al final de la animacin.
- ofInt(Object objeto, String nombrePropiedad, int ... valores): establece
los nuevos valores de tipo entero que debe tomar el objeto para la propiedad nombrePropiedad al final de la animacin.
- ofObject(Object objeto, String nombrePropiedad, TypeEvaluator evaluador,
Object... valores): establece los nuevos valores del tipo object que debe tomar el
objeto para la propiedad nombrePropiedad y deben interpretarse con el evaluador al

final de la animacin.

- ofPropertyValuesHolder(Object objeto, PropertyValuesHolder... valores): establece los nuevos conjuntos de valores del tipo PropertyValueHolder que debe tomar el
objeto. Como su nombre indica, PropertyValueHolder se usa para denotar un conjunto

de propiedades del objeto con los valores asociados. En el Ejemplo 6 de esta Unidad veremos cmo se utiliza.

5.2.1.4 AnimatorSet
Esta clase AnimatorSet facilita la creacin de animaciones en secuencia o en paralelo. Veamos los mtodos ms destacables de esta clase:
- playSequentially(Animator... animaciones): las animaciones indicadas se ejecutaran secuencialmente.

U1 Multimedia y Grficos en Android

- playTogether(Animator... animaciones): las animaciones sealadas se ejecutaran al

mismo tiempo.

- play(Animator animacion): este mtodo devuelve un objeto del tipo AnimatorBuilder

que permite interrelacionar las animaciones. A continuacin, veremos un ejemplo.

5.2.1.5 AnimatorBuilder
La clase AnimatorBuilder permite aadir animaciones a la clase AnimatorSet y relacionarlas de forma ms compleja indicando la interdependencia entre ellas. Veamos un ejemplo
para aclarar el concepto:
// Creamos un objeto de la clase AnimatorSet
AnimatorSet s = new AnimatorSet();
// Ejecutamos la animacin 1 con la animacin 2 (a la vez)
s.play(anim1).with(anim2);
// Ejecutamos la animacin 3 antes de la animacin 2, es decir, antes de
la animacin 1 tambin.
s.play(anim2).before(anim3);
// Ejecutamos la animacin 3 despus de la animacin 4
s.play(anim4).after(anim3);

Es importante saber que la clase AnimatorBuilder no dispone de constructor, es decir, se


construye internamente al invocar el mtodo play().
Veamos los mtodos de esta clase:
- after(Animator animacin): ejecuta la animacin despus de la animacin indicada en
el mtodo play().
- after(long retraso): marca el tiempo de retraso en ejecutar la animacin establecida

en el mtodo anterior.

- before(Animator animacin): ejecuta la animacin antes de la animacin indicada en


el mtodo play().
- with(Animator animacin): ejecuta la animacin a la vez que la animacin indicada en
el mtodo play().

5.2.1.6 AnimationListener
Como es habitual, los eventos de la animacin se definen en la clase de tipo interface:







public static interface Animator.AnimatorListener


{
// Se invoca cuando se inicia la animacin
abstract void onAnimationStart(Animator animation);
// Se invoca cuando se repite la animacin
abstract void onAnimationRepeat(Animator animation);
// Se invoca cuando se cancela la animacin
abstract void onAnimationCancel(Animator animation);
// Se invoca cuando la animacin termina
abstract void onAnimationEnd(Animator animation);
}

Como puedes ver, los listeners son bastante intuitivos y sencillos de implementar.

77

Aula Mentor

5.2.1.7 PropertyValuesHolder
Esta clase permite animar mltiples valores durante el ciclo de animacin. Puede entenderse
esta clase como un objeto que contiene la dupla propiedad/valor. Podemos utilizar esta clase
para crear animaciones con ValueAnimator y ObjectAnimator y, as, ejecutar varios cambios de propiedades en paralelo.
Veamos los mtodos ms importantes de esta clase:
- ofFloat(String nombrePropiedad, float... valores): establece los nuevos valores
de tipo float que debe tomar la propiedad nombrePropiedad al final de la animacin.
- ofInt(String nombrePropiedad, int
... valores): establece los nuevos valores de
tipo entero que debe tomar la propiedad nombrePropiedad al final de la animacin.
- ofObject(String nombrePropiedad, TypeEvaluator evaluador, Object... valores): establece los nuevos valores del tipo object que debe tomar la propiedad nombrePropiedad y deben interpretarse con el evaluador al final de la animacin.
- ofKeyframe(Property propiedad, Keyframe... valores): permite crear una animacin de tipo de fotograma para la propiedad.

5.2.1.8 Keyframe

78

Esta clase base permite crear animaciones con fotogramas. Esta clase alberga la dupla tiempo/
valor que se debe aplicar a una animacin. Se puede utilizar con las clases ValueAnimator
y ObjectAnimator.
Para entender cmo funciona, podemos hacer una analoga con el cine, es decir, cada
fotograma Keyframe es un estado del objeto y el movimiento de la animacin se produce al
cambiar el fotograma en el tiempo.
Incluso es posible definir para cada Keyframe un objeto del tipo TimeInterpolator
que define cmo se hace la interpolacin en el cambio de fotograma.
Veamos los mtodos ms importantes de esta clase:
- ofFloat(float tiempo, float... valores): establece los nuevos valores de tipo float
que debe tener la animacin al final del fotograma en la fraccin de tiempo medida como

porcentaje de 0 a 1 del tiempo total.

- ofInt(float tiempo, int

... valores): establece los nuevos valores de tipo


entero que debe tener la animacin al final del fotograma en la fraccin de tiempo medida
como porcentaje de 0 a 1 del tiempo total.
- ofObject(float tiempo, Object... valores): establece los nuevos valores de tipo
Object que debe tener la animacin al final del fotograma en la fraccin de tiempo medida
como porcentaje de 0 a 1 del tiempo total.

5.2.1.9 TypeEvaluator
Esta interface permite interactuar con un objeto cuando ste es la propiedad que queremos
animar. Hay que tener en cuenta que los objetos tienen mucha complejidad y debemos indicar
cmo se animan.
Slo dispone del mtodo evaluate(float tiempo, T valorInicial, T valorFinal)
que devuelve el resultado de evaluar el cambio desde el valorInicial al valorFinal del
objeto durante el tiempo.

U1 Multimedia y Grficos en Android

Android dispone de los evaluadores por defecto:


- ArgbEvaluator: para cambios de color.
- FloatEvaluator: para cambios en nmeros decimales.
- IntEvaluator: para cambios en nmeros enteros.

Vemos ahora un sencillo ejemplo que evala una animacin que utiliza puntos (clase PointF)
de Android:
// Definimos el evaluador de la clase PointF (punto de Android)
public class MiPuntoEvaluator implements TypeEvaluator<PointF>
{
// Definimos su mtodo evaluate
public PointF evaluate(float fraction, PointF valorInicio, PointF
valorFinal)
{

// Obtenemos la posicin actual
PointF valorInicio = (PointF) valorInicio;
PointF valorFinal = (PointF) valorFinal;
// Devolvemos la nueva posicin moviendo el punto la fraccin
// indicada
return new PointF(

valorInicio.x + fraction * (valorFinal.x - valorInicio.x),

valorInicio.y + fraction * (valorFinal.y - valorInicio.y));
}
}

79

5.2.1.10 ViewPropertyAnimator
Esta clase permite animar de forma automtica y optimizada varias propiedades de una Vista
exclusivamente al mismo tiempo.
La sintaxis de esta clase es muy intuitiva y fcil de aplicar ya que nicamente debemos
decirle a la Vista que deseamos animarla indicando el nuevo valor de la propiedad. Es decir,
no tenemos que utilizar todo el mecanismo de Animator.
Esta clase no dispone de constructor, por lo que para crear un objeto de este tipo
debemos invocar la orden animate() en la Vista que deseamos animar y que devolver una
instancia a la clase ViewPropertyAnimator.
Veamos los mtodos ms importantes de esta clase que permiten modificar propiedades de
una Vista:
- alpha(float valor): cambia la opacidad de la Vista segn el valor.
- rotationX(float valor): rota la Vista en el eje X segn el valor.
- rotationY(float valor): rota la Vista en el eje Y segn el valor.
- scaleX(float valor): escala la Vista en el eje X segn el valor.
- scaleY(float valor): escala la Vista en el eje Y segn el valor.
- translationX(float valor): traslada la Vista en el eje X segn el valor.
- translationY(float valor): traslada la Vista en el eje Y segn el valor.
- x(float valor): cambia la posicin X de la Vista segn el valor.
- y(float valor): cambia la posicin Y de la Vista segn el valor.

Aula Mentor

5.2.1.11 LayoutTransition
Esta clase permite animar los Layout mediante transiciones cada vez que se aade o se quita
una Vista de este contenedor. Para ello, debemos invocar el mtodo setLayoutTransition
(LayoutTransition) en el Layout que deseemos animar.

5.3 Animacin de Actividad


Tambin es posible aplicar animaciones a los cambios entre Actividades de una aplicacin. Para
hacerlo, debemos definir el mtodo overridePendingTransition() en la Actividad activa.
Este mtodo tiene dos parmetros: la animacin de salida de la Actividad actual y la animacin
de entrada de la Actividad nueva.
En el Ejemplo 6 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza
la API de animacin.
Si abres el archivo res/layout/main_layout.xml advertirs que hemos definido una
etiqueta que animaremos y un conjunto de botones que permitirn al usuario elegir cmo desea
animarla:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:orientation=vertical
android:layout_marginTop=10dp >

80






<TextView

android:id=@+id/etiqueta
android:layout_width=fill_parent
android:layout_height=wrap_content
android:background=@color/azul
android:textColor=@color/blanco
android:text=@string/texto
android:padding=4dp/>

<TableLayout

android:layout_width=fill_parent

android:layout_height=wrap_content

android:stretchColumns=0,1

android:layout_marginTop=10dp>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Fundido
android:layout_column=0 />
<Button
android:id=@+id/boton2

U1 Multimedia y Grficos en Android

android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Movimiento
android:layout_column=1 >
</Button>

</TableRow>
<TableRow

android:layout_width=fill_parent

android:layout_height=wrap_content>
<Button
android:id=@+id/boton3
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Anim. secuencial
android:layout_column=0 />
<Button
android:id=@+id/boton4
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Animac. XML
android:layout_column=1 />
</TableRow>
<TableRow

android:layout_width=fill_parent

android:layout_height=wrap_content>
<Button
android:id=@+id/boton5
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=PropertiesHolder
android:layout_column=0 />
<Button
android:id=@+id/boton6
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=ViewAnimator
android:layout_column=1 />
</TableRow>
<TableRow

android:layout_width=fill_parent

android:layout_height=wrap_content>

81

Aula Mentor

<Button
android:id=@+id/boton7
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=TypeEvaluator
android:layout_column=0 />
<Button
android:id=@+id/boton8
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=KeyFrames
android:layout_column=1 />
</TableRow>

</TableLayout>
</LinearLayout>

Si abres el archivo res/anim/fundidos.xml advertirs que hemos definido un conjunto de


transformaciones en la opacidad (alpha) utilizando la etiqueta set:

82

<set xmlns:android=http://schemas.android.com/apk/res/android
android:ordering=sequentially>
<objectAnimator
android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=1
android:valueTo=0
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
<objectAnimator
android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=0
android:valueTo=1
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
</set>

Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin, observars


que contiene las siguientes sentencias:
public class MainActivity extends Activity implements AnimatorListener {
// Etiqueta que vamos a animar
private TextView etiqueta = null;
private float fuenteEtiqueta;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
etiqueta = (TextView)this.findViewById(R.id.etiqueta);

U1 Multimedia y Grficos en Android

fuenteEtiqueta=etiqueta.getTextSize();
}
// Evento que se lanza cuando el usuario pulsa un botn
public void animar(View boton) {
// Variables que se usan ms abajo
float dest = 0;
float h, w , x , y;
PropertyValuesHolder pvhX;
// Animacin de tipo objeto
ObjectAnimator animacion=null;
// Animacin secuencial
AnimatorSet as = null;

// Ponemos la etiqueta con su color y tamao fuente originales


etiqueta.setBackgroundColor(getResources().getColor(
R.color.azul));

etiqueta.setTextSize(fuenteEtiqueta);

// Si el usuario pulsa el botn...

switch (boton.getId()) {


// Boton que hace fundido a negro o blanco
case R.id.boton1:

// Ponemos la etiqueta en posicin 0 en eje X
etiqueta.setX(0);

// Obtenemos el botn que ha sido pulsado

Button tButton = (Button)boton;

// Si la etiqueta no est en negro

if (etiqueta.getAlpha() != 0)
{

// Definimos una animacin que cambia la propiedad

// alpha (opacidad) de la etiqueta al negro (0f)

animacion = ObjectAnimator.ofFloat(etiqueta,
alpha, 0f);

// Cambiamos la etiqueta botn

tButton.setText(Fundido a negro);
}
else
{

// Definimos una animacin que cambia la propiedad

// alpha de la etiqueta al blanco (1f)

animacion = ObjectAnimator.ofFloat(etiqueta,
alpha, 1f);

// Cambiamos la etiqueta botn

tButton.setText(Fundido a blanco);
}

// La animacin dura 5 segundos
animacion.setDuration(5000);
break;

// Botn Mover
case R.id.boton2:
// Dejamos la etiqueta sin fundido

// Usamos la clase Paint para medir el tamao que

// ocupa el texto de la etiqueta

Paint paint = new Paint();

float longitudEtiqueta = paint.measureText(
etiqueta.getText().toString());

// Vamos a mover la etiqueta hacia la izq hasta el

83

Aula Mentor

84


// final de la longitud del texto

dest = 0 - longitudEtiqueta;

// Si la etiqueta ya est fuera de la pantalla

// volvemos a ponerla en su sitio

if (etiqueta.getX() < 0) {
dest = 0;
}

// Definimos una animacin que cambia la propiedad

// X de la etiqueta hacia dest
animacion = ObjectAnimator.ofFloat(etiqueta,x,

dest);

// Definimos la animacin durante 2 segundos
animacion.setDuration(2000);
break;

// Animacin secuencial por lotes

case R.id.boton3:

// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Definimos una animacin que cambia el color de la

// etiqueta del azul actual al negro
ObjectAnimator color1 =
ObjectAnimator.ofInt(etiqueta, backgroundColor,
getResources().getColor(R.color.negro));

// Definimos una animacin que cambia de nuevo el

// color de la etiqueta del negro actual al azul
ObjectAnimator color2 =

ObjectAnimator.ofInt(etiqueta,backgroundColor,
getResources().getColor(R.color.azul));

// Cambiamos el tamao de la fuente a 40f
ObjectAnimator fuente1 =


ObjectAnimator.ofFloat(etiqueta,

textSize, 40f);

// Dejamos el tamao de la fuente inicial
ObjectAnimator fuente2 =


ObjectAnimator.ofFloat(etiqueta,
textSize, fuenteEtiqueta);

// Definimos un animador por lotes

as = new AnimatorSet();

// Ejecutamos la animacin en secuencia
as.playSequentially(color1,color2, fuente1, fuente2);

// La animacin dura 4 segundos
as.setDuration(4000);
break;

// Animacin desde fichero XML
case R.id.boton4:

// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Cargamos la animacin de un archivo XML

as = (AnimatorSet)AnimatorInflater.loadAnimator(this,

R.anim.fundidos);

// Indicamos la Vista sobre la que se debe aplicar
as.setTarget(etiqueta);
break;

// Botn PropertiesHolder (Contenedor de propiedades)

U1 Multimedia y Grficos en Android

case R.id.boton5:

// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();

// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);

// Creamos un PropertiesHolder en el eje X e Y para

// que vuelva a la posicin inicial

pvhX = PropertyValuesHolder.ofFloat(x, x);

PropertyValuesHolder pvhY =
PropertyValuesHolder.ofFloat(y, y);

// Definimos la animacin con estos PropertiesHolder
animacion =ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhX, pvhY);

// La animacin dura 5 segundos
animacion.setDuration(5000);

// Definimos un acelerador de la animacin
animacion.setInterpolator(new
AccelerateDecelerateInterpolator());
break;

// Botn ViewAnimator
case R.id.boton6:

// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();

// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);

// Definimos una animacin del tipo ViewAnimator

ViewPropertyAnimator vpa = etiqueta.animate();

// Definimos la posicin final de la etiqueta en la

// animacin
vpa.x(x);
vpa.y(y);

// La animacin dura 5 segundos
vpa.setDuration(5000);

// Definimos el listener de la animacin
vpa.setListener(this);

// Definimos un acelerador de la animacin
vpa.setInterpolator(new
AccelerateDecelerateInterpolator());
break;

// Botn TypeEvaluator
case R.id.boton7:

// Ponemos la etiqueta en su sitio y sin fundido

85

Aula Mentor

86

etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Definimos el color inicial y final de la animacin

Integer colorIni =
getResources().getColor(R.color.rojo);
Integer colorFin =
getResources().getColor(R.color.azul);
// Definimos la animacin utilizando el TypeEvaluator

// ArgbEvaluator e indicando los colores inicial y

// final

ValueAnimator colorAnimation = ValueAnimator.ofObject(
new ArgbEvaluator(), colorIni, colorFin);
// Definimos el listener que se lanza cada vez que es
// necesario actualizar la animacin
colorAnimation.addUpdateListener(


new AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator
animator) {

// Cambiamos el color de la etiqueta

etiqueta.setBackgroundColor(
(Integer)animator.getAnimatedValue());

}
});

// La animacin dura 5 segundos
colorAnimation.setDuration(5000);

// Quitamos todos los listeners de la animacin
colorAnimation.removeAllListeners();

// Aadimos el listener local
colorAnimation.addListener(this);

// Iniciamos la animacin
colorAnimation.start();
break;

// Botn KeyFrames (Fotogramas)
case R.id.boton8:

// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);

// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();

// Fotograma de inicio : 0.2

// Valor alpha: 0.8

Keyframe kf0 = Keyframe.ofFloat(0.2f, 0.8f);

// Fotograma intermedio: 0.5

// Valor alpha: 0.2

Keyframe kf1 = Keyframe.ofFloat(.5f, 0.2f);

// Fotograma final: 0.8

// Valor alpha: 0.8

Keyframe kf2 = Keyframe.ofFloat(0.8f, 0.8f);

// Definimos un PropertyHolder para almacenar los

// cambios
PropertyValuesHolder pvhAlpha =

U1 Multimedia y Grficos en Android

PropertyValuesHolder.ofKeyframe(alpha, kf0,
kf1, kf2);

// Definimos tambin el movimiento horizontal

pvhX = PropertyValuesHolder.ofFloat(x, w, x);

// Definimos la animacin a partir de fotogramas
animacion =


ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhAlpha,pvhX);

// La animacin dura 5 segundos

animacion.setDuration(5000);
break;
default:
break;
}

if (animacion!=null) {

// Quitamos todos los listeners de la animacin
animacion.removeAllListeners();

// Aadimos el listener local
animacion.addListener(this);

// Iniciamos la animacin
animacion.start();

} else

if (as!=null) {

// Quitamos todos los listeners de la animacin
as.removeAllListeners();

// Aadimos el listener local
as.addListener(this);
as.start();
}
} // end animar
// Mtodos que lanza Android cuando ocurre un evento en la animacin
@Override
public void onAnimationCancel(Animator animation) {

Toast.makeText(this, Se cancela la animacin,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationEnd(Animator animation) {

Toast.makeText(this, Finaliza la animacin,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationRepeat(Animator animation) {}
@Override
public void onAnimationStart(Animator animation) {

Toast.makeText(this, Empieza la animacin,
Toast.LENGTH_SHORT).show();
}
} // end clase

El cdigo fuente anterior utiliza la clase ObjectAnimator aprovechando sus mtodos ofFloat
y ofInt para cambiar la opacidad (propiedad alpha) y mover la etiqueta (propiedad x)

87

Aula Mentor

respectivamente.
Mediante la clase AnimatorSet y su mtodo playSequentially() hemos ejecutado
dos animaciones de forma secuencial que llevan a cabo un fundido a negro y a blanco.
Tambin hemos desarrollado una animacin desde un fichero XML mediante la orden
AnimatorInflater.loadAnimator(this, R.anim.fundidos). El fichero fundidos.xml se
ha descrito anteriormente.
La clase PropertyValuesHolder mueve la etiqueta en el eje X e Y con el mtodo
ofFloat().
De forma similar, la clase ViewPropertyAnimator desplaza la etiqueta con los mtodos
x() e y().
En el botn sptimo hemos utilizado la subclase ArgbEvaluator de TypeEvaluator
para cambiar los colores de fondo de la etiqueta.
La clase KeyFrames desarrolla una animacin de tipo fotograma mediante sus mtodos
ofFloat() de la propiedad alpha de la etiqueta.
Finalmente, se crea un listener del tipo AnimationListener para detectar cundo la
animacin se inicia, se para o se repite, y mostrar al usuario un mensaje de tipo Toast.
El resto de mtodos son intuitivos y se ha explicado en la parte terica de este apartado,
te sugerimos que le eches un vistazo detallado al cdigo fuente completo.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (API Animaciones) de la


Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la API de Animacin de Android.
88
Si ejecutas en Eclipse DT este Ejemplo 6 en el AVD, vers que se muestra la siguiente aplicacin:

U1 Multimedia y Grficos en Android

Recomendamos al alumno que pruebe en Eclipse ADT el efecto de animacin que se produce
sobre la etiqueta superior cuando pulsa sobre los diferentes botones.

5.4 Interpolators (Interpoladores)


La clase Interpolator permite controlar cmo se acelera o desacelera una animacin. El
listado completo de interpoladores es el siguiente:
- AccelerateDecelerateInterpolator: la animacin comienza y termina desacelerando el
movimiento. La aceleracin se produce cuando la animacin se encuentra a la mitad.
- AccelerateInterpolator: la animacin acelera de forma continua hasta que termina.
- AnticipateInterpolator: la animacin comienza volviendo hacia atrs y termina con una
aceleracin de forma continua.
- AnticipateOvershootInterpolator: la animacin comienza volviendo hacia atrs y
avanza ms all del punto final, terminando en este punto final.
- BounceInterpolator: la animacin acelera de forma continua hasta que llega al final y
termina con unos botes.
- CycleInterpolator: la animacin se acelera en forma de ciclo a lo largo de los valores de
cambio definidos.
- DecelerateInterpolator: la animacin desacelera de forma continua hasta que termina.
- LinearInterpolator: la animacin cambia de forma continua sin acelerar.
- OvershootInterpolator: la animacin comienza acelerando y avanza ms all del punto
final, terminando en este punto final.

En esta Unidad puedes encontrar el vdeo API de Animacin - Interpoladores,


que muestra de manera visual cmo cambia una animacin en funcin del
interpolador aplicado.
En el Ejemplo 7 de esta Unidad hemos desarrollado una sencilla aplicacin que utiliza
interpoladores.
Si abres el archivo res/layout/main_layout.xml advertirs que hemos definido una
imagen a la izquierda a la que animaremos mediante un conjunto de botones que llaman a un
interpolador diferente.
Si abres los archivos de la carpeta res/anim/ advertirs que hemos definido un conjunto
de animaciones que trasladan la imagen de arriba abajo con los mimos parmetros mediante el
atributo translate. Sin embargo, mediante el atributo android:interpolator hemos indicando que se aplique un interpolador distinto en cada una de ellas.
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin
observars que contiene las siguientes sentencias:
public class MainActivity extends Activity

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);

// Definimos las animaciones a partir de archivos XML. Estas


// animaciones mueven siempre la imagen de arriba a abajo pero

89

Aula Mentor

//cambian el interpolador.
final Animation animAccelerateDecelerate =

AnimationUtils.loadAnimation(this,
R.anim.accelerate_decelerate);
final Animation animAccelerate =
AnimationUtils.loadAnimation(this, R.anim.accelerate);
final Animation animAnticipate =

AnimationUtils.loadAnimation(this, R.anim.anticipate);
final Animation animAnticipateOvershoot =

AnimationUtils.loadAnimation(this,
R.anim.anticipate_overshoot);
final Animation animBounce = AnimationUtils.loadAnimation(this,
R.anim.bounce);
final Animation animCycle = AnimationUtils.loadAnimation(this,
R.anim.cycle);
final Animation animDecelerate =

AnimationUtils.loadAnimation(this, R.anim.decelerate);
final Animation animLinear = AnimationUtils.loadAnimation(this,
R.anim.linear);
final Animation animOvershoot = AnimationUtils.loadAnimation(this,
R.anim.overshoot);
// Buscamos la imagen que vamos a animar
final ImageView imagen = (ImageView)findViewById(R.id.imagen);
// Botones de la interfaz de usuario
Button botonAccelerateDecelerate =

(Button)findViewById(R.id.acceleratedecelerate);
Button botonAccelerate = (Button)findViewById(R.id.accelerate);
Button botonAnticipate = (Button)findViewById(R.id.anticipate);
Button botonAnticipateOvershoot =

(Button)findViewById(R.id.anticipateovershoot);
Button botonBounce = (Button)findViewById(R.id.bounce);
Button botonCycle = (Button)findViewById(R.id.cycle);
Button botonDecelerate = (Button)findViewById(R.id.decelerate);
Button botonLinear = (Button)findViewById(R.id.linear);
Button botonOvershoot = (Button)findViewById(R.id.overshoot);

90

// Cada botn inicia la animacin que corresponde teniendo en


// cuenta el interpolador
botonAccelerateDecelerate.setOnClickListener(new
Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animAccelerateDecelerate);

}
});
botonAccelerate.setOnClickListener(new Button.OnClickListener(){
@Override

public void onClick(View arg0) {
imagen.startAnimation(animAccelerate);

}
});

botonAnticipate.setOnClickListener(new Button.OnClickListener(){
@Override

U1 Multimedia y Grficos en Android


public void onClick(View arg0) {

imagen.startAnimation(animAnticipate);

}
});
botonAnticipateOvershoot.setOnClickListener(new
Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animAnticipateOvershoot);

}
});
botonBounce.setOnClickListener(new Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animBounce);

}
});
botonCycle.setOnClickListener(new Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animCycle);

}
});
botonDecelerate.setOnClickListener(new Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animDecelerate);

}
});
botonLinear.setOnClickListener(new Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animLinear);

}
});
botonOvershoot.setOnClickListener(new Button.OnClickListener(){

@Override

public void onClick(View arg0) {

imagen.startAnimation(animOvershoot);

}
});
} // end onCreate
} // end clase

Como puedes observar, el cdigo anterior carga las animaciones a partir de archivos XML y asocia
a cada botn la animacin de la imagen que corresponde teniendo en cuenta el interpolador.

91

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (API Interpoladores) de la


Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado distintos Interpolators de Android.

Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente
aplicacin:

92

6. Vista de tipo Superficie (ViewSurface)


El tipo de Vista Superficie (clase SurfaceView) es muy importante
para dibujar grficos en 2D y 3D mediante la librera OPENGL y,
tambin, para desarrollar juegos, ya que permite al programador
controlar completamente lo que muestra la interfaz de usuario e
interaccionar con ella. No obstante, hay que tener en cuenta que
requiere ms esfuerzo programar interfaces desde cero utilizando
este tipo de Vista.

U1 Multimedia y Grficos en Android

6.1 Arquitectura de Grficos en Android


A continuacin, mostramos la arquitectura de grficos de Android exponiendo los pasos que el
sistema operativo ejecuta cuando un usuario abre una aplicacin:

93

- El usuario ejecuta una nueva aplicacin que, a su vez, crea la Actividad principal.
- El sistema operativo crea una nueva ventana para esa Actividad y la registra en el gestor de
ventanas WindowManagerService.
- Android crea una nueva superficie para la ventana y la devuelve a la Actividad. Para ello, usa
el Compositor de pantalla (Screen Compositor) denominado SurfaceFlinger.
- La aplicacin dibuja jerrquicamente las Vistas de la Actividad (TextView, Button, ImageView, etctera) en la superficie.
- Finalmente, se componen todas las superficies visibles en la pantalla del dispositivo, es decir,
la de la Actividad que aparece en la pantalla y la de la barra de notificacin.

6.2 Qu es la clase ViewSurface?


ViewSurface (Vista de tipo Superficie) proporciona una superficie de dibujo dedicado que se
incrusta dentro de una jerarqua de vistas.
Es posible dibujar el contenido de esta superficie, su posicin y su tamao en la pantalla
del dispositivo.
Esencialmente, la clase ViewSurface es una Vista que contiene una Superficie de Android (Surface) que, a su vez, es un buffer en crudo de datos y que utiliza el Compositor de
pantalla de Android, que es el encargado de dibujar toda la interfaz de usuario en Android.

Aula Mentor

Veamos algunas diferencias entre las clases View y SurfaceView:


- Todas las Vistas (Views) de una aplicacin se dibujan en el mismo hilo principal de la interfaz de usuario, que tambin se usa para gestionar la interaccin con l.
- Sin embargo, es posible dibujar la clase SurfaceView desde varios hilos diferentes al principal.
- Un objeto Surfaceview no puede ser transparente; sin embargo, puede estar detrs de otros
elementos en la jerarqua de vistas.
- Si queremos utilizar animaciones, un objeto SurfaceView se ejecuta ms rpido que una
Vista (View); por lo tanto, es recomendable utilizar el primero.
- Sin embargo, nada es gratis en esta vida: un objeto del tipo Surfaceview consume ms
recursos (CPU y batera) que View.
En el Ejemplo 8 de este Unidad vamos a mostrar cmo desarrollar una sencilla aplicacin en
Android que utiliza la clase SurfaceView. En ella se presenta un juego sencillo de una pelota
que rebota en toda la pantalla del dispositivo y, si el usuario pulsa sobre ella, cambia el sentido
de su movimiento.
Es recomendable abrir el Ejemplo 8 en Eclipse ADT para entender la explicacin siguiente.

Fjate que en el cdigo fuente de esta aplicacin no hemos incluido ningn archivo
Layout que defina la interfaz de usuario.
94

La interfaz de usuario de la aplicacin se compone de una Actividad que crea una superficie y
la asigna a la interfaz de usuario. Veamos su contenido en el fichero MainActivity.java:
public class MainActivity

extends Activity {

// Evento onCreate de la Actividad


public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);

// La ventana de esta Actividad no tiene ttulo
requestWindowFeature(Window.FEATURE_NO_TITLE);

//Ocultamos la barra de notificaciones

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN);
// El contenido de esta Actividad es el objeto de la clase
// SuperficieView
setContentView(new SuperficieView(this));
} // end onCreate
}

En el cdigo anterior hemos utilizado la orden requestWindowFeature() para ocultar el ttulo


de la aplicacin y ampliar el espacio que ocupa la superficie. Tambin definimos el atributo
FLAG_FULLSCREEN que muestra la pantalla completa (sin barra de notificacin) con el mtodo
setFlags() de la ventana (Window). As, el usuario disfruta de toda la pantalla del dispositivo
y puede jugar sin distracciones.
Aunque es posible definir la interfaz de usuario mezclando layout y superficie, en este
ejemplo establecemos directamente el contenido de esta Actividad creando un objeto de la clase
SuperficieView. Es decir, no definimos un layout.

U1 Multimedia y Grficos en Android

A continuacin, se muestra cmo se implementa la interfaz visual del usuario mediante esta
superficie definida en el fichero SuperficieView.java:
// Clase que se extiende de SurfaceView e implementa el mtodo
// Callback de SurfaceHolder
public class SuperficieView extends SurfaceView implements
SurfaceHolder.Callback {
// Hilo que se encarga de actualizar la superficie
private BucleThread bucleThread;
// Constructor de la Superficie
public SuperficieView(Context context) {
super(context);
// Creamos el hilo
bucleThread = new BucleThread(this);
// Obtenemos el Holder de la superficie y le asignamos los

// eventos definidos en esta clase
getHolder().addCallback(this);
// Delegamos el evento onTouch al Hilo

setOnTouchListener(new View.OnTouchListener() {

public boolean onTouch(View v, MotionEvent event) {
if(bucleThread!=null) {
return bucleThread.onTouch(event);
}

else return false;
}

}); // end onTouch
} // end onCreate
// Si la superficie cambia, entonces guardamos el tamao de la
// pantalla
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int

width, int height) {
// Indicamos el nuevo tamao de la superficie al hilo
bucleThread.setSurfaceSize(width, height);
}
// Cuando la superficie se ha creado, marcamos que se ejecuta el
// hilo y lo iniciamos
@Override
public void surfaceCreated(SurfaceHolder holder) {
bucleThread.setRunning(true);
bucleThread.start();
}
// Si la superficie se destruye, entonces paramos el hilo
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean reintentar = true;
bucleThread.setRunning(false);
// Mientras el hilo est activo lo intentamos parar
while (reintentar) {
try {
// Unimos el Hilo de actualizacin con el Hilo

// principal de la aplicacin invocando la orden

95

Aula Mentor

// join hasta que lo conseguimos. Es necesario unir


// todos los hilos para destruirlos correctamente.
bucleThread.join();
reintentar = false;
} catch (InterruptedException e) { }

} // end clase superficie

Veamos ahora cmo acceder a la superficie subyacente de toda la Actividad.


Para utilizar una superficie debemos crear una clase que se extienda de SurfaceView e
implemente la interfaz SurfaceHolder.Callback de Android para poder gestionar los mtodos relacionados con esta superficie:
- surfaceCreated(): ocurre cuando se crea la superficie.
- surfaceDestroyed(): se lanza cuando la superficie ya no es necesaria y hay que liberarla
de la memoria.
- surfaceChanged(): ocurre cuando la superficie cambia, por ejemplo, su tamao si el usuario cambia la posicin de la pantalla.
- onTouchEvent(): se lanza cuando el usuario toca la pantalla.
- onDraw(): se lanza cuando es necesario dibujar la superficie.

96

En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
getHolder().addCallback(this) para gestionar los eventos del SurfaceView y poder
acceder a la superficie.
En este caso hemos implementado algunos de los eventos anteriores. Veamos las sentencias ms novedosas de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos
creado en el constructor de esta clase y al que hemos pasado como parmetro la referencia del
surfaceholder. Despus, establecemos el estado de ejecucin del hilo y lo arrancamos con la
orden start(). La funcin de este hilo es actualizar la interfaz de usuario dibujndola cuando
lo consideremos necesario.
El mtodo SurfaceDestroyed() se lanza cada vez que la aplicacin pasa a segundo
plano y el SurfaceView se va a destruir junto con todo su contenido. Por lo tanto, es necesario
parar el hilo. Para ello, unimos el Hilo de actualizacin con el Hilo principal de la aplicacin
invocando la orden join hasta que lo conseguimos. Es necesario unir todos los hilos para destruirlos correctamente.
Es imprescindible que la interaccin del usuario con la aplicacin sea suave y
los grficos se muestren sin parpadeos. Para conseguirlo, tenemos que ejecutar dos hilos
en la aplicacin: el hilo principal, que se encarga de gestionar la Actividad de la aplicacin, y el
segundo hilo, que dibuja la superficie y gestiona la interaccin del usuario con sta.
La razn principal de esta divisin de tareas est en que, como estudiamos en el curso
de Iniciacin a Android, no debemos bloquear nunca el hilo principal de una aplicacin. Por
ejemplo, si el usuario presiona la pantalla tctil puede crear paradas en la ejecucin del cdigo.

Es muy importante separar siempre el cdigo en dos o ms hilos: el hilo principal


de la aplicacin y un segundo o tercer hilo que se encargan de dibujar las superficies, para que el usuario obtenga una sensacin ms fluida del movimiento e
interaccin con la superficie.

U1 Multimedia y Grficos en Android

Hemos implementado el Hilo BucleThread para actualizar el dibujo del objeto SurfaceView
y gestionar los toques sobre ste. En el constructor de este hilo le pasamos como parmetro la
referencia (this) al objeto SuperficieView para poder modificar el contenido de sta.
Por esto, en el mtodo surfaceChanged() del SuperficieView obtenemos el tamao
de la pantalla del dispositivo Android y se lo pasamos al hilo BucleThread.
Adems, en el constructor de esta clase, hemos delegado el mtodo onTouchEvent() en
este Hilo mediante la sentencia setOnTouchListener().
Finalmente, no hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo
quien se encargue de dibujarla.
A continuacin, se muestra cmo se implementa este hilo en el archivo BucleThread.
java:
// Clase que se extiende de la clase Thread (Hilo)
public class BucleThread extends Thread {
// Nmero de actualizaciones por segundo que hace el hilo
static final long FPS = 10;
// Variable donde guardamos la superficie
private SuperficieView superfView;
// Variables que almacenan el ancho y largo de la pantalla
private int width, height;
// Sirve para saber si se est ejecutando el hilo
private boolean running = false;
// Posicin actual de la pelota
private int pos_x = -1;
private int pos_y = -1;
// Velocidad del movimiento
private int xVelocidad = 10;
private int yVelocidad = 5;
// Variables que indican las coordenadas donde el usuario toc la
// pantalla
public int touched_x, touched_y;
// Indica si se est tocando la pantalla o no
public boolean touched;
// Bitmap donde cargamos la imagen de la pelota
private BitmapDrawable pelota;
// Constructor que guarda la superficie
public BucleThread(SuperficieView view) {

this.superfView = view;

// Buscamos la imagen pelota
pelota = (BitmapDrawable) view.getContext().

getResources().getDrawable(R.drawable.pelota);
}
// Mtodo para establecer la variable running
public void setRunning(boolean run) {

running = run;
}
// Mtodo tpico de un hilo
@Override
public void run() {

// N de actualizaciones que debemos hacer cada segundo

long ticksPS = 1000 / FPS;

97

Aula Mentor

98


// Variables temporales para controlar el tiempo

long startTime;

long sleepTime;

// Mientras estemos ejecutando el hilo

while (running) {

Canvas canvas = null;

// Obtenemos el tiempo actual

startTime = System.currentTimeMillis();
try {

// Bloqueamos el canvas de la superficie para dibujarlo

canvas = superfView.getHolder().lockCanvas();
// Sincronizamos el mtodo draw() de la superficie para

// que se ejecute como un bloque

synchronized (superfView.getHolder()) {

if (canvas!=null)
doDraw(canvas);

}

} finally {

// Liberamos el canvas de la superficie desbloquendolo

if (canvas != null) {
superfView.getHolder().unlockCanvasAndPost(canvas);

}
}

// Tiempo que debemos parar la ejecucin del hilo

sleepTime = ticksPS - System.currentTimeMillis()-startTime;

// Paramos la ejecucin del hilo
try {

if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);

} catch (Exception e) { }
} // end while
} // end run()
// Evento que se lanza cada vez que es necesario dibujar la
// superficie
protected void doDraw(Canvas canvas) {
// Primera posicin de la pelota en el centro
if (pos_x<0 && pos_y <0) {

pos_x = this.width/2;

pos_y = this.height/2;
} else {
// La nueva posicin es la posicin anterior + la velocidad en
// cada coordenada X e Y
pos_x += xVelocidad;
pos_y += yVelocidad;
// Si el usuario ha tocado la pelota cambiamos el sentido de
// la misma
if (touched && touched_x>(pos_x-pelota.getBitmap().getWidth())

&& touched_x<(pos_x+pelota.getBitmap().getWidth())

&& touched_y>(pos_y-pelota.getBitmap().getHeight())

&& touched_y<(pos_y+pelota.getBitmap().getHeight()))
{

U1 Multimedia y Grficos en Android

//
//
//
if

touched=false;
xVelocidad = xVelocidad*-1;
yVelocidad = yVelocidad*-1;
Si pos_x es mayor que el ancho de la pantalla teniendo en
cuenta el ancho de la pelota o la nueva posicin es < 0
entonces cambiamos el sentido de la pelota
((pos_x > this.width - pelota.getBitmap().getWidth()) ||
(pos_x < 0)) {
xVelocidad = xVelocidad*-1;

}
// Si pos_y es mayor que el alto de la pantalla teniendo en

// cuenta el alto de la pelota o la nueva posicin es < 0
// entonces cambiamos el sentido de la pelota
if ((pos_y > this.height - pelota.getBitmap().getHeight()) ||
(pos_y < 0)) {
yVelocidad = yVelocidad*-1;
}
}
// Color gris para el fondo de la aplicacin
canvas.drawColor(Color.LTGRAY);
// Dibujamos la pelota en la nueva posicin
canvas.drawBitmap(pelota.getBitmap(), pos_x, pos_y, null);
}
// Evento que se lanza cuando el usuario hace clic sobre la
// superficie
public boolean onTouch(MotionEvent event) {
// Obtenemos la posicin del toque
touched_x = (int) event.getX();
touched_y = (int) event.getY();
// Obtenemos el tipo de Accion
int action = event.getAction();
Log.e(Toque (X,Y), ( + touched_x + , + touched_y + ));

switch (action) {
// Cuando se toca la pantalla
case MotionEvent.ACTION_DOWN:
Log.e(TouchEven ACTION_DOWN, Usuario toca la pantalla );
touched = true;
break;
// Cuando se desplaza el dedo por la pantalla
case MotionEvent.ACTION_MOVE:
touched = true;
Log.e(TouchEven ACTION_MOVE, Usuario desplaza dedo por la
pantalla );
break;
// Cuando levantamos el dedo de la pantalla que estbamos
// tocando
case MotionEvent.ACTION_UP:
touched = false;
Log.e(TouchEven ACTION_UP, Ya no tocamos la pantalla);
break;
// Cuando se cancela el toque. Es similar a ACTION_UP

99

Aula Mentor

case MotionEvent.ACTION_CANCEL:
touched = false;
Log.e(TouchEven ACTION_CANCEL, );
break;
// El usuario ha tocado fuera del rea de la interfaz del
// usuario
case MotionEvent.ACTION_OUTSIDE:
Log.e(TouchEven ACTION_OUTSIDE, );
touched = false;
break;
default:
}
return true;
} // end onTouch
// Se usa para establecer el nuevo tamao de la superficie
public void setSurfaceSize(int width, int height) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (superfView) {
// Guardamos el nuevo tamao
this.width = width;
this.height = height;
}
}

100

} // end clase

El cdigo anterior es un hilo tpico de Java con su correspondiente mtodo run(). En


ste puedes ver que bloqueamos el canvas de la superficie para dibujarlo con la orden
superfView.getHolder().lockCanvas() y, cuando es necesario, sincronizamos el mtodo
local doDraw(), que dibuja la pelota y actualiza la interfaz del usuario. Una vez hemos ejecutado
este bloque de cdigo, liberamos el canvas de la superficie desbloquendolo con la orden
unlockCanvasAndPost(canvas).
Para simular el movimiento de la imagen hemos creado un temporizador mediante la
sentencia sleep(), que para la ejecucin de este hilo durante el tiempo establecido en la contante FPS (Frames Per Second = Fotogramas por segundos).
El mtodo doDraw() especifica lo que se debe dibujar en la pantalla del dispositivo
utilizando la clase Canvas (lienzo o zona de dibujo que ya hemos estudiado) y la orden drawBitmap() dibujamos una pelota a partir de una imagen almacenada como recurso en la aplicacin, teniendo en cuenta las dimensiones de la pantalla y hacia dnde se debe mover. Adems,
tambin se tiene en cuenta si el usuario ha hecho clic cerca de la pelota y, en ese caso, se cambia
el sentido del movimiento de esta pelota.
El mtodo onTouch() sirve para interactuar con la pulsacin del usuario en la pantalla.
Este mtodo tiene como parmetro la clase MotionEvent que permite conocer la accin del
usuario mediante la orden getAction(). As, toma los siguientes valores en funcin del comportamiento del usuario:
- MotionEvent.ACTION_DOWN: cuando toca la pantalla.
- MotionEvent.ACTION_MOVE: cuando desplaza el dedo por la pantalla.
- MotionEvent.ACTION_UP: cuando levanta el dedo de la pantalla que estaba tocando.
- MotionEvent.ACTION_CANCEL: cuando se cancela el toque. Es similar a ACTION_UP.
- MotionEvent.ACTION_OUTSIDE: cuando ha tocado fuera del rea de la interfaz del usuario.

U1 Multimedia y Grficos en Android

En la teora de la Unidad 5 puedes encontrar una descripcin detallada de la gestin de pulsaciones del usuario sobre la pantalla.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 8 (Vista de Superficie) de la


Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado un elemento SurfaceView.

Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:

101

Puedes probar a hacer clic sobre la bola para comprobar que su movimiento cambia de sentido.
Si instalas este sencillo juego en un dispositivo real vers que es ms sencillo probar la aplicacin.

7. Grficos en 3D en Android
Android dispone de dos bibliotecas para pintar grficos en 3D:
- OpenGL: biblioteca estndar de diseo 3D.
- RenderScript: nueva biblioteca de bajo nivel de dibujo grfico en 3D disponible desde la
versin 3.0 de Android que aprovecha el procesador GPU de la tarjeta grfica para pintar
dejando as al procesador principal del dispositivo capacidad disponible para otras tareas.
Estas grficas se desarrollan con un lenguaje especfico llamado C99.

Aula Mentor

7.1 OpenGL
En este apartado vamos a aprender a aplicar los conceptos bsicos de la
biblioteca grfica ms utilizada y difundida: OpenGL.
La biblioteca OpenGL (Open Graphics Library) es una especificacin estndar de una API
multilenguaje y multiplataforma que permite escribir aplicaciones que pinten grficos en 2D
y en 3D. Contiene ms de 250 funciones diferentes que pueden usarse para dibujar grficos
tridimensionales complejos a partir de primitivas geomtricas simples, tales como puntos, lneas
y tringulos.
Android soporta las siguientes versiones:
- OpenGL 2.0 ES: a partir de la versin 2.2 de Android.
- OpenGL 1.1 ES: en todas las versiones de Android.
Empezaremos diseando grficos sencillos en Android para acabar aumentando la complejidad
y llegar a dibujar grficos en 3D con texturas.

102

Las clases bsicas de esta biblioteca son las siguientes:


- GLSurfaceView: clase base que permite escribir las aplicaciones que usen OpenGL. Esta
clase se extiende de SurfaceView que hemos estudiado en el apartado anterior. As, segn
lo estudiado en el apartado anterior, podramos utilizar un objeto GLSurfaceView como
parmetro del mtodo setContentView() para dibujar la interfaz de la Actividad.
- GLSurfaceView.Renderer: interfaz (interface) de dibujo (renderizado) genrica donde
se detalla el cdigo que indica lo que se debe dibujar. Como ya sabes, al implementar una
interfaz, es necesario implementar todos sus mtodos, que en este caso son los siguientes:
onSurfaceCreated(): mtodo invocado cuando se crea o se recrea la superficie
(surface). Como este mtodo se invoca cuando comienza el renderizado, es un buen
sitio para definir lo que no cambiar durante el ciclo del renderizado, por ejemplo, el
color del fondo, etctera.
onDrawFrame(): mtodo que se invoca cada vez que es necesario dibujar sobre la
superficie.
onSurfaceChanged(): mtodo llamado cuando la superficie cambia de alguna manera, por ejemplo, al girar el mvil.

Para entender de forma grfica estas dos clases anteriores, podemos hacer una
analoga con un pintor y su lienzo. En este caso, el pintor es la interfaz GLSurfaceView.Renderer y el lienzo es GLSurfaceView.

Por compatibilidad entre las distintas versiones de Android vamos a utilizar la versin 1.0 de la biblioteca OpenGL para desarrollar los ejemplos del curso.

7.1.1 Conceptos bsicos de geometra


Antes de empezar a dibujar grficos es importante recordar los siguientes conceptos bsicos

U1 Multimedia y Grficos en Android

de geometra:
- Lnea o Recta: conjunto de puntos que se extienden con determinada longitud.
- Vrtice: punto donde dos o ms rectas se encuentran. Veamos unos ejemplos sobre cmo
utilizar vrtices para definir un punto o polgonos en Android:
Punto
// Punto ubicado en la coordenada (1,1,0)
float vertice[] = { 1f ,1f ,0f };

Cuadrado

float vertices[] = {
-1f, 1f, 0f,//
-1f, -1f, 0f, //
1f, -1f, 0f, //
1f,
1f, 0f//
};

vrtice
vrtice
vrtice
vrtice

ubicado
ubicado
ubicado
ubicado

en
en
en
en

(-1,1,0)
(-1,-1,0)
(1,-1,0)
(1,1,0)

vrtice
vrtice
vrtice
vrtice

ubicado
ubicado
ubicado
ubicado

en
en
en
en

(-1,-1,0)
(1,-1,0)
(0,0.8,0)
(0, 0, 2)

Pirmide de base triangular

float vertices[] = {
-1f, -1f, 0f, //
1f, -1f, 0f, //
0f, 0.8f, 0f, //
0f, 0f, 2f //
};

Como puede observar, para definir vrtices, simplemente debemos indicar las coordenadas
(x,y,z) en una matriz de tipo float.
- Cara: cada lado que forma un polgono. La modificacin de una cara afecta a sus vrtices y
a sus aristas.
- Arista o Borde: unin de dos vrtices. En un modelo 3D, la arista puede estar compartida
por dos caras o polgonos.
- Polgono: figura cerrada que est formada por tres o ms caras que se unen en sus aristas.
- Eje de coordenadas: tipo de coordenadas ortogonales usadas en espacios euclidianos caracterizadas por que usa como referencia ejes ortogonales entre s que se cortan en un punto
origen. En el espacio 3D estos ejes tienen el aspecto que se refleja en el grfico inferior. En
el dispositivo Android, X e Y se encuentran en los ejes planos horizontal y vertical. El eje
Z es un eje abstracto perpendicular a su pantalla, que en grficos 3D se usa para crear la
sensacin de lejana o cercana del grfico respecto al observador. Cuanto ms lejos est un
objeto respecto al observador, menor ser su valor en el eje Z.

103

Aula Mentor

7.1.2 Conceptos bsicos de OpenGL


- Framebuffer: parte de la memoria donde se dibuja una escena. El tamao en memoria

viene dado por la siguiente frmula: Tamao total en memoria = Ancho pantalla x Alto
pantalla x Nmero de canales x bytes por muestra.
- Primitivas de OpenGL: funciones que permiten dibujar puntos, lneas, tringulos, etctera.
En general, es recomendable utilizar tringulos para dibujar figuras complejas, ya que esta
biblioteca es ms eficiente as y el grfico aparece en menos tiempo.
- Estados de OpenGL: OpenGL guarda informacin sobre diferentes flags que determinan
qu factores y qu valores afectarn a la hora de dibujar. Cada vez que cambiamos el estado
mediante la llamada a una funcin de la API debemos tener en cuenta que el estado se quedar as hasta que lo volvamos a cambiar de nuevo. Por ejemplo, la funcin glColor cambia
el color del pincel de todas las funciones primitivas que utilicemos desde ese momento en
adelante. Podemos usar glEnable y glDisable para habilitar y deshabilitar propiedades de
OpenGL segn las necesidades.
- Buffer de profundidad o Z-Buffer: normalmente, en grficos en 3D interesa que OpenGL
tenga en cuenta qu objetos estn delante o detrs de otros (eje Z de perspectiva) a la hora
de pintarlos. El buffer de profundidad (del ingls, DepthBuffer) o Z-Buffer almacena el
valor de profundidad de cada pxel de la imagen en un buffer separado que se usa para
descartar pxeles que quedan detrs de objetos ya existentes en la imagen.
- Sistema de coordenadas: en la biblioteca OpenGL utilizaremos el sistema de coordenadas
ortogonal que hemos visto anteriormente al que aadiremos otra coordenada nueva que
indica la perspectiva (desde donde miramos el objeto). As, el sistema de coordenadas inicial
de OpenGL puede representarse con esta matriz:
104

Transformaciones: funciones que cambian la posicin del objeto relativa al centro de la figura
y pueden trasladarla, escalarla y rotarla. Matemticamente, estas transformaciones multiplican la
matriz anterior de coordenadas por otra matriz que es la que genera la transformacin. Veamos
unos ejemplos grficos:
Esquema

Descripcin
La orden glTranslatef(0.1, 0.1,
0) mueve el objeto en el eje X,Y de
coordenadas.

U1 Multimedia y Grficos en Android

Esquema

Descripcin
La orden glRotatef(-45, 0, 0, 1)
rota el objeto. Los ngulos positivos
rotan al contrario que el movimiento
de las agujas del reloj.
La orden glScalef(2,
2,
aumenta el tamao del objeto.

1)

Por ejemplo, al trasladar un objeto dos unidades en el eje X, se genera la siguiente matriz de
transformacin:

Si aplicamos esta transformacin a la matriz anterior, nos quedar que la nueva matriz de transformacin es:

Si ahora dibujamos el punto (1, 0, 0) teniendo en cuenta que la matriz de transformacin indica
un desplazamiento de dos unidades en eje X, el punto deber dibujarse en la posicin (3,0,0).
Para esto, se multiplica el punto por la matriz de transformacin:

- Matriz de visualizacin/modelado: matriz que guarda la transformacin de los objetos una


vez aplicada la transformacin, es decir, es la ltima matriz anterior.
- Matriz de proyeccin: matriz que guarda la informacin relativa a la perspectiva a travs de
la cual el usuario visualiza la escena.
Al realizar operaciones que modifiquen alguna de estas dos ltimas matrices, tendremos que

105

Aula Mentor

cambiar al modo de matriz correspondiente, para que las operaciones afecten a esa matriz
especficamente.
- Modo de dibujo: OpenGL dispone de distintas formas de pintar:
GL_POINTS: dibuja nicamente los vrtices.
GL_LINES: dibuja las lneas entre dos vrtices independientemente de otros vrtices.
GL_LINE_STRIP: dibuja las lneas conectadas entre s por un vrtice.
GL_LINE_LOOP: pinta de la misma forma que el anterior mtodo, pero une el punto
final con el punto inicial.
GL_POLYGON: dibuja una superficie de tipo polgono.
GL_TRIANGLES: dibuja superficies mediante tringulos usando tres puntos de referencia.
GL_TRIANGLE_STRIP: pinta superficies mediante tringulos continuos usando tres
puntos de referencia
GL_TRIANGULE_FAN: pinta superficies mediante tringulos usando un punto en comn, a modo de abanico.
GL_SQUADS: dibuja cuadrilteros de cuatro vrtices.
GL_QUAD_STRIP: dibuja cuadrilteros continuos de cuatro vrtices.
Como una imagen vale ms que mil palabras, veamos esquemticamente estos mecanismos:

106

U1 Multimedia y Grficos en Android

OpenGL es una biblioteca muy compleja y requiere conocimientos previos por


parte del alumno o alumna para utilizarla con soltura. Existen multitud de cursos
especficos donde puede adquirirlos. De todas formas, se explicarn las funciones
bsicas de esta biblioteca, si bien no es objeto de este apartado estudiar la biblioteca OpenGL en s misma.

Una vez explicados los conceptos bsicos de OpenGL, vamos a desarrollarlos mediante ejemplos prcticos, pues es ms sencillo y pedaggico entender en la prctica cmo se utiliza esta
biblioteca.

7.2 Grficos en 2D
En el Ejemplo 9 de esta Unidad hemos dibujado los polgonos tringulo y cuadrado en 2D. Es
recomendable abrir el Ejemplo 9 en Eclipse ADT para entender la explicacin siguiente.
Si abres el archivo res/layout/main_layout.xml, advertirs que hemos definido una
barra de botones que permitirn al usuario elegir el polgono que desea visualizar:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android

android:layout_width=fill_parent

android:layout_height=fill_parent>








<LinearLayout
android:id=@+id/linearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=horizontal
android:gravity=top
android:layout_marginTop=1dp
android:layout_marginBottom=1dp>

<Button
android:id=@+id/cuadrado
android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Ver cuadrado />
<Button
android:id=@+id/triangulo
android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Ver tringulo />
<CheckBox

android:id=@+id/cbColorear

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Colorear />

</LinearLayout>

107

Aula Mentor

<FrameLayout
android:id=@+id/frame

android:layout_width=fill_parent

android:layout_height=fill_parent

android:layout_below=@+id/linearLayout>
</FrameLayout>

</RelativeLayout>

En el diseo de la interfaz de usuario anterior hemos incluido la etiqueta FrameLayout, que


sirve de contenedor de otras vistas, como los grficos que incluiremos dentro de ste.
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin,
observars que contiene las siguientes sentencias:
public class MainActivity extends Activity {
// Superficie de OpenGL
private GLSurfaceView glSurface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);

108

// Creamos la instancia de la superficie de OpenGL


glSurface = new GLSurfaceView(this);
// Definimos el Renderer de la superficie indicando que se

// dibuje un tringulo sin color
glSurface.setRenderer(new

MiRenderer(MiRenderer.MOSTRAR_TRIANGULO, false));

// Buscamos el FrameLayout para aadirle la superficie anterior

final FrameLayout frame = (FrameLayout)findViewById(R.id.frame);

frame.addView(glSurface, 0);

// Buscamos el resto de Vistas de la Actividad

final CheckBox conColor = (CheckBox)

findViewById(R.id.cbColorear);

final Button boton_triang = (Button)

findViewById(R.id.triangulo);

// Creamos el evento onClick de los botones

boton_triang.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {

// Recreamos la superficie con el polgono tringulo e

// indicamos si es necesario aadir el color
glSurface = new GLSurfaceView(MainActivity.this);
glSurface.setRenderer(new
MiRenderer(MiRenderer.MOSTRAR_TRIANGULO,
conColor.isChecked()));
// Quitamos las Vistas del Frame y aadimos la nueva
// superficie
frame.removeAllViews();
frame.addView(glSurface, 0);
}

});

final Button boton_cuadrado = (Button)

U1 Multimedia y Grficos en Android


findViewById(R.id.cuadrado);
boton_cuadrado.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {

// Recreamos la superficie con el polgono cuadrado e

// indicamos si es necesario aadir el color

glSurface = new GLSurfaceView(MainActivity.this);

glSurface.setRenderer(new
MiRenderer(MiRenderer.MOSTRAR_CUADRADO,
conColor.isChecked()));

// Quitamos las Vistas del Frame y aadimos la nueva

// superficie

frame.removeAllViews();

frame.addView(glSurface, 0);
}
});

} // end onCreate
// Cuando la Actividad vuelve a primer plano, indicamos a la
// superficie que se actualice de nuevo
@Override
protected void onResume() {
super.onResume();
glSurface.onResume();
}
// Cuando la Actividad pasa a segundo plano, indicamos a la
// superficie que NO se actualice
@Override
protected void onPause() {
super.onPause();
glSurface.onPause();
}
} // end clase

Puedes observar en el cdigo anterior que, para crear los grficos, hemos utilizado la clase
superficie GLSurfaceView de OpenGL a la que asociamos con el mtodo setRenderer() el
Renderer que se encargar de dibujar la imagen en funcin de los parmetros pasados en el
constructor de este mtodo.
Adems, hemos utilizado la Layout del tipo FrameLayout para mostrar en la interfaz
del usuario la superficie creada anteriormente.
Hemos detallado el Renderer anterior en la clase MiRenderer del proyecto, que contiene el siguiente cdigo fuente:
public class MiRenderer implements Renderer {
// Constantes que indican el tipo de polgono que debemos dibujar
public static int MOSTRAR_TRIANGULO=1;
public static int MOSTRAR_CUADRADO=2;
// Guarda el polgono que estamos mostrando
private int mostrar_poligono=-1;
// Objeto del tipo Tringulo que describe su geometra
private Triangulo triangulo;
// Objeto del tipo Cuadrado que describe su geometra

109

Aula Mentor

private Cuadrado cuadrado;


// Constructor de la clase
public MiRenderer(int mostrar_poligono, boolean ver_colores) {

// Guardamos el tipo de polgono que queremos mostrar y

// creamos la geometra correspondiente
this.mostrar_poligono=mostrar_poligono;

if (mostrar_poligono == MOSTRAR_TRIANGULO)

triangulo = new Triangulo(ver_colores);
else

cuadrado = new Cuadrado(ver_colores);
}

110

// Evento que se lanza cuando se crea la superficie.


// Aqu debemos indicar todo aquello que no cambia en la superficie
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Indicamos el modo de sombreado suave
gl.glShadeModel(GL10.GL_SMOOTH);

// Establecemos el color GRIS como fondo de la superficie

gl.glClearColor(0.6f, 0.6f, 0.6f, 0.5f);

// Configura el buffer de profundidad
gl.glClearDepthf(1.0f);

// Modo de renderizado de la profundidad
gl.glEnable(GL10.GL_DEPTH_TEST);

// Funcin de comparacin de la profundidad
gl.glDepthFunc(GL10.GL_LEQUAL);

// Cmo se calcula la perspectiva

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
}
// Evento que se lanza cada vez que es necesario dibujar la
// superficie
public void onDrawFrame(GL10 gl) {

// Cada vez que se dibuja una escena hay que limpiar

// tanto el buffer de color como el de profundidad

gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

// Reinicia la matriz de proyeccin del grfico
gl.glLoadIdentity();

// Movemos la posicin de los ejes en (x,y,z).

// Es decir, alejamos el dibujo en el eje Z dando

// sensacin de profundidad.

gl.glTranslatef(0.0f, 0.0f, -6.0f);

// Dibujamos el polgono solicitado

if (mostrar_poligono == MOSTRAR_TRIANGULO)
triangulo.draw(gl);
else
cuadrado.draw(gl);
} // end onDrawFrame
// Evento que se lanza cuando cambia la superficie
public void onSurfaceChanged(GL10 gl, int width, int height) {

// Nos aseguramos de que no se puede dividir por 0

if(height == 0) {

height = 1;
}

// Reiniciamos la zona de visin

U1 Multimedia y Grficos en Android


gl.glViewport(0, 0, width, height);

// Establecemos la matriz de proyeccin como

// activa para modificarla y poder girar el polgono
gl.glMatrixMode(GL10.GL_PROJECTION);

// Reinicia la matriz de proyeccin del grfico
gl.glLoadIdentity();

// Calculamos el tamao de la perspectiva en funcin del nuevo

// tamao de la pantalla

GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);

// Establecemos de nuevo la matriz de vista/modelo

// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}
} // end clase

En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos:
- Sombreado suave en el dibujo (renderizado) mediante el mtodo glShadeModel().
- Color del fondo gris mediante glClearColor().
- Configurar el tamao del buffer de profundidad mediante glClearDepth() cuando se vaca.
El buffer de profundidad o z-buffer se utiliza para gestionar la visibilidad de varios grficos
3D superpuestos segn las coordenadas de sus pixeles.
- Indicamos el modo de renderizado mediante glEnable(GL10.GL_DEPTH_TEST), que tambin se conoce como algoritmo de z-Buffer porque gestiona qu elementos de una escena
son visibles y cules permanecern ocultos segn sus posiciones en el eje Z (distancia a cmara). En definitiva, lo que se hace es comparar las profundidades de todos los grficos para
representar el objeto ms cercano o lejano al observador segn el parmetro que indiquemos. Es decir, con el modo GL_DEPTH_TEST, los objetos cercanos ocultan a los ms lejanos.
- Especificamos la funcin que se usar para comparar la profundidad de los objetos mediante
el mtodo glDepthFunc(). El parmetro GL_LEQUAL indica que el valor de profundidad se
almacena si la cercana al observador es menor o igual al valor existente.
- Con el mtodo glHint() establecemos, con la propiedad GL_PERSPECTIVE_CORRECTION_
HINT, la calidad del color y la interpolacin de las coordenadas de la textura al valor GL_NICEST, que es la mxima calidad posible.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de profundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). Como
hemos visto, la matriz de proyeccin se utiliza para poder realizar cambios de perspectiva
(giros, translaciones, etctera). Realmente, lo que estamos haciendo es borrar la matriz de
proyeccin dejndola a 0, es decir, no hay transformacin ninguna.
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, alejamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado anteriormente.

111

Aula Mentor

Finalmente, en el mtodo onSurfaceChanged() colocamos las sentencias que se ejecutarn


cuando la superficie sobre la que dibujamos sufra alguna modificacin como la orientacin de
posicin de la pantalla. En este caso:
- Utilizamos glViewPort() para redefinir el ancho y largo de la vista de dibujo (viewport)
teniendo en cuenta el nuevo tamao de la pantalla.
- Usamos el mtodo glMatrixMode() para especificar a qu matriz se le van a aplicar los
cambios posteriores. En este caso, seleccionamos la matriz de proyeccin (GL_PROJECTION).
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity().
- El mtodo gluPerspective() establece el tamao de la perspectiva en funcin del nuevo
tamao de la pantalla. As, conseguimos que el polgono entre dentro de la nueva pantalla.
- Usamos de nuevo el mtodo glMatrixMode() para especificar a qu matriz se le aplican
los cambios posteriores. En este caso, seleccionamos la matriz de vista/modelo (GL_MODELVIEW). Esta matriz de vista/modelo (modelview) se usa para mapear las coordenadas del
polgono utilizando el sistema de coordenadas de OpenGL.
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity().
Veamos ahora cmo hemos definido las clases que describen la geometra de los polgonos.
Empezamos por el tringulo desarrollado en el archivo Triangulo.java:

112

public class Triangulo {


// Indica si deseamos colorear el tringulo
private boolean conColor;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices del tringulo
private FloatBuffer bufferVertices;
// Vrtices del tringulo
private float vertices[] =
{

0.0f, 1.0f, 0.0f,
// Arriba

-1.0f, -1.0f, 0.0f,
// Abajo izquierda

1.0f, -1.0f, 0.0f
// Abajo derecha
};
// Constructor del tringulo
public Triangulo(boolean conColor) {

// Definimos el buffer con los vrtices del polgono.

// Un nmero float tiene 4 bytes de longitud, as que

// multiplicaremos x 4 el nmero de vrtices.

ByteBuffer byteBuf=ByteBuffer.allocateDirect(vertices.length*4);

// Establecemos el orden de los bytes en el buffer con el valor

// nativo (es algo as como indicar cmo se leen los bytes de

// izq a dcha o al revs).
byteBuf.order(ByteOrder.nativeOrder());

// Asignamos el nuevo buffer al buffer de esta clase

bufferVertices = byteBuf.asFloatBuffer();

// Introducimos los vrtices en el buffer
bufferVertices.put(vertices);

// Movemos la posicin del buffer al inicio
bufferVertices.position(0);

// Guardamos si es necesario colorear el polgono
this.conColor=conColor;
}
// Mtodo que invoca el Renderer cuando debe dibujar el tringulo

U1 Multimedia y Grficos en Android

public void draw(GL10 gl) {



// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);

// Indicamos el n de coordenadas (3), el tipo de datos de la

// matriz (float), la separacin en la matriz de los vrtices

// (0) y el buffer con los vrtices

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);

// Indicamos al motor OpenGL que le hemos pasado una matriz de



// vrtices
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

// Dibujamos la superficie mediante la matriz en el modo


// tringulo
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);


// Si hemos indicado que queremos color...

if (conColor)

// Establecemos el color del tringulo en modo RGBA

gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);

// Desactivamos el buffer de los vrtices
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
} // end clase

Podemos ver en el cdigo anterior que hemos definido los vrtices del tringulo en el constructor
utilizando un buffer de tipo float, ya que la biblioteca OpenGL necesita disponer, en este tipo
de variable, de los vrtices del polgono.
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el tringulo, realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW). Este sentido de dibujo es importante ya que determina la
direccin del vector normal de iluminacin; si ste apunta a la direccin equivocada, no veremos nada si utilizamos iluminacin. El orden contrario a las agujas del reloj indica que la
superficie del polgono est dirigida hacia el observador (regla de la mano derecha). Fjate
en este esquema:

- Con el mtodo glVertexPointer() indicamos el nmero de coordenadas (3), el tipo de


datos de la matriz (float), la separacin (0) en la matriz de los vrtices (podra haber ms
informacin en esta matriz adems de los vrtices) y el buffer con los vrtices.
- Indicamos al motor OpenGL que le hemos pasado una matriz de vrtices mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays()para dibujar la superficie del polgono utilizando la
matriz de vrtices y en el modo de dibujo de tringulos continuos (GL_TRIANGLE_STRIP).

113

Aula Mentor

- Si hemos indicado que debemos colorear el polgono, establecemos el color del tringulo en
modo RGBA mediante glColor4f().
- Finalmente, se desactiva el buffer de los vrtices con glDisableClientState().
Existen dos mtodos para dibujar un grfico mediante sus vrtices:
- public abstract void glDrawArrays(int mode, int first, int count): dibuja el

grfico segn el orden definido en la construccin de los vrtices. Ya hemos utilizado este
mtodo en el cdigo anterior.

- public abstract void glDrawElements(int mode, int count, int type, Buffer
indices): el segundo mtodo es similar al anterior, si bien debemos indicar el orden en el

que se pintarn los vrtices.

En ambos mtodos debemos indicar el modo de dibujo empleado por OpenGL para pintar el
grfico.
Veamos ahora el cuadrado especificado en el archivo Cuadrado.java:
public class Cuadrado {

114

// Indica si debemos dibujar el cuadrado con color


private boolean conColor;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices del cuadrado
private FloatBuffer bufferVertices;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los colores del cuadrado
private FloatBuffer bufferColores;
// Vrtices del cuadrado
private float vertices[] = {

-1.0f, -1.0f, 0.0f, // Abajo izq

1.0f, -1.0f, 0.0f, // Abajo dcha

-1.0f, 1.0f, 0.0f, // Arriba izq

1.0f, 1.0f, 0.0f
// Arriba dcha
};
// Matriz con los colores rojo, verde y azul (RGBA)
private float colores[] = {

1.0f, 0.0f, 0.0f, 1.0f, // Color Rojo con 100% de luminosidad

0.0f, 1.0f, 0.0f, 1.0f, // Color Verde con 100% de luminosidad

0.0f, 0.0f, 1.0f, 1.0f // Color Azul con 100% de luminosidad
};
// Constructor del cuadrado
public Cuadrado(boolean conColor) {

// Definimos el buffer con los vrtices del polgono.

// Un nmero float tiene 4 bytes de longitud, as que

// multiplicaremos x 4 el nmero de vrtices.

ByteBuffer byteBuf=ByteBuffer.allocateDirect(vertices.length*4);

// Establecemos el orden de los bytes en el buffer con el valor
// nativo (es algo as como indicar cmo se leen los bytes de

// izq a dcha o al revs).
byteBuf.order(ByteOrder.nativeOrder());

// Asignamos el nuevo buffer al buffer de esta clase

bufferVertices = byteBuf.asFloatBuffer();

// Introducimos los vrtices en el buffer

U1 Multimedia y Grficos en Android

bufferVertices.put(vertices);

// Movemos la posicin del buffer al inicio
bufferVertices.position(0);

// Guardamos si es necesario colorear el polgono
this.conColor=conColor;

// Si es necesario colorear el cuadrado...

if (conColor) {

// Definimos el buffer de la matriz de colores de igual

// forma que hemos hecho con la matriz de vrtices

byteBuf = ByteBuffer.allocateDirect(colores.length * 4);
byteBuf.order(ByteOrder.nativeOrder());

bufferColores = byteBuf.asFloatBuffer();
bufferColores.put(colores);
bufferColores.position(0);
}
} // end constructor
// Mtodo que invoca el Renderer cuando debe dibujar el cuadrado
public void draw(GL10 gl) {

// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);

// Indicamos el n de coordenadas (3), el tipo de datos de la


// matriz (float), la separacin en la matriz de los vrtices
// (0) y el buffer con los vrtices
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);

// Indicamos al motor OpenGL que le hemos pasado una matriz de



// vrtices
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

// Buffer de colores

if (conColor) {
// Indicamos el n de campos que definen el color (4), el

// tipo de datos de la matriz (float), la separacin en la

// matriz de los colores (0) y el buffer con los colores.

gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);
// Indicamos al motor OpenGL que le hemos pasado una matriz

// de colores
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
}

// Dibujamos la superficie mediante la matriz en el modo


// tringulo
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);


// Desactivamos el buffer de los vrtices
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

// Desactivamos el buffer de los colores si es necesario

if (conColor)
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
} // end clase

Podemos ver en el cdigo anterior que hemos definido los vrtices y colores del cuadrado en el
constructor utilizando dos buffer de tipo float ya que la biblioteca OpenGL necesita disponer,

115

Aula Mentor

en este tipo de variable, de los vrtices y colores del polgono.


Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el cuadrado, realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con el mtodo glVertexPointer() indicamos el nmero de coordenadas (3), el tipo de
datos de la matriz (float), la separacin (0) en la matriz de los vrtices (podra haber ms
informacin en esta matriz adems de los vrtices) y el buffer con los vrtices.
- De forma similar, usamos el mtodo glColorPointer() para sealar el nmero de campos
que definen un color (4), el tipo de datos de la matriz (float), la separacin en la matriz
de colores (0) y el buffer con los colores. Hemos definido en esta matriz los colores Rojo>Verde->Azul. La biblioteca OpenGL aplica los colores siguiendo el orden de dibujo de los
vrtices. As el vrtice 1 (abajo izq.) usa el color rojo, el 2 vrtice (abajo dcha.) aplica el
color verde, el vertice 3 (arriba izq.) utiliza el color Azul y el 4 vrtice (arriba dcha.) vuelve
a utilizar un color intermedio entre el azul y verde.
- Indicamos al motor OpenGL que le hemos pasado una matriz de vrtices y colores mediante
el mtodo glEnableClientState().
Usamos el mtodo glDrawArrays() para dibujar la superficie del polgono mediante la matriz
en el modo tringulos continuos GL_TRIANGLE_STRIP. Es decir, el cuadrado est formado
por dos tringulo pegados:

116

- Finalmente, se desactivan los buffers de los vrtices y colores con glDisableClientState().


Finalmente, para que la aplicacin aparezca sin ttulo y en el modo de pantalla completa hemos
definido en el fichero AndroidManifest.xml el tipo de estilo siguiente:

android:theme=@android:style/Theme.NoTitleBar.Fullscreen

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 9 (OpenGL 2D) de la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la biblioteca OpenGL de Android.

Si ejecutas en Eclipse ADT este Ejemplo 9 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja dos polgonos:

U1 Multimedia y Grficos en Android

117

7.3 Grficos en 3D con movimiento


En el Ejemplo 10 de esta Unidad hemos dibujado los polgonos tringulo y cuadrado en 3D,
es decir, una pirmide y un cubo a los que hemos animado con movimientos. Es recomendable
abrir el Ejemplo 10 en Eclipse ADT para seguir la explicacin posterior.
Slo se describirn en detalle aquellos ficheros de cdigo fuente que son distintos del
Ejemplo 9 anterior.
Los archivos res/layout/main_layout.xml, MainActivity.java, Cuadrado.java y
Triangulo.java de este proyecto son muy parecidos a los estudiados en el ejemplo anterior.
Te invitamos a que los abras en Eclipse ADT.
Hemos desarrollado el Renderer en la clase MiRenderer del proyecto, que contiene el
siguiente cdigo fuente:
public class MiRenderer implements Renderer {
// Constantes que indican el tipo de polgono que debemos dibujar
public static int MOSTRAR_TRIANGULO=1;
public static int MOSTRAR_CUADRADO=2;
public static int MOSTRAR_CUBO=3;
public static int MOSTRAR_PIRAMIDE=4;
// Guarda el polgono que estamos mostrando
private int mostrar_poligono=-1;
// Objeto del tipo Tringulo que describe su geometra
private Triangulo triangulo;
// Objeto del tipo Cuadrado que describe su geometra

Aula Mentor

private Cuadrado cuadrado;


// Objeto del tipo Pirmide que describe su geometra
private Piramide piramide;
// Objeto del tipo Cubo que describe su geometra
private Cubo cubo;
// ngulo de giro del movimiento del polgono
private float angulo;
// Constructor de la clase
public MiRenderer(int mostrar_poligono) {

// Guardamos el tipo de polgono que queremos mostrar y
// creamos la geometra correspondiente
this.mostrar_poligono=mostrar_poligono;
if (mostrar_poligono == MOSTRAR_TRIANGULO)
triangulo = new Triangulo();
else
if (mostrar_poligono == MOSTRAR_CUADRADO)
cuadrado = new Cuadrado();
else
if (mostrar_poligono == MOSTRAR_CUBO)
cubo = new Cubo();
else
if (mostrar_poligono == MOSTRAR_PIRAMIDE)
piramide = new Piramide();
}

118

// Evento que se lanza cuando se crea la superficie.


// Aqu debemos indicar todo aquello que no cambia en la superficie
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Indicamos el modo de sombreado suave
gl.glShadeModel(GL10.GL_SMOOTH);
// Establecemos el color GRIS como fondo de la superficie
gl.glClearColor(0.6f, 0.6f, 0.6f, 0.5f);
// Configuramos el buffer de profundidad
gl.glClearDepthf(1.0f);
// Modo de renderizado de la profundidad
gl.glEnable(GL10.GL_DEPTH_TEST);
// Funcin de comparacin de la profundidad
gl.glDepthFunc(GL10.GL_LEQUAL);
// Cmo se calcula la perspectiva
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
}
// Evento que se lanza cada vez que es necesario dibujar la
// superficie
public void onDrawFrame(GL10 gl) {
// Cada vez que se dibuja una escena hay que limpiar
// tanto el buffer de color como el de profundidad
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// Reinicia la matriz de proyeccin del grfico
gl.glLoadIdentity();
// Movemos la posicin de los ejes en (x,y,z).
// Es decir, alejamos el dibujo en el eje Z dando
// sensacin de profundidad.
gl.glTranslatef(0.0f, 0.0f, -6.0f);

U1 Multimedia y Grficos en Android

// Dibujamos el polgono solicitado


if (mostrar_poligono == MOSTRAR_TRIANGULO) {

// Rotamos el tringulo un ngulo sobre el eje Y

gl.glRotatef(angulo, 0.0f, 1.0f, 0.0f);
triangulo.draw(gl);
// Definimos el ngulo del siguiente giro
angulo -= 0.45f;
}
else

if (mostrar_poligono == MOSTRAR_CUADRADO) {

// Rotamos el cuadrado un ngulo sobre el eje Y

gl.glRotatef(angulo, 0.0f, 1.0f, 0.0f);

cuadrado.draw(gl);

// Definimos el ngulo del siguiente giro

angulo += 0.4f;

}

else

if (mostrar_poligono == MOSTRAR_CUBO) {

// Escalamos el cubo al 80% para que quepa en

// la pantalla del AVD

gl.glScalef(0.8f, 0.8f, 0.8f);

// Rotamos el cubo un ngulo sobre el eje X, Y, Z

gl.glRotatef(angulo, 1.0f, 1.0f, 1.0f);

cubo.draw(gl);

// Definimos el ngulo del siguiente giro

angulo -= 0.45f;

}

else {

// Movemos la pirmide 1.0 al fondo para que quepa

// en la pantalla del AVD

gl.glTranslatef(0.0f, 0.0f, -1.0f);

// Rotamos la pirmide un ngulo sobre el eje X,Y

gl.glRotatef(angulo, 0.5f, 1.0f, 0.0f);

piramide.draw(gl);

// Definimos el ngulo del siguiente giro

angulo -= 0.45f;
}
} // end onDrawFrame
// Evento que se lanza cuando cambia la superficie
public void onSurfaceChanged(GL10 gl, int width, int height) {

// Nos aseguramos de que no se puede dividir por 0

if(height == 0) {

height = 1;
}

// Reiniciamos la zona de visin

gl.glViewport(0, 0, width, height);

// Establecemos la matriz de proyeccin como

// activa para modificarla y poder girar el polgono
gl.glMatrixMode(GL10.GL_PROJECTION);

// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();

// Calculamos el tamao de la perspectiva en funcin del nuevo

// tamao de la pantalla

GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);

119

Aula Mentor


// Establecemos de nuevo la matriz de vista/modelo

// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}
} // end clase

En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos los mismos parmetros
que en el Ejemplo 9.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de profundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). La matriz
de proyeccin se utiliza para poder realizar cambios de perspectiva (giros, translaciones,
etctera). Realmente, lo que estamos haciendo es mover de nuevo el puntero de dibujo al
centro del eje de coordenadas (0,0,0).
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, alejamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Rotamos el polgono mediante el mtodo glRotatef(). As, se logra la sensacin de movimiento del polgono.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado anteriormente.

120

Finalmente, en el mtodo onSurfaceChanged() colocamos las sentencias que se ejecutarn


cuando la superficie sobre la que dibujamos sufra alguna modificacin, como la orientacin de
posicin de la pantalla. En este caso, ejecutamos las mismas sentencias que en el Ejemplo 9.
Veamos ahora cmo hemos definido las clases que describen la geometra de los nuevos
polgonos. Empezamos por la pirmide desarrollada en el archivo Piramide.java:
public class Piramide {
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices de la pirmide
private FloatBuffer bufferVertices;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los colores de la pirmide
private FloatBuffer bufferColores;
// Vrtices de la pirmide
private float vertices[] = {

0.0f, 1.0f, 0.0f, // Punto superior del tringulo frontal

-1.0f, -1.0f, 1.0f, // Punto izq. del tringulo frontal

1.0f, -1.0f, 1.0f, // Punto dcho. del tringulo frontal


0.0f, 1.0f, 0.0f,


1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,

// Punto superior del tringulo dcho.


// Punto izq. del tringulo dcho.
// Punto dcho. del tringulo dcho.

U1 Multimedia y Grficos en Android

0.0f, 1.0f, 0.0f,


1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,

// Punto superior del tringulo de la base


// Punto izq. del tringulo de la base
// Punto dcho. del tringulo de la base

0.0f, 1.0f, 0.0f,


-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f

// Punto superior del tringulo izq.


// Punto izq. del tringulo izq.
// Punto dcho. del tringulo izq.

};
// Matriz con colores en formato RGBA
private float colores[] = {
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 1.0f, 0.0f, 1.0f, // Verde
0.0f, 0.0f, 1.0f, 1.0f, // Azul
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 0.0f, 1.0f, 1.0f, // Azul
0.0f, 1.0f, 0.0f, 1.0f, // Verde
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 1.0f, 0.0f, 1.0f, // Verde
0.0f, 0.0f, 1.0f, 1.0f, // Azul
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 0.0f, 1.0f, 1.0f, // Azul
0.0f, 1.0f, 0.0f, 1.0f // Verde
};

// Constructor de la pirmide
public Piramide() {

// Definimos el buffer con los vrtices del polgono.

// Un nmero float tiene 4 bytes de longitud, as que

// multiplicaremos x 4 el nmero de vrtices.

ByteBuffer byteBuf=ByteBuffer.allocateDirect(vertices.length*4);
byteBuf.order(ByteOrder.nativeOrder());

bufferVertices = byteBuf.asFloatBuffer();
bufferVertices.put(vertices);
bufferVertices.position(0);

// Definimos el buffer de la matriz de colores de igual forma


// que hemos hecho con la matriz de vrtices

byteBuf = ByteBuffer.allocateDirect(colores.length * 4);
byteBuf.order(ByteOrder.nativeOrder());

bufferColores = byteBuf.asFloatBuffer();
bufferColores.put(colores);
bufferColores.position(0);
}

// Mtodo que invoca el Renderer cuando debe dibujar la pirmide


public void draw(GL10 gl) {

// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);


// Indicamos el n de coordenadas (3), el tipo de datos de la


// matriz (float), la separacin en la matriz de los vrtices
// (0) y el buffer con los vrtices
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);
// Indicamos el n de campos que definen el color (4), el tipo
// de datos de la matriz (float), la separacin en la matriz de
// los colores (0) y el buffer con los colores.

121

Aula Mentor

gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);

// Indicamos al motor OpenGL que le hemos pasado una matriz de



// vrtices y de colores
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

// Dibujamos la superficie mediante la matriz en el modo


// tringulo
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vertices.length / 3);


// Desactivamos el buffer de los vrtices y colores
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
} // end clase

Podemos ver en el cdigo anterior que hemos definido los vrtices de los cuatro tringulos
que forman las superficies de la pirmide en el constructor utilizando un buffer de tipo float.
Adems, definimos tambin un buffer adicional para los colores de sta.

122

Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de los
vrtices y colores a la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays()para dibujar la superficie del polgono mediante la matriz en el modo tringulo GL_TRIANGLES.
- Finalmente, se desactiva los buffer de vrtices y colores con glDisableClientState().
Veamos ahora el Cubo especificado en el fichero Cubo.java:
public class Cubo {

// Buffer de tipo float que usamos para pasar

// a la librera OpenGL los vrtices del cubo

private FloatBuffer bufferVertices;

// Buffer de tipo float que usamos para pasar

// a la librera OpenGL los colores del cubo

private FloatBuffer bufferColores;

// Buffer de tipo float que usamos para pasar

// a la librera OpenGL los ndices del cubo

private ByteBuffer bufferIndices;

// Vrtices del cubo

private float vertices[] = {

-1.0f, -1.0f, 1.0f,
// abajo delante izq. (V0)

1.0f, -1.0f, 1.0f,
// abajo delante dcha. (V1)

-1.0f,
1.0f, 1.0f,
// arriba delante izq. (V2)

1.0f,
1.0f, 1.0f,
// arriba delante dcha. (V3)

1.0f, -1.0f, -1.0f,
// abajo detrs dcha. (V4)
-1.0f, -1.0f, -1.0f,
// abajo detrs izq. (V5)

1.0f,
1.0f, -1.0f,
// arriba detrs dcha. (V6)

U1 Multimedia y Grficos en Android

};

-1.0f, 1.0f, -1.0f

// arriba detrs izq. (V7)


// Matriz con colores en formato RGBA

private float colores[] = {

1.0f, 0.0f, 0.0f, 1.0f,

1.0f, 0.0f, 0.0f, 1.0f,

1.0f, 0.0f, 1.0f, 1.0f,

0.0f, 0.0f, 1.0f, 1.0f,

0.0f, 1.0f, 0.0f, 1.0f,

0.0f, 1.0f, 0.0f, 1.0f,

1.0f, 0.5f, 0.0f, 1.0f,

1.0f, 0.5f, 0.0f, 1.0f
};


// Matriz con los ndices que definen los tringulos que


// se usan para crear las caras
private byte indices[] = {

/*

* Por ejemplo:

* Cara definida mediante los vrtices abajo detrs izq. (0),

* abajo delante izq. (4), abajo delante dcha. (5), 0, 5, 1

*/

5, 0, 1,
5, 1, 4,
// Y as las 6 caras en total
4, 1, 3,
4, 3, 6,
6, 3, 2,
6, 2, 7,
7, 2, 0,
7, 0, 5,
0, 2, 3,
0, 3, 1,
7, 5, 4,
7, 4, 6
};

// Constructor del cubo

public Cubo() {
// Definimos el buffer con los vrtices del polgono.

// Un nmero float tiene 4 bytes de longitud, as que

// multiplicaremos x 4 el nmero de vrtices.
ByteBuffer byteBuf=
ByteBuffer.allocateDirect(vertices.length*4);
byteBuf.order(ByteOrder.nativeOrder());

bufferVertices = byteBuf.asFloatBuffer();
bufferVertices.put(vertices);
bufferVertices.position(0);
// Definimos el buffer de la matriz de colores de igual

// forma que hemos hecho con la matriz de vrtices

byteBuf = ByteBuffer.allocateDirect(colores.length * 4);
byteBuf.order(ByteOrder.nativeOrder());

bufferColores = byteBuf.asFloatBuffer();
bufferColores.put(colores);
bufferColores.position(0);
// Definimos el buffer de la matriz de ndices de igual

// forma que hemos hecho con la matriz de vrtices

bufferIndices = ByteBuffer.allocateDirect(indices.length);
bufferIndices.put(indices);
bufferIndices.position(0);
} // end constructor

123

Aula Mentor


// Mtodo que invoca el Renderer cuando debe dibujar el cubo

public void draw(GL10 gl) {

// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);
// Indicamos el n de coordenadas (3), el tipo de datos de

// la matriz (float), la separacin en la matriz de los

// vrtices (0) y el buffer con los vrtices

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);
// Indicamos el n de campos que definen el color (4), el

// tipo de datos de la matriz (float), la separacin en la

// matriz de los colores (0) y el buffer con los colores.

gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);
// Indicamos al motor OpenGL que le hemos pasado una matriz

// de vrtices y de colores
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

124

// Dibujamos la superficie mediante la matriz en el modo



// tringulo utilizando los ndices para unirlos y formar

// las caras

gl.glDrawElements(GL10.GL_TRIANGLES, 36,
GL10.GL_UNSIGNED_BYTE, bufferIndices);

// Desactivamos el buffer de los vrtices y colores
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
} // end clase

Podemos ver en el cdigo anterior que hemos definido los vrtices y colores del cubo en el
constructor utilizando dos buffer de tipo float ya que la biblioteca OpenGL necesita disponer,
en este tipo de variable, de los vrtices y colores del polgono.
Adems, hemos definido el buffer adicional bufferIndices que usamos para pasar a la
biblioteca OpenGL los ndices del cubo. Las caras del cubo se componen de dos tringulos
pegados, por lo tanto, debemos definir las caras del cubo mediante esta matriz. Veamos el
aspecto geomtrico del cubo:

Vrtices del cubo

Cara delantera

Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el cubo, realizamos
los siguientes trabajos:

U1 Multimedia y Grficos en Android

- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con el mtodo glVertexPointer() y glColorPointer() asociamos los vrtices y colores
al grfico.
- Indicamos al motor OpenGL que le hemos pasado una matriz de vrtices y colores mediante
el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie del polgono mediante la
matriz en el modo tringulo GL_TRIANGLE utilizando el buffer de ndices bufferIndices
para unirlos y formar las caras.
- Finalmente, se desactivan los buffers de vrtices y colores con glDisableClientState().

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 10 (3D Movimiento) de la


Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la API de OpenGL para crear grficos en movimiento en 3D de Android.

Si ejecutas en Eclipse ADT este Ejemplo 10 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja cuatro polgonos (en 2D y 3D):

125

7.4 Grficos en 3D con textura y movimiento


En el Ejemplo 11 de esta Unidad hemos dibujado una pirmide en 3D con textura y que
responde con movimientos a los toques del usuario girando sobre sus ejes. Es recomendable

Aula Mentor

abrir el Ejemplo 11 en Eclipse ADT para seguir la explicacin posterior.


El archivo MainActivity.java de este proyecto es muy parecido a los estudiados en
los ejemplos anteriores. Te invitamos a que los abras en Eclipse ADT.
Hemos desarrollado conjuntamente el Renderer y la superficie GLSurfaceView en la
clase Superficie del proyecto, que contiene el siguiente cdigo fuente:
public class Superficie extends GLSurfaceView implements Renderer {
// Objeto que define la pirmide con textura
private Piramide piramide;
// ngulos de rotacin en X e Y. Inicialmente la pirmide
// aparece un poco girada
private float anguloX=-15.0f;
private float anguloY=-45.0f;
// Variables donde se almacenan los ngulos anteriores
// para as poder calcular el cambio de ngulo y girar
// la pirmide nicamente la diferencia de ngulo.
private float anguloAntiguoX;
private float anguloAntiguoY;
// Constante que usamos de escala del toque del usuario
private final float ESCALA_TOQUE = 0.2f;

126

// Constructor de la clase
public Superficie(Context context) {
super(context);
// Indicamos que el Renderer se define en esta misma superficie
this.setRenderer(this);

// Solicitamos el foco de la aplicacin para que el usuario

// pueda hacer clic en la imagen. Nota: esto slo es necesario

// si la aplicacin tuviera otras Vistas, como botones
this.requestFocus();

// Indicamos que la superficie responde a toques del usuario,

// es decir, implementa el evento onTouchEvent
this.setFocusableInTouchMode(true);
}
// Evento que se lanza cuando se crea la superficie.
// Aqu debemos indicar todo aquello que no cambia en la superficie
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Deshabilitamos la mezcla de colores
gl.glDisable(GL10.GL_DITHER);

// Habilitamos las texturas en 2D
gl.glEnable(GL10.GL_TEXTURE_2D);

// Establecemos el color NEGRO como fondo de la superficie

gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);

// Configuramos el buffer de profundidad
gl.glClearDepthf(1.0f);

// Modo de renderizado de la profundidad
gl.glEnable(GL10.GL_DEPTH_TEST);

// Funcin de comparacin de la profundidad
gl.glDepthFunc(GL10.GL_LEQUAL);
// Cmo se calcula la perspectiva
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

U1 Multimedia y Grficos en Android

// Cargamos la imagen en un bitmap que luego servir de textura


// a la pirmide
Bitmap textura = BitmapFactory.decodeResource(getResources(),
R.drawable.textura_piramide);

// Como usamos una nica textura, la matriz de texturas slo

// debe tener un elemento
int texturaIds[] = new int[1];
// Asociamos la matriz de texturas al grfico OpenGL
gl.glGenTextures(1, texturaIds, 0);
// Seleccionamos la primera textura de la matriz
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturaIds[0]);
// Cargamos la imagen en formato bitmap en la textura
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, textura, 0);
// Indicamos cmo tratar la textura cuando es necesario

// modifiarla porque debe hacerse ms pequea
gl.glTexParameterf(GL10.GL_TEXTURE_2D,

GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
// Indicamos cmo tratar la textura cuando es necesario

// modificarla porque debe hacerse ms grande

gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);

// Libera el bitmap ya que ya no es necesario
textura.recycle();

// Creamos la pirmide usando la textura correspondiente

piramide = new Piramide(texturaIds[0]);
}

// Evento que se lanza cada vez que es necesario dibujar la


// superficie
public void onDrawFrame(GL10 gl) {
// Establecemos el color NEGRO como fondo de la superficie
gl.glClearColor(0.0f, 0.0f, 0.0f, 1f);
// Cada vez que se dibuja una escena hay que limpiar
// tanto el buffer de color como el de profundidad
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();

// Movemos la posicin de los ejes en (x,y,z).


// Es decir, alejamos el dibujo en el eje Z dando
// sensacin de profundidad.
gl.glTranslatef(0.0f, 0.0f, -3.0f);
// Escalamos la pirmide un 50% para que quepa en la pantalla
gl.glScalef(0.5f, 0.5f, 0.5f);


// Rotamos en los ejes X e Y el toque del usuario

gl.glRotatef(anguloX, 1.0f, 0.0f, 0.0f);

gl.glRotatef(anguloY, 0.0f, 1.0f, 0.0f);

// Dibujamos la pirmide
piramide.draw(gl);
} // end onDrawFrame
// Evento que se lanza cuando cambia la superficie
public void onSurfaceChanged(GL10 gl, int width, int height) {

127

Aula Mentor


// Nos aseguramos de que no se puede dividir por 0

if(height == 0) {

height = 1;
}

// Reiniciamos la zona de visin

gl.glViewport(0, 0, width, height);

// Establecemos la matriz de proyeccin como

// activa para modificarla y poder girar la pirmide
gl.glMatrixMode(GL10.GL_PROJECTION);

// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();

// Calculamos el tamao de la perspectiva en funcin del nuevo

// tamao de la pantalla

GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);

// Establecemos de nuevo la matriz de vista/modelo

// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}

128

// Evento que se lanza cuando el usuario toca la pantalla


@Override
public boolean onTouchEvent(MotionEvent event) {

// Obtenemos el toque del usuario

float x = event.getX();
float y = event.getY();
// Si el toque es un movimiento sobre la pantalla
if(event.getAction() == MotionEvent.ACTION_MOVE) {

// Calculamos el cambio de ngulo restando con los

// anteriores

float dx = x - anguloAntiguoX;

float dy = y - anguloAntiguoY;

// Los nuevos ngulos son el actual + cambio de ngulo

// teniendo en cuenta un factor de correccin

anguloX += dy * ESCALA_TOQUE;

anguloY += dx * ESCALA_TOQUE;
}
// Almacenamos los nuevos ngulos
anguloAntiguoX = x;
anguloAntiguoY = y;
// Indicamos que se controla el evento onTouch
return true;
} // end onTouchEvent
} // end clase

Puedes observar en el cdigo anterior que hemos extendido esta clase de GLSurfaceView e
implementado Renderer; as, en un mismo fichero Java desarrollamos la superficie de dibujo
(lienzo) y el dibujante.
En el constructor indicamos que el Renderer se define en esta misma clase y que la
superficie responde a toques del usuario, es decir, implementa el evento onTouchEvent().

U1 Multimedia y Grficos en Android

Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin


ser poca o nula durante la ejecucin del programa; en este caso, fijamos los mismos parmetros que los ejemplos anteriores e incluimos las sentencias necesarias que cargan la textura que
usaremos luego para cubrir la pirmide.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de profundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity().
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, alejamos el dibujo en el eje Z dando al observador la sensacin de profundidad.
- Rotamos la pirmide mediante el mtodo glRotatef(). As, se logra la sensacin de movimiento cuando el usuario toca la pantalla.
- Invocamos el mtodo draw del objeto que debemos dibujar, que hemos instanciado anteriormente.
En el mtodo onSurfaceChanged()colocamos las sentencias que se ejecutarn cuando la
superficie sobre la que dibujamos sufra alguna modificacin, como la orientacin de posicin
de la pantalla. En este caso, ejecutamos las mismas sentencias que en los ejemplos anteriores.
Finalmente, en el mtodo onTouchEvent() definimos las sentencias que se ejecutarn
cuando el usuario toca la pantalla para mover la pirmide. En este caso, obtenemos la informacin del evento ocurrido, calculamos la diferencia de ngulos para as girar la pirmide
nicamente esta diferencia, establecemos los nuevos ngulos teniendo en cuenta un factor de
correccin.
Veamos ahora cmo hemos definido la clase que describe la geometra de la pirmide
en el archivo Piramide.java:
public class Piramide {
// Id de OpenGL de la textura de la pirmide
private int texturaId;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices de la pirmide
private FloatBuffer bufferVertices;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices de la base de la pirmide
private FloatBuffer bufferVerticesBase;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL las texturas de la pirmide
private FloatBuffer bufferTexturas;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los colores del cubo
private FloatBuffer bufferColores;
// Vrtices de la pirmide
private float vertices[] = {

0.0f, 1.0f, 0.0f,

-1.0f, -1.0f, 1.0f,

1.0f, -1.0f, 1.0f,

0.0f, 1.0f, 0.0f,

1.0f, -1.0f, 1.0f,

1.0f, -1.0f, -1.0f,

// Punto superior del tringulo frontal


// Punto izq. del tringulo frontal
// Punto dcho. del tringulo frontal
// Punto superior del tringulo dcho.
// Punto izq. del tringulo dcho.
// Punto dcho. del tringulo dcho.

129

Aula Mentor

0.0f, 1.0f, 0.0f,


1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,

// Punto sup. del tringulo de la base


// Punto izq. del tringulo de la base
// Punto dcho. del tringulo de la base

0.0f, 1.0f, 0.0f,


-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f

// Punto superior del tringulo izq.


// Punto izq. del tringulo izq.
// Punto dcho. del tringulo izq.

130

};

// Vrtices de la base de la pirmide


private float vertices_base[] = {
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f
};
// Matriz con texturas
private float texturas[] = {
0.5f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
0.5f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
0.5f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};

// Matriz con colores de


private float colores[] =

0.6f, 0.4f, 0.0f,

0.6f, 0.4f, 0.0f,

0.6f, 0.4f, 0.0f,
0.6f, 0.4f, 0.0f,
};


public
//
//
//

la base en formato RGBA


{
1.0f,
1.0f,
1.0f,
1.0f

Piramide(int texturaId) {
Usando el mtodo local makeFloatBuffer definimos
los buffer de la pirmide: vrtices, texturas,
vrtices de su base y colores de su base.

bufferVertices = makeFloatBuffer(vertices);
bufferTexturas = makeFloatBuffer(texturas);
bufferVerticesBase = makeFloatBuffer(vertices_base);
bufferColores = makeFloatBuffer(colores);

// Guardamos el id de la imagen que sirve de textura


this.texturaId = texturaId;
} // end constructor
public void draw(GL10 gl) {

U1 Multimedia y Grficos en Android


// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);

// Activamos la texturas en 2D (superficie)
gl.glEnable(GL10.GL_TEXTURE_2D);
// Indicamos el ID de la textura
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturaId);
// Indicamos el n de coordenadas (3), el tipo de datos de la

// matriz (float), la separacin en la matriz de los vrtices

// (0) y el buffer con los vrtices
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);
// Seleccionamos la textura indicando el tamao y tipo de la

// matriz de texturas
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, bufferTexturas);
// Indicamos al motor OpenGL que le hemos pasado una matriz de
// vrtices y de texturas
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

// Dibujamos la superficie mediante la matriz en el modo


// tringulo
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vertices.length / 3);
// Deshabilitamos la textura para seguir pintando sin sta
gl.glDisable(GL10.GL_TEXTURE_2D);
// Indicamos el n de campos que definen el color (4), el tipo
// de datos de la matriz (float), la separacin en la matriz de
// los colores (0) y el buffer con los colores.
gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);
// Indicamos al motor OpenGL que le hemos pasado una matriz de
// colores para la base
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

// Establecemos los nuevos vrtices de la base de la pirmide


gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVerticesBase);
// Dibujamos la base de la pirmide mediante la matriz en el

// modo tringulo
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,

vertices_base.length/3);

// Desactivamos el buffer de los vrtices, texturas y colores.


gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
} // end draw
// Mtodo que crea un FloatBuffer. As optimizamos el cdigo fuente
protected static FloatBuffer makeFloatBuffer(float[] matriz)
{
// Definimos el buffer multiplicando x 4 ya que un nmero float
// tiene 4 bytes de longitud.

ByteBuffer bb = ByteBuffer.allocateDirect(matriz.length*4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer fb = bb.asFloatBuffer();
fb.put(matriz);
fb.position(0);

131

Aula Mentor

return fb;
}
} // end clase

En el constructor de la clase anterior hemos usado el mtodo local makeFloatBuffer() para


simplificar el cdigo fuente y definir los siguientes cuatro buffer de tipo float:
- los vrtices de los cuatro tringulos que forman las superficies de la pirmide.
- las texturas que se aplican a estas 4 caras superiores que definen la pirmide, en total, 4 caras
* 3 vrtices = 12 puntos.
- el cuadrado que forma la base de la pirmide.
- el color de la base de sta.

132

Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar la pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Activamos la texturas en 2D (tipo superficie) con glEnable() y seleccionamos la textura
activa invocando glBindTexture().
- Con los mtodos glVertexPointer() y glTexCoordPointer() asociamos los buffer de
vrtices y texturas de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y texturas mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la pirmide mediante la
matriz en el modo tringulo GL_TRIANGLES.
- Deshabilitamos la textura para seguir pintando sin sta con el mtodo glDisable().
- Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de vrtices y colores de la base de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la base de la pirmide
mediante la matriz en el modo tringulo GL_TRIANGLES_STRIP.
- Finalmente, se desactiva los buffer de vrtices, colores y texturas con glDisableClientState().

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 11 (3D con texturas e interaccin) de la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el
resultado del programa anterior, en el que hemos utilizado la API de OpenGL para
crear una pirmide en 3D con textura y que interacciona con el usuario.

U1 Multimedia y Grficos en Android

Si ejecutas en Eclipse ADT este Ejemplo 11 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja cuatro una pirmide con textura en 3D:

133

Recomendamos al alumno que desplace el ratn sobre la pirmide para ver cmo gira sobre sus
ejes y cambia la perpectiva de sta.

Las posibilidades de diseo grfico con OpenGL son prcticamente infinitas.


El diseador grfico puede incluir texturas complejas, transparencias, luces, sombras, cambios de perspectiva, etctera. En este apartado se ha mostrado el uso
bsico de esta biblioteca.

Aula Mentor

8. Resumen
Hay que saber al final de esta unidad:
- Las clases de Android que permiten acceder a los servicios Multimedia son las
siguientes:
MediaPlayer: reproduce audio y vdeo.
MediaController: representa los controles estndar para MediaPlayer.
VideoView: Vista que permite la reproduccin de vdeo.
MediaRecorder: permite grabar audio y vdeo.
AsyncPlayer: reproduce una lista de archivos de tipo audio.
AudioManager: gestor del sonido del sistema operativo.
AudioTrack: reproduce un archivo de audio PCM.
SoundPool: gestiona y reproduce una coleccin de recursos de audio de corta duracin.
JetPlayer: reproduce audio y vdeo interactivo.
Camera: cmara para tomar fotos y vdeo.
FaceDetector: clase para identificar la cara de personas en una imagen.
- No todos los dispositivos Android incluyen el formato DivX de forma nativa.

134

- Android representa un color utilizando un nmero entero de 32 bits. Estos bits


se dividen en 4 campos de 8 bits: alfa, rojo, verde y azul (ARGB, si usamos las
iniciales en ingls).
- Las clases ms importantes para dibujar en Android son:
Paint se emplea para definir un pincel que utilizaremos para pintar.
Rect permite dibujar un rectngulo.
Path (del ingls, camino) permite definir un trazado mediante segmentos de
lneas y curvas.
Canvas (del ingls, lienzo) representa la superficie bsica donde podemos
dibujar grficos.
- Un Dibujable (del ingls, Drawable) es un mecanismo para dibujar la interfaz de
una aplicacin Android. Podemos entender la clase Drawable como una abstraccin que representa algo que se puede dibujar.
- Android dispone de tres mecanismos para crear animaciones:
AnimationDrawable: drawables que reproducen una animacin fotograma a
fotograma (en ingls se denomina Frame Animation).
Animaciones Tween: permiten crear efectos de translacin, rotacin, zoom y
alfa a cualquier vista de una aplicacin Android, cambiando su representacin
en la pantalla.
API de animacin de Android: anima cualquier propiedad de un objeto Java
sea del tipo Vista o no, modificando el objeto en s mismo. Esta API se denomina tambin Animacin de Propiedades.

U1 Multimedia y Grficos en Android

- ViewSurface (Vista de tipo Superficie) proporciona una superficie de dibujo dedicado que se incrusta dentro de una jerarqua de vistas.
- Al utilizar Superficies es imprescindible que la interaccin del usuario con la aplicacin sea suave y los grficos se muestren sin parpadeos. Para conseguirlo,
debemos ejecutar dos hilos en la aplicacin: el hilo principal, que se encarga de
gestionar la Actividad, y un segundo hilo, que dibuja la superficie y gestiona la
interaccin del usuario con sta.
- Android dispone de dos bibliotecas para pintar grficos en 3D:
OpenGL: librera estndar de diseo 3D.
RenderScript: nueva librera de bajo nivel de dibujo grfico en 3D disponible
desde la versin 3.0 de Android, que aprovecha el procesador GPU de la
tarjeta grfica para pintar.
- Las clases bsicas de la biblioteca OpenGL son las siguientes:
GLSurfaceView: clase base que permite escribir aplicaciones con OpenGL.
GLSurfaceView.Renderer: interfaz de dibujo genrica donde se detalla el
cdigo que indica lo que se debe dibujar.
Podemos hacer una analoga con un pintor y su lienzo: el pintor es la interfaz
GLSurfaceView.Renderer y el lienzo es GLSurfaceView.
- Es importante recordar los conceptos bsicos de geometra: Lnea o Recta, Vrtice, Cara, Arista, Polgono y Eje de coordenadas.
- Las posibilidades de diseo grfico con OpenGL son prcticamente infinitas. El
diseador grfico puede incluir texturas complejas, transparencias, luces, sombras, cambios de perspectiva, etctera.

135

Aula Mentor

Unidad 2. Interfaz de usuario avanzada

1. Introduccin
En esta segunda Unidad vamos a explicar cmo se aplican los temas y estilos de Android para
cambiar el aspecto visual de una aplicacin. Adems, implementaremos Widgets en el Home y
Lock Screen y crearemos un Live WallPaper (fondo de escritorio animado).
Asimismo, utilizaremos los Fragmentos en Android y las Barras de Accin (Action Bars)
en un proyecto Android.
Finalmente, usaremos las clases GridView, Interruptor(Switch) y Navigation
Drawer para mejorar la interfaz de usuario con varios ejemplos de aplicacin en Android.

2. Estilos y Temas en las aplicaciones de Android

136

A todos los programadores nos gusta desarrollar funciones, procedimientos o mtodos sencillos
y eficientes que realicen un cometido especfico. Cuando juntamos todos estos trocitos de
cdigo fuente obtenemos una aplicacin tan compleja como queramos. Es decir, para resolver
un problema complejo, hay que separarlo en pequeos problemas.
Aunque la aplicacin desarrollada cumpla con su cometido excelentemente, hoy en da
es necesario que tambin tenga un aspecto visual atractivo con el diseo apropiado. A
todos nos gusta que un plato est bueno y, adems, tenga un buen aspecto.
Para un programador nativo es difcil afrontar el diseo visual de una aplicacin; para eso
existen diseadores que conciben la interfaz visual sin estas ataduras del ingeniero.
Desde el punto de vista de un programador, cuantas menos partes movibles, cambios de
colores o fuentes tenga una aplicacin ms fcil ser desarrollarla.
En este apartado de teora vamos a mostrar ejemplos sobre cmo implementar de forma sencilla
y rpida estas mejoras visuales que hacen que las aplicaciones sean ms agradables. Para ello
utilizaremos temas (themes, en ingls) y estilos (styles, en ingls).
Como sabes, en Android se utilizan Vistas (Views) para disear la interfaz visual de las
Actividades (Activities). Aunque es posible definir las Vistas mediante sentencias de Java dentro
de las Actividades, lo usual es declararlas en un archivo de diseo layout de tipo XML.
El entorno de desarrollo en Eclipse ADT incluye un editor visual para disear estos
archivos xml. Sin embargo, este editor no permite cambiar el aspecto (color, fuente, mrgenes
y otros atributos) que van a tener estas Vistas. En ingls, se denomina look and feel al
aspecto visual de una aplicacin.
Para facilitar el diseo visual, el programador/diseador de Android dispone de temas
y estilos. Si has diseado alguna vez una pgina web, sabrs que para optimizar recursos, el
estilo de cada pgina se incluye en un archivo CSS con los atributos de las diferentes etiquetas
de HTML.
A continuacin, vamos a ver cmo crear un tema en una aplicacin Android.

U2 Multimedia y Grficos en Android

2.1 Cmo crear un Tema


Los temas en Android se incluyen dentro de la carpeta res/values/ mediantes un archivo xml
(por defecto, se llama styles.xml) donde se definen todos los atributos que aplican a este
tema.

Nota: Dependiendo de la versin de Android para la que vayamos a compilar el


proyecto es necesario incluir en directorios distintos la definicin de los temas, ya
que la versin 14 (Android 4.0) dispone de ms atributos que la versin 11 (Android
3.0). En el ejemplo de este apartado, por simplificacin, slo definimos un tema
para la versin de Android del curso.
137
Es recomendable que abras desde Eclipse el proyecto Ejemplo 1 (Temas y estilos)
de la Unidad 2 que vamos a describir a continuacin.

Si abres el archivo res/values/temas.xml, vers que hemos creado el tema Tema a partir
del tema padre por defecto de Android android:Theme y aadido un par de atributos:

<?xml version=1.0 encoding=utf-8?>


<resources>
<style name=Tema parent=android:Theme>

<item name=margenActividad>2sp</item>

<item name=android:windowTitleSize>20dip</item>
</style>

En el cdigo anterior podemos ver que hemos personalizado los atributos margenActividad
(margen de la pgina) y android:windowTitleSize (tamao de la barra del ttulo de la
aplicacin). Es recomendable establecer el tema padre (parent=android:Theme) dentro del
tema por defecto del sistema operativo Android para heredar todos los atributos del sistema.

Aula Mentor

Los temas visuales de Android son un medio rico y complejo que permiten definir todos los
atributos del aspecto de Vistas de Android. Dada la cantidad de atributos que define Android, es
recomendable definir nuestro nuevo tema a partir de uno ya existente en el sistema operativo,
es decir, debemos extenderlo de uno predefinido.
Veamos los cuatro temas bsicos por defecto que define el sistema operativo Android:
- Theme: es el Tema ms bsico que se incluy en la primera versin de Android y que hemos
utilizado en el ejemplo del curso. Se trata de un tema con fondo oscuro y los textos en color
claro que est disponible en todas las versiones de Android, si bien puede variar ligeramente
dependiendo del fabricante del dispositivo.
- Theme.Light: variacin de tema Theme, muestra texto oscuro con fondo claro.
- Theme.Holo: este tema se introdujo en la versin 3.0 de Android y presenta un aspecto ms
moderno que los dos anteriores. Tiene una particularidad: los fabricantes de dispositivos no
deben modificarlo.
- Theme.Holo.Light: variacin en color claro del tema anterior.
Para indicar el tema visual por defecto de la aplicacin podemos establecer el atributo android_
theme en la aplicacin o en cada una de las actividades en su archivo Manifest.xml. Por
ejemplo, as:
<application android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/Tema>

138

Tambin se puede establecer el tema de una actividad mediante sentencias de Java. Para ello,
debemos utilizar el mtodo setTheme() en el evento onCreate() de la actividad justo antes de
la ejecucin de la sentencia setContentView(). Hay que tener en cuenta que, por lo general,
se debe evitar esta forma de indicar el tema visual, salvo que la aplicacin que desarrollemos
deba cambiar de forma dinmica sus temas.

2.2 Atributos personalizados


Como hemos visto anteriormente, para modificar los mrgenes de todas las actividades, podemos
definir el nuevo tamao en un nico lugar: archivo temas.xml de la aplicacin estableciendo
el atributo margenActividad:
<item name=margenActividad>2sp</item>

Sin embargo, si copiaras el texto anterior en el archive tema.xml veras que Eclipse mostrara el
error: No resource found that matches the given name: attr margenActividad.
Esto ocurre porque el atributo margenActividad no existe por defecto en el sistema Android.
No obstante, el atributo android:windowTitleSize es correcto ya que s est definido en el
SDK de Android.
Para definir este atributo nuevo, debemos crear el archivo atributos.xml dentro de la
carpeta res/values/. Podemos ver cmo hemos definido el atributo margenActividad en
este fichero:

<?xmlversion=1.0encoding=utf-8?>
<resources>
<attrname=margenActividadformat=reference|dimension/>
</resources>

Ahora, Eclipse no mostrar un error al estar definido el atributo margenActividad. El campo

U2 Multimedia y Grficos en Android

format indica el tipo de valores que se pueden definir para este atributo; en este caso, hemos
indicado que debe indicarse el nombre de otro atributo (reference) o (|) una dimensin
(dimension) como, por ejemplo 2sp o 4px. Adems, podemos incluir tantos formatos como

queramos del siguiente listado:


- reference: el nombre de otro atributo
- string: de tipo cadena
- color
- dimension: dimensin
- boolean: lgico
- integer: nmero entero
- float: nmero con decimales
- fraction: fraccin
- enum: campo enumerado
- flag: de tipo marcas

Una vez definidos los mrgenes de las Vistas, podemos establecerlo haciendo referencia al
mismo en la definicin del layout correspondiente: por ejemplo, as:
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Texto de prueba
android:layout_margin=?margenActividad
android:textSize=?tamanioTexto/>

Es decir, si defino el atributo margenActividad en un nico archivo y se hace referencia a ste


en la definicin de las Vistas en el Layout de la interfaz de la aplicacin, puedo modificarlo de
forma sencilla y rpida sin tener que alterar el resto del cdigo fuente.
Si te fijas en el cdigo anterior, hemos definido tambin el atributo tamanioTexto, es
decir, para todas las Vistas es necesario incluir tantas propiedades como atributos hayamos definido en el tema.
Android soluciona este inconveniente mediante los Estilos (Styles) que agrupan varios
atributos de un Vista en un nico bloque que se pueden aplicar al aspecto de esta Vista. Veamos
cmo se hace.
Por ejemplo, si todos los TextViews de una aplicacin deben tener exactamente el
mismo aspecto (color, fuente, margen, alineamiento, etctera), podemos definir un estilo en el
archivo res/values/atributos.xml con sus caractersticas de igual forma que hemos hecho
en el caso de los atributos:
<attr name=textoTitulo format=reference />

En este caso, hemos indicado que el estilo, que se define como un atributo ms, debe hacer
referencia a otro atributo.
En el ejemplo del curso, en el archivo res/layout/main.xml podemos ver cmo se
asigna el estilo textoTitulo a un TextView:
<TextView

android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=@string/texto_titulo

style=?textoTitulo/>

Finalmente, para definir el estilo textoTitulo debemos declararlo en el tema correspondiente


en el fichero temas.xml:
<style name=Tema.Blanco>

139

Aula Mentor

<item name=paginaBackground>@style/pagina_background_blanco
</item>
...
<item name=textoTitulo>@style/texto_titulo_blanco</item>

Si te fijas en el cdigo anterior, vers que se hace referencia al estilo texto_titulo_blanco


que se define en el archivo values/estilos.xml:

<style name=texto_titulo_blanco>

<item name=android:background>@drawable/barra_titulo_blanco
</item>

<item name=android:gravity>center_vertical|center_horizontal
</item>

<item name=android:textColor>#FFF</item>

<item name=android:textSize>24sp</item>

<item name=android:shadowDx>1.0</item>

<item name=android:shadowDy>1.0</item>

<item name=android:shadowRadius>3</item>

<item name=android:shadowColor>#888</item>
</style>

Aqu ya s se establecen las propiedades visuales de las distintas Vistas teniendo en cuenta las
propiedades que posean.

140

A modo de resumen, para utilizar los temas conjuntamente con estilos debemos dar los siguientes
pasos:
- Definir el nombre del tema y sus atributos (caractersticas de aspecto) disponibles en el archivo res/values/temas.xml del proyecto.
- En el fichero res/values/atributos.xml especificar los mismos atributos anteriores mediante referencia a otro atributo (format=reference).
- Disear el aspecto de cada atributo con las propiedades visuales de una Vista Android en el
fichero res/values/estilos.xml.
- Aplicar el estilo correspondiente a las vistas que define la interfaz del usuario (en el archivo
layout xml) mediante su propiedad style.

2.3 Definicin de recursos dibujables (Drawable)


Como hemos estudiado en la Unidad 1, un recurso dibujable (del ingls, Drawable) es una
forma de definir cmo se dibuja la interfaz de una aplicacin Android. Hemos visto que existen
muchos tipos de recursos dibujables, tales como archivos de imgenes, colores, gradientes,
formas geomtricas, etctera. A continuacin, vamos a estudiar algunos recursos, los que estn
directamente relacionados con los estilos:

2.3.1 Recurso de color


Como su nombre indica, con este recurso podemos definir un color comn a toda la aplicacin
creando el fichero res/values/colores.xml y escribiendo lo siguiente:
<?xml version=1.0 encoding=utf-8?>
<resources>

<color name=azul>#FFF</color>
</resources>

U2 Multimedia y Grficos en Android

Como vers, su utilizacin es muy sencilla: en el cdigo anterior hemos declarado la etiqueta

color con su nombre e indicado el color en formato hexadecimal. Despus, para poder aplicar
este color a un estilo o Vista, basta con abrir el archivo estilos.xml y usar este color con la
nomenclatura @color:
<style name=texto_titulo_azul>

<item name=android:textColor>@color/azul</item>

2.3.2 Recurso de dimensin


Como su nombre indica, un recurso de tipo Dimensin permite definir una longitud comn
a toda la aplicacin. Por ejemplo, en el fichero res/values/estilos.xml hemos incluido lo siguiente:
<dimen name=pagina_margen_azul>2sp</dimen>

Como vers, su utilizacin es tambin muy sencilla; en el cdigo anterior hemos declarado la
etiqueta dimen con su nombre e indicado el tamao en formato sp como unidad de medida.
Despus, para poder aplicar esta dimensin a un estilo o Vista, basta con abrir el archivo estilos.xml y usarla con la nomenclatura @dimen:
<style name=pagina_background_azul>
<item name=android:background>#000</item>
<item name=android:padding>@dimen/pagina_margen_azul</item>
</style>

En este caso hemos establecido un margen interno (padding) del fondo del tema azul.
Las magnitudes de medida en interfaces grficas de Android son las siguientes:
- px: Pxeles - unidad en pxeles independientemente de la resolucin de la pantalla.
- in: Inches (Pulgadas) - unidad en pulgadas independientemente del tamao de la pantalla.
- mm: Milmetros - unidad en milmetros que depende del tamao de la pantalla.
- pt: Points (Puntos) - unidad en puntos por pulgada que depende del tamao de la pantalla
del dispositivo.
- dp: Density-independent Pixels (Densidad independiente en pxeles) - unidad abstracta
basada en la densidad fsica de la pantalla. Es recomendable intentar utilizar siempre esta
unidad ya que los elementos grficos y Vistas se representarn con el mismo tamao independientemente del tamao y resolucin del dispositivo Android.
- sp: Scale-independent Pixels (Independiente de la escala en pxeles) unidad parecida a la
anterior (dp) pero utilizada para definir los tamaos de la fuente de las textos que aparecen
en la interfaz del usuario.

2.3.3 Gradiente Drawable (Gradiente dibujable)


Un Gradient Drawable permite al programador dibujar un gradiente de colores que consiste
en mostrar un degradado de dos colores en el fondo de cualquier Vista.
En el ejemplo del curso, puedes abrir al archivo res/drawable/ gradiente_gris_background.xml para ver cmo se define este tipo de elemento visual:
<shape xmlns:android=http://schemas.android.com/apk/res/android>
<gradient
android:startColor=@color/gris
android:endColor=@color/negro

android:angle=270/>
</shape>

141

Aula Mentor

En este caso hemos definido un degradado que comienza (startColor) en el color gris y termina (endColor) en el color negro. Como puedes ver, estos colores tambin estn definidos
como un recurso de color. Adems, hemos indicado que aplique un giro (angle) al degradado
de 270 grados.
Una vez hemos definido el gradiente, es sencillo aplicarlo al tema en el archivo estilos.xml
as:
<style name=pagina_background_blanco>
<item name=android:background>@drawable/gradiente_gris_background
</item>
<item name=android:padding>@dimen/pagina_margen_blanco</item>
</style>

Aplicndolo al tema en el archivo temas.xml:

<style name=Tema.Blanco>
<item name=paginaBackground>@style/pagina_background_blanco</item>

Y asociando el estilo en el fichero layout main.xml que define la interfaz de la aplicacin:


<ScrollView

android:layout_width=fill_parent
android:layout_height=0px
android:layout_weight=1

style=?paginaBackground>

2.3.4 Selector Drawable (Selector dibujable)


142

Un Selector Drawable es un tipo de elemento que permite realizar cambios


automticos basados en el aspecto de una Vista teniendo en cuenta el
estado actual de sta.
En el ejemplo del curso, puedes abrir al archivo res/drawable/boton_blanco.xml

para ver cmo se define este tipo de elemento visual:

<selector xmlns:android=http://schemas.android.com/apk/res/android>
<item android:state_pressed=true
android:drawable=@drawable/boton_blanco_presionado />
<item android:state_focused=true
android:drawable=@drawable/boton_blanco_seleccionado />
<item android:drawable=@drawable/boton_blanco_normal />
</selector>

Vers que hemos definido un selector que cambia el aspecto de una Vista cuando ocurre alguno de estos eventos:
- state_pressed: si el usuario presiona (hace clic) sobre la Vista que tiene asociado este
estilo, entonces se debe cambiar su aspecto utilizando el elemento dibujable boton_blanco_presionado. Definido tambin dentro de la carpeta drawable con el nombre boton_
blanco_presionado.xml. Puedes abrir este archivo para ver su contenido.
- state_focused: si el usuario selecciona (centra el foco con el tabulador) la Vista que tiene
asociado este estilo, entonces se debe cambiar su aspecto utilizando el elemento dibujable boton_blanco_seleccionado. Definido tambin dentro de la carpeta drawable con
el nombre boton_blanco_seleccionado.xml. Puedes abrir este archivo para ver su contenido.
- Sin evento: en este caso, no se incluye ningn evento y se define el aspecto normal de la
Vista utilizando el elemento dibujable boton_blanco_normal. Definido tambin dentro
de la carpeta drawable con el nombre boton_blanco_normal.xml. Puedes abrir este archivo para ver su contenido.

U2 Multimedia y Grficos en Android

- state_selected: establece el aspecto de la Vista al estar seleccionada, por ejemplo, en el


caso de una opcin de un listado. En este caso, al tratarse de un botn, no hemos incluido
este evento al no tener sentido.
En el archivo res/values/estilos.xml puedes encontrar cmo se define el botn blanco:
<style name=boton_blanco parent=texto_grande_blanco>
<item name=android:background>@drawable/boton_blanco
</item>
<item name=android:layout_margin>3sp</item>
</style>

Despus, en el archivo res/values/temas.xml es muy sencillo asociar este estilo en el tema


que corresponda:
<style name=Tema.Blanco>
...
<item name=blancoBackground>@style/blanco_background_blanco</item>
<item name=boton>@style/boton_blanco</item>

Si haces clic en los botones del ejemplo del curso, vers que cambia el aspecto de botn:

2.3.5 Nine-patch drawable con botones


Un Nine-patch Drawable es un tipo especial de imagen que escala o crece tanto a lo largo
como a lo ancho y que mantiene su relacin de aspecto visual. Es decir, Android va a aumentar
el tamao de este elemento teniendo en cuenta las dimensiones de las vistas.
Aunque se pueden utilizar en cualquier tipo de Vista, comnmente se utilizan en Botones. En el Ejemplo 1 hemos implementado este tipo de elemento en el Tema Azul. Veamos
cmo se hace siguiendo estos sencillos pasos:
- Disponemos de un archivo de tipo imagen que representa el botn en tamao pequeo
res/drawable/boton.png:

- Definimos un estilo en el archivo estilos.xml indicando el padre de la Vista y el fondo


que debe aplicarse en este caso:
<style name=MiBoton parent=android:Widget.Button>
<item name=android:background>@drawable/boton</item>
</style>

En el fichero temas.xml, aplicamos al tema visual correspondiente el estilo de botn


que hemos definido en el punto anterior:
<style name=Tema.Azul>
...
<item name=blancoBackground>@style/blanco_background_azul
</item>
<item name=boton>@style/MiBoton</item><

143

Aula Mentor

Finalmente, indicamos el estilo que debe aplicarse a los botones en el layout de la


aplicacin:
<Button

android:id=@+id/boton1

android:layout_width=0px

android:layout_height=wrap_content

android:layout_weight=1

android:text=Sin tema

style=?boton/>

Si ejecutamos la aplicacin del ejemplo y hacemos clic en el botn Tema


Azul, veremos que el aspecto de los botones cambia cuando utilizamos este
archivo de tipo imagen como fondo de estos botones:

En el manual oficial sobre DRAWABLES de Android puedes encontrar un listado completo de


todos los drawables disponibles, as como sus propiedades. En este apartado hemos incluido
los ms interesantes o utilizados por el programador en el diseo de estilos.

2.4 Atributos de los temas


144

Como hemos visto, mediante temas visuales podemos definir en las Vistas una amplia variedad
de atributos que cambian el aspecto de stas. A continuacin, vamos a indicar los ms utilizados
por el programador:
- colorBackgroundCacheHint
Mediante este atributo establecemos el color del fondo de pantalla de una Vista (en los
ejemplos hemos utilizado tambin el atributo background) y debera ser vaco (null)
cuando el fondo es una textura o traslcido, es decir, slo sirve para establecer colores
slidos.
- textAppearance
Atributo para establecer el aspecto por defecto del texto: color, tipo de fuente, tamao y
estilo.
- textColorPrimary
Color principal del texto.
- textAppearanceLarge
Aspecto por defecto del texto del tamao grande: color, tipo de fuente, tamao y estilo.
- textAppearanceMedium
Aspecto por defecto del texto del tamao medio: color, tipo de fuente, tamao y estilo.
- textAppearanceSmall
Aspecto por defecto del texto del tamao pequeo: color, tipo de fuente, tamao y estilo.
- buttonStyle
Estilo del botn normal.
- listDivider
Atributo que define el drawable del divisor de una lista.

U2 Multimedia y Grficos en Android

- windowBackground
Drawable utilizado como el fondo de la ventana. A diferencia del atributo anterior colorBackgroundCacheHint, podemos establecer tanto texturas, colores slidos y traslcidos
como fondos de pantalla.
- windowFrame
Drawable utilizado para definir el marco de una ventana.
- windowActionBar
Marca que indica que la ventana debe mostrar una Barra de Accin (Action Bar) en lugar
del usual ttulo de la ventana. Ms adelante, en esta misma Unidad 2, veremos en qu consiste un Action Bar.
- alertDialogTheme
Tema que se debe aplicar a una ventana de dilogo de alerta.
- progressBarStyle
Estilo por defecto de la vista ProgressBar. Normalmente, es un crculo que gira.actionBarStyle

Atributo que permite establecer el estilo de un Action Bar.

Recuerda que para indicar un atributo por defecto del sistema operativo de Android es necesario escribir el texto android: antes del nombre del mismo, por
ejemplo, as: android:windowBackground.

En el manual oficial sobre ATRIBUTOS de Android puedes encontrar un listado completo con
la descripcin de todos los atributos disponibles. En este apartado hemos incluido los ms
interesantes o utilizados por el programador.

2.5 Carga dinmica de Temas


Es posible definir en tiempo de ejecucin, el tema visual que debe cargar una aplicacin Android.
Si abres el archivo temas.xml, vers que hemos definido varios temas visuales:
<style name=Tema.Blanco>
<item name=paginaBackground>@style/pagina_background_blanco</item>
<item name=paginaPaddingLayout>@style/pagina_padding_layout_blanco
</item>
<item name=textoTitulo>@style/texto_titulo_blanco</item>
...
<style name=Tema.Azul>
<item name=paginaBackground>@style/pagina_background_azul</item>
<item name=paginaPaddingLayout>@style/pagina_padding_layout_azul
</item>
<item name=textoTitulo>@style/texto_titulo_azul</item>

Para intercambiar los diferentes temas en tiempo de ejecucin hemos reiniciado la Actividad
principal y utilizado el mtodo setTheme() en el evento onActivityCreateSetTheme() antes de
que la aplicacin invoque el mtodo setContentView():
// Reinicia y establece el tema de una Actividad.
public static void changeToTheme(Activity activity, int tema)
{

145

Aula Mentor

nTema = tema;
// Finalizamos la actividad
activity.finish();
// Volvemos a ejecutar de nuevo la Actividad
activity.startActivity(new Intent(activity,
activity.getClass()));

}
// Establece el tema de la Actividad de acuerdo con la variable nTema
public static void onActivityCreateSetTheme(Activity activity)
{

switch (nTema)
{
default:

case TEMA_DEFECTO:
break;

case TEMA_BLANCO:
activity.setTheme(R.style.Tema_Blanco);
break;

case TEMA_AZUL:
activity.setTheme(R.style.Tema_Azul);
break;
}
}

146

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Temas y estilos) de la Unidad


2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado Temas visuales.

Si ejecutas en Eclipse este Ejemplo 1, vers que se muestra la siguiente aplicacin en el Emulador:

U2 Multimedia y Grficos en Android

Al pulsar en los distintos botones, vers que la carga de un tema hace que el aspecto de la
aplicacin cambie radicalmente. As de potente es esta tcnica si se emplea correctamente.
Aunque a priori pueda parecer complicado gestionar temas visuales, basta con recorrer
el cdigo fuente del Ejemplo 1 del curso para ir entendiendo las relaciones entre Estilos, Temas
y Vistas. Te animamos a que estudies con atencin su cdigo fuente.

3. Implementacin de Widgets en la pantalla principal


En informtica, un Widget es una aplicacin reducida o programa de tamao pequeo que
permite al usuario visualizar informacin de forma rpida en la pantalla y/o acceder a funciones
utilizadas frecuentemente. Dado que son pequeas aplicaciones, los Widgets en Android pueden
hacer casi todo lo que nuestra imaginacin desee e interactuar con servicios e informacin
diversa. Por ejemplo, pueden consistir en relojes vistosos, notas, calculadoras, calendarios,
agendas, juegos, ventanas con informacin del tiempo en tu ciudad, etctera.
En Android, se pueden disear Widgets tanto en la pantalla principal del dispositivo
(en ingls, Home Screen) como en su pantalla de bloqueo (en ingls, Lock Screen); esto ltimo,
nicamente es posible a partir de la versin 4.2 de Android).
Sobre los Widgets de Android debemos saber que:
- Se pueden arrastrar y cambiar su posicin en el escritorio.
- Es posible eliminarlo del escritorio.
- El usuario puede modificar su tamao a partir de la versin 3.1 de Android.
- Ofrecen una interfaz en la que el usuario puede interactuar como si fuera una Actividad ms.
- Actualizan su informacin de forma peridica al recibir informacin de cualquier componente del sistema.
- Se puede crear un Widget tantas veces como queramos.
- Un Widget al ser creado puede invocar a una Actividad de configuracin. En esta actividad
se suelen recoger los parmetros iniciales necesarios para generar la instancia del Widget.
- Un Widget se ejecuta como parte del proceso de la aplicacin que lo define y, por lo tanto,
mantiene los permisos de seguridad de sta.
Para programar un Widget de Android hay que tener en cuenta lo siguiente:
- Como hemos visto, en el escritorio de un dispositivo pueden existir varias instancias de un
mismo Widget, pues el sistema operativo los distingue asignndoles un ID para cada instancia del mismo.
- El comportamiento de todas sus instancias se programa en una clase que hereda de AppWidgetProvider. Esta clase dispone del mtodo onUpdate() que el sistema invoca cada vez
que es necesario actualizar el Widget. Esta clase es la que da forma al concepto de Widget
en el sistema operativo.
- Para crear la interfaz visual de usuario de los Widgets debemos utilizar la clase RemoteViews. Un objeto de tipo RemoteView puede ser modificado por otro proceso como un
servicio que tenga los mismos permisos que la aplicacin original.
- La interfaz de usuario del Widget se define mediante un BroadcastReceiver que infla el
layout en un objeto del tipo RemoteViews que es el que pasa a formar parte del escritorio
del dispositivo. En el archivo AndroidManifest.xml del proyecto debe quedar registrado
este receptor (receiver) para que el Widget reciba mensajes del tipo android.appwidget.action.APPWIDGET_UPDATE cuando sea necesario crearlo o actualizarlo.
- Los parmetros que definen el Widget se incluyen en un archivo xml que determina aspectos visuales tales como sus dimensiones, la frecuencia de actualizacin, los controles de la
interfaz y la Actividad que permite al usuario configurarlo.

147

Aula Mentor

- Si hemos definido una Actividad de configuracin en el fichero anterior, cada vez que se cree
el Widget se ejecutar esta Actividad para indicar sus parmetros iniciales. Si deseamos que
el usuario pueda reconfigurar el Widget debemos crear un Actividad extra que aparezca en
el escritorio y permita realizar esta tarea.

3.1 Tipos de Widgets y sus limitaciones

148

Los Widgets de Android parecen muy simples cuando los utilizamos, sin embargo existen ciertas
limitaciones a la hora de programarlos que debes conocer.
Si desarrollas un Widget sencillo que no requiere la gestin de su estado y se actualiza un par
de veces al da, vers que es muy fcil de implementar.
Si implementas un Widget con gestin del estado que se actualiza con poca frecuencia
con cambios elementales, podemos definir su comportamiento dentro del propio Widget.
Sin embargo, para un Widget complejo que se actualiza cada poco tiempo (segundos o
milisegundos), debemos utilizar el gestor de alarmas (AlarmManager) o, probablemente, sea necesario desarrollar un servicio que gestione el estado del Widget. En el ejemplo del curso hemos
utilizado los servicios, aunque no es estrictamente necesario, para mostrar cmo se implementa
este tipo de Widget complejo.
La nica interaccin posible del usuario con las Vistas incluidas en un Widget es mediante el evento OnClickListener que se lanza cuando el usuario hace clic sobre ste.
En relacin con los gestos, nicamente se permite un toque (Touch) y arrastrar verticalmente (Vertical Swipe) En el Ejemplo 3 veremos cmo funciona este ltimo gesto al cambiar
las imgenes.
Otro factor a considerar es que los objetos RemoteViews que especifican la interfaz del
usuario tienen restricciones sobre los tipos Vistas y layouts que podemos incluir en ellos. Es decir, no podemos incluir en un Widget todas las Vistas disponibles en el SDK de Android. Adems,
el programador no puede gestionar directamente las Vistas internas del RemoteView, sino debe
hacerlo mediante los mtodos proporcionados por esta clase.
Seguidamente, mostramos las Vistas y layouts que podemos incluir en el diseo de un Widget:
FrameLayout
LinearLayout
RelativeLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

Aunque esta lista parezca reducida, aumenta con cada nueva versin de Android. La razn
principal para restringir lo que se puede incluir en una RemoteView es que estas Vistas deben
estar desconectadas de los procesos que las controlan. En realidad, estas Vistas estn alojadas
en otra aplicacin como es Home Application (aplicacin de lanzadera de aplicaciones que
funciona como escritorio en Android que, adems, el usuario puede cambiar a su antojo). Los
gestores de estas Vistas son procesos en segundo plano que se invocan por temporizadores, por

U2 Multimedia y Grficos en Android

esta razn, a esas vistas se las denomina vistas remotas (remote views) y pueden ser modificadas por otros procesos con los mismos permisos.

Las Vistas ListView, GridView, StackView y AdapterViewFlipper se pueden


utilizar en el interior de un Widget a partir de la versin Android 3.0, aunque su uso
tiene algunas particularidades ya que debemos emplear Colecciones (Collections). Al final de esta apartado comentaremos algo ms sobre ellas.

3.2 Ciclo de vida de un Widget


Antes de empezar a implementar un Widget, vamos a analizar el ciclo de vida de ste dentro
del sistema operativo Android estudiando la secuencia de creacin y actualizacin.

149

La descripcin de estos pasos es la siguiente:


- Cuando el usuario decide aadir un Widget al escritorio de su dispositivo, el sistema operativo captura la orden y enva el mensaje android.appwidget.action.APPWIDGET_UPDATE
a la aplicacin.
- Android infla el Widget y crea su instancia obteniendo su ID nico.
- A continuacin, si existe, invoca la Actividad de configuracin en el mtodo onCreate()
con un mensaje Intent que incluye entre sus parmetros el ID del widget creado anteriormente.
- La Actividad de configuracin realiza las operaciones oportunas como el inicio de un servicio, uso de un ContentProvider, etctera; y actualiza las preferencias sobre la instancia
del Widget.
- Finalmente, se devuelve el control al sistema operativo.
- Cada vez que es necesario actualizar el Widget, Android ejecuta el mtodo onUpdate() de
su Proveedor de Widgets (WidgetProvider, que veremos ms adelante).
- El WidgetProvider actualiza todas las instancias de Widgets creadas en el escritorio.
Adems, el sistema operativo puede desactivar o activar un Widget como una Actividad normal
y corriente mediante los mtodos ya conocidos por el alumno onDisable() onEnabled()
respectivamente y onDestroy() en caso de eliminacin.

Aula Mentor

3.3 Ejemplo de Creacin de un Widget


Para iniciar un proyecto que implemente un Widget debemos crear en Eclipse ADT un proyecto
normal de tipo Android.
El Ejemplo 2 de esta Unidad consta de los siguientes archivos:
- Interfaz de usuario del Widget: /res/layout/widget_layout_rojo.xml y /res/layout/
widget_layout_verde.xml

- Fichero de configuracin del widget: /res/xml/widget_info.xml


- Clase que define el Widget (extendida de AppWidgetProvider): HorarioTrabajoWidgetProvider.java

- Servicio que actualiza el Widget: ServicioActualizacionWidget.java


- Interfaz de la Actividad de configuracin del Widget: /res/layout/preferencias_widget.xml

- Actividad de configuracin de las preferencias: PreferenciasWidget.java


- Definicin de la aplicacin: AndroidManifest.xml

3.4 Ejemplo de implementacin de un Widget


En esta parte, vamos a ver como se implementa un Widget estudiando los ficheros que hemos
nombrado anteriormente.
Interfaz de usuario del Widget: /res/layout/widget_layout_rojo.xml y /res/layout/
widget_layout_verde.xml

150

Estos dos primeros archivos definen los layout que dibujan la interfaz del Widget que est
compuesta nicamente por un LinearLayout, una imagen (ImageView) y una etiqueta de
texto (TextView) donde aparece el mensaje al usuario. Veamos, por ejemplo, cmo es el layout
widget_layout_verde.xml:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/layout_widget
android:layout_width=match_parent
android:layout_height=match_parent
android:layout_margin=1dip
android:orientation=horizontal
android:background=@drawable/forma_verde >
<ImageView

android:src=@drawable/out

android:id=@+id/icono

android:layout_gravity=center

android:gravity=center_horizontal|center_vertical

android:layout_width=wrap_content

android:layout_margin=5dip

android:layout_height=wrap_content/>
<TextView
android:id=@+id/texto
style=@android:style/TextAppearance.Medium
android:layout_width=match_parent
android:layout_height=match_parent
android:layout_gravity=center
android:gravity=center_horizontal|center_vertical
android:text=Sin datos >
</TextView>

U2 Multimedia y Grficos en Android

</LinearLayout>

Importante: como vers, hemos utilizado nicamente Vistas permitidas dentro de


un Widget accesibles mediante la clase RemoteViews.

Adems, como fondo del layout hemos utilizado el dibujable @drawable/forma_verde en el


que se define un rectngulo redondeado con un gradiente de color verde as:
<shape xmlns:android=http://schemas.android.com/apk/res/android
android:shape=rectangle >
<stroke
android:width=2dp
android:color=#FFFFFFFF />
<gradient
android:angle=225
android:endColor=#008000
android:startColor=#00FF00 />
<corners
android:bottomLeftRadius=7dp
android:bottomRightRadius=7dp
android:topLeftRadius=7dp
android:topRightRadius=7dp />
</shape>

151

3.4.1 Fichero de configuracin del widget:


/res/xml/widget_info.xml

En este fichero XML se definen las propiedades del Widget como, por ejemplo, el tamao en
pantalla o la frecuencia de actualizacin. Este XML se debe crear en la carpeta \res\xml del
proyecto. En el ejemplo del curso tiene la siguiente estructura:
<appwidget-provider
xmlns:android=http://schemas.android.com/apk/res/android
android:initialLayout=@layout/widget_layout_verde
android:minHeight=40dp
android:minWidth=180dp
android:resizeMode=horizontal|vertical
android:updatePeriodMillis=180000
android:configure=es.mentor.
unidad2.eje2.widgethomescreen.PreferenciasWidget>
</appwidget-provider>

Hemos declarado las siguientes propiedades:


- initialLayout: layout XML creado en el paso anterior que debe inflar el Widget al iniciarse.
- minHeight: alto mnimo del widget en pantalla en dp (density-independent pixels).
- minWidth: ancho mnimo del widget en pantalla en dp (density-independent pixels).
- resizeMode: modo de redimensionamiento (slo para Android 3.1 en adelante).
- updatePeriodMillis: frecuencia de actualizacin del widget en milisegundos. En el ejemplo definimos 30 minutos = 180.000 milisegundos.
- configure: actividad de configuracin del widget.

Aula Mentor

Existen otras propiedades que se pueden definir como, por ejemplo, el icono de la vista previa
del widget (android:previewImage), aunque slo para Android >3.1. Puedes consultarlas
todas en AppWidgetProviderInfo.
La pantalla inicial de Android se divide en un mnimo de 44 celdas (un dispositivo con
mayor pantalla pueden contener ms) donde se pueden colocar aplicaciones, accesos directos
y Widgets. Teniendo en cuenta las diferentes dimensiones de estas celdas segn el dispositivo y
la orientacin de la pantalla, existe una frmula sencilla para ajustar las dimensiones de nuestro
widget para que ocupe un nmero determinado de celdas sea cual sea la orientacin:
- ancho_mnimo = (num_celdas * 70) 30
- alto_mnimo = (num_celdas * 70) 30
Empleando esta frmula, el widget del Ejemplo del curso ocupa un tamao mnimo de 3 celdas
de ancho por 1 celda de alto; por lo tanto, debemos indicar unas dimensiones de 146dp x 40dp.

En la Gua de diseo de Widgets de Android puedes encontrar recomendaciones de estilo a la hora de disearlos.

3.4.2 Clase que define el Widget:


HorarioTrabajoWidgetProvider.java

152

Esta clase implementa la funcionalidad del widget y deber extenderse de la clase AppWidgetProvider que, a su vez, es una clase derivada de BroadcastReceiver, ya que los widgets de
Android son un caso particular de este tipo de componentes.
En esta clase se implementan los mensajes a los que vamos a responder desde el Widget, entre los que destacan:
- onEnabled(): lanzado cuando se crea la primera instancia de un Widget.
- onUpdate(): ejecutado peridicamente cuando hay que actualizar un Widget cada vez que
se cumple el periodo de tiempo definido por el parmetro updatePeriodMillis descrito
anteriormente o al aadir el Widget al escritorio.
- onDeleted(): lanzado cuando se elimina del escritorio una instancia de un widget.
- onDisabled(): ejecutado cuando se elimina del escritorio la ltima instancia de un Widget.
- onReceive(): se implementa para enviar llamadas a los distintos mtodos de otros AppWidgetProvider o para actualizar un Widget individualmente.
En la mayora de los casos, tendremos que implementar, como mnimo, el evento onUpdate().
El resto de mtodos dependern de la funcionalidad de nuestro Widget. En el Ejemplo del curso implementamos este mtodo onUpdate() y el mtodo onReceive():
// Define la Clase que llama Android cada vez que se crea y actualiza // el
Widget. Debe extenderse de la clase AppWidgetProvider
public class HorarioTrabajoWidgetProvider extends AppWidgetProvider {
private static final String LOG =
es.mentor.unidad2.eje2.widgethomescreen;
// Evento que inicia Android cada vez que tiene que actualizar el
// widget
@Override
public void onUpdate(Context context, AppWidgetManager

U2 Multimedia y Grficos en Android

appWidgetManager, int[] appWidgetIds) {


// Actualizamos el Widget llamando a un servicio
actualizaWidget(context, appWidgetManager);
// Nota: si el Widget fuera sencillo no es necesario utilizar un
// Servicio y podramos incluir aqu las sentencias que actualizan
// el Widget

}
// Si quisiramos actualizar un nico Widget al hacer clic sobre l
// deberamos implementar aqu la actualizacin
@Override
public void onReceive(Context context, Intent intent) {

final String action = intent.getAction();

final int appWidgetId =
intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

Log.i(LOG,Mensaje recibido: + action + del Widget:
+appWidgetId);

super.onReceive(context,intent);
}
// Mtodo que lanza el servicio que actualiza el contenido del
// Widget
public static void actualizaWidget(Context context, AppWidgetManager
appWidgetManager)
{

// Generamos un log para ver si se actualiza el widget

Log.w(LOG, Mtodo onUpdate ejecutado);

// Obtenemos el nombre del componente del Widget
ComponentName
esteWidget = new ComponentName(context,


HorarioTrabajoWidgetProvider.class);

// Obtenemos todos los IDs de los widget mediante el mtodo
// getAppWidgetIds del AppWidgetManager y los copiamos a una
// matriz
int[] todosWidgetIds =
appWidgetManager.getAppWidgetIds(esteWidget);
// Construimos un Intent para llamar al servicio correspondiente
Intent intent = new Intent(context.getApplicationContext(),

ServicioActualizacionWidget.class);
// Incluimos los IDs de todos los Widgets
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
todosWidgetIds);
// Actualizamos el widget invocando el servicio de actualizacin
context.startService(intent);
}
}

En el cdigo anterior hemos utilizado un servicio o el gestor de Alarmas (AlarmManager) para


actualizar el Widget. Si la ejecucin del mtodo onUpdate()conlleva complejas operaciones
o se hace una peticin a un servicio web en Internet que puede tardar en responder, Android
puede cerrar el Widget y mostrar el tpico mensaje de error Aplicacin no responde (Application Not Responding = ANR) al pasar ms de 10 segundos.
Por el contrario, si el Widget es sencillo, no es necesario utilizar un servicio y podramos
incluir en este mtodo onUpdate() las sentencias que lo actualizan utilizando la clase RemoteViews de la misma manera que se hacemos en el servicio.

153

Aula Mentor

3.4.3 Servicio que actualiza el Widget:


ServicioActualizacionWidget.java

Como hemos visto, un Widget puede actualizar su aspecto/informacin cada cierto tiempo,
definido en su archivo xml de informacin con la propiedad updatePeriodMillis.
Cuando la actualizacin implica operaciones de larga duracin (ms de 10 segundos), podemos aplicar dos mtodos en el mtodo onUpdate() de la clase WidgetProvider para implementarla:
- Mediante un servicio que veremos en el ejemplo de este curso avanzado
- Utilizando el Gestor de alarmas (AlarmManager) estudiado en el curso de iniciacin de
Mentor (Unidad 7 Ejemplo 3).
La diferencia entre un mtodo y otro radica en que el mnimo intervalo que podemos definir
en un servicio es de 1.800.000 milisegundos (30 minutos), mientras que si usamos el Gestor de
alarmas (AlarmManager) podemos aumentar la frecuencia de actualizacin y, adems, es ms
eficiente desde el punto de vista energtico para el dispositivo.
Para utilizar este segundo mtodo debemos definir un servicio similar al primer mtodo
pero, en lugar de llamarlo, hay que programarlo en este Gestor de alarmas (AlarmManager).
En la Unidad 7 del curso de Iniciacin se estudia cmo hacerlo.

154

Cuanto mayor sea la frecuencia de actualizacin del Widget, el dispositivo, despertar ms a menudo para ejecutar esa tarea y consumir ms batera. Muchos
Widget, al configurarlos, permiten al usuario que elija el intervalo de actualizacin;
as, ste decide cmo quiere gastar la batera de su dispositivo.

Veamos cmo hemos definido el servicio de actualizacin en la clase ServicioActualizacionWidget.java:


// Servicio que se ejecuta cada x segundos para actualizar el Widget o
// se lanza desde la Actividad de configuracin cuando sta acaba
public class ServicioActualizacionWidget extends Service {
private static final String LOG =
es.mentor.unidad2.eje2.widgethomescreen;
@Override
public void onStart(Intent intent, int startId) {

Log.i(LOG, Servicio iniciado);

// Obtenemos los datos de las preferencias

boolean trabajando= PreferenciasWidget.obtenTrabajando(
ServicioActualizacionWidget.this);

String nombre= PreferenciasWidget.obtenNombre(
ServicioActualizacionWidget.this);

// Obtenemos la hora actual

long hora = System.currentTimeMillis();

// Obtenemos de forma directa los IDs de los Widgets que se

// pasan como parmetro de tipo Intent en la llamada al servicio

// con el mtodo onStart

int[] intentWidgetIds = intent

.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

U2 Multimedia y Grficos en Android

// Ahora obtenemos todos los IDs de los Widgets de forma


// indirecta mediante el gestor de Widget de Android


AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(this
.getApplicationContext());

// Nombre del componente que define nuestro Widget

ComponentName esteWidget = new
ComponentName(getApplicationContext(),

HorarioTrabajoWidgetProvider.class);

// Obtenemos todos los Widgets de este tipo

int[] losWidgetIds =
appWidgetManager.getAppWidgetIds(esteWidget);

// Mostramos un log con el nmero de Widgets obtenidos de forma
// directa e indirecta: debe coincidir el nmero

Log.w(LOG, Nmero de Widgets en Intent: +
String.valueOf(intentWidgetIds.length));

Log.w(LOG, Nmero de Widgets de appWidgetManager: +
String.valueOf(losWidgetIds.length));

// Formateador que usamos para mostrar la hora

SimpleDateFormat curFormater = new SimpleDateFormat(hh:mm,
Locale.getDefault());

// Cada vez que se ejecuta el servicio dejamos de trabajar

// (hay que descansar cada 30 minutos :-)

trabajando=!trabajando;

// Actualizamos todos los widgets: podemos usar tanto la matriz
// intentWidgetIds como losWidgetIds ya que deben contener los
// mismos IDs de widgets. Se incluyen aqu los dos mtodos para
// que el alumno los conozca

for (int widgetId : losWidgetIds) {

// Objeto donde inflamos el layout de Widget

RemoteViews remoteViews;

// Log de control interno

Log.i(nombre + Est trabajando?: ,
String.valueOf(trabajando));
// Si no trabajamos usamos el layout rojo para crear el
// RemoteViews. Nota: podramos haber tambin definido un
// nico layout y haber modificado sus Vistas internas

if (!trabajando) {

// Creamos la imagen con el layout

remoteViews = new RemoteViews(
this.getApplicationContext().getPackageName(),

R.layout.widget_layout_rojo);

// Establecemos el mensaje en la vista remota texto
remoteViews.setTextViewText(R.id.texto,

nombre + no trabaja - Hora salida: +
curFormater.format(hora));

} else
{
// Si trabajamos usamos el layout verde para crear el
// RemoteViews
remoteViews = new RemoteViews(this

.getApplicationContext().getPackageName(),

R.layout.widget_layout_verde);

// Establecemos el mensaje en la vista remota texto
remoteViews.setTextViewText(R.id.texto,

nombre + trabaja Hora entrada:+
curFormater.format(hora));

155

Aula Mentor

156

// Guardamos las preferencias


PreferenciasWidget.guardarTrabajando(
ServicioActualizacionWidget.this, trabajando);
PreferenciasWidget.guardarHora(
ServicioActualizacionWidget.this, hora);
// Registramos el evento onClickListener mediante un
// PendingIntent

Intent clickIntent = new
Intent(this.getApplicationContext(),
HorarioTrabajoWidgetProvider.class);

// Indicamos el tipo de accin de Intent
clickIntent.setAction(
AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// Indicamos todos los IDs de los Widgets porque onUpdate

// del WidgetProvider lo necesita para actualizar todos los

// Widgets
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
losWidgetIds);

// Indicamos tambin el ID de este Widget porque onReceive del

// WidgetProvider lo necesita para actualizarlo (si estuviera

// desarrollado este evento)
clickIntent.
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
widgetId);

// Definimos una Intencin Pendiente para este widgetId

PendingIntent pendingIntent = PendingIntent.getBroadcast(
getApplicationContext(), widgetId, clickIntent,

PendingIntent.FLAG_UPDATE_CURRENT);

// Establecemos el evento onClick sobre el layout completo del
// Widget para detectar el clic en cualquier sitio de ste.
remoteViews.setOnClickPendingIntent(R.id.layout_widget,
pendingIntent);

// Indicamos al appWidgetManager que actualice la vista con el

// nuevo remoteViews


appWidgetManager.updateAppWidget(widgetId, remoteViews);
} // end for
// Paramos el servicio al acabar para que no se actualice
// continuamente
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {

// No es posible que un Cliente se conecte (Bind) al servicio

return null;
}
}

Es importante destacar que, como hemos comentado repetidamente, las Vistas internas de
un Widget se basan en un tipo especial llamadoRemoteViews. En el cdigo anterior puedes observar que para acceder a estas Vistas remotas que constituyen la interfaz del Widget
construimos un nuevo objeto RemoteViews inflando su layout correspondiente.
Una vez creado este objeto, podemos modificar el contenido de las Vistas internas
mediante una serie de mtodosset (uno para cada tipo de datos bsicos) que permiten establecer las propiedades de cada Vista individual del Widget. Por ejemplo, remoteViews.set-

U2 Multimedia y Grficos en Android

TextViewText(). Estos mtodos reciben como parmetros el ID de la Vista y el valor que se


desea establecer. Puedes consultar todos los mtodos disponibles en la documentacin oficial
de Android sobre la clase RemoteViews.
Despus, vamos a implementar la funcionalidad del evento onClick sobre el Widget para
forzar la actualizacin del mismo. Ya sabemos que a las Vistas contenidas en un Widget de
Android son del tipo RemoteView y no podemos asociar eventos de la forma tradicional. Sin
embargo, en su lugar, Android permite asociar a un evento (por ejemplo, el click sobre una
Vista o Layout) un determinado mensaje (Pending Intent) de tipo broadcast que ser ejecutado cada vez que se produzca dicho evento.
Adems, podemos configurar el Widget, que es un componente de tipo broadcast receiver, para que capture esos mensajes e implemente en su evento onReceive() las acciones
necesarias que debe ejecutar tras capturar dicho mensaje. De esta forma, podremos simular la
captura de eventos sobre un Widget.
Vamos a estudiar el cdigo anterior. En primer lugar, hacemos que se lance un Intent
de tipo broadcast cada vez que se pulse el botn del widget. Para ello, creamos un nuevo
Intent asocindole la accin de Android AppWidgetManager.ACTION_APPWIDGET_UPDATE
(tambin podramos haber definido una accin personalizada). Como parmetros del nuevo
Intent insertaremos mediante putExtra() todos los IDs de los Widget y, separadamente,
el ID del Widget actual de forma que ms tarde podamos obtener el Widget en concreto que
ha lanzado el mensaje (recuerda que podemos tener varias instancias del mismo widget en el
escritorio).
Por ltimo, crearemos un mensaje del tipo PendingIntent mediante el mtodo getBroadcast() y lo asociaremos al evento onClick del control llamando a setOnClickPendingIntent() pasndole el ID de la Vista interna del Widget que, en nuestro caso, es el
Layout que lo define.
Adems, es muy importante destacar la sentencia hacia el final del cdigo donde se
invoca el mtodo updateAppWidget() del WidgetManager para que se actualice correctamente la interfaz del Widget.

3.4.4 Interfaz de la Actividad de configuracin del Widget:


/res/layout/preferencias_widget.xml

En el fichero definimos la interfaz de configuracin inicial del widget de forma similar a como
haramos con cualquier otra actividad Android diseando su layout xml. En este caso, el diseo
es muy sencillo con un cuadro de texto para introducir el nombre personalizado y dos botones,
uno para aceptar la configuracin y otro para cancelar, en cuyo caso el widget no se crea en
el escritorio. Veamos su contenido:
<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/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/MensajeConfiguracion
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText

157

Aula Mentor

android:id=@+id/txtNombre
android:layout_width=match_parent
android:layout_height=wrap_content
android:hint=Sin nombre
android:text=Sin texto >
<requestFocus />
</EditText>
<TableRow android:layout_width=fill_parent

android:layout_height=wrap_content
android:gravity=center>
<Button
android:id=@+id/btnAceptar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=@string/Aceptar />
<Button
android:id=@+id/btnCancelar
android:layout_width=wrap_content
android:layout_height=wrap_content

android:layout_gravity=center_horizontal
android:text=@string/Cancelar />
</TableRow>
</LinearLayout>

158

3.4.5 Actividad de configuracin de las preferencias:


PreferenciasWidget.java

Una vez diseada la interfaz de la Actividad de configuracin tendremos que implementar su


funcionalidad con cdigo java en esta clase.
En primer lugar, es necesario conocer el identificador de la instancia concreta del widget que
se configurar con esta actividad. Este ID lo podemos obtener de un parmetro del Intent
que ha lanzado la actividad con configuracin. Para ello, usamos el mtodo getIntent() para
obtener un puntero al Intent y mediante el mtodo getExtras()accedemos a su parmetro
AppWidgetManager.EXTRA_APPWIDGET_ID.
Veamos qu aspecto tiene el cdigo:
public class PreferenciasWidget extends Activity{
private static final String PREFS_NAME=
es.mentor.unidad2.eje2.widgethomescreen.
HorarioTrabajoWidgetProvider;
private static final String PREF_PREFIX_KEY = nombre;
private static final String PREF_PREFIX_KEY2 = trabajando;
private static final String PREF_PREFIX_KEY3 = hora;
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
EditText txtValorInicial;
Button btnAceptar, btnCancelar;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

U2 Multimedia y Grficos en Android

// Establecemos el valor de retorno de la actividad a CANCEL


// para que, en caso de que el usuario pulse el botn back
// del dispositivo, se cancele la creacin del widget.
setResult(RESULT_CANCELED);
// Establecemos la interfaz del layout a utilizar.
setContentView(R.layout.preferencias_widget);
// Definimos las referencias a las Vistas
txtValorInicial = (EditText)findViewById(R.id.txtNombre);
btnAceptar =(Button)findViewById(R.id.btnAceptar);
btnAceptar.setOnClickListener(mOnClickListener);
btnCancelar =(Button)findViewById(R.id.btnCancelar);
//Implementacin del botn Cancelar
btnCancelar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//Devolvemos como resultado: CANCELAR (RESULT_CANCELED)
finish();
}
});
// Obtenemos el mensaje (Intent) del sistema que ha iniciado la
// actividad de configuracin. Este mensaje contiene el ID del
// widget que hay que configurar.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
// Obtenemos el ID o un ID invlido si no existe
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
// Si el ID es invlido acabamos la actividad de configuracin
// -> No se crea Widget
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
// Obtenemos el nombre inicial del usuario.
// Aqu podramos utilizar la variable mAppWidgetId si varios
// widgets pudieran contener varios
// valores diferentes y definiramos un mtodo del estilo
obtenNombre(PreferenciasWidget.this, mAppWidgetId);
// No es el caso del ejemplo donde todos muestran lo mismo
String nombreinicial=obtenNombre(PreferenciasWidget.this);
// Si no tenemos nombre no escribimos nada en el TextView
correspondiente
if (nombreinicial.equals(Sin nombre))
txtValorInicial.setText();
else
txtValorInicial.setText(nombreinicial);
}
// Definimos el evento onClick del botn Aceptar
View.OnClickListener mOnClickListener = new View.OnClickListener()
{
public void onClick(View v) {
final Context context = PreferenciasWidget.this;
// Cuando se pulsa el botn grabamos el valor escrito en
// la caja de texto. No incluimos validacin de formato.

159

Aula Mentor

String value = txtValorInicial.getText().toString();


if (value.isEmpty()) value=Currante;
guardarNombre(context, value);
// Al crear el Widget, por defecto no se trabaja
guardarTrabajando(context, false);
// Apuntamos la hora actual
guardarHora(context, System.currentTimeMillis());
// A continuacin, vamos a lanzar el servicio que
// actualiza el contenido del Widget.
// Para ello, usamos el gestor de Widgets de Android
AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(context);
// Actualizamos el Widget
HorarioTrabajoWidgetProvider.actualizaWidget(context,
appWidgetManager);

};

160

// Notificamos la terminacin de la actividad de


// configuracin.
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
mAppWidgetId);
setResult(RESULT_OK, resultValue);
// Finalizamos la Actividad
finish();

// Mtodos utilizados para gestionar las preferencias del widget.


// Se utilizan preferencias compartidas SharedPreferences.
static void guardarNombre(Context context, String value) {
SharedPreferences.Editor prefs =
context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putString(PREF_PREFIX_KEY, value);
prefs.commit();
}
static void guardarTrabajando(Context context, Boolean value) {
SharedPreferences.Editor prefs =
context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putBoolean(PREF_PREFIX_KEY2, value);
prefs.commit();
}
static void guardarHora(Context context, long value) {
SharedPreferences.Editor prefs =
context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putLong(PREF_PREFIX_KEY3, value);
prefs.commit();
}
static String obtenNombre(Context context) {
SharedPreferences prefs =
context.getSharedPreferences(PREFS_NAME, 0);
return prefs.getString(PREF_PREFIX_KEY, Sin nombre);
}

U2 Multimedia y Grficos en Android

static Boolean obtenTrabajando(Context context) {


SharedPreferences prefs =
context.getSharedPreferences(PREFS_NAME, 0);
return prefs.getBoolean(PREF_PREFIX_KEY2, true);
}
static long obtenHora(Context context) {
SharedPreferences prefs =
context.getSharedPreferences(PREFS_NAME, 0);
return prefs.getLong(PREF_PREFIX_KEY3,
System.currentTimeMillis());
}
}

En el cdigo anterior podemos ver cmo establecemos el resultado por defecto que devuelve
la Actividad de configuracin mediante el mtodo setResult().
Una actividad de configuracin de un Widget debe siempre devolver un resultado
RESULT_OK si la configuracin es correcta o RESULT_CANCELED si el usuario sale de la configuracin sin aceptar los cambios. En las sentencias anteriores establecemos al principio un
resultado RESULT_CANCELED por defecto para asegurarnos de que, si el usuario abandona la
configuracin pulsando el botn Volver del dispositivo o pulsando el botn Cancelar de esta
Actividad de configuracin, no se aade el Widget al escritorio por error.
El cdigo del botn Aceptar es algo ms complejo:
- Guarda el nombre que ha introducido el usuario utilizando la API de Preferencias ya estudiada en el curso de Iniciacin.
- Actualiza la interfaz del Widget segn la configuracin establecida ejecutando el mtodo
actualizaWidget() de HorarioTrabajoWidgetProvider.
Si incluimos una Actividad de configuracin para un Widget, es necesario que esta misma
Actividad sea la responsable de realizar la primera actualizacin del mismo (si es necesario).
Es decir, tras finalizar la actividad de configuracin Android no lanza automticamente el
evento onUpdate() del Widget (aunque s se lanzar posteriormente y de forma peridica
segn la configuracin del parmetro updatePeriodMillis del WidgetProvider), sino
que tendr que ser la propia actividad quien fuerce esta primera actualizacin. Para ello,
basta con obtener una referencia al WidgetManager de nuestro contexto mediante el mtodo
AppWidgetManager.getInstance() y con esta referencia llamaremos al mtodo esttico
de actualizacin del Widget HorarioTrabajoWidgetProvider.actualizaWidget(), que
actualizar los datos de todos las Vistas internas.
- Devuelve el resultado RESULT_OK incluyendo en el Intent de respuestas el ID del Widget.
Por ltimo, invocamos al mtodo finish() para finalizar la actividad.

3.4.6 Definicin de la aplicacin:


AndroidManifest.xml

Finalmente, para acabar, debemos declarar el Widget dentro del manifest de la aplicacin. As,
incluimos los elementos siguientes dentro de la etiqueta <application>:
<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>

161

Aula Mentor

<receiver
android:name=HorarioTrabajoWidgetProvider
android:icon=@drawable/maletin
android:label=Control de horario >
<intent-filter>
<action android:name=android.appwidget.action.
APPWIDGET_UPDATE />
</intent-filter>
<meta-data
android:name=android.appwidget.provider
android:resource=@xml/widget_info />
</receiver>
<service
android:name=es.mentor.unidad2.eje2.widgethomescreen.
ServicioActualizacionWidget >
</service>

162

<activity
android:name=es.mentor.unidad2.eje2.widgethomescreen.
PreferenciasWidget>

<intent-filter>

<action android:name=android.appwidget.action.
APPWIDGET_CONFIGURE/>

</intent-filter>

</activity>
</application>

El Widget se declarar como un elemento <receiver> indicando la siguiente informacin:


- Atributo name: referencia a la clase java del WidgetProvider.
- Atributo icon: imagen que se muestra al usuario cuando desea aadir el Widget.
- Atributo label: texto que se muestra al usuario cuando desea aadir el Widget.
- Elemento <intent-filter>: indica los eventos a los que responder el widget, por ejemplo, aqu indicamos el evento APPWIDGET_UPDATE para detectar la accin de actualizacin.
Nota: en este apartado podramos incluir ms elementos <intent-filter> para capturar
ms eventos, incluso eventos personalizados por la propia aplicacin.
- Elemento <meta-data>: establecemos la referencia el XML que define la informacin del
Widget.
El servicio que actualiza el Widget se declara mediante la etiqueta <service>.
Para indicar la Actividad de configuracin, incluimos una nueva etiqueta <activity> con el
elemento <intent-filter> utilizando el evento APPWIDGET_CONFIGURE para detectar la accin de configuracin.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Control horario de trabajo) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el
resultado del programa anterior, en el que hemos utilizado Widgets.

Para probarlo, podemos ejecutar el proyecto desde Eclipse ADT en el emulador de Android
y esperar a que se ejecute la aplicacin principal (sin desarrollar, ya que no hemos incluido

U2 Multimedia y Grficos en Android

ninguna funcionalidad) e ir a la pantalla principal del emulador y aadir nuestro Widget al escritorio tal cmo lo haramos en un dispositivo fsico:
- Hasta Android 2.x: pulsacin larga sobre el escritorio o pulsacin en la tecla Men y seleccionar la opcin Widgets seleccionando el Widget del curso.
- Desde Android 3.0 en adelante: accedemos al men principal, pulsamos la pestaa Widgets y
buscamos el Widget en la lista y realizamos sobre l una pulsacin larga hasta que el sistema
nos permite arrastrarlo y soltarlo sobre el escritorio.

163

Prueba a aadir varias instancias al escritorio, actualizarlos haciendo click sobre ellos, desplazarlos por la pantalla, cambiar su tamao y eliminarlos.
Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin en el
Emulador:

Aula Mentor

Importante: al probar en Eclipse ADT un Widget que hemos desarrollado, a veces,


es fundamental borrarlos del escritorio y volverlos a aadir ya que, segn la documentacin de Google, algunas veces no se actualizan correctamente cuando se
instala una nueva versin con las ltimas modificaciones que hemos hecho.

3.5 Colecciones de Vistas en Widgets


Las Vistas ListView, GridView
(que estudiaremos ms adelante), StackView y
AdapterViewFlipper se pueden utilizar en el interior de un Widget a partir de la versin
Android 3.0 empleando las Vistas de Colecciones (Collections). Estas Vistas complejas
contienen en su interior otras Vistas.
Las Vistas de Colecciones se definen mediante dos layouts: uno para el Widget y el otro
para definir cada elemento interno en la coleccin.
Estos elementos internos se construyen utilizando instancias de la clase factorial RemoteViewsFactory que se obtiene del servicio del sistema RemoteViewsService. Este servicio
requiere que la aplicacin tenga el permiso android.permission.BIND_REMOTEVIEWS.
Para conectar las Vistas con este servicio en el mtodo onUpdate()del Widget es necesario definir un Intent que apunte al servicio y utilizar el mtodo setRemoteAdapter() de la
clase RemoteViews. En realidad, es como si definiramos un adaptador para el listado interno.
Fjate en el siguiente ejemplo de cdigo fuente del Ejemplo 3 de esta Unidad:
164

// Obtenemos el servicio StackViewService mediante un Intent


// que nos permite acceder a las Vistas internas
Intent intent = new Intent(context, StackWidgetService.class);
// Incluimos el ID del Widget en cuestin
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
// Obtenemos el RemoteViews
RemoteViews rv = new RemoteViews(context.getPackageName(),
R.layout.widget_layout);
// Asociamos al StackView del Widget el intent anteriormente creado
// que se define en el layout del Widget
rv.setRemoteAdapter(R.id.stack_view, intent);
// La Vista vaca (empty view) se muestra cuando la coleccin no tiene
// elementos que mostrar
// que se define en el layout del Widget
rv.setEmptyView(R.id.stack_view, R.id.vista_vacia);
// Indicamos a Android que actualice el Widget. Muy importante!
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (El tiempo) de la Unidad
2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado Widgets con colecciones de Vistas.

U2 Multimedia y Grficos en Android

Para probarlo, podemos ejecutar el proyecto desde Eclipse ADT en el emulador de Android
y esperar a que se ejecute la aplicacin principal (sin desarrollar, ya que no hemos incluido
ninguna funcionalidad) e ir a la pantalla principal del emulador y aadir nuestro Widget al
escritorio tal cmo lo haramos en un dispositivo fsico:

165
Si arrastramos la imagen superior hacia arriba veremos cmo van rotando las imgenes del
tiempo en el Widget.

3.6 Activando Widgets en la pantalla de Bloqueo


Desde la versin 4.2 de Android en adelante es posible incluir Widgets en la pantalla de bloqueo
(Lock Screen) del dispositivo.
Para ello, basta con incluir en el archivo XML que define el Widget el atributo
android:widgetCategory de esta forma:
<appwidget-provider xmlns:android=http://schemas.android.com/apk/res/
android
android:widgetCategory=keyguard|home_screen
...
>
...
</appwidget-provider>

En el ejemplo anterior indicamos que el Widget se puede incluir tanto en la pantalla de bloqueo
como en el escritorio normal.
Adems, es posible detectar en tiempo de ejecucin en qu pantalla se est ejecutando el
Widget, basta con aadir el mtodo onUpdate() del WidgetProvider estas sentencias:

Aula Mentor

Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);


int categoria =
options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
boolean esLockScreen =
categoria == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;

As, es posible que un mismo Widget presente diferente aspecto si se ejecuta en la pantalla de
bloqueo o en el escritorio normal.
De forma anloga, es posible definir un atributo similar a android:initialLayout que
define el layout que se infla por primera vez al crear el Widget; pero asociado nicamente a la
pantalla de bloqueo mediante el nuevo atributo android:initialKeyguardLayout cuando se
aade a sta.

Te animamos a que programes tus propios Widgets en Android para extender


las funcionalidades de los dispositivos mediante estos programas, a veces, no
tan pequeos o simples.

4. Creacin de fondos de pantalla animados


166

Hoy en da Android dispone de infinitas posibilidades de personalizacin de modo que es


fcil que no existan dos dispositivos completamente iguales. En este apartado vamos a estudiar
cmo desarrollar un fondo de pantalla animado (del ingls, Live Wallpaper) para este sistema
operativo.
Aunque los fondos de pantalla animados estn un poco pasados de moda debido a que
consumen batera y, en consecuencia, afectan a la autonoma del dispositivo, por lo que muchos
usuarios han dejado de utilizarlos.
Sin embargo, muchos usuarios siguen buscando este tipo de fondos para personalizar
sus dispositivos.
Los Live Wallpapers son fondos de pantalla animados e interactivos de Android similares
a las aplicaciones de Android que pueden incluir casi la misma funcionalidad que estas ltimas.

4.1 Ejemplo de Creacin de un fondo de pantalla animado


Para iniciar un proyecto que implemente un fondo de pantalla animado debemos crear en
Eclipse ADT un proyecto normal de tipo Android.
El Ejemplo 4 de esta Unidad consta de los siguientes archivos:
- Fichero de configuracin del fondo animado: /res/xml/miwallpaper.xml
- Servicio que implementa el fondo animado: MiServicioWallpaper.java
- Interfaz de la Actividad de configuracin del fondo animado: /res/xml/preferencias.
xml

- Actividad de configuracin de las preferencias: PreferenciasActivity.java


- Actividad principal del usuario: EstablecerWallpaperActivity.java
- Definicin de la aplicacin: AndroidManifest.xml

U2 Multimedia y Grficos en Android

4.2 Ejemplo de implementacin de un fondo animado


En esta parte, vamos a ver cmo se implementa un Widget estudiando los ficheros que hemos
nombrado anteriormente.

4.2.1 Fichero de configuracin del fondo animado:


/res/xml/miwallpaper.xml

Para crear un fondo de pantalla animado es necesario componer un archivo XML que lo describa.
Si abrimos en Eclipse ADT este archivo veremos que contiene lo siguiente:
<wallpaper
android:settingsActivity=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
xmlns:android=http://schemas.android.com/apk/res/android
android:description=@string/wallpaper_descripcion

android:thumbnail=@drawable/sphere />

Este archivo debe incluir la descripcin del fondo animado (atributo android:description)
y puede contener una vista previa o icono del fondo (atributo android:thumbnail) y la referencia a la Actividad de configuracin (atributo android:settingsActivity) que permite
personalizar el fondo de pantalla animado. Como puedes ver, en Android se pueden establecer
preferencias hasta en los fondos animados.

4.2.2 Servicio que implementa el fondo animado:


MiServicioWallpaper.java

En este archivo se implementa el comportamiento del fondo animado mediante un servicio de


Android que se debe extender de la clase WallpaperService, que es la clase base del sistema
operativo para fondos de pantalla animados.
Debemos implementar el mtodo abstracto onCreateEngine() que tiene que devolver
un objeto del tipo WallpaperService.Engine. Por qu se hace esto y no se utiliza el servicio directamente? El servicio de fondos de pantalla puede tener mltiples instancias en ejecucin, por ejemplo, como fondo de pantalla funcionando y como una vista previa, cada uno de
los cuales debe tener su propia instancia del motor separadamente.
Este objeto Engine controla el ciclo de vida de los eventos, animaciones y de dibujo del fondo
animado a travs de,los mtodos siguientes, entre otros:
- onCreate(): se lanza al crearlo.
- onVisibilityChanged(): aparece cuando el fondo animado cambia su visibilidad. Por
ejemplo, cuando el usuario ejecuta una aplicacin y el fondo de pantalla deja de estar visible
y viceversa.
- onTouchEvent(): se ejecuta cuando el usuario toca el fondo de pantalla.
- onSurfaceDestroyed(): ejecutado cuando se destruye el fondo de tipo SurfaceHolder.

167

Aula Mentor

- onSurfaceChanged(): se aplica cuando cambia el fondo del dispositivo como un cambio

de orientacin de ste.

Veamos ahora el cdigo fuente de este servicio de Android:


public class MiServicioWallpaper extends WallpaperService {
// Mtodo abstracto que debemos implementar que
// devuelve un objeto extendido de Engine que es
// realmente el que implementa el fondo animado
@Override
public Engine onCreateEngine() {

return new MiWallpaperEngine();
}
// Clase que crea el fondo animado e implementa
// OnSharedPreferenceChangeListener para detectar cambios en
// las preferencias
private class MiWallpaperEngine extends Engine implements
SharedPreferences.OnSharedPreferenceChangeListener {

168

//
//
//
//
//

Para actualizar el fondo animado y no mostrar un


mensaje de error del tipo ANR, vamos a usar la
clase Runnable con su Handler asociado. As,
podemos dibujar el fondo con un proceso en segundo
plano sin bloquear el hilo principal

// Gestor de runnables
private final Handler handler = new Handler();


// Definimos un objeto Runnable que es el que se

// encargar de procesar la tarea que actualiza el

// fondo animado

private final Runnable drawRunnable = new Runnable() {
@Override

public void run() {
dibuja();
}
};














// Lista con todos los crculos que se dibujan en el


// fondo animado
private List<MiCirculo> circulos;
// Variable que se usa para dibujar
private Paint paint = new Paint();
// Largo y Ancho de la pantalla
private int width;
int height;
// Indica si el fondo animado est visible para el usuario
private boolean visible = true;
// N mximo de crculos que muestra el fondo animado
private int numeroMax;
// Flag que indica si el fondo animado es sensible a los
// toques en pantalla del usuario
private boolean touchActivado;

U2 Multimedia y Grficos en Android

// Constructor de la clase
public MiWallpaperEngine() {
// Accedemos a las preferencias del fondo animado
SharedPreferences prefs = PreferenceManager

.getDefaultSharedPreferences(MiServicioWallpaper.this);

// Recuperamos preferencias

numeroMax = Integer

.valueOf(prefs.getString(numeroMaxCirculos, 10));

touchActivado = prefs.getBoolean(touch, false);
// Definimos el listado de crculos mediante la clase

// Punto (coordenada en la pantalla)

circulos = new ArrayList<MiCirculo>();

// Indicamos a paint que suavice los bordes de todo

// lo que dibuje

paint.setAntiAlias(true);

// Forma de unir las lneas (redondeada)

paint.setStrokeJoin(Paint.Join.ROUND);

// Indicamos al handler que ejecute el Runnable

handler.post(drawRunnable);

// Creamos un listener que notifica si hay un cambio

// en las preferencias del liveWallpaper

prefs.registerOnSharedPreferenceChangeListener(this);
}

// Evento que aparece cuando el fondo animado cambia su


// visibilidad
@Override

public void onVisibilityChanged(boolean visible) {

// Guardamos el estado

this.visible = visible;

// Si est visible, dibujamos el fondo de pantalla

if (visible) {
handler.post(drawRunnable);

// Si no lo est, paramos el proceso de dibujo

} else {
handler.removeCallbacks(drawRunnable);

}
}

// Evento ejecutado cuando se destruye el fondo
@Override

public void onSurfaceDestroyed(SurfaceHolder holder) {

super.onSurfaceDestroyed(holder);

this.visible = false;

// Paramos de dibujar el fondo animado

handler.removeCallbacks(drawRunnable);
}

// Evento que se produce cuando cambia el fondo del dispositivo

// como un cambio de orientacin de ste
@Override

public void onSurfaceChanged(SurfaceHolder holder, int format,

int width, int height) {

// Guardamos el ancho y largo de la pantalla

this.width = width;

this.height = height;

super.onSurfaceChanged(holder, format, width, height);

169

Aula Mentor

170

}

// Evento que se ejecuta cuando el usuario toca el fondo de
// pantalla
@Override

public void onTouchEvent(MotionEvent event) {

// Si est activado el toque en preferencias

if (touchActivado) {

// Obtenemos las coordenadas del toque

float x = event.getX();

float y = event.getY();


// Obtenemos la referencia al SurfaceHolder donde se
// ejecuta el fondo animado

SurfaceHolder holder = getSurfaceHolder();

// Definimos un canvas donde vamos a dibujar los crculos

Canvas canvas = null;

try {

// Obtenemos el canvas bloqueando el fondo SurfaceHolder
// para que el usuario no pueda manipularlo

canvas = holder.lockCanvas();

// Si lo hemos obtenido correctamente entonces...

if (canvas != null) {

// Definimos el fondo de color negro
canvas.drawColor(Color.BLACK);
// Si no podemos aadir 5 crculos borramos los 5

// primeros

while (circulos.size()+5 > numeroMax) {
circulos.remove(0);
}
// Aadimos nuevos crculos alrededor del punto de

// toque

circulos.add(new MiCirculo(x, y, colorAleatorio(),
radioAleatorio()));

circulos.add(new MiCirculo(x, y-30, colorAleatorio(),
radioAleatorio()));

circulos.add(new MiCirculo(x+30, y, colorAleatorio(),
radioAleatorio()));

circulos.add(new MiCirculo(x, y+30, colorAleatorio(),
radioAleatorio()));

circulos.add(new MiCirculo(x-30, y, colorAleatorio(),
radioAleatorio()));

// Dibujamos los crculos
dibujaCirculos(canvas, circulos);

}

} finally {

// Muy importante! Hay que desbloquear el fondo SurfaceHolder

if (canvas != null)
holder.unlockCanvasAndPost(canvas);

}

super.onTouchEvent(event);
}
}
// Mtodo local que dibuja los crculos en el fondo
private void dibuja() {

// Obtenemos el fondo SurfaceHolder

SurfaceHolder holder = getSurfaceHolder();

// Definimos un canvas donde vamos a dibujar los crculos

U2 Multimedia y Grficos en Android


Canvas canvas = null;

try {

// Obtenemos el canvas bloqueando el fondo SurfaceHolder para

// que el usuario no pueda manipularlo

canvas = holder.lockCanvas();

// Si lo hemos obtenido correctamente entonces...

if (canvas != null) {
// Si hemos llegado al n mximos de crculos

// borramos el primero de todos para poder aadir otro

if (circulos.size() == numeroMax) {
circulos.remove(0);
}

// Aadimos el crculo

int x = (int) (width * Math.random());

int y = (int) (height * Math.random());

circulos.add(new MiCirculo(x, y, colorAleatorio(),
radioAleatorio()));

// Finalmente, dibujamos los crculos

dibujaCirculos(canvas, circulos);

}

} finally {

// Muy importante! Hay que desbloquear el fondo SurfaceHolder

if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}

// Quitamos de la cola de ejecucin el Runnable
handler.removeCallbacks(drawRunnable);
// Aadimos el Runnable a la cola de ejecucin para

// que se ejecute cada 3 segundos

if (visible) {

handler.postDelayed(drawRunnable, 3000);
}
}

// Mtodo que dibuja los crculos en un canvas

private void dibujaCirculos(Canvas canvas, List<MiPunto>
circulos) {
// Random que usamos para obtener color y radio

// de los crculos aleatorios

Random rnd = new Random();

// El fondo es negro
canvas.drawColor(Color.BLACK);

// Recorremos todos los puntos del listado

for (MiPunto circulo : circulos) {

// Establecemos el color

paint.setColor(circulo.color);

// Dibujamos un crculo en el canvas

canvas.drawCircle(circulo.x, circulo.y, circulo.radio,
paint);
}
}


// Mtodos que devuelven un color y un radio aleatorios para

// dibujar los crculos

private int colorAleatorio() {

return Color.argb(255, (int) (256 * Math.random()), (int)
(256 * Math.random()), (int) (256 * Math.random()));

171

Aula Mentor

}

private int radioAleatorio() {

return (int) (30 * Math.random()) + 5;

}

// Ocurre cuando hay un cambio en las preferencias del

// liveWallpaper
@Override

public void onSharedPreferenceChanged(

SharedPreferences sharedPreferences, String key) {

// Actualizamos las preferencias

if (key.equals(numeroMaxCirculos))

numeroMax =
Integer.valueOf(sharedPreferences.getString(
numeroMaxCirculos, 10));

else if (key.equals(touch))

touchActivado =sharedPreferences.getBoolean(touch,false);
}
}
}

En el cdigo anterior se ha nombrado mucho el objeto SurfaceHolder de Android. Esta clase es una Interfaz abstracta que permite controlar el tamao, el formato, editar los pxeles y
gestionar los cambios de una superficie, en este caso, es la superficie del fondo de pantalla de
un dispositivo Android. Esta clase hereda propiedades y mtodos de la clase SurfaceView de
Android que hemos estudiado en profundidad en la Unidad 1.
172

Mediante el mtodo getSurfaceHolder() obtenemos acceso al SurfaceHolder del fondo


animado. Posteriormente utilizamos los mtodos siguientes de esta clase:
- holder.lockCanvas(): obtenemos el Canvas (lienzo donde vamos a dibujar) y bloqueamos el fondo SurfaceHolder para que el usuario no pueda manipularlo e intente dibujar
infinitamente haciendo clic sobre el fondo de pantalla. Hay que tener en cuenta que cada
vez que bloqueamos esta superficie es necesario dibujar de nuevo todos los elementos que
la componen.
- holder.unlockCanvasAndPost(): desbloquea el fondo SurfaceHolder.

Muy importante! Hay que desbloquear el fondo SurfaceHolder una vez hayamos acabado de dibujarlo. Si no lo hacemos, se quedar congelado y ya no ser
animado.

4.2.3 Interfaz de la Actividad de configuracin del fondo animado:


/res/xml/preferencias.xml

En este fichero establecemos las propiedades de la ventana de preferencias del fondo animado.
En este ejemplo vamos a usar la gestin de preferencias de Android mediante la clase PreferenceScreen:
<PreferenceScreen xmlns:android=http://schemas.android.com/apk/res/
android>

<CheckBoxPreference android:key=touch

U2 Multimedia y Grficos en Android


android:title=Habilitar toque></CheckBoxPreference>

<EditTextPreference android:key=numeroMaxCirculos

android:title=Nmero mximo de crculos
android:negativeButtonText=@string/cancelar
android:positiveButtonText=@string/aceptar>
</EditTextPreference>
</PreferenceScreen>

Con el cdigo anterior definimos una ventana con un CheckBox y un campo de tipo texto que
indica el nmero mximo de crculo que dibujar el fondo animado.

4.2.4 Actividad de configuracin de las preferencias:


PreferenciasActivity.java

En esta clase definimos el comportamiento de la ventana de preferencias. Como puedes observar hemos utilizado Fragmentos para implementar que estudiaremos en el siguiente apartado
de teora de esta Unidad. Las sentencias que forman el cdigo fuente son auto explicativas:
public class PreferenciasActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// A partir de la API 4.1 hay que usar PreferenceFragment
getFragmentManager().beginTransaction().replace(
android.R.id.content, new MiPreferenceFragment()).commit();

// Si no disponemos de ella, usaramos esto:
//addPreferencesFromResource(R.xml.preferencias);
}
// Definimos el fragmento de tipo Preference
public static class MiPreferenceFragment extends PreferenceFragment
{

// Constructor de la clase
@Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Aadimos al fragmento el layout de preferencias
addPreferencesFromResource(R.xml.preferencias);
// Buscamos la preferencia numeroMaxCirculos
Preference nCirculosPreference =
findPreference(numeroMaxCirculos);

// Asignamos listener que valida el formato correcto
nCirculosPreference.setOnPreferenceChangeListener(
compruebaNumeroListener);
}
// Comprobamos que la preferencia introducida es correcta; si
// no, Android no la guarda automticamente
Preference.OnPreferenceChangeListener compruebaNumeroListener =
new OnPreferenceChangeListener() {

@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {

// Comprobamos que el n introducido es un entero cuyo

173

Aula Mentor


// valor es > 5

if (newValue != null && newValue.toString().length() > 0

&& newValue.toString().matches(\\d*) &&
Integer.valueOf(newValue.toString()) > 4) {

return true;

}

// Si no, mostramos un mensaje de error al usuario


Toast.makeText(getActivity().getBaseContext(), Error: no
has introducido un nmero o el nmero es menor que 5,


Toast.LENGTH_SHORT).show();

return false;
}
};
} // end clase MiPreferenceFragment

4.2.5 Actividad principal del usuario:


EstablecerWallpaperActivity.java

Aunque no es necesario definir esta Actividad de arranque de la aplicacin, hemos decidido


hacerlo para que el alumno vea cmo se puede utilizar un Intent para invocar el cambio de
fondo de pantalla y que el usuario seleccione el que hemos desarrollado en esta aplicacin. El
cdigo es muy sencillo:
174

// Actividad principal de la aplicacin


public class EstablecerWallpaperActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// El usuario hace clic sobre el botn de la actividad principal
public void onClick(View view) {

// Creamos un nuevo Intent que llama al servicio de cambio de

// fondo de pantalla del dispositivo

Intent intent = new Intent(
WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);

// Indicamos que queremos activar el fondo animado de esta

// aplicacin

intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,

new ComponentName(this, MiServicioWallpaper.class));

// Iniciamos la nueva actividad con ese intent
startActivity(intent);
}
}

4.2.6 Definicin de la aplicacin:


AndroidManifest.xml

Finalmente, como toda aplicacin Android, debemos establecer un servicio:

U2 Multimedia y Grficos en Android

<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>
<service
android:name=MiServicioWallpaper
android:enabled=true
android:label=Ejemplo de live Wallpaper
android:permission=android.permission.BIND_WALLPAPER >
<intent-filter>
<action
android:name=android.service.wallpaper.WallpaperService >
</action>
</intent-filter>
<meta-data android:name=android.service.wallpaper
android:resource=@xml/miwallpaper >
</meta-data>
</service>
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
android:exported=true
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
</activity>
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
EstablecerWallpaperActivity
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
</application>

En la declaracin anterior hemos establecido un servicio con el permiso android.permission.


BIND_WALLPAPER y registrado el intent-filter android.service.wallpaper.WallpaperService.
Al principio de este fichero puedes ver que aparece la propiedad:
<uses-feature android:name=android.software.live_wallpaper />

Esto evita que el fondo animado se instale en dispositivos que no tienen hardware compatible.
Adems, hemos definido la Actividad de Preferencias y la principal de la aplicacin.

175

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Live Wallpaper de curso
Mentor) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el
AVD el resultado del programa anterior, en el que hemos diseado un Fondo de
pantalla animado.

Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra el siguiente fondo animado
en el Emulador:

176

Prueba a arrastrar el dedo sobre el mismo y vers cmo se actualiza el fondo de pantalla.
En Internet, existen mltiples listados para fondos animados de pantalla: Veamos un
conjunto de ejemplos segn su funcionalidad:
- Launchers: este tipo de fondos de pantalla permite lanzar aplicaciones y realizar mltiples
acciones de forma rpida. Un ejemplo es Launcher Wall:

U2 Multimedia y Grficos en Android

Acceso a Contactos: fondo que muestra los contactos de una forma atractiva y rpida con los
que podremos interactuar. Un ejemplo es Contact Pro Live Wallpaper:

- Relojes: existen infinidad de fondos animados de este tipo. Vemos algunos:

WP Clock

Lightwell Live

Superclock

177

Aula Mentor

Analogy

178

TextClock

Prediccin del tiempo: existen mltiples fondos de este tipo como GoWeather
que, adems de ser un Widget estupendo, dispone tambin de un excelente
Fondo Animado que cambia segn el tiempo que hace.

U2 Multimedia y Grficos en Android

- Notificaciones: muestran notificaciones como fondo de pantalla.

Bubbleator

Notification Bubbles

Aunque este listado pueda parecer muy completo, Google Play est lleno de fondos animados
cada da que proporcionan las funcionalidades ms inverosmiles. Animamos al alumno a que
busque o, mejor, que desarrolle su propio fondo animado: Sirva la lista anterior de ejemplo
para comenzar.

5. Fragmentos
Cuando surgieron los dispositivos con pantallas de gran tamao de tipo Tableta u ordenadores,
Google tuvo que desarrollar la versin 3.0 de Android especficamente diseada para solucionar
el problema de la adaptacin de la interfaz grfica de las aplicaciones a ese nuevo tamao de
pantallas. Una interfaz de usuario diseada para un telfono mvil no se adaptaba fcilmente
a una pantalla 5 7 pulgadas. La solucin fue implementar un nuevo tipo de componente
denominado Fragment.
Un Fragment no puede considerarse ni una Actividad, ni una Vista, es algo parecido a
un contenedor. Un Fragment es un pedazo de la interfaz de usuario que se puede aadir
o eliminar de la interfaz global de usuario de forma independiente al resto de elementos de
la Actividad. Esto permite que pueda reutilizarse en otras Actividades.
As, podemos dividir la interfaz de usuario en varios segmentos o fragmentos; de ah su
nombre, de forma que podamos disear diversas configuraciones de pantalla, dependiendo del
tamao y orientacin de sta sin tener que duplicar cdigo reutilizando los distintos fragmentos
para cada una de las posibles configuraciones de pantalla.
Por ejemplo, supongamos que estamos diseando una aplicacin de correo electrnico
en la que queremos mostrar la lista de mensajes recibidos con los campos tpicos De y Asunto y, por otro lado, mostrar el contenido completo del mensaje seleccionado. En un telfono
mvil, lo habitual es tener una primera Actividad que muestra el listado completo de los mensajes y, cuando el usuario selecciona uno de ellos, ejecutar una nueva Actividad que muestre el

179

Aula Mentor

contenido de dicho mensaje. Sin embargo, en una tableta puede existir espacio ms que suficiente para tener ambas partes de la interfaz en la misma pantalla. Por ejemplo, en una tableta
en posicin apaisada podramos tener una columna a la izquierda con el listado de mensaje y
destinar la zona derecha a presentar el detalle del mensaje seleccionado, todo esto sin necesidad
de cambiar de Actividad.
En las versiones de Android anteriores a la 3.0 (sin Fragments) podramos haber implementado diferentes Actividades con sus respectivos layouts para cada configuracin de pantalla,
pero esto nos hubiera obligado a duplicar gran parte del cdigo fuente en cada Actividad. Si,
en su lugar, utilizamos fragmentos, podramos desarrollar un fragmento que muestra el listado
de mensajes y otro fragmento que muestra el contenido del mensaje seleccionado, cada uno de
ellos acompaado de su lgica de aplicacin y definir nicamente varios layouts en funcin del
tamao de la pantalla que incluya los fragmentos necesarios en cada momento.

5.1 Cmo se implementan los Fragmentos

180

En el Ejemplo 5 de este Unidad vamos a simular la aplicacin de correo que hemos descrito,
adaptndola a tres configuraciones distintas de pantalla: normal, grande horizontal y grande
vertical. En el primer caso, ubicaremos el listado de mensajes en una Actividad y, en otra
Actividad, el detalle. Sin embargo, en el segundo y tercer caso estos dos elementos visuales
estarn en la misma Actividad, ocupando la parte derecha/izquierda para la pantalla en el modo
apaisado y la parte arriba/abajo en el modo vertical.
Por lo tanto, en estos ds ltimos casos hemos desarrollado dos fragmentos: uno para el
listado de mensajes y el otro para mostrar su contenido.
De la misma forma que una Actividad, un Fragmento se infla a partir de un fichero de
layout XML para la interfaz visual (en la carpeta /res/layout) y una clase Java con las sentencias que incluyen sus funcionalidades.
El primero de los Fragmentos creados contiene una Vista ListView con su Adaptador
personalizado que muestra dos campos por fila: De y Asunto. Durante el curso de Iniciacin
ya estudiamos este tipo de elemento, por lo que aqu no daremos indicaciones sobre su uso.
Si abrimos su layout XML fragmento_listado.xml vemos el siguiente contenido tpico:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:padding=5dp >
<ListView
android:id=@+id/listado
android:layout_width=match_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>

Como hemos comentado, si un fragmento incluye lgica, debe tener asociadasu propia clase
java que indique las sentencias que debe ejecutar y que se extiende de la clase Fragment.

U2 Multimedia y Grficos en Android

Aunque los fragmentos estn disponibles slo a partir de la versin 3.0 de Android, sin embargo
este sistema operativo permite utilizar esta caracterstica en versiones anteriores
incluyendo la librera de compatibilidad android-support en el proyecto:

Nota: esta librera se puede incluir manualmente en el proyecto mediante la opcin Add Support
Library del men contextual del proyecto:

181

Atencin: debemos estar seguros de que la librera de compatibilidad aparece en


el Android SDK Manager instalada correctamente.

Aula Mentor

Veamos dnde se comprueba que la librera est disponible en el entorno de desarrollo Eclipse
ADT:

182

Usando este truco, podemos utilizar Fragmentos en la mayora de versiones de Android, incluso
desde la versin 1.6.
A continuacin, veamos el aspecto de la clase FragmentListado asociada al fragmento del
listado:
public class FragmentListado extends Fragment {
// Matriz que simula un listado de mensajes
private Mensaje[] datos =
new Mensaje[]{

new Mensaje(Pedro del Bosque, Peticin de informacin,

Hola.\n\n Por favor, remteme la informacin lo

antes posible.\n\n Gracias!),

new Mensaje(Alicia Navas, Hola!, Hola!\n\n Te espero

en la fiesta.\n\n Besos!),
new Mensaje(Daniel Fernndez, Sobre viaje, Al final

U2 Multimedia y Grficos en Android


vienes el fin de semana?\n\n Saludos),
new Mensaje(Jaime del Monte, Ms trabajo, Por favor,

ponte en contacto conmigo.\n\n Gracias)};
// ListView donde mostramos los mensajes
private ListView listado;
// Listener que detecta cundo un usuario hace clic sobre un mensaje
private MensajesListener listener;
// Mtodo equivalente al onCreate() de una Actividad
@Override
public View onCreateView(LayoutInflater inflater,

ViewGroup container,

Bundle savedInstanceState) {
// Devolvemos el xml inflado que define el listado
return inflater.inflate(R.layout.fragmento_listado, container,
false);
}
// Mtodo que se ejecuta cuando la Actividad
// contenedora del fragmento est completamente creada
// Aqu es donde debemos asociar el adaptador al listado
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
// Buscamos el listado del layout
listado = (ListView)getView().findViewById(R.id.listado);
// Asociamos el adaptador
listado.setAdapter(new AdaptadorMensajes(this));
// Definimos el evento onClic del listado
listado.setOnItemClickListener(new OnItemClickListener() {
@Override

public void onItemClick(AdapterView<?> list, View view, int
pos, long id) {

if (listener!=null) {
// Pasamos como parmetro la posicin del elemento
// seleccionado
listener.onMensajeSeleccionado(
(Mensaje)listado.getAdapter().getItem(pos));
}

}
}); // end onItemClick
}
// Clase que define el adaptador del Listado
class AdaptadorMensajes extends ArrayAdapter<Mensaje> {

// Variable para guardar el contexto de la Actividad

Activity contexto;




// Constructor del Adaptador


AdaptadorMensajes(Fragment context) {
// Usamos el layout correspondiente del listado
super(context.getActivity(), R.layout.listitem_mensaje, datos);
this.contexto = context.getActivity();

// Mtodo que define la forma de dibujar de las opciones

183

Aula Mentor

public View getView(int position, View convertView, ViewGroup


parent) {

// Se infla el layout y se completa con informacin las Vistas

// internas de la opcin.

// Atencin: en el curso de Iniciacin vimos que es

// conveniente reutilizar las vistas dentro de un listado.

// Por simplificacin del cdigo fuente no lo hemos hecho.

LayoutInflater inflater = contexto.getLayoutInflater();

View item = inflater.inflate(R.layout.listitem_mensaje, null);

TextView lblDe = (TextView)item.findViewById(R.id.lblDe);


lblDe.setText(datos[position].getDe());

TextView lblAsunto = TextView)item.findViewById(R.id.lblAsunto);


lblAsunto.setText(datos[position].getAsunto());

return(item);
}
}
// Definimos una interfaz que se implementar en la clase principal
// de la aplicacin y que tendr en cuenta el tamao de la pantalla
// disponible
public interface MensajesListener {

void onMensajeSeleccionado(Mensaje mensaje);
}

184

public void setMensajesListener(MensajesListener listener) {


this.listener=listener;
}
}

El fichero Mensaje.java contiene una sencilla clase que almacena los campos De, Asunto y
Texto de un mensaje. Puedes abrirla en Eclipse ADT y ver su contenido.
Si analizamos con detenimiento la clase FragmentListado anterior es fcil advertir que
existen muy pocas diferencias de cuando desarrollamos un listado de opciones con su adaptador personalizado en un Actividad normal.
En este caso, las diferencias radican en los mtodos que sobrescribimos. En el caso de
los fragmentos normalmente son los siguientes: onCreateView() y onActivityCreated().
Veamos para qu se usan.
- onCreateView(): es el mtodo equivalente al onCreate() de una Actividad. Es decir, aqu
inflamos el layout asociado al fragmento.
- onActivityCreated(): mtodo que se ejecuta cuando la Actividad contenedora del fragmento est creada por completo. En el ejemplo del curso, aprovechamos este mtodo para
obtener la referencia a la Vista ListView y asociarle su adaptador correspondiente.
Ahora vamos a explicar el segundo fragmento que, como comentamos, se encarga de mostrar
el detalle del mensaje seleccionado. La definicin de este fragmento es ms simple que la del
anterior. Si accedemos a su layout XML fragmento_detalle.xml vemos el siguiente contenido:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent

U2 Multimedia y Grficos en Android

android:background=#FFBBBBBB
android:orientation=vertical
android:padding=10dp >
<TextView
android:id=@+id/cuerpoMensaj
android:layout_width=wrap_content
android:layout_height=wrap_content />
</LinearLayout>

Igualmente, la clase java DetalleActivity asociada se limita a inflar el layout de la interfaz.


Adems, se incluye el mtodo pblico mostrarDetalle(), que utilizaremos para asignar el
contenido a las Vistas que correspondan. Veamos su aspecto:
// Fragmento que define el detalle del Mensaje
public class FragmentDetalle extends Fragment {
// Inflamos la Vista
@Override
public View onCreateView(LayoutInflater inflater,

ViewGroup container,

Bundle savedInstanceState) {

return inflater.inflate(R.layout.fragmento_detalle, container,


false);
}

// Definimos el mtodo que asigna el texto del cuerpo del mensaje


public void mostrarDetalle(String texto) {

TextView cuerpoMensaj =

(TextView)getView().findViewById(R.id.cuerpoMensaj);
cuerpoMensaj.setText(texto);
}
}

Una vez definidos los dos fragmentos, hay que desarrollar las Actividades de la aplicacin, con
sus respectivos layouts que utilizan los fragmentos anteriores.
Para la Actividad principal hemos diseado tres layouts diferentes:
- un layout cuando la aplicacin se ejecute en una pantalla de tamao normal, como un telfono mvil. Se encuentra en la carpeta por defecto /res/layout.
- dos layouts para pantallas mayores (uno para orientacin apaisada y otro para la vertical). Se
hallan en las carpetas /res/layout-large (pantalla grande con orientacin apaisada) y /
res/layout-large-port (pantalla grande con orientacin vertical) respectivamente.
Notamos que todos estos layouts se denominan con el mismo nombre activity_main.xml; sin
embargo, su uso lo marca la carpeta donde colocamos cada uno. Por lo tanto, Android elegir
automticamente el layout en funcin del tamao y orientacin de la pantalla.

Atencin: la terminacin port del directorio layout de pantalla grande con


orientacin vertical se debe a la palabra portrait (del ingls, retrato).

185

Aula Mentor

Para el caso de pantalla normal, la Actividad principal mostrar slo el listado de mensajes; por
lo tanto, el layout incluir nicamente el fragmento FragmentListado:
<fragment xmlns:android=http://schemas.android.com/apk/res/android

class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_width=match_parent

android:layout_height=match_parent />

Para incluir un fragmento en un layout, utilizamos la etiqueta <fragment> asignando el atributo


class a la ruta completa de la clase java correspondiente al fragmento.
En este caso de pantalla normal, la vista de detalle se muestra en una segunda Actividad
cuyo layout se denominaactivity_detalle.xml:
<fragment xmlns:android=http://schemas.android.com/apk/res/android
class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_width=match_parent

android:layout_height=match_parent />

Se trata de un layout anlogo al anterior, pero indicamos el fragmento de detalle.


En el caso de pantalla grande apaisada el layout tiene el siguiente aspecto en el directorio
layout-large:
186

<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=horizontal
android:layout_width=match_parent

android:layout_height=match_parent>

<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado

android:layout_weight=30
android:layout_width=0px

android:layout_height=match_parent />

<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_weight=70
android:layout_width=0px

android:layout_height=match_parent />
</LinearLayout>

En este caso, al haber mucha pantalla disponible, incluimos los dos fragmentos dentro de un
LinearLayout horizontal, asignando al primer fragmento un peso (propiedad layout_weight)
de 30 y al segundo de 70 para que la columna izquierda con el listado de mensajes ocupe un
30% de la pantalla y la de detalle ocupe el resto.
Por ltimo, para el caso de la pantalla grande vertical (directorio layout-large-port)
utilizamos un LinearLayout vertical.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical

U2 Multimedia y Grficos en Android

android:layout_width=match_parent

android:layout_height=match_parent>

<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_weight=40
android:layout_width=match_parent

android:layout_height=0px />

<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_weight=60
android:layout_width=match_parent

android:layout_height=0px />
</LinearLayout>

Ahora podemos ejecutar la aplicacin en el emulador y comprobar que se selecciona


automticamente el layout correcto dependiendo de las caractersticas del AVD (Dispositivo
Virtual de Android) que estemos utilizando.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 5 (Mensajes) de la Unidad


2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado Fragmentos.
187
Para poder ver las diferencias debes crear dos AVDs, uno con pantalla normal y otro grande.
Para cambiar la orientacin de la pantalla de un dispositivo virtual debes usar el atajo de teclado
[Ctrl+F12]. Si ejecutas en Eclipse ADT este ejemplo, vers que se muestra el siguiente fondo
animado en el Emulador:
Pantalla normal (dispositivo del curso):

Aula Mentor

Pantalla grande vertical (Nexus 7 de 7.3 pulgadas):

188
Pantalla grande apaisada (Nexus 7 de 7.3 pulgadas):

U2 Multimedia y Grficos en Android

Como vemos en las imgenes anteriores, la interfaz se adapta perfectamente a la pantalla en


cada caso mostrando uno o dos fragmentos y distribuyndolos horizontal o verticalmente segn
el espacio disponible en la pantalla.
Todava no hemos descrito la lgica de la aplicacin, es decir, lo que sucede cuando el
usuario selecciona un mensaje del listado. Para ello, en la clase FragmentListado en el mtodo
onActivityCreated() asignamos el evento onItemClick() de la lista de mensajes teniendo
en cuenta que debemos tener en cuenta el tamao de la pantalla, es decir, si se visualiza el
fragmento de detalle:
- Si el fragmento de la clase FragmentDetalle de detalle est visible, hay que obtener su
referencia e invocar su mtodo mostrarDetalle() que mostrar el texto del mensaje seleccionado.
- En caso contrario, hay que iniciar la Actividad secundaria DetalleActivity para que se
muestre el detalle del mensaje.
No obstante, hemos comentado anteriormente que un fragmento se caracteriza por ser
una porcin completa de interfaz reutilizable en distintas partes del cdigo fuente y
diseada de forma independiente del resto de la interfaz de usuario. Es decir, a priori, no
tiene relacin con ningn otro fragmento de la misma aplicacin.
Teniendo en cuenta esta caracterstica, no es recomendable tratar el evento en el propio
fragmento, sino definir y lanzar un evento personalizado cuando el usuario selecciona un mensaje de la lista y delegar la lgica del evento en la Actividad contenedora, ya que sta s conoce
qu fragmentos componen su interfaz.
Para hacer esto que parece tan complejo, primero definimos un evento personalizado en
la clase FragmentListado para la Vista de listado mediante el mtodo onMensajeSeleccionado() del listener MensajesListener. Hemos declarado en esta clase el atributo onMensajeSeleccionado() de tipo interfaz y definimos su mtodo setMensajesListener(). Este
cdigo permite asignar el evento desde fuera de la propia clase. Veamos cmo queda:
public class FragmentListado extends Fragment {
//...
// Listener que detecta cundo un usuario hace clic sobre un mensaje
private MensajesListener listener;
//..
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);

// Buscamos el listado del layout

listado = (ListView)getView().findViewById(R.id.listado);

// Asociamos el adaptador

listado.setAdapter(new AdaptadorMensajes(this));

// Definimos el evento onClic del listado

listado.setOnItemClickListener(new OnItemClickListener() {
@Override

public void onItemClick(AdapterView<?> list, View view, int

pos, long id) {

if (listener!=null) {
// Lanzamos el evento onMensajeSeleccionado y le pasamos

// como parmetro la posicin del elemento

// seleccionado

189

Aula Mentor

listener.onMensajeSeleccionado(
(Mensaje)listado.getAdapter().getItem(pos));

}
}
}); // end onItemClick
}
// Definimos una interfaz que se implementar en la clase principal
// de la aplicacin y que tendr en cuenta el tamao de la pantalla
// disponible
public interface MensajesListener {

void onMensajeSeleccionado(Mensaje mensaje);
}
public void setMensajesListener(MensajesListener listener) {
this.listener=listener;
}
}

190

Como vemos, una vez definido este truco, debemos en el evento onItemClick() de la lista
lanzar nuestro evento personalizado onMensajeSeleccionado() pasndole como parmetro el
contenido del mensaje, que hemos obtenido mediante el adaptador con el mtodo getAdapter()
y recuperamos el elemento con getItem().
Ahora ya tenemos todas las piezas del puzle que debemos encajar en la clase java MainActivity que define la Actividad principal. Para ello, en su mtodo onCreate(), obtendremos
una referencia al fragmento de listado mediante el mtodo getFragmentById() del gestor de
fragmentos (Fragment Manager es el componente del sistema operativo encargado de
gestionar los fragmentos de una aplicacin) y le asignaremos el evento mediante su mtodo
setMensajesListener() que acabamos de definir anteriormente:
// Clase principal que contiene los fragmentos e implementa la
// interfaz Mensajes Listener
public class MainActivity extends FragmentActivity implements
MensajesListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Definimos el fragmento del listado mediante

// referencia a su id

FragmentListado frgListado
=(FragmentListado)getSupportFragmentManager()

.findFragmentById(R.id.FrgListado);

// Establecemos el listener del listado
frgListado.setMensajesListener(this);
}
// Se implementa el mtodo onMensajeSeleccionado del listener
// MensajesListener
@Override
public void onMensajeSeleccionado(Mensaje mensaje) {

// Buscamos si Android est mostrando el detalle del mensaje

// buscando si est definido el fragmento de detalle

U2 Multimedia y Grficos en Android


boolean hayDetalle =

(getSupportFragmentManager().findFragmentById
(R.id.FrgDetalle) != null);

// Si se ven los dos fragmentos entonces cuando el usuario haga

// clic en un mensaje se muestra su detalle

if(hayDetalle) {
((FragmentDetalle)getSupportFragmentManager()


.findFragmentById(R.id.FrgDetalle)).mostrarDetalle(
mensaje.getTexto());
}

// Si no existe el fragmento de detalle lanzamos la actividad de

// detalle con un Intent pasando el texto como una propiedad en

// ste

else {

Intent i = new Intent(this, DetalleActivity.class);
i.putExtra(DetalleActivity.EXTRA_TEXTO,
mensaje.getTexto());
startActivity(i);
}
}
}

Se puede observar en el cdigo anterior que esta Actividad principal implementa la interfaz
MensajesListener, por lo que nos basta indicar this al mtodo setMensajesListener().
Adems, esta Actividad no se hereda de la clase Activity como suele ser habitual, sino de
FragmentActivity ya que se utiliza la librera de compatibilidad android-support para
utilizar fragmentos conservando la compatibilidad con versiones de Android anteriores a la
3.0. En caso de no necesitar esta compatibilidad se puede seguir heredando de Activity sin
inconvenientes.
Finalmente, si prestamos atencin al mtodo onMensajeSeleccionado(), vemos que se
ejecutar cada vez que el fragmento del listado indique que se ha seleccionado un determinado
mensaje de la lista y aplicar la lgica ya mencionada, es decir, si en la pantalla existe el fragmento de detalle simplemente lo actualizaremos mediante su mtodo mostrarDetalle() y, en
caso contrario, abriremos la nueva Actividad DetalleActivity utilizando un nuevo Intent
con la referencia a dicha clase y aadiendo como propiedad un campo extra de texto con el
contenido del mensaje seleccionado. Acabamos iniciando la Actividad con el mtodo startActivity().
El cdigo de la segunda Actividad DetalleActivity se limita a recuperar esta propiedad extra pasada en el Intent y mostrarla en el fragmento de detalle mediante su mtodo
mostrarDetalle():
public class DetalleActivity extends FragmentActivity {
// Definimos una constante que usaremos en Intent para pasar la
// informacin al fragmento
public static final String EXTRA_TEXTO =
es.mentor.unidad2.eje5.fragmentos.EXTRA_TEXTO;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalle);

// Buscamos el fragmento de detalle que es un fragmento

FragmentDetalle detalle =

191

Aula Mentor

(FragmentDetalle)getSupportFragmentManager()

.findFragmentById(R.id.FrgDetalle);

// Cargamos el texto del detalle en este fragmento
detalle.mostrarDetalle(getIntent().getStringExtra(EXTRA_TEXTO));
}
}

Animamos al alumno o alumna a que vuelva a probar la aplicacin en Eclipse ADT para
comprobar el correcto funcionamiento de la seleccin de mensajes en las distintas configuraciones
de pantalla.
Una vez hemos visto cmo crear y utilizar un fragmento, vamos a estudiar algunas de sus
funcionalidades avanzadas que pueden interesar al programador.

5.2 Ciclo de vida de un Fragmento


De igual forma que ocurre con una Actividad en Android, un Fragmento dispone de su propio
ciclo de Vida. A continuacin, se muestra el ciclo de vida de un Fragmento en funcin del estado
de la Actividad que lo contiene:

192

U2 Multimedia y Grficos en Android

Como puedes ver, el ciclo de vida de un Fragmento est muy relacionado con el ciclo
de vida de la Actividad que lo contiene. Cada invocacin que hace el sistema operativo de
un evento de una Actividad provoca que se invoquen a su vez eventos de los Fragmentos que
contiene.
Por ejemplo, cuando la Actividad recibe el evento onPause(), todos sus fragmentos reciben tambin el mismo evento onPause().
Adems, los Fragmentos disponen de algunos eventos ms en su ciclo de vida:
- onAttach(Activity): Android lo invoca cuando el fragmento se asocia con la Actividad.
- onCreateView(LayoutInflater, ViewGroup, Bundle): Android lo invoca cuando hay
que crear las Vistas internas del fragmento.
- onActivityCreated(Bundle): Android lo invoca cuando la Actividad ya est creada.
- onDestroyView(): Android lo invoca cuando se van a destruir las Vistas del fragmento.
- onDetach(): Android lo invoca cuando el fragmento se desliga de la Actividad.
El resto de eventos se usan de la misma forma que en una Actividad.

Atencin: para sobreescribir cualquier mtodo del ciclo de vida (con excepcin del
mtodo onCreateView()) debemos siempre llamar al mtodo de la clase superior
(mediante super.nombreMetodo()). Si no lo hacemos, al ejecutar la aplicacin
veremos un error de ejecucin.

5.2.1 Cmo guardar el estado de un Fragmento


De igual forma que ocurre con una Actividad en Android, un Fragmento
dispone del mtodo onSaveInstanceState(Bundle) que permite al programador
guardar el estado actual de las Vistas de ste. Sin embargo, no tiene el
mtodo onRestoreInstanceState(). Veamos cmo gestionar el estado de un
Fragmento:

- Por defecto, Android almacena el estado de todas las Vistas de un fragmento que tengan
asociado un ID, es decir, su contenido o caractersticas se pueden modificar.
- Es posible sobreescribir el mtodo onSaveInstanceState(Bundle) para aadir informacin adicional del fragmento.
- Cuando, por ejemplo, el usuario vuelve a abrir una aplicacin y el sistema operativo crea el
fragmento, se utiliza el parmetro Bundle, guardado con el mtodo anterior, como parmetro en los mtodos onCreate(), onCreateView() y onActivityCreated() para que el
programador devuelva el Fragmento al estado almacenado.

Como puedes ver, la forma de guardar el estado de un Fragmento es similar a la de guardar


una Actividad.

5.2.2 Cmo mantener los Fragmentos cuando la Actividad se recrea automticamente


Como sabes, por defecto, cuando ocurre un cambio dinmico y automtico de la configuracin
del dispositivo (por ejemplo, un cambio de orientacin de la pantalla de ste), la Actividad de
una aplicacin Android se recrea (se destruye y se vuelve a crear). Esto le sucede tambin a
los Fragmentos que contenga dicha Actividad, que se destruyen y se vuelven a recrear auto-

193

Aula Mentor

mticamente.
Sin embargo, la clase Fragment dispone del mtodo setRetainInstance(boolean)
que permite al programador indicar con el parmetro true que se desea no destruir la instancia del Fragmento cuando esto ocurra. Podemos invocar este mtodo en el evento onCreate()
del Fragmento. Examinemos sus caractersticas y cmo se utiliza. Si indicamos con la sentencia
setRetainInstance(true) que deseamos que Android no destruya el Fragmento en los
cambios de configuracin automticos, ocurre lo siguiente:
- Los mtodos onDestroy() y onCreate() no se invocarn de nuevo cuando se produzca
la recreacin de la Actividad.
- El resto de eventos del ciclo de vida de un Fragmento se siguen invocando igualmente y en
la misma secuencia.
- El parmetro Bundle de los mtodos onCreateView() y onActivityCreated() en nulo
(null) porque el Fragmento no se recrea.

5.2.3 Cmo buscar Fragmentos

194

Como hemos comentado anteriormente, el componente del sistema operativo encargado de


gestionar los fragmentos de una aplicacin es FragmentManager. Este gestor de fragmentos
dispone de los siguientes mtodos para encontrar un fragmento incluido en la Actividad:
- findFragmentById(int id): encuentra un fragmento por el ID especificado como parmetro.
- findFragmentByTag(String tag): encuentra un fragmento por el tag indicado como parmetro. Un tag es una etiqueta que podemos asociar para distinguir distintos fragmentos
que tengan el mismo ID, es decir, el mismo layout.
Ambos mtodos devuelven una referencia al fragmento o null si no existe dentro de la Actividad.

5.2.4 Otras operaciones sobre Fragmentos (Transacciones)


Es posible llevar a cabo otras operaciones dinmicas con Fragmentos en tiempo de ejecucin
que modifican la interfaz del usuario, tales como, mostrar u ocultar un fragmento. Android denomina transaccin (transaction) a un cambio de este tipo.
Estudiemos qu tipo de operaciones podemos realizar durante una transaccin:
- add(): aade un Fragmento a la Actividad.
- remove(): quita un fragmento de la actividad. Esta operacin destruye el fragmento salvo
que utilicemos la pila de transacciones que estudiaremos ms adelante en este apartado.
- replace(): sustituye un fragmento por otro.
- hide(): oculta un fragmento de la interfaz de usuario sin necesidad de destruirlo, pasando
el fragmento a ser no visible.
- show(): muestra un fragmento que no est visible.
- detach() : separa un fragmento de la interfaz de usuario destruyendo sus vistas internas
pero manteniendo su instancia.
- attach(): une de nuevo un fragmento separado a la interfaz de usuario recreando sus vistas
internas.

U2 Multimedia y Grficos en Android

No es posible utilizar los mtodos remove(), replace(), detach() y attach()


en un fragmento esttico utilizado directamente en el layout de una Actividad.

Veamos ahora los pasos para realizar transacciones con Fragmentos:


- Mediante el mtodo FragmentManager.beginTransaction() obtenemos una instancia a
la clase FragmentTransaction que va a gestionar toda la transaccin.
- Llevamos a cabo las operaciones de movimiento de fragmentos que deseemos utilizando la
instancia de la clase FragmentTransaction. Todas estas operaciones devuelven de nuevo
la instancia original modificada de forma que es posible encadenar operaciones en una nica
sentencia.
- Invocamos el mtodo commit() para que los cambios en la Actividad se hagan efectivos.

Slo es posible utilizar el mtodo commit() mientras la Actividad (y, por lo tanto, el
Fragmento) no se encuentre guardando su estado con el mtodo onSaveInstanceState. La sentencia commit mostrar una excepcin si la ejecutamos despus
de haber guardado el estado de la Actividad.

Veamos un ejemplo de cdigo fuente:

FragmentManager fragmentManager = getFragmentManager()


// Si usamos la librera de compatibilidad tambin podemos escribir:
// FragmentManager fragmentManager = getSupportFragmentManager()
fragmentManager.beginTransaction()
.remove(fragmento1)
.add(R.id.fragmento_layout, fragmento2)
.show(fragmento3)
.hide(fragmento4)
.commit();

Como puedes observar en el cdigo anterior, hemos encadenado operaciones en una nica
sentencia. Es muy importante no olvidarse nunca de la orden commit(), ya que si no la usamos, no se realizan los cambios.
Adems, debes saber que el orden en que realices las operaciones determina el orden
en que Android las realizar en la interfaz del usuario.

5.2.5 Cmo Gestionar la pila (Back Stack) de Fragmentos


En el curso de iniciacin de Android vimos que el sistema operativo organiza las Actividades
en una pila de ejecucin (en ingls stack) donde se van apilando las actividades que el usuario va invocando. De esta forma, el usuario puede moverse en esta pila utilizando la tecla retroceso del dispositivo
.
De forma similar, Android tambin dispone de una pila de ejecucin llamada back
stack para almacenar los Fragmentos.
Si se aade mediante una transaccin un fragmento a la pila, entonces el usuario puede
ir al fragmento anterior mediante la tecla retroceso del dispositivo. Cuando el usuario llega al

195

Aula Mentor

primer fragmento de la pila, si pulsa de nuevo la tecla de retroceso, entonces se destruye la


Actividad.
Para aadir una transaccin a la pila debemos utilizar el mtodo FragmentTransaction.addToBackStack(String) antes de invocar el mtodo commit(). El argumento de tipo
String es un nombre opcional para identificar el estado de la pila que supone ese cambio; por
ejemplo, podemos indica el texto pantalla_inicial, para luego poder volver fcilmente a ese
estado. Lo ms frecuente es que tenga el valor null.
Adems, la clase FragmentManager dispone del mtodo popBackStack(String), que
permite al programador volver al estado anterior si no se indica parmetro o al estado marcado
por este parmetro (por ejemplo pantalla_inicial).
Si se aaden multiples cambios en una transaccin a una pila de ejecucin y aplicamos
el commit() al final, entonces estamos aadiendo una nica transaccin, y si el usuario pulsa
la tecla volver del dispositivo, se desharn todos estos cambios de una sola vez.
Si quitamos o reemplazamos un fragmento y, despus, llamamos el mtodo addToBackStack(), el sistema operativo invocar los mtodos onPause(), onStop() y onDestroyView() del fragmento cuando los aada a la pila de ejecucin. Cuando el usuario vuelva
a este fragmento, entonces el sistema invocar los mtodos onCreateView(), onActivityCreated(), onStart()y onResume()del fragmento.
A continuacin, se muestra un ejemplo que aade a la pila de ejecucin un nuevo fragmento:

196

// Definimos un nuevo fragmento


Fragment nuevoFragment = new Fragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
// Aadimos una animacin de entrada y salida del fragmento
ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
R.animator.fragment_slide_left_exit,
R.animator.fragment_slide_right_enter,
R.animator.fragment_slide_right_exit);
// Reemplazamos un fragment por otro
ft.replace(R.id.simple_fragment, nuevoFragment);
// Aadimos a la pila la transaccin
ft.addToBackStack(null);
ft.commit();

Hemos aprovechado este ejemplo para incluir el mtodo setCustomAnimations(param1,


param2, param3, param4) de la clase FragmentTransaction que permite aadir una ani-

macin cuando se muestra u oculta un fragmento segn estos parmetros:


- Parmetro 1: ocurre cuando aparece el fragmento. En este caso hemos indicado R.animator.
fragment_slide_left_enter, que desliza el fragmento desde la parte izquierda de la pantalla hasta mostrarlo.
- Parmetro 2: ocurre cuando desaparece el fragmento. En este caso hemos indicado
R.animator.fragment_slide_left_exit, que desliza el fragmento desde la pantalla hacia la izquierda hasta que desaparece.
- Parmetro 3: ocurre cuando aparece el fragmento porque lo aadimos a la pila de ejecucin.
En este caso hemos indicado R.animator.fragment_slide_right_enter, que desliza el
fragmento desde la parte izquierda de la pantalla.
- Parmetro 4: ocurre cuando desaparece el fragmento porque se ha quitado de la pila de
ejecucin (por ejemplo, el usuario ha pulsado la tecla volver del dispositivo). En este caso
hemos indicado R.animator.fragment_slide_right_exit, que desliza el fragmento desde la parte izquierda de la pantalla.

U2 Multimedia y Grficos en Android

5.2.6 Cmo utilizar Fragmentos sin layout


Como hemos indicado, no es obligatorio que un Fragmento tenga asociado una interfaz de
usuario, es decir, un layout. Por ejemplo, un Fragmento se puede utilizar para mantener informacin de estado o gestionar hilos (threads). Por lo tanto, no necesita disponer de interfaz
de usuario y no es necesario sobreescribir el mtodo onCreateView(). Es decir, mediante un
fragmento podemos ejecutar sentencias en segundo plano.
Para aadir un fragmento a la Actividad debemos usar el mtodo FragmentTransaction.add(Fragment, String), donde el segundo parmetro indica una etiqueta (tag) para
identificar el fragmento.
Fjate en el siguiente ejemplo de cdigo fuente:
FragmentManager fragmentManager = getFragmentManager();
// Tambin podemos escribir: FragmentManager fragmentManager =
//
getSupportFragmentManager()
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
BackgroundFragment fragmento = new BackgroundFragment();
fragmentTransaction.add(fragmento, thread_manager);
fragmentTransaction.commit();

5.2.6.1 Comunicacin entre Fragmentos y con la Actividad


Como sabes, un fragmento est directamente ligado a la Actividad que lo contiene. Por lo tanto,
es posible una comunicacin entre ambos:
- La Actividad puede llamar a todos los mtodos pblicos del fragmento mediante una referencia al objeto del fragmento. Si no se guarda la referencia del fragmento una vez creado,
es posible utilizar los mtodos findFragmentById() o findFragmentByTag() de la clase
FragmentManager como hemos visto anteriormente.
- El fragmento puede accede a la Actividad que lo contiene a travs del mtodo Fragment.
getActivity(). Por ejemplo, es posible obtener una referencia a una Vista de la Actividad
principal as:
View listView = getActivity().findViewById(R.id.lista);

Atencin: La clase Fragment no deriva de la clase Context. Por lo tanto, si el


fragmento necesita obtener la referencia al contexto de la Actividad (por ejemplo,
para mostrar un mensaje Toast) podemos invocar el mtodo getApplicationContext() de la Actividad que contiene el fragmento.

Desde el punto de vista de programacin es importante evitar que las Actividades y Fragmentos sean muy interdependientes entre s ya que esto reduce las posibilidades de poder
reusar el fragmento en otra Actividad o aplicacin. Lo recomendado es que sea la Actividad la
que interaccione con el Fragmento.
Para conseguir este objetivo es recomendable definir interfaces y listerners en el
fragmento que luego implementar la clase que contiene la Actividad. En el Ejemplo 5 de esta
Unidad hemos utilizado este mtodo que recordamos:

197

Aula Mentor

Cdigo fuente del Fragmento:


public class FragmentListado extends Fragment {
...
// Listener personalizado que detecta cundo un usuario hace clic
// sobre un mensaje
private MensajesListener listener;
...
// Definimos una interfaz que se implementar en la clase principal
// de la aplicacin y que tendr en cuenta el tamao de la pantalla
// disponible
public interface MensajesListener {

void onMensajeSeleccionado(Mensaje mensaje);
}
public void setMensajesListener(MensajesListener listener) {
this.listener=listener;
}
}

Cdigo fuente de la Actividad:

198

// Clase principal que contiene los fragmentos e implementa la


// interfaz Mensajes Listener
public class MainActivity extends FragmentActivity implements
MensajesListener {
...
// Se implementa el mtodo onMensajeSeleccionado del listener
// MensajesListener
@Override
public void onMensajeSeleccionado(Mensaje mensaje) {
...
}
...
}

5.2.7 Recomendaciones a la hora de programar Fragmentos


- Disea la aplicacin para que la Actividad sea el componente intermediario entre los fragmentos, ya que a stos ltimos no es posible asociarles intenciones, es decir, un Intent no
puede llamar directamente a un fragmento.
- Todos los fragmentos deben tener definido un constructor por defecto.
- Los fragmentoss generalmente no deben implementar constructores adicionales o sobreescribir el ya existente de la clase heredada.
- El primer mtodo de un fragmento donde podemos escribir una sentencia para que la ejecute Android es el mtodo onAttach().
- Una vez se ha creado un fragmento, es posible inicializarlo desde la Actividad mediante parmetros que se pasan en su mtodo setArguments(Bundle). Fjate en el siguiente ejemplo:
Cdigo fuente de la Actividad:
// Creamos los parmetros con el nombre ID y CAMPO

U2 Multimedia y Grficos en Android

Bundle arguments = new Bundle();


arguments.putInt(ID, 1);
arguments.putString(CAMPO, Texto del campo);
// Instanciamos el fragmento
mFragment = Fragment.instantiate(mActivity, FragmentoClass.getName());
mFragment.setArguments(arguments);
// Aadimos a la Actividad
ft.add(android.R.id.content, mFragment);
En el cdigo anterior hemos utilizado el mtodo instaciate de la clase
Fragment que es equivalente a escribir:
FragmentoClass mFragment = new FragmentoClass();

En el fragmento podemos usar ahora estos parmetros como deseemos a travs del mtodo
getArguments(); por ejemplo, en el mtodo onActivityCreated:
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

if (getArguments().containsKey(ID)) {
...
}
}
...

5.2.8 Implementar dilogos con Fragmentos


A partir de la version 3.0 de Android, las clsicas ventanas de dilogo (clase Dialog) estn en
desuso (en ingls, se denomina deprecated) en favor de los fragmentos. Este cambio tiene
mucho sentido si pensamos que las ventanas de dialogo son componentes que se reutilizan
mucho.
La clase DialogFragment de Android define la clase bsica que implementa un fragmento que muestra una ventana de dilogo flotante en una Actividad. Este fragmento incluye
un objeto Dialog que contiene el diseo de la ventana de dilogo. La aplicacin debe gestionar la ventana de dilogo de tipo fragmento mediante los mtodos disponibles en en la clase
DialogFragment.

Es posible implementar una clase DialogFragment de forma que se puede utilizar


nicamente como una ventana de dilogo o como un fragmento normal dentro
de una Actividad.

Si slo vas a utilizar el fragmento como una ventana de dilogo, debemos sobreescribir el
mtodo onCreateDialog() devolviendo una instancia de la clase Dialog o de sus subclases.
Si vas a emplear el nuevo fragmento como dilogo y como fragmento propiamente
dicho dentro de una Activiad, debes sobreescribir el mtodo onCreateView() y devolver la
Vista creada.
La clase DialogFragment dispone del mtodo sobrecargado (overload) show() que

199

Aula Mentor

muestra la ventana de dilogo como una transaccin, es decir, crea una transaccin en el objeto
FragmentManager, aade el fragmento y ejecuta commit(). Cuando se cierra la ventana de
dilogo, se crea otra transaccin para quitar el fragmento de la ACtividad.
Como no poda ser de otra forma, la clase DialogFragment dispone del mtodo dismiss() para cerrar el fragmento explcitamente. De igual forma, este mtodo se ejecuta mediante transaciones.
A continuacin, vamos a ver un sencillo ejemplo de ventana de dilogo, utilizando fragmentos,
que pide confirmacin para realizar cualquier operacin.
Cdigo fuente del fragmento:
public class ConfirmacionDialogFragment extends DialogFragment

implements DialogInterface.OnClickListener {
// Como hemos dicho, la Actividad debe gestionar el listener
private ConfirmacionDialogFragmentListener listener;
// Contructor: ventana esttica de dilogo
public static ConfirmacionDialogFragment newInstance(String titulo){
ConfirmacionDialogFragment frag =

new ConfirmacionDialogFragment();
// Leemos los argumentos que pasa la Actividad que lo contiene

Bundle args = new Bundle();
args.putString(titulo, titulo);
frag.setArguments(args);
return frag;
}

200

// Definimos el listener con los 2 mtodos de callback que debe


// definir la Actividad
public interface ConfirmacionDialogFragmentListener {
public void onPositiveClick();
public void onNegativeClick();
}

// Mtodo para establecer el listener anterior desde la Actividad


public void setConfirmacionDialogFragmentListener(
ConfirmacionDialogFragmentListener listener) {
this.listener = listener;
}
// Evento que se lanza cuando se crea la ventana de dilogo
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String titulo = getArguments().getString(titulo);
// Creamos el tpico dilogo del tipo AlertDialog con el ttulo
return new AlertDialog.Builder(getActivity())
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(titulo)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
}
// Evento que sucede cuando el usuario hace click en un botn ya que
// se est implementando el mtodo DialogInterface.OnClickListener
@Override

U2 Multimedia y Grficos en Android

public void onClick(DialogInterface dialog, int which) {


if (listener != null) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
listener.onPositiveClick();
default:
listener.onNegativeClick();
}
}
}
}

Cdigo fuente de la Actividad:


public class SimpleConfirmacionDialogFragmentActivity
// Se debe extender de la clase FragmentActitity
extends FragmentActivity
// Implementa ConfirmacionDialogFragmentListener y OnClickListener
// del botn principal de esta Actividad
implements OnClickListener, ConfirmacionDialogFragmentListener {
// Contructor tpico de la Actividad
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// Definimos el botn boton_dialog en el layout de la Actividad
Button botonMuestraDialog =

(Button) findViewById(R.id.boton_dialog);
// Asignamos el evento onClick de este botn
botonMuestraDialog.setOnClickListener(this);
}

// Definimos el evento onClick del botn botn_dialog


@Override
public void onClick(View v) {
// Creamos el fragmento
ConfirmacionDialogFragment confirmacionDialog =

ConfirmacionDialogFragment.newInstance(Ttulo);
// Definimos los listener
confirmacionDialog.setConfirmacionDialogFragmentListener(this);
// Mostramos la ventana de dilogo
confirmacionDialog.show(getSupportFragmentManager(), null);
}
// Definimos la implementacin de los mtodos onPositiveClick y
// onNegativeClick mostrando un mensaje de tipo Toast
@Override
public void onPositiveClick() {
Toast.makeText(this, android.R.string.ok,
Toast.LENGTH_LONG).show();
}
@Override
public void onNegativeClick() {

201

Aula Mentor

Toast.makeText(this, android.R.string.cancel,
Toast.LENGTH_LONG).show();
}

5.2.9 Otras clases de Fragmentos


Existen en Android subclases adicionales extendidas de Fragment desarrolladas para usos muy
comunes y que facilitan el trabajo al programador. Veamos las ms importantes:
- ListFragment: fragmento que proporciona una lista del tipo ListView. Es anlogo a la
clase ListActivity.
- WebViewFragment: fragmento que crea y gestiona automticamente la clase WebView para
presentar un mini navegador de Internet.

5.3 Barra de Accin (Action Bar)


La Barra de Accin de Android o, del ingls, Action Bar es, como su propio nombre indica, una
barra que aparece en la parte superior de una aplicacin. Puede incluir un icono, el ttulo de la
Actividad y varios botones de accin o un men extendido desplegable (en ingls, se denomina
overflow). Este men extendido, a su vez, incluye ms acciones cuando stas no caben como
botones en el espacio disponible de la pantalla del dispositivo o el programador decide por
motivos de diseo que se deben ocultar de la barra.
202

Vemos un ejemplo visual de Barra de accin:


Icono y Ttulo

Botones

Men
Extendido

Desgraciadamente, la Barra de accin de Android no est incluida en la librera de


compatibilidad android-support que hemos utilizado ms arriba. Es decir, slo funciona a
partir de la versin Android 3.0 de forma nativa.
Sin embargo, Google no se ha olvidado del todo de las versiones anteriores de Android
y propone una alternativa para compatibilizar las aplicaciones con versiones anteriores a la 3.0
mediante un ejemplo que incluye en el SDK llamado ActionBarCompat. Puedes encontrar su
cdigo fuente en la siguiente carpeta de la instalacin del SDK: <carpeta-sdk>\samples\
android-XX\ActionBarCompat.
Adems, a modo de informacin, indicamos que existe una librera muy utilizada entre
los programadores llamada ActionBarSherlock que proporciona al desarrollador una implementacin alternativa de este componente y es compatible con Android 2.0 o superior.
Aunque, como hemos dicho, es posible utilizar esta funcionalidad en versiones de Android anteriores a la 3.0, en este apartado vamos a mostrar el uso de la funcionalidad
nativa de la Barra de accin de Android.
Cuando generamos un proyecto nuevo con una versin de Android 3.0 o superior, Eclipse ADT aade automticamente por defecto a la nueva aplicacin su barra de accin correspon-

U2 Multimedia y Grficos en Android

diente. Si a continuacin, lo ejecutamos directamente sobre un AVD veremos que ya incluye la


barra de accin con el men de accin Settings segn muestra la siguiente imagen:

A continuacin, vamos a estudiar cmo se implementa una barra de accin en un proyecto


Android. En primer lugar, lo usual es que una barra muestre el icono de la aplicacin (definido en
el fichero AndroidManifest mediante el atributo android:icon del elemento <application>)
y el ttulo de la Actividad actual (definido tambin en el archivo AndroidManifest mediante el
atributo android:label del elemento <activity>).
Los botones y los mens de accin se definen de la misma forma que los tpicos mens
de una aplicacin de Android; de hecho, se usa la misma implementacin. Es decir, el programador define el men de la aplicacin y es el sistema operativo el que decide cmo lo muestra:
como un men clsico para las versiones 2.X o anteriores (no se visualiza la barra de accin al
no ser compatible) o como una barra de accin si se ejecuta en Android 3.0 o superior.
De esta forma, Android mantiene la compatibilidad del men con todas sus versiones,
aunque dependiendo de la versin muestra la barra de accin o, en su defecto, el clsico men
de aplicacin.
Aunque en el curso de Iniciacin de Android ya estudiamos cmo definir los Mens de
aplicacin, vamos a repasar cmo se disea indicando las diferencias que aade la barra de
accin.
Como sabes, un men se define mediante un fichero XML que se almacena en la carpeta
/res/men del proyecto. Este men se especifica mediante el elemento raz <menu> que contiene una serie de elementos <item> que constituyen cada una de las opciones del men. Estos
elementos <item> pueden incluir varios atributos que lo especifican. Entre ello, destacamos los
siguientes (ya deberas conocer la mayora):
- android:id: ID identificativo de la opcin que, por ejemplo, podremos usar para saber en
qu opcin hace clic el usuario.
- android:title: texto que muestra la opcin.
- android:icon: icono asociado a la accin.
- android:showAsAction: slo se usa si se muestra la barra de accin. Este atributo indica
si la opcin del men se mostrar como un botn de accin o como parte del men extendido (overflow). Puede contener uno o varios valores simultneamente, entre los siguientes:
ifRoom: la opcin se muestra como botn de accin slo si hay espacio disponible en
la pantalla; si no lo hay, la opcin aparecer en el men extendido.
withText: indica que se mostrar el texto al lado del icono si se est mostrando como
botn de accin.
never: la opcin se mostrar siempre como parte del men extendido.
Always: la opcin se mostrar siempre como botn de accin. Atencin: este valor
puede provocar que los elementos se solapen si no hay espacio suficiente para ellos
en la pantalla. Recomendamos al programador que la utilice con mesura.

203

Aula Mentor

Adems de poder definir mediante un archivo XML el men, es posible definir el tipo de men
dinmicamente mediante sentencias Java de la clase MenuItem con su mtodo menuItem.
setShowAsAction(int actionEnum) e indicando una de las siguientes acciones:
- SHOW_AS_ACTION_ALWAYS
- SHOW_AS_ACTION_IF_ROOM
- SHOW_AS_ACTION_NEVER
- SHOW_AS_ACTION_WITH_TEXT
Por ejemplo, si creas con Eclipse ADT un proyecto nuevo, vers que el men definido por
defecto (llamado normalmente /res/menu/activity_main.xml) tiene este contenido:
<menu xmlns:android=http://schemas.android.com/apk/res/android >
<item
android:id=@+id/menu_settings
android:showAsAction=never
android:title=@string/menu_settings/>
</menu>

En el cdigo anterior puedes ver que aparece un men con una nica opcin denominada
Settings y con el atributo showAsAction=never, es decir, la opcin aparece en el men
extendido.
En el Ejemplo 6 del curso hemos definido la tpica barra de accin con las opciones: Nuevo,
Guardar y Opciones:

<menu xmlns:android=http://schemas.android.com/apk/res/android >

204

<item
android:id=@+id/menu_guardar
android:showAsAction=ifRoom
android:icon=@android:drawable/ic_menu_save
android:title=@string/menu_guardar/>
<item
android:id=@+id/menu_nuevo
android:showAsAction=ifRoom|withText
android:icon=@android:drawable/ic_menu_add
android:title=@string/menu_nuevo/>
<item
android:id=@+id/menu_opciones
android:showAsAction=never
android:title=@string/menu_opciones/>
</menu>

En el cdigo anterior se puede observar que la segunda opcin combina varios valores de
showAsAction utilizando el carcter |.
Como puedes ver, la opcin Guardar se muestra como botn si hay espacio, la segunda
es igual que la primera pero aade el texto y la tercera opcin no aparece nunca, es decir, es
un men.

U2 Multimedia y Grficos en Android

En el cdigo anterior hemos empleado las imgenes del propio sistema operativo
@android:drawable/ic_menu_save y @android:drawable/ic_menu_add para
establecer el icono de los botones. Puedes encontrar todas las imgenes disponibles por defecto en Android en tu SDK de Android en el directorio (calidad media):
path_sdk_Android\platforms\android-17\data\res\drawable-mdpi

Una vez definido el men mediante su correspondiente fichero XML, hay que asociarlo a la
Actividad principal utilizando el tpico mtodo OnCreateOptionsMenu(). Para ello, de igual
forma que se hace con un men, vamos a inflarlo llamando al mtodo inflate() e indicando el
ID del fichero XML donde se ha definido dicho men:
public class MainActivity extends Activity {
...

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflamos el men -> Infla la barra de accin.

getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}

Ahora podemos ejecutar la aplicacin en el emulador y comprobar que se selecciona


automticamente el layout correcto dependiendo de las caractersticas del AVD (Dispositivo
Virtual de Android) que estemos utilizando.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (Barra de accin) de la


Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado
del programa anterior, en el que hemos empleado una Barra de accin en lugar
de mens de aplicacin.

205

Aula Mentor

Si ejecutas la aplicacin, puedes ver que, dependiendo del tamao de pantalla del AVD, aparecen
diferentes opciones en la barra de accin, por ejemplo, en el modo vertical slo se visualiza:

En este caso hemos ejecutado el proyecto en un AVD de 800 dpi de resolucin de pantalla
y vemos que aparecen como botones de accin las dos opciones que hemos marcado como
showAsAction=ifRoom, pero no aparece el texto de la segunda opcin ni el men extendido
(overflow) ya que no hay espacio disponible con esta pantalla en vertical. Tampoco se visualiza
el ttulo completo de la aplicacin. Sin embargo, si rotamos la pantalla del emulador al modo
horizontal (pulsando la combinacin de teclado [Ctrl + F12]) vemos lo siguiente:

206

Ahora s se ve el texto de la opcin Nuevo tal y como lo habamos indicado el atributo


showAsAction =withText.
Animamos al alumno a que cree varios dispositivos virtuales con diferentes tamaos de
pantalla y compruebe cmo cambia la distribucin y la aparicin de las distintas opciones en
funcin del tamao de la pantalla.
Una vez que ya hemos definido los elementos de la barra de accin podemos implementar el comportamiento de stos cuando el usuario haga clic sobre ellos. Esto se implementa de
la misma manera que se sigue con los mens tradicionales, es decir, ha que reescribir el mtodo
OnOptionsItemSelected()segn la funcionalidad de la aplicacin. En este ejemplo mostramos un mensaje sencillo de tipo Toast al usuario:
@Override
// En funcin de la opcin seleccionada mostramos un Toast en la
// aplicacin
public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {
case R.id.menu_nuevo:

Toast.makeText(getApplicationContext(),
Has pulsado en accin Nuevo,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_guardar:

Toast.makeText(getApplicationContext(),
Has pulsado en accin Guardar,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_opciones:

Toast.makeText(getApplicationContext(),
Has pulsado en accin Opciones,
Toast.LENGTH_SHORT).show();
return true;

U2 Multimedia y Grficos en Android

}
}

default:
return super.onOptionsItemSelected(item);

5.3.1 Cmo integrar pestaas en la Barra de accin


Hasta ahora hemos estudiado cmo introducir una Barra de accin en las
aplicaciones de Android. Pero, adems, Android permite que el programador
pueda aadir pestaas a la citada barra de forma sencilla. Es ms, el
interior de estas pestaas pueden ser fragmentos. El hecho de integrar
pestaas en la misma barra presenta una ventaja adicional ya que Android
va a adaptar automticamente la interfaz del usuario a los distintos
tamaos y configuraciones de pantalla mejorando la visualizacin de la
aplicacin, sobre todo en los dispositivos de pantalla grande.

Por ejemplo, si Android detecta que hay suficiente espacio disponible en la Barra de
accin, integrar las pestaas dentro de la propia barra de forma que no ocupan espacio extra
en una fila inferior. Por el contrario, si no hubiera espacio suficiente situara las pestaas debajo
de la barra.
A continuacin, vamos a ver cmo se integran pestaas.
Lo primero que vamos a hacer es crear un nuevo fragmento por cada pestaa que tenga la aplicacin. Lo usual en las aplicaciones con pestaas es que cada una de ellas contenga diferente
funcionalidad. En este ejemplo hemos definido dos pestaas que cargan dos fragmentos. En
este apartado ya hemos visto cmo se implementan los fragmentos: con un fichero layout xml
y su clase Java asociada:
Pestaa 1:
- Tab1Fragmento.java
- fragmento1.xml

Pestaa 2:
- Tab2Fragmento.java
- fragmento2.xml
Por motivos pedaggicos, la interfaz de los fragmentos ser minimalista y contendrn nicamente la etiqueta de texto Pestaa 1 o Pestaa 2 para poder detectar el cambio de pestaa.
Por ejemplo, para la pestaa 1 el fichero fragmento1.xml contiene:
<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/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/tab_1 />
</LinearLayout>

207

Aula Mentor

Su clase java asociada Tab1Fragmento.java no desarrolla ninguna funcionalidad, por lo que


el cdigo se limita a inflar el layout:
public class Tab1Fragmento extends Fragment {
@Override

public View onCreateView(LayoutInflater inflater,

ViewGroup container, Bundle savedInstanceState) {

return inflater.inflate(R.layout.fragmento1, container, false);
}
}

Hemos definido la pestaa 2 de forma similar. Desde Eclipse ADT puedes abrir el proyecto y
observar su contenido.
As, ya disponemos del contenido de las pestaas y vamos a aadirlas al Ejemplo del
curso y enlazarlas en la barra de accin asignndoles un listener desde el que se responde
a los eventos que se produzcan.
Veamos el contenido del listener que incluye el cdigo que gestiona los eventos clsicos de pestaascomo la seleccin, reseleccin y deseleccin.
Para esto, hemos definido la nueva clase MiTabListener que se extiende de ActionBar.TabListener en la que reescribimos los mtodos de sus eventos onTabSelected(), onTabUnselected() y onTabReselected(). Veamos su cdigo fuente:

208

// Listener que se ejecuta cada vez que se produce un cambio


// de pestaa en la Barra de accin
public class MiTabListener implements ActionBar.TabListener {

// Variables que almacenan el fragmento que debemos cargar
// y la actividad que lo ejecuta
private Fragment fragment;
private Activity actividad;

// Definimos el constructor de la clase
public MiTabListener(Fragment fg, Activity actividad)
{

this.fragment = fg;

this.actividad= actividad;
}

// Evento que ocurre cuando se vuelve a seleccionar una pestaa
// pero ya se est visualizando
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(actividad.getApplicationContext(),

Pestaa seleccionada de nuevo,
Toast.LENGTH_SHORT).show();
}

// Eventos que ocurre cuando seleccionamos o deseleccionamos una
// pestaa
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {

// Mostramos un mensaje
Toast.makeText(actividad.getApplicationContext(),

Pestaa + tab.getText() +

seleccionada,Toast.LENGTH_SHORT).show();

// Reemplazamos en el contenedor (LinearLayout en el archivo xml

U2 Multimedia y Grficos en Android

// de la Actividad principal) por el fragmento que debemos


// activar
ft.replace(R.id.contenedor, fragment);

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {

// Mostramos un mensaje
Toast.makeText(actividad.getApplicationContext(),

Pestaa + tab.getText() + sin
seleccionar,Toast.LENGTH_SHORT).show();

// Quitamos el fragmento del contenedor.
ft.remove(fragment);
}
}

Estos mtodos lo nico que hacen es mostrar u ocultar los fragmentos en funcin de la pestaa
seleccionada por el usuario. As, en el evento onTabSelected() se reemplaza el fragmento
que est visible en la Actividad principal por el de la pestaa seleccionada. Por el contrario,
en el mtodo onTabUnselected() se oculta el fragmento asociado a la pestaa que pierde el
foco.
Ambos eventos utilizan el parmetro del tipo FragmentTransaction que permite gestionar los fragmentos de la actividad. En el primer caso, invoca su mtodo replace() y, en el
segundo, su mtodo remove(). Hemos aadido adems un mensaje para mostrar al usuario
que se ha realizado un cambio sobre las pestaas.
Una vez implementado este listener, en el evento onCreate() de la Actividad principal
de la aplicacin, tenemos que crear las pestaas, asociar sus respectivos fragmentos y engancharlas a la barra de accin.
Veamos el cdigo fuente de esta clase:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Obtenemos una referencia a la actionbar (Barra Accin)


ActionBar barra = getActionBar();

// Establecemos el modo de navegacin por pestaas


barra.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Podemos ocultar el ttulo de la actividad si es necesario
// barra.setDisplayShowTitleEnabled(false);

// Creamos las pestaas


ActionBar.Tab tab1 =

barra.newTab().setText(getString(R.string.tab_1));
ActionBar.Tab tab2 =

barra.newTab().setText(getString(R.string.tab_2));

209

Aula Mentor

// Creamos los fragmentos de cada pestaa


Fragment tab1frag = new Tab1Fragmento();
Fragment tab2frag = new Tab2Fragmento();
// Asociamos los listener a las pestaas
tab1.setTabListener(new MiTabListener(tab1frag, this));
tab2.setTabListener(new MiTabListener(tab2frag, this));

}
...

// Aadimos las pestaas a la action bar


barra.addTab(tab1);
barra.addTab(tab2);

En el cdigo anterior podemos ver que hemos obtenido una referencia a la barra de accin
mediante el mtodo getActionBar() y establecemos el mtodo de navegacin a NAVIGATION_MODE_TABS para que se muestren las pestaas.
El mtodo setNavigationMode() puede tomar los siguientes valores que establecen el tipo
de navegacin en la barra de accin:
- NAVIGATION_MODE_STANDARD: muestra la barra con un ttulo y un icono clsicos.
- NAVIGATION_MODE_LIST: las pestaas se integran en el ttulo de la barra mostrando un listado desplegable en ste.
- NAVIGATION_MODE_TABS: las pestaas se integran en la barra.
210

A continuacin, creamos las pestaas con el mtodo newTab() de la barra y, en la misma sentencia, determinamos su texto con setText(). Despus, instanciamos los dos fragmentos y los
asociamos a cada pestaa utilizando el listener setTabListener(). Para acabar, aadimos las
pestaas a la barra de accin mediante el mtodo addTab().

Nota: hemos incluido en el cdigo fuente el mtodo setDisplayShowTitleEnabled()de la barra de accin que permite dinmicamente mostrar u ocultar el ttulo

de sta.

Si ejecutamos la aplicacin en el emulador, veremos lo siguiente (depende del tamao de pantalla de tu AVD):

U2 Multimedia y Grficos en Android

Como podemos observar, Android ha colocado las pestaas debajo de la barra de accin porque no hay suficiente espacio disponible. Si cambiamos el emulador a la orientacin horizontal
[Ctrl+F12], vemos que las pestaas ya aparecen integradas en la barra:

Fjate en el espacio que queda libre para definir el resto de la interfaz optimizando al usuario
la comodidad y usabilidad de la aplicacin final.
Si ejecutas el proyecto en Eclipse ADT y cambias varias veces de pestaa vers que se
cambia de fragmento y se muestran los mensajes correspondientes.

6. Nuevas Vistas: GridView, Interruptor (Switch) y Navigation Drawer


Vamos a finalizar esta Unidad estudiando tres nuevos elementos: GridView (Vista en rejilla),
Switch (Interruptor) y Navigation Drawer (Men lateral deslizante) que el programador
puede incluir en la interfaz del usuario mejorando la usabilidad y aspecto de sus aplicaciones.

6.1 Grid View


En el curso de Iniciacin hemos estudiado los controles de seleccin ms utilizados por el
programador en la interfaz de usuario, como son las Listas desplegables (Spinner) y las listas
normales (ListView) con sus respectivos Adaptadores personalizados
GridView es una Vista del tipo ViewGroup, que muestra opciones en dos dimensiones
en una matriz desplazable. Las opciones del esta Vista se introducen mediante su Adaptador
correspondiente.

211

Aula Mentor

Veamos un esquema visual de la distribucin de este tipo de Vista:

212

Como puedes ver, el conjunto de opciones seleccionables estn distribuidas de forma tabular
o, dicho de otra forma, divididas en filas y columnas como una matriz. Dada la naturaleza de la
Vista, sus propiedades ms importantes son las tpicas de cualquier listado:
- android:numColumns: indica el nmero de columnas de la tabla. Tambin podemos indicar auto_fit si deseamos que el propio sistema operativo las establezca a partir de ciertas
propiedades.
- android:columnWidth: marca el ancho de las columnas de la tabla.
- android:horizontalSpacing: establece el espacio horizontal entre las celdas.
- android:verticalSpacing: indica el espacio vertical entre las celdas.
- android:stretchMode: marca qu hacer con el espacio horizontal sobrante. Si se establece
el valor columnWidth, este espacio ser dividido a partes iguales por las columnas de la
tabla. Por el contrario, si se establece a spacingWidth ser dividido a partes iguales por los
espacios entre las celdas.
Veamos cmo definiramos un GridView en la aplicacin del Ejemplo 7 del curso en el archivo

activity_main.xml:

<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:id=@+id/labelMensaje

android:layout_width=match_parent

android:layout_height=wrap_content

android:text=@string/selecciona_una_opcion />
<GridView

android:id=@+id/gridOpciones

android:layout_width=match_parent

android:layout_height=match_parent

android:columnWidth=150dp

android:horizontalSpacing=1dp

U2 Multimedia y Grficos en Android

android:numColumns=auto_fit
android:stretchMode=columnWidth
android:verticalSpacing=5dp />

</LinearLayout>

Una vez est definida la interfaz de usuario, la forma de asignarle sus opciones es exactamente
la misma que en otro tipo de listados.
Por motivos pedaggicos, vamos a utilizar una matriz simple como adaptador utilizando
la clase ArrayAdaptery un layout genrico (simple_list_item_1, compuesto por un simple
TextView). Asociamos el adaptador al control GridView mediante su mtodo ya conocido
setAdapter(). Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 7
veremos:
public class MainActivity extends Activity {


private TextView labelMensaje;


private GridView gridOpciones;
private String[] datos;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Obtenemos la matriz datos del archivo arrays.xml
datos = this.getResources().getStringArray(R.array.datos);
// Creamos un adaptador sencillo
ArrayAdapter<String> adaptador =

new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, datos);
// Obtenemos las referencias a las Vistas
labelMensaje = (TextView)findViewById(R.id.labelMensaje);
gridOpciones = (GridView)findViewById(R.id.gridOpciones);

// Creamos el evento onClic en una de las opciones


gridOpciones.setOnItemClickListener(

new AdapterView.OnItemClickListener() {

public void onItemClick(AdapterView<?> parent,

android.view.View v, int position, long id) {

labelMensaje.setText(Mes pulsado: +

datos[position]);

}
});
// Establecemos el adaptador
gridOpciones.setAdapter(adaptador);
}

Por defecto, las opciones de la matriz se aaden al GridView ordenados por filas y, si no caben
todas en la pantalla, se activa el desplazamiento (scroll) sobre la lista.
En cuanto a los eventos disponibles, el ms frecuente es el lanzado cuando un usuario
selecciona una opcin determinada de la lista: onItemClick. Este evento se captura de igual
forma que con las lista Spinner y ListView.

213

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (Android GridView) de la


Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado
del programa anterior, en el que hemos utilizado la Vista GridView.

Si ejecutas en Eclipse ADT este Ejemplo 7, vers que se muestra la siguiente lista con los
meses del ao:

214

Como has visto, hemos mostrado el uso bsico del listado GridView. El alumno o alumna puede aplicar por su cuenta, de forma prcticamente directa, todo lo comentado sobre listas en el
curso de Iniciacin de Android de Mentor.
Entre los conocimientos que ya debe tener podemos citar la personalizacin de las opciones para presentar datos complejos, la creacin de un adaptador personalizado y las distintas
optimizaciones para mejorar el rendimiento de la aplicacin, como la reutilizacin de las Vistas
de las opciones.
Animamos al alumno a que pruebe con todas estas opciones realizando una aplicacin
propia.

U2 Multimedia y Grficos en Android

6.2 Interruptores (Switches)


Switch es una Vista del tipo botn compuesto que muestra un interruptor que permite al

usuario indicar si una opcin est activa o inactiva.


Como una imagen vale ms que mil palabras, veamos el aspecto de este tipo de Vista
activado y desactivado:

Como puedes ver, se trata del clsico interruptor donde el usuario puede activar o desactivar
una opcin con un desplazamiento sobre ste.
Teniendo en cuenta la naturaleza de esta Vista, sus propiedades ms importantes son:
- android:text: indica el texto que debe mostrar la opcin del interruptor en el lado izquierdo de ste.
- android:checked: establece si el interruptor est activado o no por defecto.
- android:textoOn: establece el texto interno del interruptor cuando est activado.
- android:textOff: establece el texto interno del interruptor cuando est desactivado.
Veamos cmo utilizamos varios Switch en la aplicacin del Ejemplo 8 del curso en el archivo
activity_main.xml:
<ScrollView xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent>
<LinearLayout
android:orientation=vertical
android:layout_width=match_parent
android:layout_height=wrap_content>
<Switch android:text=@string/interruptor_por_defecto
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:text=@string/interruptor_encendido
android:checked=true
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:id=@+id/switch_gestionado
android:text=@string/interruptor_gestionado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch
android:text=@string/interruptor_con_texto_personalizado

215

Aula Mentor

android:layout_width=wrap_content
android:layout_height=wrap_content
android:textOn=Encendido
android:textOff=Apagado
android:layout_marginBottom=32dip />
</LinearLayout>
</ScrollView>
Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 8
observamos:
public class MainActivity extends Activity {

216

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el switch gestionado para definir el evento
// onCheckedChange
Switch s = (Switch) findViewById(R.id.switch_gestionado);
if (s != null) {
s.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
@Override
// Evento que ocurre cuando el usuario enciende o
// apaga un interruptor
public void onCheckedChanged(CompoundButton
buttonView, boolean isChecked) {

// Mostramos un sencillo mensaje
Toast.makeText(MainActivity.this, El
interruptor est + (isChecked ?
ENCENDIDO! : APAGADO!),
Toast.LENGTH_SHORT).show();
}
});
} //end if not null
} // end onCreate
}

En el cdigo fuente anterior puedes observar que es fcil definir el evento onCheckedChanged
que se lanza cuando el usuario activa o desactiva un interruptor.
Es sencillo utilizar el mtodo setCheck() para indicar si el interruptor est o no activado. Adems, entre otros, tambin dispone del mtodo isCheck() para conocer el estado actual
del interruptor. En la ayuda oficial de Android puedes encontrar otros mtodos interesantes.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 8 (Interruptores en Android) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD
el resultado del programa anterior, en el que hemos utilizado la Vista Switch.

Si ejecutas en Eclipse ADT este Ejemplo 8, vers que se muestra la siguiente aplicacin con
interruptores:

U2 Multimedia y Grficos en Android

7. Navigation Drawer (Men lateral deslizante)


El elemento Navigation Drawer es un men lateral deslizante donde el usuario puede
seleccionar diferentes opciones. Tambin podemos denominarlo men de navegacin.
Este tipo de mens de navegacin aparece en muchas aplicaciones, si bien los desarrolladores los incluan gracias a bibliotecas externas que implementaban este componente. A partir
de la versin 4.3 de Android, Google pone a disposicin del programador la implementacin
nativa de este componente y define su uso en su gua de diseo.
Veamos un esquema visual de este tipo de men:

El navigation drawer est disponible como parte de la biblioteca de compatibilidad androidsupport; as, es posible incluir este elemento visual desde la versin Android 1.6 (API 4).

217

Aula Mentor

Atencin: la versin de la biblioteca android-support debe ser de la revisin 18


(Android 4.3) o superior. Si se usa una versin anterior, no funcionar el cdigo
siguiente.

La clase principal se denomina DrawerLayout y se sirve de contenedor del men lateral


deslizante.
Para aadir el navigation drawer a una aplicacin hay que indicar que el elemento
raz del layout XML sea del tipo android.support.v4.widget.DrawerLayout. Dentro de este
elemento colocaremos nicamente dos componentes, en este orden:
- FrameLayout: es el contenedor de la interfaz real de la actividad, que completamos mediante fragmentos.
- ListView: es el contenedor de las distintas opciones del men lateral.

Vamos a partir del Ejemplo 5 de esta Unidad, para desarrollar este ejemplo. Para
mejorarlo lo hemos modificado utilizando la biblioteca de compatibilidad y, as,
poder ejecutarlo desde la versin 2.2 de Android.

218

Veamos cmo empleamos este elemento visual en la aplicacin del Ejemplo 9 del curso en el
archivo activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/drawer_layout
android:layout_width=match_parent
android:layout_height=match_parent >
<!-- Contenido Principal -->
<FrameLayout
android:id=@+id/frame_contenido
android:layout_width=match_parent
android:layout_height=match_parent />
<!-- Men Lateral -->
<ListView
android:id=@+id/menu_lateral
android:layout_width=240dp
android:layout_height=match_parent
android:layout_gravity=start
android:background=#111
android:choiceMode=singleChoice />
</android.support.v4.widget.DrawerLayout>
Para el FrameLayout hemos ajustado con match_parent el ancho y el alto, para que ocupe
todo el espacio disponible en la pantalla.
En este caso del ListView el alto ocupa todo el espacio y el ancho es de 240dp; as,

U2 Multimedia y Grficos en Android

cuando el men est abierto, no ocultar totalmente el contenido principal de la pantalla.


Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 9 encontramos el
cdigo siguiente:
public class MainActivity extends ActionBarActivity

// Clase que contiene el layout
private DrawerLayout drawerLayout;
// Opciones del layout anterior
private ListView drawerList;
// Listener del men lateral
private ActionBarDrawerToggle drawerToggle;

private CharSequence tituloMenuLateral;


private CharSequence tituloAplicacion;
@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Buscamos las Vistas de la Interfaz usuario

drawerLayout = (DrawerLayout)

findViewById(R.id.drawer_layout);

// Listado con las opciones del men lateral

drawerList = (ListView) findViewById(R.id.menu_lateral);


// Definimos las opciones del men lateral

final String[] opcionesMenu = new String[] {Opcin 1,

Opcin 2};


// Definimos el adaptador del listado con las opciones del

// men lateral

drawerList.setAdapter(new ArrayAdapter<String>(

getSupportActionBar().getThemedContext(),

android.R.layout.simple_list_item_1, opcionesMenu));


// Definimos el evento onClick en el men lateral

drawerList.setOnItemClickListener(new OnItemClickListener()

{

@Override

public void onItemClick(AdapterView<?> parent, View

view, int position, long id) {

// Variable con el fragmento que vamos a crear

Fragment fragmento = null;


//Creamos los fragmentos segn la opcin

// seleccionada

switch (position) {

case 0:

fragmento = new Tab1Fragmento();

break;

case 1:

fragmento = new Tab2Fragmento();

break;

} // end case

219

Aula Mentor

220

// Hacemos los cambios de fragmento en la interfaz


// del usuario
FragmentManager fragmentManager =
getSupportFragmentManager();
fragmentManager.beginTransaction()

.replace(R.id.frame_contenido, fragmento)
.commit();
drawerList.setItemChecked(position, true);
// Obtenemos el ttulo de la opcin seleccionada y
// lo ponemos como ttulo de la aplicacin
tituloMenuLateral = opcionesMenu[position];










getSupportActionBar().setTitle(tituloMenuLateral);

// Cerramos el men lateral

drawerLayout.closeDrawer(drawerList);

}

}); // end onClick


// Inicializamos las variables de ttulos con el ttulo

// inicial de la aplicacin

tituloMenuLateral = getTitle();

tituloAplicacion = getTitle();



// Definimos el icono de apertura del men lateral

drawerToggle = new ActionBarDrawerToggle(this,
drawerLayout,
R.drawable.ic_navigation_drawer,
R.string.drawer_open,
R.string.drawer_close)
{
// Evento que ocurre cuando se cierra el men

// lateral

public void onDrawerClosed(View view) {
// Cambiamos el ttulo de la cabecera a

// la opcin elegida del men
getSupportActionBar().setTitle(

tituloMenuLateral);
// Ejecutamos el evento onPrepareOptionsMenu()

//para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(

MainActivity.this);
}
// Evento que ocurre cuando se abre el men

// lateral

public void onDrawerOpened(View drawerView) {
// Cambiamos el ttulo de la cabecera al

// de la aplicacin
getSupportActionBar().setTitle(

tituloAplicacion);
// Ejecutamos el evento onPrepareOptionsMenu()

// para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(

MainActivity.this);
}
};

U2 Multimedia y Grficos en Android



// Definimos el listener del men lateral
drawerLayout.setDrawerListener(drawerToggle);

// Indicamos que el icono de la aplicacin es activo
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} // end onCreate()
@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflamos el men -> Infla la barra de accin.

getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override

// En funcin de la opcin seleccionada mostramos un Toast en la

// aplicacin

public boolean onOptionsItemSelected(MenuItem item) {


// Si el usuario hace clic en el icono de la barra

// mostramos el men lateral

if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
else // Si no, mostramos el onClick de la opcin del men

// correspondiente

switch (item.getItemId()) {

case R.id.menu_nuevo:


Toast.makeText(this,

Has pulsado en accin Nuevo,
Toast.LENGTH_SHORT).show();

return true;

case R.id.menu_guardar:


Toast.makeText(this,
Has pulsado en accin Guardar,
Toast.LENGTH_SHORT).show();

return true;

case R.id.menu_buscar:


Toast.makeText(this,
Has pulsado en accin Buscar,
Toast.LENGTH_SHORT).show();

return true;

default:

return super.onOptionsItemSelected(item);
}
} // end onOptionsItemSelected


// Evento que se lanza antes de mostrar el men
@Override

public boolean onPrepareOptionsMenu(Menu menu) {

// Vemos si el men lateral est abierto

boolean menuAbierto = drawerLayout.isDrawerOpen(

drawerList);

// Si est abierto, ocultamos la opcin del men buscar
if(menuAbierto)

menu.findItem(R.id.menu_buscar).setVisible(false);
else // Si no, la mostramos

221

Aula Mentor


menu.findItem(R.id.menu_buscar).setVisible(true);

return super.onPrepareOptionsMenu(menu);
}

// Evento que ocurre despus de crear la Actividad
@Override

protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);

// Sincronizamos el listener del men lateral
drawerToggle.syncState();
}


// Evento que ocurre si cambia la configuracin de la Actividad
@Override

public void onConfigurationChanged(Configuration newConfig) {

super.onConfigurationChanged(newConfig);

// Notificamos el cambio al listener del men lateral

drawerToggle.onConfigurationChanged(newConfig);
}

} // end clase

222

En el cdigo anterior, dentro del mtodo onCreate() de la actividad, buscamos las Vistas de
la Interfaz de usuario y definimos las opciones del men lateral en un matriz de tipo String.
Adems, creamos el adaptador del listado con las opciones del men lateral pasando
como parmetro a este adaptador el contexto obtenido invocando el mtodo getThemedContext() de la action bar. Hemos utilizando getSupportActionBar() para acceder a la barra
de accin en lugar del habitual getActionBar() ya que estamos utilizando la biblioteca de
compatibilidad.
Como el layout de los elementos de la lista hemos establecido el estndar
android.R.layout.simple_list_item_1. De esta forma, el listado ser compatible con la
mayora de versiones de Android. Sin embargo, este estilo hace que la opcin seleccionada no
est resaltada en el men cada vez que se cierre y se vuelva a abrir. Para hacer esto, en Android
4, podemos aplicar el layout android.R.layout.simple_list_item_activated_1, si bien
aparecer un error si se ejecuta la aplicacin en versiones anteriores de Android.
Si deseamos evitar este problema de compatibilidad tenemos varias opciones:
Aplicar un layout diferente dependiendo de la versin de Android en la que se ejecuta la
aplicacin:
drawerList.setAdapter(
new ArrayAdapter<String>(getSupportActionBar().getThemedContext(),
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?
android.R.layout.simple_list_item_activated_1 :
android.R.layout.simple_list_item_1, opcionesMenu));

Es decir, se mantendr la opcin seleccionada en Android 4, pero no en versiones anteriores.


Aplicar un layout distinto que mantenga la opcin marcada, aunque no se el de Android
4. Por ejemplo, si utilizamos simple_list_item_checked de Android 2.x, la opcin se mantendr resaltada con una marca de tipo check a la derecha de su nombre. Veamos su aspecto:
drawerList.setAdapter(
new ArrayAdapter<String>(getSupportActionBar().getThemedContext(),
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?
android.R.layout.simple_list_item_activated_1 :
android.R.layout.simple_list_item_checked, opcionesMenu));

U2 Multimedia y Grficos en Android

Desarrollar un adaptador personalizado que establezca manualmente el estilo de la opcin


seleccionada del listado, tal y como hemos estudiado en el curso de Iniciacin de Android de
Mentor.
A continuacin, detallamos el evento onClick del men lateral donde vamos a crear los
fragmentos que mostraremos al seleccionar una de las opciones. En este caso hemos utilizado
los mismos fragmentos que el Ejemplo 5 de esta Unidad, por lo tanto, no explicaremos su
detalle.
En este caso en concreto, utilizamos el Fragment Manager con getSupportFragmentManager(), haciendo uso de la biblioteca de compatibilidad, para sustituir el contenido del
FrameLayout que definimos en el layout por el nuevo fragmento creado. Despus, marcamos
la opcin pulsada del listado mediante el mtodo setItemChecked() y actualizamos el ttulo
de la barra de accin con el ttulo de la opcin seleccionada. A continuacin, cerramos el men
lateral invocando la orden closeDrawer().
Segn las recomendaciones de la gua de diseo de este componente, es aconsejable
mostrar un indicador en la barra de accin que informa al usuario de que la aplicacin dispone
de un men lateral. Adems si el usuario hace clic sobre el icono de la aplicacin, debera desplegarse el men. Tambin podemos actualizar el ttulo de la barra de accin y ocultar aquellas
opciones del men principal cuando est abierto el men lateral. Para todo esto vamos a utilizar
la clase ActionBarDrawerToggle.
En el cdigo anterior hemos creado un objeto del tipo ActionBarDrawerToggle y lo
hemos asociado al navigation drawer mediante el mtodo setDrawerListener() para responder a los eventos de apertura y cierre del men sobrescribiendo sus mtodos onDrawerOpened() y onDrawerClosed(), que actualizan el ttulo de la barra de accin mostrando el ttulo
de la aplicacin, cuando el men est abierto, o el ttulo de la opcin seleccionada actualmente,
cuando el men est cerrado.
El constructor de la clase ActionBarDrawerToggle recibe cinco parmetros: el contexto actual, una referencia al navigation drawer, el ID del icono a utilizar como indicador del
navigation drawer y los ID de dos cadenas de caracteres que se utilizar a efectos de accesibilidad de la aplicacin (literales Men Abierto y Men Cerrado).
Para aplicar el icono apropiado para el indicador del navigation drawer puedes usar
la utilidad Navigation Drawer Indicator Generator de la pgina Android Asset Studio, que permite generar y descargar el icono correspondiente y copiarlo a las carpetas /res/
drawable-xxx del proyecto.
Al final de cada mtodo onDrawerOpened() y onDrawerClosed() llamamos a la orden
invalidateOptionsMenu() para que se ejecute el evento onPrepareOptionsMenu() de la
Actividad y, as, ocultar las acciones de la barra de accin que no se apliquen cuando este men
lateral est abierto.
De nuevo, hemos hecho uso de la orden alternativa incluida en la biblioteca de compatibilidad de la clase ActivityCompat ya que el mtodo invalidateOptionsMenu() apareci
en la versin Android 3.0 (API 11).
Tambin habilitamos que el usuario pueda abrir el men lateral pulsando sobre el icono
de la aplicacin de la barra de accin llamando al mtodo setDisplayHomeAsUpEnabled().
Si te fijas en el evento onOptionsItemSelected(), encargado de gestionar los clics del
usuario sobre la barra de accin, hemos aadido una llamada inicial al mtodo onOptionsItemSelected() del objeto ActionBarDrawerToggle creado anteriormente, ya que as podemos gestionar esta pulsacin sobre el icono de la aplicacin y salir directamente de este mtodo.

223

Aula Mentor

Para acabar, las recomendaciones de la gua de diseo de ActionBarDrawerToggle indican


que debemos:
- Implementar el evento onPostCreate() de la Actividad que ocurre despus de crearla,
llamando al mtodo syncState() del objeto ActionBarDrawerToggle para sincronizar el
listener del men lateral.
- Implementar el evento onConfigurationChanged() de la Actividad que ocurre si cambia la
configuracin del dispositivo (por ejemplo, su orientacin), invocando a su mtodo homlogo del objeto ActionBarDrawerToggle.
Para poder utilizar la biblioteca de compatibilidad es necesario importarla. Para ello, hacemos
clic en la opcin File->Import de Eclipse ADT y buscamos el directorio adt-bundlewindows-x86\sdk\extras\android\support\v7\appcompat del SDK de Android:

224

A continuacin pulsamos aceptar y veremos que ya aparece en el entorno de desarrollo esta


biblioteca:

U2 Multimedia y Grficos en Android

A continuacin, importamos la biblioteca en este Ejemplo 9 abriendo sus propiedades y


aadiendo la biblioteca en la pestaa Android (en la teora de la Unidad 4 sobre Bibliotecas
puedes encontrar ms informacin sobre este proceso):

225

Si pulsamos el botn OK, vers que la biblioteca queda cargada en el proyecto:

Aula Mentor

Para acabar, volvemos a pulsar el botn OK.


Para que el tema visual de la aplicacin sea compatible en todas las versiones de Android,
debemos sustituirlo o extenderlo aplicando un tema definido especficamente en la biblioteca
de compatibilidad:
Theme.AppCompat
Theme.AppCompat.Light
Theme.AppCompat.Light.DarkActionBar

En este ejemplo vamos a aplicar el ltimo de ellos modificando el archivo fichero


AndroidManifest.xml e indicando el nuevo tema en el atributo android:theme del elemento
<application>:
<application
android:allowBackup=true
android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/Theme.AppCompat.Light.DarkActionBar >

Si no hacemos esto, aparecer el siguiente error de compilacin en Eclipse ADT: No resource


found that matches the given name (at theme with value @style/Theme.
AppCompat.Light.DarkActionBar).

226

Hay que tener tambin en cuenta que, en versiones anteriores a Android 3.0, no existen los
atributos de los mens utilizados por la funcionalidad de la barra de accin como, por ejemplo,
el atributo android:showAsAction y, por lo tanto, no podemos utilizarlos directamente.
Para solucionar este problema, vamos a utilizar dichos atributos indicando un espacio
de nombres personalizado que definimos en el elemento <menu> y usamos en los elementos
<item>. Veamos el aspecto del archivo unidad2.eje9.navigation_drawer\res\menu\activity_main.xml:
<menu xmlns:android=http://schemas.android.com/apk/res/android
xmlns:aplicacion=http://schemas.android.com/apk/res-auto>
<item
android:id=@+id/menu_guardar
aplicacion:showAsAction=ifRoom
android:icon=@android:drawable/ic_menu_save
android:title=@string/menu_guardar/>
<item
android:id=@+id/menu_nuevo
aplicacion:showAsAction=always
android:icon=@android:drawable/ic_menu_add
android:title=@string/menu_nuevo/>

<item
android:id=@+id/menu_buscar

U2 Multimedia y Grficos en Android

</menu>

aplicacion:showAsAction=always
android:icon=@drawable/ic_action_search
android:title=@string/menu_buscar/>

Hemos incluido un nuevo espacio de nombres (namespace) llamado aplicacion en el elemento


<menu>. Despus, en las opciones del men, indicamos el nuevo espacio de nombres en los
atributos especficos de la barra de accin.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 9 (Navigation Drawer) de la


Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado
del programa anterior, en el que hemos utilizado un Men deslizable lateral.

Si ejecutas en Eclipse ADT este Ejemplo 9, vers que se muestra la siguiente aplicacin:

227

Si ejecutas en un AVD de Android 2.x puedes ver que la aplicacin funciona correctamente y el
aspecto es muy similar:

Aula Mentor

228

Si al importar el proyecto en Eclipse ADT aparece la siguiente ventana de error:

Debes importar la biblioteca de compatibilidad siguiendo los pasos anteriormente descritos.

U2 Multimedia y Grficos en Android

8. Resumen
Hay que saber al final de esta unidad:
- Hoy en da, es necesario que una aplicacin tenga un aspecto visual atractivo
con el diseo apropiado.
- En ingls, se denomina look and feel al aspecto visual de una aplicacin.
- Los temas visuales de Android (themes) son un medio rico y complejo que permiten definir todos los atributos del aspecto de Vistas de Android.
- Un atributo es una propiedad de una Vista que permite modificar su aspecto
visual.
- Los Estilos (Styles) agrupan varios atributos visuales de una Vista en un nico
bloque que podemos aplicar al aspecto de sta.
- Para utilizar temas conjuntamente con estilos debemos dar los siguientes pasos:
Definir el nombre del tema y sus atributos (caractersticas de aspecto de las
Vistas).
Especificar los atributos anteriores mediante referencia a otro atributo
(format=reference).
Disear el aspecto de cada atributo con las propiedades visuales de una Vista
Android.
Aplicar el estilo que corresponda a las vistas que definen la interfaz del usuario mediante su propiedad style.
- Un recurso de color permite definir un color comn a toda la aplicacin.
- Un recurso de tipo Dimensin permite definir una longitud comn a toda la aplicacin.
- Un Gradient Drawable permite al programador dibujar un gradiente de colores
que consiste en mostrar un degradado de dos colores en el fondo de cualquier
Vista.
- Un Selector Drawable es un tipo de elemento que permite realizar cambios automticos basados en el aspecto de una Vista teniendo en cuenta el estado actual
de sta.
- Un Nine-patch Drawable es un tipo especial de imagen que escala o crece tanto
a lo largo como a lo ancho y que mantiene su relacin de aspecto visual.
- Un Widget es una aplicacin reducida o programa de tamao pequeo que permite al usuario visualizar informacin de forma rpida en la pantalla y/o acceder
a funciones utilizadas frecuentemente.
- Debemos saber que los Widgets:
Se pueden arrastrar y cambiar de posicin.
El usuario puede eliminarlos o modificar su tamao.
El usuario puede interactuar con ellos como si fueran una Actividad ms.
Actualizan informacin de forma peridica.
Se puede crear un mismo Widget tantas veces como sea necesario.
Se puede configurar un Widget al crearlo.
- Para crear la interfaz visual de usuario de los Widgets debemos utilizar la clase
RemoteViews.

229

Aula Mentor

230

- Las Vistas ListView, GridView, StackView y AdapterViewFlipper se pueden utilizar en el interior de un Widget a partir de la versin Android 3.0 empleando las
Vistas de Colecciones (Collections).
- Desde la versin 4.2 de Android es posible incluir Widgets en la pantalla de bloqueo (Lock Screen) del dispositivo.
- Los Live Wallpapers son fondos de pantalla animados e interactivos de Android
similares a las aplicaciones de Android.
- La clase SurfaceHolder de Android es una Interfaz abstracta que permite controlar el tamao, el formato, editar los pxeles y gestionar los cambios de una
superficie, en este caso, es la superficie del fondo de pantalla.
- Un Fragmento (Fragment) es un pedazo de la interfaz de usuario que se puede
aadir o eliminar de la interfaz global de usuario de forma independiente al resto
de elementos de la Actividad.
- Android permite utilizar Fragmentos en versiones anteriores a la 3.0 si incluimos
la librera de compatibilidad android-support en el proyecto.
- Fragment Manager es el componente del sistema operativo encargado de gestionar los fragmentos de una aplicacin.
- El ciclo de vida de un Fragmento est muy relacionado con el ciclo de vida de la
Actividad que lo contiene.
- Las transacciones permiten llevar a cabo otras operaciones dinmicas con Fragmentos en tiempo de ejecucin que modifican la interfaz del usuario.
- Android tambin dispone de una pila de ejecucin de Fragmentos llamada back.
- Un fragmento est directamente ligado a la Actividad que lo contiene y, por lo
tanto, es posible una comunicacin entre ambos.
- Desde el punto de vista de programacin es importante evitar que las Actividades y Fragmentos sean muy interdependientes entre s ya que esto reduce las
posibilidades de poder reusar el fragmento.
- A partir de la versin 3.0 de Android, las clsicas ventanas de dilogo (Dialog)
estn en desuso en favor de los fragmentos.
- La Barra de Accin de Android (o Action Bar) es, como su propio nombre indica,
una barra que aparece en la parte superior de una aplicacin que puede incluir
un icono, el ttulo de la Actividad y varios botones de accin o un men extendido desplegable.
- Android permite que el programador pueda aadir pestaas a la Barra de Accin
de forma sencilla.
- GridView es una Vista de tipo matriz que muestra opciones en dos dimensiones
en una matriz desplazable.
- Switch es una Vista del tipo botn compuesto que muestra un interruptor que
permite al usuario indicar si una opcin est activa o inactiva.
- Un Navigation Drawer es un men lateral deslizante donde el usuario puede
seleccionar diferentes opciones.

U3 Sensores y dispositivos de Android

Unidad 3. Sensores y dispositivos de Android

1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan los sensores y dispositivos de Android en una
aplicacin. Primero, haremos una introduccin a los sensores; despus, veremos cmo se usan
conjuntamente con el Simulador de sensores.
Adems, mostraremos mediante ejemplos la integracin de los dispositivos WIFI, Bluetooth, GPS y Cmara de fotos en una aplicacin Android.
Finalmente, veremos cmo aplicar sensores a un juego sencillo.

2. Introduccin a los sensores y dispositivos


La mayora de los dispositivos de Android incorpora sensores que miden su movimiento,
orientacin y otras varias magnitudes fsicas. Estos sensores proporcionan datos de magnitudes
fsicas con alta precisin y exactitud y son tiles, por ejemplo, para controlar la posicin o el
movimiento del dispositivo en tres dimensiones o su localizacin mediante GPS.
As, un juego puede monitorizar el estado del sensor de gravedad de un dispositivo para
reconocer movimientos complejos de usuario como su inclinacin, su movimiento, su rotacin
o su giro. Del mismo modo, una aplicacin de prediccin del tiempo podra utilizar un sensor
de temperatura y de humedad del dispositivo. Igualmente, una aplicacin de viajes podra usar
el sensor de campo magntico y acelermetro para mostrar el rumbo de la brjula.
Podemos dividir los sensores de Android en tres categoras:
- Sensores de Movimiento
Miden las fuerzas de aceleracin y giro de un dispositivo en sus tres ejes. Dentro de esta
categora podemos incluir acelermetros, sensores de gravedad, giroscopios y sensores de
rotacin.
- Sensores del Medioambiente
Miden magnitudes medioambientales, como la temperatura, la presin, la iluminacin y la
humedad. Dentro de esta categora tenemos: barmetros, fotmetros y termmetros.
- Sensores de Posicin
Miden la posicin fsica del dispositivo. Entre ellos, encontramos: los sensores de orientacin
y los magnetmetros.
En los dispositivos Android existe otro tipo de elementos que, si bien no pueden considerarse
estrictamente sensores en realidad, son componentes de hardware que contienen sensores
que se usan con un propsito muy definido. Por ejemplo, un dispositivo WIFI puede usarse
para conectase a una red WIFI (o para medir la potencia de sta). Entre ellos, encontramos:
dispositivos GPS, WIFI, Bluetooth y Cmara de fotos. Este tipo de componentes los estudiaremos
de forma separada un poco ms adelante en esta Unidad.

231

Aula Mentor

Para acceder a los sensores disponibles en un dispositivo y obtener sus datos, Android
proporciona varias clases e interfaces que realizan una amplia variedad de tareas relacionados
con los sensores. Por ejemplo, se pueden utilizar para:
- Obtener los sensores disponibles en el dispositivo.
- Conseguir las capacidades e informacin de un sensor en particular, como el rango de medida, el fabricante, la potencia y resolucin de la medida.
- Recibir los datos de las magnitudes medidas de los sensores definiendo un intervalo de actualizacin de las medidas.
- Registrar y quitar los listeners asociados a la monitorizacin y medida que realizan los
sensores.
En este apartado vamos a estudiar cmo se usan los sensores de Android. Todos los sensores se
manipulan de forma homognea y con ellos podremos implementar mejorar la interaccin del
dispositivo con el usuario.

No todos los dispositivos disponen de los mismos sensores. Cada modelo y fabricante incluye los que considera apropiados. Adems, para gestionar estos sensores el dispositivo emplea drivers que el fabricante no suele hacer pblicos.

2.1 Gestin de Sensores de Android


232

Android permite acceder a los sensores internos del dispositivo a travs de las clases del
paqueteandroid.hardware siguientes:
- Sensor: clase que representa a un sensor con todas sus propiedades.
- SensorEvent: clase que se usa para pasar los datos medidos por el sensor a la aplicacin.
- SensorManager: gestor que permite acceder a los sensores de un dispositivo. Para obtener
una instancia del mismos debemos invocar el mtodo Context.getSystemService() con
el argumento SENSOR_SERVICE.
- SensorEventListener: interfaz utilizada para recibir las notificaciones del SensorManager cuando se comunican nuevas medidas de los sensores.
Desde el punto de vista del desarrollador, las clases Java que debemos usar son pocas y
sencillas. Esto es as porque, para que el dispositivo Android gestione sensores, debe utilizar
drivers cuyo cdigo fuente no suele hacer pblico su fabricante. Por esto, Android tiene
tanto xito entre los fabricantes de dispositivos, ya que stos no tienen que publicar
el cdigo fuente de los drivers que, en realidad, muestra cmo funciona su hardware.
La clase Sensor contiene la informacin y propiedades completas de un sensor. Para
obtener el tipo de sensor que contiene un objeto de esta clase, debemos usar su mtodo getType() que devuelve 11 tipos de sensores mediante alguna de las siguientes constantes:

U3 Sensores y dispositivos de Android

Tipo / CONSTANTE

Descripcin

Dimensiones

Desde
API

acelermetro

Mide aceleraciones por gravedad


y cambios de movimiento

Brjula, detecta campo


magnticos

Detectar giros

Indica direccin a la que apunta


el dispositivo. (Obsoleto desde
API 8)

Se usa para ajustar iluminacin


pantalla.

Detecta un objeto a menos de 5


cm para, por ejemplo, apagar la
pantalla al hablar por telfono.

lgico

Altmetro y barmetro

Evita sobrecalentamientos del


dispositivo.
(Obsoleto desde API 14)

Mide la aceleracin debida a la


gravedad.

Mide aceleraciones sin tener en


cuenta la gravedad.

Detecta giros

Mide la temperatura del aire.

14

Mide la humedad absoluta y


relativa.

14

TYPE_ACCELEROMETER

campo magntico
TYPE_MAGNETIC_FIELD

giroscopio
TYPE_GYROSCOPE

orientacin
TYPE_ORIENTATION

luz ambiental
TYPE_LIGHT

proximidad
TYPE_PROXIMITY

presin atmosfrica
TYPE_PRESSURE

temperatura interna
TYPE_TEMPERATURE

gravedad
TYPE_GRAVITY

acelermetro lineal
TYPE_LINEAR_ACCELERATION

vector de rotacin
TYPE_ROTATION_VECTOR

temperatura ambiental
TYPE_AMBIENT_TEMPERATURE

humedad relativa
TYPE_RELATIVE_HUMIDITY

Esta lista anterior se va ampliando con las nuevas versiones de Android, si bien los sensores
disponibles varan mucho en funcin del dispositivo utilizado.
Adems, podemos dividir los sensores en dos categoras ms: real y virtual. Real se
refiere a que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de la combinacin de las medidas de otros sensores o son medidas
relativas.

233

Aula Mentor

234

Puedes observar que los sensores TEMPERATURE y ORIENTATION son obsoletos. El primero se ha
sustituido por el sensor AMBIENT_TEMP. El segundo se quitar de Android porque normalmente
no hay un sensor de orientacin en los dispositivos. Para conocer esta orientacin, el dispositivo
combina los datos de los sensores acelermetro y del campo magntico.
Para obtener la orientacin del dispositivo puedes utilizar el mtodo android.view.Display.
getRotation() de Android.
Para acceder a los sensores de un dispositivo Android proporciona el gestor SensorManager. Para obtener el listado completo de los sensores del dispositivo, debemos usar su
mtodo getSensorList(tipo) que devuelve una lista de tipo Sensor. En el parmetro tipo
hay que marcar el tipo de sensores que queremos obtener mediante uno de los tipos del listado
anterior o Sensor.TYPE_ALL para indicar todos los sensores.

Atencin: para desarrollar aplicaciones que utilizan sensores, es necesario disponer de un dispositivo fsico, ya que el emulador de Android no permite depurar
todos los tipos de sensores correctamente.

Sin embargo, en el apartado siguiente veremos cmo instalar un software de emulacin que
permitir realizar pruebas sobre algunos sensores simulados del emulador.

2.1.1 Cmo se utilizan los Sensores


Como los sensores estn muy relacionados con el mundo real, la mejor forma de entender su
funcionamiento es mediante un ejemplo prctico.
Vamos a comenzar con el Ejemplo 1 de este Unidad donde vamos a obtener todos los
sensores de un dispositivo y mostraremos los datos medidos por stos.
Si abrimos su layout XML activity_main.xml vemos el siguiente contenido tpico del layout
de una Actividad con etiquetas:
<?xml version=1.0 encoding=utf-8?>

U3 Sensores y dispositivos de Android

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android

android:orientation=vertical android:layout_width=fill_parent

android:layout_height=fill_parent>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=LISTADO DE SENSORES
android:textAppearance=?android:attr/textAppearanceLarge
android:textColor=@color/color_fuente
android:typeface=monospace />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<TextView
android:id=@+id/sensores
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=
android:textColor=@color/color_fuente />
<TextView

android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=DATOS
android:textStyle=bold
android:layout_gravity=center_horizontal
android:typeface=monospace
android:textColor=@color/color_fuente />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<ScrollView
android:id=@+id/scrollView1
android:layout_width=match_parent
android:layout_height=wrap_content
android:fadeScrollbars=false >

<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TextView
android:id=@+id/datos
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Datos />
</LinearLayout>

235

Aula Mentor

</ScrollView>
</LinearLayout>

En el fichero anterior puedes ver que hemos definido la Vista ScrollView con el atributo
android:fadeScrollbars=false para que no se oculte la barra de desplazamiento vertical.
Si ahora abres el archivo Java que define la Actividad, vers el siguiente contenido:
public class MainActivity extends Activity implements
SensorEventListener {
private TextView sensores, datos;

236

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensores = (TextView) findViewById(R.id.sensores);
datos = (TextView) findViewById(R.id.datos);
// Conectamos con el gestor de sensores
SensorManager sensorManager =
(SensorManager)getSystemService(SENSOR_SERVICE);
// Obtenemos el listado con todos los sensores
List<Sensor> listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ALL);
// Mostramos en pantalla el listado de sensores
for(Sensor sensor: listaSensores) {
log(sensor.getName(), sensores);
} // end for

// A continuacin, vamos a buscar los sensores de varios tipos en
// concreto y le asignamos al primero que encontremos a un
// listener para leer sus medidas
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ROTATION_VECTOR);
if (!listaSensores.isEmpty()) {
Sensor orientationSensor = listaSensores.get(0);
// Registramos el lister para este sensor e indicamos que

// se realice una medida cada SENSOR_DELAY_UI=1000 miliseg.
sensorManager.registerListener(this, orientationSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (!listaSensores.isEmpty()) {
Sensor acelerometerSensor = listaSensores.get(0);
sensorManager.registerListener(this, acelerometerSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
if (!listaSensores.isEmpty()) {
Sensor magneticSensor = listaSensores.get(0);
sensorManager.registerListener(this, magneticSensor,

U3 Sensores y dispositivos de Android

SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_AMBIENT_TEMPERATURE);
if (!listaSensores.isEmpty()) {
Sensor temperatureSensor = listaSensores.get(0);
sensorManager.registerListener(this, temperatureSensor,
SensorManager.SENSOR_DELAY_UI);
}
} // end onCreate
// Mtodo que aade a la vista un texto
private void log(String string, TextView vista) {
vista.append(string + \n);
}

// Invocada cuando cambia se exactitud del sensor.


// Por ejemplo, cuando la deteccin del GPS pasa de hacerse
// de la red de telefona mvil al sensor GPS del dispositivo (ms
// preciso)
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {

}

// Evento que se lanza cada vez que se modifican los datos del
// sensor. Es decir, es una nueva medida.
@Override
public void onSensorChanged(SensorEvent evento) {

String sensorStr=Desconocido;
// Como estamos monitorizando todos los sensores, es necesario
// controlar el acceso a las Vistas internas de la Actividad.

// Es decir, cada sensor puede provocar que un thread principal
// invoque a la vez este evento. Si sincronizamos esas
// sentencias, entonces indicamos mediante Java que se deben
// ejecutar todas ellas antes de que se pueda volver a ejecutar
// de nuevo este bloque de cdigo.
synchronized (this) {
switch(evento.sensor.getType()) {
case Sensor.TYPE_ROTATION_VECTOR:
sensorStr=Rotacin;
break;
case Sensor.TYPE_ACCELEROMETER:
sensorStr=Acelermetro;
break;
case Sensor.TYPE_MAGNETIC_FIELD:

sensorStr=Campo magntico;
break;
case Sensor.TYPE_AMBIENT_TEMPERATURE:
sensorStr=Temperatura;
break;
default:
} // end case

for (int i=0 ; i<evento.values.length ; i++) {
log(sensorStr + + i + : + evento.values[i], datos);
} // end for
} // end synchronized

237

Aula Mentor

} // end onSensorChanged

El cdigo anterior comienza en el evento onCreate() indicando el Layout de la Actividad y


obteniendo referencias a los TextView que muestran el listado de sensores y los datos medidos
por stos.
A continuacin, vamos a utilizar el mtodo getSystemService() para solicitar al sistema operativo el acceso al servicio especfico de sensores SENSOR_SERVICE. Lo haremos a
travs del objeto sensorManager. En primer lugar, llamamos al mtodo getSensorList() de
este objeto para obtener un listado de sensores del tipo Sensor. Despus, recorremos todos los
elementos de esta lista e invocamos su mtodo getName() para mostrar el nombre de sensor.
Como hemos visto, la clase Sensor permite manipular sensores. A continuacin, se listan los
mtodos pblicos de esta clase Sensor:

238

public float getMaximumRange()

Rango mximo en las unidades del sensor

public String getName()

Nombre del sensor

public float getPower()

Potencia (mA) usada por el sensor mientras


est en uso

public float getResolution()

Resolucin de las unidades del sensor

public int getType()

Tipo genrico del sensor

Public String getVendor()

Fabricante del sensor

public int getVersion()

Versin del sensor

La clase SensorManager dispone, adems, de los tres mtodos: getInclination(), getOrientation() y getRotationMatrix() que se usan para calcular transformaciones de
coordenadas.
Veamos ahora cmo obtener medidas de cada uno de los sensores. Para ello, empezamos consultando si disponemos de determinados tipos de sensores indicando al sistema que
los liste mediante el mtodo getSensorList(). Si esta lista contiene datos, vamos a utilizar
nicamente el primer sensor (el elemento 0).
Para que la Actividad reciba las medidas de un sensor, es necesario registrar un listener mediante el mtodo registerListener(). ste toma como primer parmetro un objeto que implementa la interface SensorEventListener (indicamos this porque la clase
MainActivity implementa esta interfaz para recoger los eventos de sensores). El segundo
parmetro es el tipo de sensor que estamos registrando. Finalmente, el tercer parmetro indica
al sistema con qu frecuencia mxima queremos recibir las medidas del sensor. Podemos establecer cuatro posibles valores (de menor a mayor frecuencia): SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME y SENSOR_DELAY_FASTEST. Este ltimo parmetro sirve
para que el sistema conozca cunta frecuencia de actualizacin de las medidas de los sensores
necesita nuestra aplicacin, pero no garantiza una frecuencia concreta, aunque el sistema s
debe proporcionar una medida en ese tiempo mximo.
Para que nuestra clase implemente la interface que hemos comentado, la clase principal
incluye el cdigo:
implements SensorEventListener

U3 Sensores y dispositivos de Android

Todo programador en Java sabe que cuando usamos una interface estamos obligados a
implementar todos sus mtodos. En este caso, la interfaz SensorEventListener que se usa
para recibir las medidas de los sensores tiene dos mtodos que debemos implementar:
- onAccuracyChanged: invocada cuando cambia la exactitud de la medida del sensor. Por
ejemplo, cuando el modo la deteccin de localizacin geogrfica cambia desde la red de
telefona mvil al sensor GPS del dispositivo (ms preciso).
- onSensorChanged: se lanza cuando un sensor indica su medida. Android indica en su parmetro de la clase SensorEvent la informacin del sensor que lo ha causado y podemos
leer los datos medidos por ste.
Los campos que contiene la clase SensorEvent son los siguientes:
accuracy

Entero que indica la exactitud del evento.

sensor

Sensor que ha generado el evento.

timestamp

Tiempo en nanosegundos en el que ha ocurrido el evento.

values

Matriz de tipo float donde se incluyen los datos de las


medidas del sensor. Dependiendo del tipo de sensor
esta matriz contiene diferente cantidad de datos.
Es recomendable que le eches un vistazo a la documentacin
oficial de Google sobre la clase SensorEvent para ver qu
valores puede tomar.

2.1.2 Sistema de Coordenadas de un evento de sensor


El sistema de coordenadas de Android se define en relacin a la pantalla del dispositivo en su
orientacin vertical (ver figura de abajo). Estos ejes no se mueven cuando cambia la orientacin
de la pantalla del dispositivo.
El eje X es horizontal y apunta hacia la derecha, el eje Y es vertical y apunta hacia arriba
y el eje Z es perpendicular al frontal de la pantalla del dispositivo. En este sistema de coordenadas, para indicar una posicin detrs de la pantalla debemos sealar valores negativos en Z.

239

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 1 (Listado Sensores) de la


Unidad 3. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado Sensores.

Si ejecutas en Eclipse ADT este Ejemplo 1, vers que se muestra la siguiente aplicacin:

240

3. Simulador de sensores de Android


Por defecto, el emulador de dispositivos virtuales (AVD) del SDK de Android con Eclipse ADT
no puede utilizar sensores en el sentido ms general para depurar aplicaciones.
En esta apartado vamos a utilizar el simulador SensorSimulator de sensores para poder depurar una aplicacin Android que utilice sensores en su funcionalidad. Tendremos que
modificar un dispositivo virtual de Android e incluir una librera en el desarrollo del proyecto
Android.
Para ello, vamos a descargar e instalar el simulador SensorSimulator de Openintens.
SensorSimulator simula en tiempo real datos del sensor mediante el ratn. Actualmente, el
emulador incluye los siguientes sensores: acelermetro, brjula, orientacin, temperatura,
luz, proximidad, presin, gravedad, aceleracin lineal, rotacin y giroscopio. Tambin
permite simular el estado de carga de la batera y la posicin del GPS mediante una conexin
telnet.
Adems, es posible guardar una secuencia de estados de un dispositivo y ejecutarla de
forma continua en el simulador. Es decir, podemos grabar el cambio de estado de los sensores

U3 Sensores y dispositivos de Android

y ejecutar este cambio para ver cmo se comporta nuestra aplicacin.

3.1 Instalacin del Simulador de Sensores


Descargamos la ltima versin del simulador desde este enlace. La ltima versin a fecha de
redaccin de este documento es la SensorSimulator 2.0-rc1.
- Descomprimimos el archivo descargado en una carpeta. Recomendamos utilizar el directorio
donde se encuentre el android-sdk.
- Iniciamos el Simulador haciendo clic sobre el archivo sensorsimulator-2.0-rc1.jar del
subdirectorio bin o ejecutando la orden en la ventana de comandos:
java -jar sensorsimulator-2.0-rc1.jar/bin/sensorsimulator-2.0-rc1.jar

A continuacin, veremos que aparece esta ventana:

241

La interfaz de usuario del simulador est compuesta por:


- Men de botones (parte superior): Sensor Simulator, Telnet, Opciones y Ayuda.
- Representacin de sensores (lado izquierdo): contiene una respresentacin visual del dispositivo, un listado con los sensores habilitados con los datos medidos en ese instante e
informacin de las IPs de los posibles servidores (ms adelante veremos cmo funcionan).
- Configuracin de sensores con cuatro pestaas en el centro:
Sensors Tab: permite marcar los sensores activos para la simulacin.
Scenario Simulator: permite grabar y ejecutar secuencias de cambios de los datos de
los sensores para una simulacin posterior.
Pulsando el boton

podemos aadir un nuevo paso en la secuencia que deseamos

ejecutar y modificar los nuevos valores de los sensores con el botn


.
Fjate en un ejemplo del tipo de cambios en los sensores que podemos programar:

Aula Mentor

242

Quick Settings: podemos establecer las propiedades iniciales de los sensores bsicos.
Sensors Parameters: podemos establecer las propiedades iniciales del resto de sensores:

U3 Sensores y dispositivos de Android

El botn derecho que aparece en la ventana principal nos permite acceder a las siguientes
opciones adicionales:

- Update sensors: establece el tiempo del intervalo de actualizacin de los sensores.


- Refresh after: determina cuntas veces debe refrescar el emulador los datos en cada iteraccin.
- Save: establece el tiempo de cambio entre dos estados dentro de un escenario de simulacin.
- Play: marca el tiempo entre los estados intermedios cuando se cambia de un valor a otro
en un sensor.
Generalmente, no es necesario modificar ninguno de estos parmetros ya que los valores por
defecto funcionan correctamente.

3.2 Cmo utilizar el Simulador de Sensores


Lo primero que debemos hacer antes de poder simular datos de sensores en un dispositivo
virtual de Android es instalar la aplicacin del SensorSimulator en l.
Para instalar la aplicacin en el AVD. primero debemos ejecutar desde Eclipse ADT el
dispositivo virtual del curso.
Una vez haya arrancado correctamente, usamos la utilidad adb que se encuentra en el
directorio android-sdk/platform-tools/adb. Debemos abrir una ventana de comandos en
este directorio y obtener los dispositivos conectados ejecutando el comando:
C:\cursosMentor\adt\sdk \platform-tools>adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached
emulator-5554
device
C:\cursosMentor\adt\sdk\platform-tools>

A continuacin, instalamos en el AVD anterior la aplicacin SensorSimulatorSettings-2.0-

243

Aula Mentor

rc1.apk, que servir de puente con el simulador de sensores:


adb -s <device> install sensorsimulator-2.0-rc1/bin/
SensorSimulatorSettings-2.0-rc1.apk

Debemos reemplazar la etiqueta <device> por el dispositivo mostrado en el paso anterior:


C:\cursosMentor\...\platform-tools>adb -s emulator-5554 install ../
sensorsimulator-2.0-rc1/bin/SensorSimulatorSettings-2.0-rc1.apk
699 KB/s (49681 bytes in 0.069s)
pkg: /data/local/tmp/SensorSimulatorSettings-2.0-rc1.apk

Success

Si no instalamos esta aplicacin en el dispositivo no podremos usar el simulador


de sensores en un AVD ya que su cometido es servir de conector con el SensorSimulator.

Finalmente, en el AVD buscamos la aplicacin Sensor Simulator Settings instalada en el paso


anterior y la ejecutamos:

244

U3 Sensores y dispositivos de Android

Debemos dejar los campos IP y Socket como estn. Esta IP debe coincidir con la IP que aparece
en el lado izquierdo (parte de abajo) en el SensorSimulator:

Cambiamos en la aplicacin del AVD a la pestaa Testing y hacemos clic en el botn Connect:

245

Aula Mentor

En la ventana anterior podemos ver los datos de los sensores que tenemos habilitados (color
azul ms oscuro) en el Simulador de sensores:

246
Si movemos la imagen del telfono arrastrando el ratn sobre sta en la aplicacin
SensorSimulator, vemos cmo se cambian los valores en el emulador.

U3 Sensores y dispositivos de Android

Podemos balancear (yaw), girar (roll) y mover (move) el dispositivo arrastrando el ratn sobre
el esquema del dispositivo virtual.
La aplicacin Sensor Simulator Settings se usa de puente entre el AVD y el SensorSimulator. De esta forma, se comunica la informacin del cambio de los valores de los sensores
que luego usar nuestra aplicacin Android.

Para aadir o quitar sensores en el dispositivo debemos usar sucesivamente los


botones Disconnect y Connect de la aplicacin Sensor Simulator Settings.

3.2.1 Ejemplo de desarrollo de aplicacin con el Simulador de Sensores


Una vez hemos instalado y comprobado que funciona el simulador de sensores, veamos cmo
podemos utilizarlo en el desarrollo de una aplicacin.
Vamos a partir del Ejemplo 1 de esta Unidad y modificaremos su cdigo fuente para
que sea compatible con este simulador. Lo primero que debemos hacer es crear el directorio libs dentro del directorio raz del proyecto y copiar dentro el archivo sensorsimulator-2.0-rc1/lib/sensorsimulator-lib-x.x.x.jar. Despus, desde Eclipse ADT hacemos clic con el botn derecho del ratn sobre este archivo y seleccionamos la opcin Build
Path y Add to Build Path:
247

Una vez hecho esto, debemos abrir las propiedades del proyecto y en la opciones Java Build
Path en la pestaa Order and Export hay que subir la librera a la primera posicin (para

que las clases de esta librera tengan prioridad frente a las de Android) y marcarla como exportable, tal y como vemos en la siguiente pantalla:

Aula Mentor

248

A continuacin, vamos a modificar el cdigo fuente del Ejemplo 1 de esta Unidad copindolo
previamente como Ejemplo 2. A continuacin, abrimos el Ejemplo 2 para ver cmo queda el
cdigo fuente una vez modificado. Empezamos comentando los imports originales y agregando los de las clases del paquete org.openintents.sensorsimulator:
/*import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;*/
import
import
import
import

org.openintents.sensorsimulator.hardware.Sensor;
org.openintents.sensorsimulator.hardware.SensorEvent;
org.openintents.sensorsimulator.hardware.SensorEventListener;
org.openintents.sensorsimulator.hardware.SensorManagerSimulator;

En el mtodo onCreate() de la Actividad reemplazamos el cdigo:


sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

por la sentencia:
sensorManager = SensorManagerSimulator.getSystemService(this, SENSOR_
SERVICE);

Adems, incluimos el mtodo para conectar la aplicacin al simulador de sensores:


sensorManager.connectSimulator();
Cuando ejecutemos la aplicacin en el AVD, veremos en la Vista Logcat de Eclipse ADT los siguientes

mensajes debidos a la ejecucin de la sentencia anterior:

U3 Sensores y dispositivos de Android

06-05
06-05
06-05
06-05
06-05
06-05
06-05
06-05

12:35:38.516:
12:35:38.806:
12:35:38.846:
12:35:38.846:
12:35:38.856:
12:35:38.946:
12:35:38.957:
12:35:38.986:

I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
D/Hardware(2964):
D/Hardware(2964):
D/Hardware(2964):

Starting connection...
Connecting to 10.0.2.2 : 8010
Read line...
Received: SensorSimulator
Connected
Sensor pressure not supported
Sensor barcode reader not supported
Sensor gyroscope not supported

As, podemos verificar que nuestra aplicacin se ha conectado correctamente al Simulador de


sensores.
Normalmente, debemos hacer el registro y desregistro de los listeners en los eventos onResume() y onStop() respectivamente. Para hacerlo, hemos escrito:
@Override
protected void onResume() {
super.onResume();

// Registramos todos los listener de los sensores
sensorManager.registerListener(eventListenerAcelerometro,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerAcceleracionLineal,
sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerGravedad,
sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerCampoMagnetico,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerOrientacion,
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerTemperatura,
sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerLuz,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerPresion,
sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerBarcode,
sensorManager.getDefaultSensor(Sensor.TYPE_BARCODE_READER),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerRotacion,
sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerGiroscopio,
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
}

249

Aula Mentor

@Override
protected void onStop() {
// Desregistramos todos los listeners de los sensores
sensorManager.unregisterListener(eventListenerAcelerometro);
sensorManager.unregisterListener(eventListenerAcceleracionLineal);
sensorManager.unregisterListener(eventListenerGravedad);
sensorManager.unregisterListener(eventListenerCampoMagnetico);
sensorManager.unregisterListener(eventListenerOrientacion);
sensorManager.unregisterListener(eventListenerTemperatura);
sensorManager.unregisterListener(eventListenerLuz);
sensorManager.unregisterListener(eventListenerPresion);
sensorManager.unregisterListener(eventListenerRotacion);
sensorManager.unregisterListener(eventListenerGiroscopio);
sensorManager.unregisterListener(eventListenerBarcode);
super.onStop();
}

Finalmente, implementamos los SensorEventListener, uno para cada tipo de sensor:

250

// Puedes observar que debemos implementar los mismos eventos:


// onSensorChanged y onAccuracyChanged
eventListenerAcelerometro = new SensorEventListener() {
@Override

public void onSensorChanged(SensorEvent event) {
// Obtenemos los valores devueltos por el evento y los

// mostramos en la Vista correspondiente

float[] values = event.values;

textViewAcelerometro.setText(Accelermetro: + values[0]

+ , + values[1] + , + values[2]);
}
@Override

public void onAccuracyChanged(Sensor sensor, int accuracy) {}
};

Adems, para que esta aplicacin pueda comunicarse con el Simulador de sensores, hay que
agregar al archivo Manifest el siguiente permiso:
<uses-permission android:name=android.permission.INTERNET/>

El permiso de INTERNET es necesario para conectar la aplicacin al SensorSimulator.


La clase SensorManagerSimulator deriva de la clase base SensorManager de Android e implementa casi sus mismas funciones y listeners (clase SensorEventListener).

Una vez hayamos terminado y depurado la aplicacin hay que volver a modificar
los imports y usar la clases de sensores nativas de Android, ya que, si no lo hacemos, la aplicacin fallar en un dispositivo real.

U3 Sensores y dispositivos de Android

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Listado Sensores- SensorSimulator) de la Unidad 3. Estudia el cdigo fuente y ejectalo para mostrar en
el AVD el resultado del programa anterior, en el que hemos utilizado un Simulador
de Sensores.

Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin:

251
En esta Unidad puedes encontrar el vdeo Cmo utilizar SensorSimulator, que muestra de manera visual los pasos seguidos en
las explicaciones anteriores. Te recomendamos que lo muestres y
leas las explicaciones que aparecen.

3.2.2 Grabacin de escenario de simulacin con un dispositivo real


Como hemos comentado anteriormente, es posible utilizar un dispositivo real para guardar los
datos de sus sensores y, despus, poder hacer una simulacin con ellos. As, el programador
puede automatizar esta tarea y repetirla tantas veces como sea necesaria cuando depuremos
una aplicacin de Android.
Para hacerlo, debes hacer lo siguiente:
- Instalar en el dispositivo real la aplicacin SensorRecordFromDevice-x.x-x.apk, que
puedes encontrar en la carpeta bin del SensorSimulator y que guardar la informacin
de los sensores:
adb -s <device> install sensorsimulator-2.0-rc1/bin/SensorRecordFromDevice2.0-rc1

Debemos reemplazar la etiqueta <device> por el dispositivo real.


- Ejecutar en el dispositivo real la aplicacin anteriormente instalada, seleccionando los sensores que deseamos monitorizar y pulsando en el botn Record:

Aula Mentor

252

Debes asegurarte de que el dispositivo Android se encuentra en la misma red que la aplicacin Java de tu ordenador (SensorSimulator). Lo ms sencillo es utilizar una red wifi. La IP
que escribas debe corresponder a la IP de tu ordenador. A veces, es conveniente tambin
deshabilitar el firewall del ordenador. Cuando hayamos, acabado debemos pulsar el botn
Stop.
- En la aplicacin externa del SensorSimulator seleccionar la pestaa Scenario Simulator.
En esta ventana veremos cmo se van cargando los valores de los sensores seleccionados:

U3 Sensores y dispositivos de Android

Animamos al alumno a que pruebe a utilizar su propio dispositivo para grabar escenarios de
prueba de simulaciones.

4. Dispositivos de Android
En este apartado vamos a mostrar los fundamentos de utilizacin de los dispositivos o conectores
ms importantes de Android: el mdulo WIFI, el mdulo Bluetooth, la Cmara de fotos y el
mdulo GPS.
Mediante un ejemplo prctico expondremos, para cada tipo de mdulo, una explicacin
detallada de las funciones propias del SDK. As, podrs realizar cualquier aplicacin que haga
uso de ellos.

4.1 Mdulo WIFI


El SDK de Android dispone de clases que permiten hacer uso del mdulo WIFI, siempre y
cuando el dispositivo disponga de este mdulo.
Wi-Fi es una abreviatura de Wireless Fidelity (Fidelidad inalmbrica) y es un mecanismo
de conexin de dispositivos electrnicos de forma inalmbrica.
La clase ms importante de la API WIFI de Android se denomina WifiManager y permite al
desarrollador realizar todo tipo de operaciones sobre este mdulo: buscar redes WIFI, obtener
informacin de dichas redes y conectarse a ellas.
Podemos realizar una bsqueda de redes con la clase WifiManager a travs de su mtodo
startScan(), que devuelve una lista de objetos de la clase ScanResult que representan cada
una de las redes WIFI encontradas. La informacin que podemos obtener de estas redes WIFI
encontradas es la que se muestra a continuacin:
- SSID: el SSID (Service Set IDentifier) denomina el nombre de una red inalmbrica que aparece en tu dispositivo cuando quieres conectarte a una red WIFI. Mediante la constante SSID
de la clase ScanResult podemos acceder a este nombre.
- Seguridad: la seguridad que implementa una red inalmbrica WIFI puede ser: WEP (Wired
Equivalent Privacy), WPA (WIFI Protected Access) o WPA2. Podemos acceder a ella a travs
del segundo parmetro del resultado de la bsqueda de redes, tal y como veremos en el
ejemplo que se explica a continuacin.
- Potencia de seal: la potencia de la seal que emite la red WIFI se mide en dB (decibelios).
Para obtener este valor debemos acceder el atributo pblico level de la clase ScanResult.
- BSSID: el BSSID (Basic Service Set Identifier) de una red WIFI es un nombre de identificacin nico de todos los paquetes que se trasmiten por dicha red. Este identificador BSSID es
la direccin fsica MAC (Media Access Control) del router WIFI de la red. Para obtener este
valor debemos acceder el atributo pblico BSSID de la clase ScanResult.
- Canal de frecuencia: el canal de frecuencia indica en qu banda emite la red WIFI. Para
obtener este valor debemos acceder el atributo pblico frecuency de la clase ScanResult.
En el Ejemplo 3 de este Unidad vamos a mostrar cmo desarrollar una sencilla aplicacin en
Android que utiliza la clase WifiManager para buscar las redes WIFI y mostrar su nombre en un
listado. Adems, si el usuario pulsa sobre una red de la lista, aparece la descripcin detallada de
esta red ofrecindole sus datos: BSSID de la red, el canal de frecuencia, la potencia de la seal
emitida que le llega al dispositivo Android y el tipo de seguridad que implementa.
Es recomendable abrir el Ejemplo 3 de esta Unidad para entender la explicacin siguiente.

253

Aula Mentor

Para empezar hemos definido la clase Red que sirve de modelo de datos de una red WIFI. En
un objeto de este tipo guardamos los datos de una red WIFI: el SSID, la seguridad, la potencia
de seal, el BSSID y el canal de frecuencia de cada una. Veamos el sencillo aspecto de la clase:
// Clase que define el modelo de datos que almacena la informacin
// de una red WIFI
public class Red {




private
private
private
private
private

String
String
String
String
String

ssid;
seguridad;
bssid;
frecuencia;
potencia;

public Red(String ssid, String seg, String bssid, String frec,


String potencia){
this.ssid=ssid;
this.seguridad=seg;
this.bssid=bssid;
this.frecuencia=frec;
this.potencia=potencia;
}

public String getSSID(){
return this.ssid;
}

254


public String getSeguridad(){
return this.seguridad;
}

public String getBSSID(){
return this.bssid;
}

public String getFrecuencia(){
return this.frecuencia;
}

public String getPotencia(){
return this.potencia;
}
}

Como puedes observar en el cdigo anterior, esta clase contiene todos los atributos y mtodos
necesarios para obtener la informacin mencionada de una red inalmbrica.
La interfaz de usuario de la aplicacin se compone de dos Actividades: la Actividad principal
contiene un botn que, al pulsarlo, realiza una bsqueda de redes WIFI y muestra el resultado
en un listado ListView en la parte inferior de esta Actividad. Si el usuario hace clic en una red
del listado, puede visualizar la informacin detallada de sta al abrir una segunda Actividad.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android

U3 Sensores y dispositivos de Android

android:orientation=vertical

android:layout_width=fill_parent

android:layout_height= fill_parent

android:background=#FFFFFF>

<Button android:id=@+id/boton

android:text=Buscar redes WIFI...

android:layout_width=260dip

android:layout_height=60dip

android:textSize=20dp

android:layout_marginTop=6dip

android:layout_gravity=center_horizontal/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView

android:id=@+id/listView

android:layout_height=fill_parent

android:layout_width=fill_parent

android:background=#FFFFFF/>
</LinearLayout>

Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
MainActivity.java:
public class MainActivity extends Activity implements OnClickListener,
OnItemClickListener {
// Matriz del tipo Red donde vamos a guardar las redes detectadas
public static ArrayList<Red> redes = new ArrayList<Red>();
private Button boton;
// Lista de redes y su adaptador
public static ListView listaredes;
public static AdapterElements adaptador;
// En la creacin de la Actividad buscamos el ListView
// y le asignamos su adaptador correspondiente
@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);

this.boton = (Button)findViewById(R. id.boton);
this.boton.setOnClickListener(this);

adaptador = new AdapterElements(this);

listaredes = (ListView)findViewById (R.id.listView) ;
listaredes.setAdapter(adaptador);
listaredes.setOnItemClickListener(this);
}
// Si el usuario hace clic en el botn entonces
// arrancamos una tarea asncrona para buscar las
// redes WIFI
@Override
public void onClick(View v) {

switch (v.getId()){

case (R.id.boton):

255

Aula Mentor

@SuppressWarnings(unused)

AsyncTask<?, ?, ?> task = new ProgressTask(this).execute();
break;

}
}

256

// Evento onClick del listado


@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int index,
long arg3) {

// Obtenemos la informacin de la red

Red red = (Red)MainActivity.redes.get(index);

String ssid = red.getSSID();

String bssid = red.getBSSID();

String seguridad = red.getSeguridad();

String frecuencia = red.getFrecuencia();

String potencia = red.getPotencia();

// Creamos un Intent nuevo en el que pasamos como parmetro

// la informacin de la red y arranca la Actividad InfoRed

Intent infoRedIntent = new Intent(this,InfoRed.class);

infoRedIntent.putExtra(SSID, ssid);

infoRedIntent.putExtra(SEGURIDAD, seguridad);

infoRedIntent.putExtra(BSSID, bssid);

infoRedIntent.putExtra(FRECUENCIA, frecuencia);

infoRedIntent.putExtra(POTENCIA, potencia);
startActivity(infoRedIntent);
}
// Clase que define el adaptador del listado con los mtodos
// habituales
class AdapterElements extends ArrayAdapter<Red> {

Activity context;

public AdapterElements(Activity context) {

super(context, R.layout.elementoitems, MainActivity.redes);

this.context = context;
}

public View getView(int position, View convertView, ViewGroup


parent) {

LayoutInflater inflater = context.getLayoutInflater();

View item = inflater.inflate(R. layout.elementoitems, null);

TextView lblTitle = (TextView)item.findViewById(R.id.ssidstr);

lblTitle.setText (MainActivity.redes.get(position).getSSID());

return(item);

}
}
}

Como se puede observar en el cdigo anterior, el evento onClick del botn invoca a la clase
AsyncTask. Como sabes, esta clase se encarga de realizar tareas asncronas en Android Ya se

estudi en el curso de Iniciacin de Mentor.


Hemos empleado una tarea asncrona porque el buscador de redes WIFI puede tardar
en responder un tiempo y no es recomendable bloquear el hilo principal de ejecucin de la
aplicacin. En esta Actividad hemos definido el atributo redes con visibilidad static de forma
que pueda ser accedido por cualquier Actividad de la aplicacin. Este atributo es un ArrayList

U3 Sensores y dispositivos de Android

de objetos de clase Red que permite almacenar todas las redes inalmbricas encontradas en el
escner de redes WIFI.
A continuacin, se muestra cmo se implementa el escner de redes WIFI mediante la clase
ProgressTask:
// Clase que inicia una tarea asncrona para buscar las redes WIFI
public class ProgressTask extends AsyncTask<String, Void, Boolean> {
// Dilogo de progreso
private ProgressDialog dialog;
private Context context;
// Gestor de Android de redes WIFI
private WifiManager manWifi;
// Lista donde guardamos las redes encontradas
private List<ScanResult> wifiList;
// Constructor de la clase
public ProgressTask(Context c){

this.context = c;

dialog = new ProgressDialog(context);
}
// Antes de ejecutar la tarea verificamos si la interfaz WIFI est
// activa
protected void onPreExecute() {

// Buscamos el servicio WIFI

this.manWifi = (WifiManager)this.context.getSystemService
(Context.WIFI_SERVICE);

// Si no esta activo mostramos un mensaje y no seguimos adelante

if (!this.manWifi.isWifiEnabled()){

Toast.makeText(context, ERROR: el dispositivo no dispone
de interfaz WIFI o sta no se encuentra activada.,
Toast.LENGTH_LONG).show();

} else

// Si no, mostramos el dilogo de progreso
{

this.dialog.setMessage(Escaneando redes WiFi...);
this.dialog.show();
}
}
// Al acabar ocultamos el dilogo de progreso y actualizamos el
// adaptador de la Actividad principal
@Override
protected void onPostExecute (

final Boolean success) {

if (dialog.isShowing()) {
dialog.dismiss();
}

if (success) {
MainActivity.adaptador.notifyDataSetChanged();
}

}
// Sentencias que se ejecutan en segundo plano
protected Boolean doInBackground (final String... args) {

257

Aula Mentor

// Si no est activa la interfaz WIFI hemos acabado


// incorrectamente

if (!this.manWifi.isWifiEnabled()) return false;

// Buscamos las redes WIFI

this.manWifi.startScan();

// Obtenemos los resultado de la bsqueda

this.wifiList = this.manWifi.getScanResults();

// Limpiamos la matriz de resultados de la Actividad principal
MainActivity.redes.clear();

// Recorremos todos los resultados de la bsqueda de redes y los

// vamos pasando a la matriz del tipo Red

for(int i = 0; i < wifiList. size () ; i++) {

ScanResult scan_res = wifiList.get(i) ;

String SSID = scan_res.SSID;

String BSSID = scan_res.BSSID;

String frec = Integer.valueOf(scan_res.frequency).toString();

String pot = Integer.valueOf(scan_res.level).toString();

String seg = scan_res.toString().split(,)[1].split(:)[1];

MainActivity.redes.add(new Red(SSID, seg, BSSID, frec, pot));
}

// Indicamos que la tarea ha acabado correctamente

return true;
}
} // end clase

258

En el cdigo anterior se pueden observar los tres estados por los que pasa la clase ProgressTask:
onPreExecute, doInBackground y onPostExecute. La bsqueda de redes se realiza en
segundo plano mediante el mtodo dolnBackground y, mientras dura la ejecucin de dicho
escner, se muestra al usuario una alerta del tipo Dialog que le informa de que se est
realizando una bsqueda de redes WIFI.
La bsqueda de redes se realiza mediante la clase WifiManager usando su mtodo
startScan(). Posteriormente, utilizamos el mtodo getScanResult() para obtener una lista de objetos del tipo ScanResult que contiene la informacin de cada red WIFI. Por ltimo,
recorremos dicha lista procesando cada objeto ScanResult y obteniendo los datos necesarios
para el presente ejemplo.
Una vez terminada la lgica del mtodo doInBackground, se finaliza la ejecucin de la
clase ProgressTask mediante el mtodo onPostExecute() liberando el Dialog y refrescando
los datos del adaptador de la Actividad principal.
Esta clase principal implementa la interface OnItemClickListener que indica las
sentencias que se deben ejecutar cuando el usuario hace clic en una opcin de la lista.
En este bloque de cdigo obtenemos la informacin de la red WIFI pulsada y la enviamos a otra
Actividad que se encarga de mostrar este detalle. Como sabes, el envo de datos o parmetros
entre Actividades se realiza a travs del mtodo putExtra() de la clase Intent.
El diseo layout de la Actividad del fichero activity_info_red.xml muestra los detalles de la
red WIFI seleccionada. Es el siguiente:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:background=#FFFFFF
android:gravity=left
android:orientation=vertical
android:padding=10dp >

U3 Sensores y dispositivos de Android

<TextView
android:id=@+id/SSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=25sp

android:textColor=#000000/>
<TextView
android:id=@+id/SeguridadTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Seguridad:

android:textColor=#000000/>
<TextView
android:id=@+id/BSSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=BSSID:

android:textColor=#000000/>
<TextView
android:id=@+id/FrecuenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Frecuencia:

android:textColor=#000000/>
<TextView
android:id=@+id/PotenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Potencia:

android:textColor=#000000/>
</LinearLayout>

El diseo de esta Actividad consta de cinco Vistas del tipo TextView que se encargan de
mostrar la informacin enviada por la Actividad principal de la aplicacin. Veamos la lgica de
esta Actividad secundaria llamada InfoRed:
public class InfoRed extends Activity {
private TextView tv_ssid;
private TextView tv_seguridad;
private TextView tv_bssid;
private TextView tv_frecuencia;
private TextView tv_potencia;
// OnCreate de la Actividad
public void onCreate(Bundle savedlnstanceState){
super.onCreate(savedlnstanceState);

// Asignamos el layout y buscamos sus Vistas
setContentView(R.layout.activity_info_red);

this.tv_ssid =(TextView)findViewById(R.id.SSIDTV);

this.tv_seguridad = (TextView)findViewById(R.id.SeguridadTV) ;

this.tv_bssid = (TextView)findViewById(R.id.BSSIDTV) ;

259

Aula Mentor

this.tv_frecuencia = (TextView)findViewById(R.id.FrecuenciaTV) ;
this.tv_potencia = (TextView)findViewById(R.id.PotenciaTV) ;

// Obtenemos los parmetros del Intent y los mostramos en las


// Vistas


Bundle extras = getIntent().getExtras();

if (extras != null){

String ssid = extras.getString(SSID);

String security = extras.getString(SEGURIDAD);

String bssid = extras.getString(BSSID);

String frec = extras.getString(FRECUENCIA);

String power = extras.getString(POTENCIA);
this.tv_ssid.append(ssid);
this.tv_seguridad.append(security);
this.tv_bssid.append(bssid);
this.tv_frecuencia.append(frec);
this.tv_potencia.append(power);
}
} // end OnCreate
}

Como puedes observar en el cdigo anterior, la informacin enviada es recogida mediante el


mtodo getExtras() de la clase Intent. La informacin se recoge utilizando el valor de la
clave y el tipo de dato enviado. Una vez obtenida esta informacin, se aade esta informacin
a la Vista correspondiente del tipo TextView mediante el mtodo append().
260

Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso a la
interfaz WIFI del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las
siguientes etiquetas:
<uses-permission android:name=android.permission.ACCESS_WIFI_STATE/>
<uses-permission android:name=android.permission.CHANGE_WIFI_STATE/>

Adems, para poder depurar la aplicacin en un dispositivo real de Android es necesario


indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (WIFI) de la Unidad 3. Estudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado
del programa anterior, en el que hemos utilizado su interfaz WIFI.

Desgraciadamente, el AVD del SDK de Android no incluye la interfaz WIFI, ni siquiera mediante
el Simulador de Sensores. Por lo tanto, es necesario probar esta aplicacin en un dispositivo
real de Android.

U3 Sensores y dispositivos de Android

Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:

261

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

4.2 Mdulo Bluetooth


El SDK de Android tambin ofrece soporte para la tecnologa Bluetooth que permite a un
dispositivo intercambiar datos de forma inalmbrica con otros dispositivos cercanos. Esta
funcionalidad permite realizar lo siguiente:
- Bsqueda de otros dispositivos Bluetooth.
- Consultar los dispositivos Bluetooth emparejados con el nuestro.
- Establecer canales de comunicacin RFCOMM (Radio Frecuency Communication) utilizando,
para simular, comunicaciones propias de puertos serie como los modem de datos.
- Conectar con otros dispositivos a travs del descubrimiento de servicios y transferir datos
entre ellos.
- Administrar conexiones mltiples.

Aula Mentor

Las clases ms importantes (no incluimos todas) de la API Bluetooth de Android son:
- BluetoothAdapter: permite realizar todo tipo de operaciones sobre este mdulo, como
buscar dispositivos bluetooth con el mtodo startDiscovery() y obtener informacin
de stos.
- BluetoothDevice: representa un dispositivo bluetooth remoto con sus atributos y propiedades correspondientes.
- BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin de datos.
La diferencia principal con el mdulo anterior WIFI est en que en este mdulo Bluetooth
es necesario definir un receptor de mensajes del tipo BroadcastReceiver para recibir
asncronamente los dispositivos encontrados mediante mensajes del BluetoothAdapter.
En este apartado, se pretende mostrar al alumno de forma prctica cmo buscar dispositivos Bluetooth en el sistema operativo Android. Es recomendable abrir el Ejemplo 4 de esta
Unidad para seguir la explicacin siguiente.
Hemos desarrollado este Ejemplo 4 con la misma estructura que el anterior Ejemplo 3. Por
esto, no mostraremos el cdigo fuente completo del mismo ya que se repiten algunas partes del
cdigo. Slo estudiaremos la parte nueva.
Podemos encontrar las diferencias en la clase principal de la aplicacin MainActivity, que
tiene este contenido:

262

public class MainActivity extends Activity implements OnClickListener,


OnItemClickListener {
private ProgressDialog dialog;
// Matriz del tipo Red donde vamos a guardar los dispositivos
// detectados
public static ArrayList<BTooth> dispositivos = new
ArrayList<BTooth>();
// Adaptador de Bluetooth
private BluetoothAdapter mBtAdapter;
// Lista de dispositivos y su adaptador
public static ListView listadispositivos;
public static AdapterElements adaptador;
// BroadcastReceiver que sirve de mtodo callback para
// recibir los eventos del BluetoothAdapter
private final BroadcastReceiver blueReceiver = new
BroadcastReceiver() {
@Override
public void onReceive(Context arg0, Intent argl) {

// Obtenemos la ACTION

String action = argl.getAction();

// Si hemos encontrado un dispositivo leemos sus datos y los

// aadimos a la matriz de datos

if (BluetoothDevice.ACTION_FOUND.equals(action)) {

// Obtenemos los datos del dispositivo

BluetoothDevice device =

argl.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

// Si el dispositivo no est vinculado obtenemos sus datos

if (device.getBondState() != BluetoothDevice.BOND_BONDED){
dispositivos.add(new BTooth(device.getName(),
device.getAddress(),


getBTMajorDeviceClass(device.getBluetoothClass().

U3 Sensores y dispositivos de Android


getMajorDeviceClass()), device.getBondState() !=

BluetoothDevice.BOND_BONDED ? DISPONIBLE :
VINCULADO));
}

} else

// Si hemos acabado la bsqueda ocultamos el dilogo y

// refrescamos el adaptador del ListView

if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))

{

if (dialog.isShowing()) {
dialog.dismiss();
}
adaptador.notifyDataSetChanged();

}
} // end onReceive
// Mtodo que traduce el tipo de dispositivo Bluetooth
private String getBTMajorDeviceClass(int major){

switch(major){

case BluetoothClass.Device.Major.AUDIO_VIDEO:

return AUDIO_VIDEO;

case BluetoothClass.Device.Major.COMPUTER:
return ORDENADOR;

case BluetoothClass.Device.Major.HEALTH:

return SALUD;

case BluetoothClass.Device.Major.IMAGING:

return FOTOGRAFA;

case BluetoothClass.Device.Major.MISC:

return VARIOS;

case BluetoothClass.Device.Major.NETWORKING:

return REDES;

case BluetoothClass.Device.Major.PERIPHERAL:

return PERIFRICO;

case BluetoothClass.Device.Major.PHONE:

return TELFONO;

case BluetoothClass.Device.Major.TOY:

return JUGUETE;

case BluetoothClass.Device.Major.UNCATEGORIZED:

return SIN CATEGORA;

case BluetoothClass.Device.Major.WEARABLE:

return PORTABLE;

default: return DESCONOCIDO;

}
}
}; //fin de la creacin de blueReceiver
// En la creacin de la Actividad buscamos el ListView
// y le asignamos su adaptador correspondiente
@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);

// La orientacin es siempre vertical
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_POR
TRAIT);
setContentView(R.layout.activity_main);

263

Aula Mentor


Button boton = (Button)findViewById(R.id.boton);
boton.setOnClickListener(this);

dialog = new ProgressDialog(this);

adaptador = new AdapterElements(this);

listadispositivos = (ListView)findViewById(R.id.listView) ;
listadispositivos.setAdapter(adaptador);
listadispositivos.setOnItemClickListener(this);
}
// Si el usuario hace clic en el botn entonces
// arrancamos las sentencias de bsqueda de
// dispositivos Bluetooth

264

@Override
public void onClick(View v) {

switch (v.getId()) {

case (R.id.boton):

// Buscamos el adaptador Bluetooth

mBtAdapter = BluetoothAdapter.getDefaultAdapter();

// Si no existe mostramos un mensaje de error

if (mBtAdapter == null) {

Toast.makeText(this, ERROR: dispositivo Bluetooth no
est disponible., Toast.LENGTH_LONG).show();
return;
}
// Si existe el dispositivo pero no est activo solicitamos

// al sistema mediante un Intent que lo active

if (!mBtAdapter.isEnabled()) {

Intent enableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) ;
this.startActivity(enableIntent);

// Nota: podemos ejecutar el Intent anterior mediante

// la orden startActivityForResult y esperar a la

// vuelta de ste para seguir con el descubrimiento

// de los dispositivos.
return;
}

// Mostramos una ventana de progreso
this.dialog.setMessage(Buscando dispositivos
Bluetooth...);
this.dialog.show();

// Limpiamos la matriz de resultados
dispositivos.clear();
// Registramos 2 Intent con un filtro para recepcin de

// eventos cuando se encuentra Intent un dispositivo

// Bluetooth o se acaba la bsqueda

IntentFilter filter = new
IntentFilter(BluetoothDevice.ACTION_FOUND);
// Indicamos el mtodo callback bluereceiver que se

// ejecutar cada vez que se encuentre un dispositivo

this.registerReceiver(blueReceiver, filter);

filter = new
IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
// Indicamos el mtodo callback bluereceiver que se

// ejecutar una vez acabe la bsqueda de dispositivos

this.registerReceiver(blueReceiver, filter);

U3 Sensores y dispositivos de Android

mBtAdapter.startDiscovery();
break;
}
}
// Evento onClick del listado
@Override
public void onItemClick(AdapterView<?> arg0, View view, int index,
long arg3){

// Obtenemos la informacin del dispositivo

BTooth bluetooth = MainActivity.dispositivos.get(index);

String nombre = bluetooth.getNombre();

String address = bluetooth.getAddress();

String tipo = bluetooth.getTipo();

String vinculado = bluetooth.getVinculado();

// Creamos un Intent nuevo en el que pasamos como parmetros

// la informacin del dispositivo y arranca la Actividad
//InfoBluetooth

Intent i_btinfo = new Intent(this,InfoBluetooth.class);

i_btinfo.putExtra(NOMBRE, nombre);

i_btinfo.putExtra(ADDRESS, address);

i_btinfo.putExtra(TIPO, tipo);

i_btinfo.putExtra(VINCULADO, vinculado);
startActivity(i_btinfo);
}
// Clase que define el adaptador del listado con los mtodos
// habituales
class AdapterElements extends ArrayAdapter<BTooth> {

Activity context;

public AdapterElements(Activity context) {

super(context, R.layout.elementoitems,
MainActivity.dispositivos);

this.context = context;
}

public View getView(int position, View convertView, ViewGroup


parent) {

LayoutInflater inflater = context.getLayoutInflater();

View item = inflater.inflate(R. layout.elementoitems, null);

TextView lblTitle =

(TextView)item.findViewById(R.id.nombrestr);

lblTitle.setText(MainActivity.dispositivos.get(position).
getNombre());

return(item);
}
}
}

Como podemos ver en el cdigo anterior, para realizar una bsqueda de dispositivos Bluetooth
debemos comprobar que el mdulo Bluetooth est presente en el dispositivo en cuestin.
El mdulo Bluetooth es instanciado a travs del mtodo getDefaultAdapter() de la clase
BluetoothAdapter.

Si dicha instancia devuelve un valor null, significa que el mdulo Bluetooth no est
disponible en el dispositivo Android. Sin embargo, si la instancia devuelve un valor distinto de

265

Aula Mentor

266

null, pero su mtodo isEnabled() devuelve un valor falso, revela que el mdulo Bluetooth
est apagado.
En el caso de que el mdulo Bluetooth se encuentre apagado, es posible encenderlo automticamente creando un Intent del sistema operativo mediante la constante BluetoothAdapter.ACTION_REQUESTENABLE.
Una vez inicializado el mdulo Bluetooth, la aplicacin debe registrar en el sistema que
desea recibir mensajes de los eventos ACTIONFOUND y ACTION_DISCOVERY_FINISHED del sistema operativo. Estos mensajes indican que se ha encontrado un dispositivo Bluetooth durante la
bsqueda y que ha finalizado la bsqueda respectivamente.
Para poder recibir dichos mensajes hay que definir un receptor de mensaje del sistema
de la clase BroadcastReceiver que se encarga de esta tarea.
Adems, debemos llamar al mtodo startDiscovery() de la clase BluetoothAdapter
para iniciar la bsqueda de dispositivos.
Todas estas operaciones anteriores se han implementado en el evento onClick del botn
de la aplicacin.
Mediante un objeto de tipo BroadcastReceiver llamado blueReceiver se filtran los
mensajes recibidos a travs del parmetro arg1 que es un Intent.
Mediante el mtodo getAction() de este Intent podemos obtener el tipo de mensaje (ACTION) recogido y ejecutar el bloque de sentencias que corresponda. Por ejemplo, en el
caso de recibir el mensaje de que se ha encontrado un dispositivo Bluetooth usamos el mtodo
getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) de este Intent para obtener sus
caractersticas.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso a la interfaz WIFI del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.
xml las siguientes etiquetas:
<uses-permission android:name=android.permission.BLUETOOTH/>
<uses-permission android:name=android.permission.BLUETOOTH_ADMIN/>

Adems, para poder depurar la aplicacin en un dispositivo real de Android, es necesario


indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.
Es sencillo mejorar la aplicacin para incluir funcionalidades nuevas, como la vinculacin entre
dispositivos y la trasmisin de datos mediante la clase BluetoothSocket. En la Gua oficial
del desarrollador Bluetooth de Android puedes encontrar ms informacin, as como ejemplos
descriptivos.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Bluetooth) de la Unidad


3. Estudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado del programa anterior, en el que hemos utilizado su mdulo Bluetooth.

Desgraciadamente, el AVD del SDK de Android no incluye el mdulo Bluetooth, ni siquiera


mediante el Simulador de Sensores. Por lo tanto, es necesario probar esta aplicacin en un
dispositivo real de Android.

U3 Sensores y dispositivos de Android

Si ejecutas en Eclipse ADT este Ejemplo 4 en un dispositivo real, vers que se muestra la
siguiente aplicacin:

267
Si ejecutas esta aplicacin en tu dispositivo fsico Android, vers que en el listado nicamente
aparecen los dispositivos que no estn vinculados a l.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

4.3 Cmara de fotos


Casi todos los dispositivos Android integran una cmara de fotos, incluso, algunos disponen
de una cmara frontal y otra en la parte posterior. El SDK de Android incluye las clases Java
necesarias para gestionar una o varias cmaras de fotos.
Existen dos formas de gestionar la cmara de fotos:
- Utilizando un Intent que gestiona por nosotros la cmara de fotos y devuelve la imagen a
la aplicacin que lo ha iniciado.
- Integrando directamente la cmara en la aplicacin utilizando las libreras (API) de Android.

Aula Mentor

4.3.1 Ejemplo de cmara mediante un Intent


Como hemos comentado, es muy sencillo utilizar un Intent de Android para lanzar la aplicacin de cmara de fotos de un dispositivo y, posteriormente, obtener la foto tomada. Fjate en
el cdigo fuente del siguiente ejemplo:
public class ImagePickActivity extends Activity {
// Constante utilizada en el Intent
private static final int REQUEST_CODE = 1;
// Bitmap donde almacenamos la imagen
private Bitmap bitmap;
// Vistas de tipo Imagen de la interfaz usuario
private ImageView imageView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Recuperamos el ImageView
imageView = (ImageView) findViewById(R.id.resultado);
}

268

// En el layout de la aplicacin hemos definido un botn y asociado


// este mtodo como onClick
public void capturaImage(View View) {
// Definimos el Intent que lanza la aplicacin de cmara de fotos
Intent intent = new Intent();
intent.setType(image/*);
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_CODE);
}
// Mtodo
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQUEST_CODE && resultCode ==
Activity.RESULT_OK)
try {
// Si el bitmap ya existe tenemos que vaciarlo de datos
if (bitmap != null) {
bitmap.recycle();
}
// Leemos los datos devuelto por el Intent en un stream
InputStream stream =
getContentResolver().openInputStream(data.getData());
// Pasamos los datos al bitmap
bitmap = BitmapFactory.decodeStream(stream);
// Liberamos el stream
stream.close();
// Asignamos la imagen al ImageView
imageView.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();

U3 Sensores y dispositivos de Android

} catch (IOException e) {
e.printStackTrace();
}
super.onActivityResult(requestCode, resultCode, data);

Como puedes ver en el cdigo anterior, basta con utilizar una Intencin con estas caractersticas:
Tipo: image/*
Accin: ACTION_GET_CONTENT
Categora: CATEGORY_OPENABLE

De esta forma podemos ejecutar la aplicacin interna del dispositivo Android y, posteriormente,
esperar el evento onActivityResult(), para capturar el resultado, que es una imagen.
Puedes incluir este cdigo en tu propia aplicacin cuando desees integrar la captura de
fotos en tus aplicaciones.

4.3.2 Ejemplo de cmara mediante API de Android


El SDK de Android incluye el soporte nativo, mediante una librera, que permite utilizar las
cmaras de los dispositivos para sacar fotos y grabar vdeos.
Existe gran cantidad de aplicaciones que hacen uso de esta cmara de fotos mejorando
la experiencia del usuario y abriendo un nuevo campo de integracin entre el mundo real y
el virtual.
Veamos las clases de la API de la cmara de Android:
- Camera: sta es la clase bsica de la API para controlar la cmara, tomar fotos y grabar vdeos. Como un dispositivo Android puede tener varias cmaras, para distinguirlas, esta clase
utiliza la variable de tipo entero cameraId.
- CameraInfo: clase que contiene la informacin de la cmara con el identificativo cameraId.
- SurfaceView: esta clase se usa para mostrar al usuario la previsualizacin de la cmara de
fotos.
- MediaRecorder: esta clase se emplea para grabar vdeos.
En este apartado se pretende mostrar al alumno, de forma prctica, cmo utilizar la cmara de
fotos. Es recomendable abrir el Ejemplo 5 de esta Unidad para seguir la explicacin que se
ofrece.
La aplicacin que desarrollamos permitir capturar una foto con la cmara o seleccionar
una imagen de la galera del dispositivo y mostrarla en la interfaz de usuario.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent

android:layout_height=fill_parent android:id=@+id/layout>

<TextView android:layout_width=fill_parent

android:layout_height=wrap_content android:text=Cmara
de fotos

android:textSize=24sp />

269

Aula Mentor


<FrameLayout android:id=@+id/preview
android:layout_weight=1

android:layout_width=fill_parent

android:layout_height=fill_parent>

</FrameLayout>
<LinearLayout

android:layout_width=fill_parent

android:layout_height=wrap_content

android:gravity=center_horizontal >

270


<Button

android:id=@+id/boton_disparador

android:layout_width=wrap_content

android:layout_height=wrap_content

android:layout_marginTop=6dip

android:layout_weight=1

android:text=Disparador />

<Button

android:id=@+id/boton_galeria

android:layout_width=wrap_content

android:layout_height=wrap_content

android:layout_marginTop=6dip

android:layout_weight=1

android:text=Ver Galera />

</LinearLayout>
</LinearLayout>

En el diseo anterior hemos incluido el elemento FrameLayout, que sirve como contenedor
de la previsualizacin de la cmara de fotos o de la imagen seleccionada por el usuario en la
galera.
Si ahora abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente contenido:
public class MainActivity extends Activity {
// Constantes que se usan en la aplicacin
public final static String DEBUG_TAG = CAMARA_FOTOS;
private static int SELECT_PICTURE = 1;
// Botn que se usa de disparador de la cmara
public Button boton_disparador;
// Objeto que sive de interfaz con la cmara
public static Camera camara;
// Objeto de previsualizacin de la cmara de fotos
Preview preview;
// Id de la cmara seleccionada del dispositivo
public static int cameraId = 0;
// Evento onCreate de la Actividad
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el botn disparador y asociamos su evento onClick
boton_disparador = (Button)findViewById(R. id.boton_disparador);
boton_disparador.setOnClickListener(new OnClickListener() {

U3 Sensores y dispositivos de Android

@Override

public void onClick(View v) {

// Si la aplicacin est visualizando una imagen entonces...

if (boton_disparador.getText().equals(Ver previsualizacin))
{

// Volvemos a modo previsualizacin de la cmara
boton_disparador.setText(Disparador);

// Intercambiamos en el frame el objeto de previsualizacin

((FrameLayout)findViewById(R.id.preview)).removeAllViews();

((FrameLayout)findViewById(R.id.preview)).addView(preview);

return;

}

// Si no se encuentra la cmara entonces no podemos sacar

// fotos

if (cameraId < 0) {

Toast.makeText(getBaseContext(), ERROR: no se encuentra

la cmara de fotos en este dispositivo.,


Toast.LENGTH_LONG).show();
} else {


// Si encontramos la cmara entonces paramos la


// previsualizacin, tomamos la foto y volvemos a

// previsualizar

camara.stopPreview();

// Tomamos la foto indicando el mtodo callback
// PictureCallback en la clase FotoHandler para que
// trate la foto una vez se ha sacado.

camara.takePicture(null, null, new
FotoHandler(getApplicationContext()));

camara.startPreview();
}
}
}); // end onClick
// Botn galera
Button boton_galeria = (Button)findViewById(R. id.boton_galeria);
// Evento onClick del botn galera
boton_galeria.setOnClickListener(new OnClickListener() {
@Override

public void onClick(View v) {

// Definimos un Intent del tipo siguiente

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent = new Intent(Intent.ACTION_PICK,

android.provider.MediaStore.Images.Media.
INTERNAL_CONTENT_URI);
// Iniciamos la Actividad con este Intent indicando que
// queremos seleccionar una imagen

startActivityForResult(intent, SELECT_PICTURE);
}
});
// A continuacin, vamos a comprobar si el dispositivo tiene cmara
// de fotos
if (!getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Toast.makeText(this, ERROR: este dispositivo no tiene cmara de
fotos., Toast.LENGTH_LONG).show();
} else {

271

Aula Mentor

// Si tiene cmara de fotos, obtenemos el ID de la misma


cameraId = encuentraCamaraPrincipal();
if (cameraId < 0) {
Toast.makeText(this, ERROR: no se encuentra la cmara de
fotos en este dispositivo., Toast.LENGTH_LONG).show();
} else
// Si obtenemos el ID de la cmara entonces creamos el objeto
// de previsualizacin y ste ltimo lo asignamos al frame
camara = Camera.open(cameraId);
preview = new Preview(this);
((FrameLayout) findViewById(R.id.preview)).addView(preview);

272

// Funcin que se ejecuta cuando concluye el intent en el


// que se solicita una imagen de la galera
@Override protected void onActivityResult(int requestCode, int
resultCode, Intent data) {

// Si el resultado es OK

if (requestCode == SELECT_PICTURE && resultCode == RESULT_OK){
// Recibimos el URI de la imagen y construimos un Bitmap a
// partir de un stream de Bytes

Uri selectedImage = data.getData();

InputStream is;

try {

// Obtenemos la imagen seleccionada

is = getContentResolver().
openInputStream(selectedImage);

// Leemos la imagen


BufferedInputStream bis = new BufferedInputStream(is);


Bitmap bitmap = BitmapFactory.decodeStream(bis);


// Definimos una Vista de tipo imagen y la asignamos al
// frame


ImageView imagen = new ImageView(this);

imagen.setImageBitmap(bitmap);

// Paramos la previsualizacin de la cmara

camara.stopPreview();

// Asignamos al frame la Vista ImagenView

((FrameLayout)findViewById(R.id.preview)).removeAllViews();

((FrameLayout) findViewById(R.id.preview)).addView(imagen);

boton_disparador.setText(Ver previsualizacin);

} catch (FileNotFoundException e) {}
}
}
// Se invoca cuando la Actividad vuelve a estar Activa
@Override
public void onResume() {

// La primera vez no podemos reiniciar la previsualizacin

// porque no hemos detectado la cmara. Slo mostramos la
// previsualizacin si no estamos mostrando una foto

if (cameraId>0 &&

boton_disparador.getText().equals(Disparador)) {
camara.startPreview();
}
super.onResume();

U3 Sensores y dispositivos de Android

}
// Mtodo que busca la cmara principal
private int encuentraCamaraPrincipal(){
cameraId=0;
// Obtenemos el n total de cmaras del dispositivo
int numberOfCameras = Camera.getNumberOfCameras();
// Recorremos todas las cmaras
for (int i = 0; i < numberOfCameras; i++) {
// Obtenemos la informacin de la cmara i
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(i, info);
// Si es la cmara de atrs indicamos que hemos acabado
if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
Log.d(DEBUG_TAG, Cmara encontrada!);
cameraId = i;
break;
}
}
return cameraId;
}
// Evento que ocurre cuando la Actividad pasa a segundo plano
@Override
protected void onPause() {

// Es muy importante parar la previsualizacin para

// no consumir recursos del sistema como CPU
if (camara != null) {
camara.stopPreview();
}
super.onPause();
}
// Evento que se lanza cuando se destruye la Actividad
@Override
protected void onDestroy(){

// Paramos la previsualizacin
camara.setPreviewCallback(null);
preview = null;
// Liberamos el objeto cmara
camara.release();
camara = null;
super.onDestroy();
}
} // end clase

En el cdigo anterior puedes ver que fijamos una constante para identificar la accin realizada
(seleccionar imagen de la galera). Adems, definimos las variables de la clase Camera de Android para gestionar la cmara y Preview de previsualizacin de la cmara definida por esta
aplicacin y que estudiaremos ms adelante.
En el evento onCreate() de esta Actividad hemos incluido las sentencias necesarias
para asignar a los dos botones las operaciones que deben realizar cuando un usuario haga clic
sobre ellos.
En el caso del botn Disparador se aplica la siguiente lgica: si el FrameLayout no est
mostrando la previsualizacin, la incluimos para que el usuario pueda sacar una foto. Si no
ocurre esto anterior y existe una cmara, entonces tomamos una fotografa mediante el mtodo

273

Aula Mentor

takePicture() clase Camera donde pasamos como parmetro un mtodo callback del tipo

PictureCallback que hemos implementado en la clase FotoHandler de esta aplicacin, para que
almacene la foto una vez se ha tomado.
Para la funcionalidad del botn Ver Galera hemos utilizado un Intent del tipo MediaStore.ACTION_IMAGE_CAPTURE y lo hemos ejecutado mediante la orden startActivityForResult() para recibir la imagen seleccionada por el usuario en el mtodo onActivityResult(). En este ltimo mtodo recibimos el URI de la imagen y construimos un Bitmap a
partir de un stream de Bytes para cargarlo en una Vista de tipo imagen y la asignamos al frame
para mostrarla al usuario parando la previsualizacin de la cmara de fotos con la sentencia
camara.stopPreview().
Adems de definir las rdenes que deben ejecutar los botones, hemos insertado la sentencia
siguiente que valida si el dispositivo tiene cmara de fotos:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)

Si existe una cmara, entonces buscamos identificarla mediante el cameraId para su uso
posterior mediante el mtodo local encuentraCamaraPrincipal(). Este mtodo obtiene el
nmero total de cmaras del dispositivo con el mtodo Camera.getNumberOfCameras() y, a
travs de un bucle, la clase CameraInfo extrae las caractersticas de cada cmara buscando la
cmara principal trasera que se define con la constante CAMERA_FACING_BACK.

274

Al final del cdigo de esta clase MainActivity hemos definido los mtodos siguientes del
ciclo de vida de la actividad:
- onPause(): cuando la Actividad pasa al segundo plano, debemos parar la previsualizacin
de la cmara con la orden stopPreview(), ya que consume recursos de sistema operativo.
- onResume(): en el caso de que sea necesario, se debe volver a iniciar la previsualizacin de
la cmara con startPreview() cuando la Actividad vuelva al primer plano.
- onDestroy(): si la Actividad se destruye, debemos eliminar el mtodo callback de la previsualizacin con camara.setPreviewCallback(null) y, a continuacin, liberar la cmara
con camara.release().

Es muy importante sobrescribir (override) los mtodos anteriores de la Actividad,


ya que la cmara de fotos consume muchos recursos del sistema, como CPU y
batera, por lo que slo debe utilizarse cuando sea necesario.

Veamos ahora el contenido de la clase Preview de la aplicacin que implementa la previsualizacin de la cmara:
class Preview extends SurfaceView implements SurfaceHolder.Callback {
// Superficie que muestra la previsualizacin
SurfaceHolder mHolder;
Preview(Context context) {
super(context);
// Obtenemos el contenedor del SurfaceView y le asignamos
// un mtodo para detectar cundo se crea y se destruye esta
// superficie
mHolder = getHolder();

U3 Sensores y dispositivos de Android

mHolder.addCallback(this);

// Evento onCreate de la superficie


public void surfaceCreated(SurfaceHolder holder) {
try {
// Indicamos a la cmara que la Vista de previsualizacin
// es esta superficie
MainActivity.camara.setPreviewDisplay(holder);
// A modo de control, definimos el mtodo PreviewCallback()
// que ocurre cuando se lanza la previsualizacin de la

// cmara. Realmente no es necesario.
MainActivity.camara.setPreviewCallback(new PreviewCallback() {

public void onPreviewFrame(byte[] data, Camera arg1) {
try {

Log.d(MainActivity.DEBUG_TAG, onPreviewFrame - +
data.length);

} finally {}
}
}); // end PreviewCallback

} catch (IOException e) {
e.printStackTrace();
}
} // end on surfaceCreated
public void surfaceDestroyed(SurfaceHolder holder) {
}
// Evento que se lanza cuando cambia el tamao de la superficie
public void surfaceChanged(SurfaceHolder holder, int format, int w,
int h) {
// Si cambia el tamao de la superficie debemos cambiar
// el parmetro de la previsualizacin de acuerdo con ste.
if (MainActivity.camara==null) return;
Camera.Parameters parameters =
MainActivity.camara.getParameters();
parameters.setPreviewSize(w, h);
MainActivity.camara.setParameters(parameters);
MainActivity.camara.startPreview();
}
// Evento que lanza la primera vez que dibujamos la superficie.
// Mostramos un texto en color rojo
@Override
public void draw(Canvas canvas) {
super.draw(canvas);

Paint p= new Paint(Color.RED);

Log.d(MainActivity.DEBUG_TAG,Dibujo de previsualizacin);

canvas.drawText(PREVISUALIZACIN, canvas.getWidth()/2,
canvas.getHeight()/2, p );
}
} // end Preview

En el cdigo anterior hemos extendido la clase Preview de la clase superficie SurfaceView


que hemos estudiado en la Unidad 1. sta se encargar de mostrar la previsualizacin de la
cmara de fotos e implementar la interfaz SurfaceHolder.Callback, que ejecutar el cdigo

275

Aula Mentor

cuando haya cambios de esta superficie.


Lo primero que hemos hecho es obtener el contenedor del SurfaceView y le asignamos los
mtodos callback para detectar cundo se crea, cambia o se destruye esta superficie. A continuacin, definimos los mtodos de esta interfaz SurfaceHolder.Callback:
- surfaceCreated(): este evento ocurre cuando el sistema crea la superficie. En l, indicamos a la cmara que la Vista de previsualizacin es esta superficie y, para poder depurarla,
definimos el mtodo PreviewCallback(), que ocurre cuando el sistema operativo actualiza
la previsualizacin de la cmara. Realmente no es necesario definirlo.
- surfaceDestroyed(): este evento ocurre cuando se destruye la superficie. En este caso
hemos delegado en la Actividad principal la destruccin de este objeto Preview. Tambin,
podramos haber destruido las variables creadas.
- surfaceChanged(): evento que sucede cuando el sistema operativo detecta que se ha producido un cambio en la superficie. En este caso, establecemos los parmetros del tamao de
previsualizacin de la cmara.
- draw(): evento que se lanza la primera vez que se dibuja la superficie. Mostramos un texto
en color rojo.
Veamos ahora el contenido de la clase FotoHandler de la aplicacin que guarda la imagen
tomada por la cmara de fotos:

276

// Clase que implementa el mtodo callback PictureCallback para que


// trate la foto una vez se ha sacado.
public class FotoHandler implements PictureCallback {
// Guardamos el contexto para poder hacer un Toast
private final Context contexto;
public FotoHandler(Context context) {
this.contexto = context;
}
// Debemos definir el evento onPictureTaken
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// Obtenemos el directorio donde vamos a guardar la foto
File pictureFileDir = getPicDir();
// Si no existe el directorio y no lo podemos crear
// entonces mostramos un error
if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {

Log.d(MainActivity.DEBUG_TAG, ERROR: no se puede crear el
directorio para guardar las fotos.);

Toast.makeText(contexto, ERROR: no se puede crear el
directorio para guardar las fotos.,

Toast.LENGTH_LONG).show();

return;
}
// Definimos el formato del nombre del archivo

SimpleDateFormat dateFormat = new SimpleDateFormat(dd-mm-yyyy
hh mm ss, Locale.ROOT);

String date = dateFormat.format(new Date());

String fotoFile = Foto_ + date + .jpg;

String filename = pictureFileDir.getPath() + File.separator +
fotoFile;
// Usamos un fichero para guardar la foto

File pictureFile = new File(filename);

U3 Sensores y dispositivos de Android

// Guardamos la foto
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
Toast.makeText(contexto, Se ha guardado la imagen: +
fotoFile, Toast.LENGTH_LONG).show();

} catch (Exception error) {

Log.d(MainActivity.DEBUG_TAG, File + filename + no
guardada: + error.getMessage());
Toast.makeText(contexto, ERROR: no se puede guardar la
imagen., Toast.LENGTH_LONG).show();
}
// Iniciamos la previsualizacin de la cmara
camera.startPreview();
}
// Mtodo que obtiene el directorio PICTURES del sistema
private File getPicDir() {
File sdDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
return new File(sdDir, CursoMentor_Unidad3_Ejemplo5);
}
} // end clase

Cuando tomamos una fotografa, hay que invocar el mtodo takePicture() de la clase Camera, donde debemos indicar como parmetro un mtodo callback del tipo PictureCallback
que hemos implementado en la clase anterior FotoHandler. Este mtodo se encarga de guardar la foto una vez se ha tomado.
Puedes observar que la clase implementa el mtodo onPictureTaken, que es el que se
encarga de almacenar la imagen en la tarjeta SD del dispositivo Android. Como parmetro de
este mtodo usamos la matriz de bytes data que contiene la informacin de la foto tomada. En
este bloque de cdigo seguimos los pasos habituales para almacenar un fichero de tipo binario
en la tarjeta SD del dispositivo; en concreto, lo guardamos en el directorio de fotos Environment.DIRECTORY_PICTURES del sistema operativo.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a la cmara y tarjeta SD del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.CAMERA/>
<uses-permission
android:name=android.permission.WRITE_EXTERNAL_STORAGE/>

Adems, recordamos que para poder depurar la aplicacin en un dispositivo real de Android,
es necesario indicarlo en el archivo manifest en la etiqueta <application> mediante el
atributo android:debuggable=true.
Es sencillo mejorar esta aplicacin incluyendo funcionalidades nuevas, como la grabacin de
vdeo. En la Gua oficial de Cmara de Android puedes encontrar ms informacin, as como
ejemplos descriptivos.

277

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 5 (Cmara de fotos) de la


Unidad 3. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la Cmara de fotos.

En este ejemplo, s podemos utilizar el AVD del SDK de Android configurndolo correctamente.
Para ello, debemos editar el dispositivo virtual y elegir en la cmara trasera (Back Camera) la
opcin Emulated para que simule una cmara (como hemos hecho en las capturas que aparecen despus) o Webcam0 para utilizar la Webcam de tu ordenador como cmara de fotos:

278

U3 Sensores y dispositivos de Android

Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente aplicacin:

Si tomas una foto y, a continuacin, pulsas el botn Ver Galera, vers que no aparece ninguna foto en sta:
279

Esto se debe a que el AVD, por motivos de rendimiento, no busca automticamente nuevos
contenidos multimedia en la tarjeta SD. Para forzar esta bsqueda debemos hacer clic en la
aplicacin Dev Tools que aparece en el escritorio de aplicaciones de Android del AVD:

Aula Mentor

Despus, debemos seleccionar la opcin Media Provider y hacer clic en el botn Scan SD
card para que el sistema busque nuevos contenidos multimedia en la tarjeta SD:

Si ahora haces clic en el botn Galera, vers que ya puedes seleccionar esa imagen correctamente:
280

En lugar de tener que lanzar manualmente el escaneo de nuevos archivos multimedia, es posible implementar en esta aplicacin un mensaje de tipo Broadcast
con el Intent correspondiente que actualice la librera multimedia del sistema
operativo. En el Ejemplo 1 de la Unidad 1 lo hemos hecho en el mtodo addRecordingToMediaLibrary().

U3 Sensores y dispositivos de Android

4.4 Mdulo GPS


En la actualidad, existen muchas aplicaciones que hacen uso de la localizacin del usuario para
ofrecer servicios avanzados. Google Maps es, posiblemente, la aplicacin que ms aprovecha
la funcionalidad del GPS para ofrecer al usuario servicios teniendo en cuenta su localizacin,
como, por ejemplo, una gua de rutas o la localizacin basada en mapas.
Sin embargo, los desarrolladores reinventan continuamente el uso de la geolocalizacin
y crean proyectos y aplicaciones que ofrecen al usuario servicios en funcin de su localizacin,
como, por ejemplo, la posibilidad de obtener recomendaciones de puntos de inters cercanos o
compartir informacin y conversaciones con personas de la misma zona.
Existen varios mtodos de obtener la localizacin de un dispositivo mvil, si bien
la ms conocida es la localizacin mediante el mdulo GPS. Tambin es posible conseguir la
posicin geogrfica de un dispositivo utilizando las antenas de telefona mvil o mediante los
puntos de acceso WI-FI cercanos. Cada uno de estos mtodos tiene una precisin, velocidad
y consumo de recursos del sistema distintos. Por otra parte, la aplicacin al cdigo fuente del
modo de funcionamiento de cada uno de estos mtodos no es directa ni intuitiva.
En este apartado, se pretende mostrar el alumno, de forma prctica, cmo utilizar la funcionalidad de geolocalizacin que ofrece el SDK de Android a travs del mdulo GPS que, hoy
en da, tiene la mayora de los dispositivos. Es recomendable abrir el Ejemplo 6 de esta Unidad
para seguir la explicacin que se ofrece a continuacin.
La aplicacin que desarrollamos muestra los mdulos disponibles de localizacin del dispositivo
Android, selecciona el mejor mdulo en funcin de los parmetros indicados por el programador
y muestra la posicin geogrfica del dispositivo y el estado del mdulo escogido.
En el cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent
android:layout_margin=3dp>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content >
<ImageView
android:id=@+id/imageView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/redpin />
<Button android:id=@+id/botonActivar
android:layout_width=wrap_content

android:layout_height=fill_parent

android:text=Activar localizacin />
<Button android:id=@+id/botonDesactivar
android:layout_width=wrap_content

android:layout_height=fill_parent

android:text=Desactivar localizacin />

281

Aula Mentor

</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:gravity=center_vertical
android:text=Mejor provider de localizacin GPS: />









282

<TextView android:id=@+id/lblMejorProvider
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosicion
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Posicin Actual: />

<TextView android:id=@+id/lblPosLatitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />

<TextView android:id=@+id/lblPosLongitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />

<TextView android:id=@+id/lblPosPrecision
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />

<TextView

android:layout_width=fill_parent

android:layout_height=wrap_content

android:layout_margin=5dip

android:text=Estado del GPS: />





<TextView android:id=@+id/lblEstado
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />

U3 Sensores y dispositivos de Android

<TextView

android:layout_width=fill_parent

android:layout_height=wrap_content

android:layout_margin=5dip

android:text=Listado de providers: />
<ListView

android:id=@+id/listView

android:layout_height=fill_parent

android:layout_width=fill_parent

android:background=#FFFFFF/>
</LinearLayout>

Como puedes ver, la interfaz del usuario es sencilla. Se incluyen dos botones que permiten
Activar/Actualizar y Desactivar la localizacin, etiquetas para mostrar los datos al usuario y un
listado de tipo ListView para mostrar los mdulos de localizacin disponibles.
Si ahora abrimos la clase principal de la aplicacin MainActivity vemos el siguiente contenido:
public class MainActivity extends Activity {
// Definimos el gestor de localizaciones
private LocationManager locManager;
//Listener para recibir cambios de localizacin
private LocationListener locListener;
// Matriz del tipo Red donde vamos a guardar los LocationProvider
// detectados
public static ArrayList<LocationProvider> locProviderList = new
ArrayList<LocationProvider>();
// Lista para mostrar los providers de localizacin y su adaptador
public static ListView listaProviders;
public static AdapterElements adaptador;
// Etiquetas y botones de la aplicacin
private TextView lblMejorProvider;
private Button btnActivar;
private Button btnDesactivar;
private TextView lblLatitud;
private TextView lblLongitud;
private TextView lblPrecision;
private TextView lblEstado;
// Evento onCreate de la Actividad
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);

// Buscamos las Vistas de la actividad

lblMejorProvider = (TextView)findViewById(R.id.lblMejorProvider);
btnActivar = (Button)findViewById(R.id.botonActivar);
btnDesactivar = (Button)findViewById(R.id.botonDesactivar);
lblLatitud = (TextView)findViewById(R.id.lblPosLatitud);
lblLongitud = (TextView)findViewById(R.id.lblPosLongitud);
lblPrecision = (TextView)findViewById(R.id.lblPosPrecision);

283

Aula Mentor

lblEstado = (TextView)findViewById(R.id.lblEstado);
btnDesactivar.setEnabled(false);
// Definimos el adaptador del listado de LocationProviders
adaptador = new AdapterElements(this);
listaProviders = (ListView)findViewById(R.id.listView) ;
listaProviders.setAdapter(adaptador);
// Mostramos los datos en blanco
muestraPosicion(null);
// Definimos lo que se debe hacer si el usuario hace clic en los
// botones
btnActivar.setOnClickListener(new OnClickListener() {
@Override

public void onClick(View v) {

// Activamos el botn Desactivar

btnDesactivar.setEnabled(true);
// Si ya conocemos el LocationProvider mostramos la

// posicin ltima conocida

if (btnActivar.getText().equals(Actualizar datos))
muestraPosicion(locManager.getLastKnownLocation(
LocationManager.GPS_PROVIDER));

// Si no, buscamos el LocationProvider con este mtodo

else obtenerGPS();
btnActivar.setText(Actualizar datos);
}
});

284

// Evento que desactiva el LocationManager


btnDesactivar.setOnClickListener(new OnClickListener() {
@Override

public void onClick(View v) {

btnActivar.setText(Activar localizacin);

btnDesactivar.setEnabled(false);
// Quitamos el listener para que ya no lleguen

// actualizaciones

locManager.removeUpdates(locListener);

// Mostramos los datos en blanco

muestraPosicion(null);

// Limpiamos el listado de LocationProviders

locProviderList.clear();

adaptador.notifyDataSetChanged();
}
});
} // end onCreate
// Mtodo que lista todos los LocationProvider disponibles
// y muestra la posicin actual
private void obtenerGPS(){

// Buscamos el LocationManager del sistema operativo

locManager =
(LocationManager)getSystemService(Context.LOCATION_SERVICE);

// Obtenemos la lista de todos los LocationProviders

List<String> listaProviders = locManager.getAllProviders();

// Limpiamos el listado local
locProviderList.clear();

// Recorremos todos los LocationProviders que hemos obtenido

U3 Sensores y dispositivos de Android

// anteriormente y los pasamos a un matriz global de la Actividad



for (int i=0; i<listaProviders.size(); i++) {

LocationProvider provider =
locManager.getProvider(listaProviders.get(i));
locProviderList.add(provider);
}

// Indicamos al adaptador que redibuje el listado
adaptador.notifyDataSetChanged();

// Ahora vamos a buscar el mejor LocationProvider con

// precisin fina (FINE), que no requiere altitud y

// da igual que est activado o no.

Criteria req = new Criteria();

req.setAccuracy(Criteria.ACCURACY_FINE) ;
req.setAltitudeRequired(false);

String mejorProviderCrit = locManager.getBestProvider(req, false);

// Mostramos el provider encontrado
lblMejorProvider.setText(mejorProviderCrit);

// Obtenemos la ltima posicin conocida para este provider


Location location =

locManager.getLastKnownLocation(mejorProviderCrit);
// Mostramos la ltima posicin conocida
muestraPosicion(location);
// Mostramos si est activado el mdulo del provider o no
if (locManager.isProviderEnabled(mejorProviderCrit))
lblEstado.setText(Mdulo Encendido);
else lblEstado.setText(Mdulo Apagado);

// Registramos para el listener recibir actualizaciones de la


// posicin o del Mdulo del provider
locListener = new LocationListener() {

public void onLocationChanged(Location location) {

muestraPosicion(location);

}

public void onProviderDisabled(String provider){
lblEstado.setText(Mdulo Apagado);
}

public void onProviderEnabled(String provider){
lblEstado.setText(Mdulo Encendido);
}

public void onStatusChanged(String provider, int status, Bundle
extras){

Log.i(Localizacin Android, Estado del Mdulo: + status);

lblEstado.setText(Estado del Mdulo: + status);

}
};
// Solicitamos el LocationManager seleccionado que actualice la
// posicin cada 15000 milisegundos
locManager.requestLocationUpdates(mejorProviderCrit, 15000, 0,
locListener);
}//fin del mtodo obtenerGPS
// Sencillo mtodo que usa un parmetro de tipo Location para
// mostrar al usuario sus datos en pantalla.
private void muestraPosicion(Location loc) {

285

Aula Mentor

if(loc != null)
{

lblLatitud.setText(Latitud: +
String.valueOf(loc.getLatitude()));

lblLongitud.setText(Longitud: +
String.valueOf(loc.getLongitude()));

lblPrecision.setText(Precision: +
String.valueOf(loc.getAccuracy()));

Log.i(Localizacin Android,
String.valueOf(loc.getLatitude() + - +
String.valueOf(loc.getLongitude())));
}
else
{

lblLatitud.setText(Latitud: (sin_datos));

lblLongitud.setText(Longitud: (sin_datos));

lblPrecision.setText(Precision: (sin_datos));

lblMejorProvider.setText();

lblEstado.setText();
}
}

286

// Clase que define el adaptador del listado con los mtodos


// habituales
class AdapterElements extends ArrayAdapter<LocationProvider> {

Activity context;

public AdapterElements(Activity context) {
super(context, R.layout.elementoitems,
MainActivity.locProviderList);

this.context = context;
}

public View getView(int position, View convertView, ViewGroup


parent) {

LayoutInflater inflater = context.getLayoutInflater();

View item = inflater.inflate(R. layout.elementoitems, null);

TextView lblTitle =
(TextView)item.findViewById(R.id.nombrestr);

lblTitle.setText(MainActivity.locProviderList.get(

position).getName());

TextView lbldatos = (TextView)item.findViewById(

R.id.datosstr);

lbldatos.setText(Precisin: +
MainActivity.locProviderList.get(position).getAccuracy() +

Consumo batera: +
MainActivity.locProviderList.get(position).
getPowerRequirement() + - Muestra altitud: +
MainActivity.locProviderList.get(position).
supportsAltitude());
return(item);
}
} // end class AdapterElements
}

A continuacin, vamos a estudiar las sentencias ms importantes del cdigo anterior.

U3 Sensores y dispositivos de Android

En el mtodo obtenerGPS() podemos ver que, para obtener la posicin GPS del dispositivo,
lo primero que debemos hacer es conseguir, mediante el mtodo getSystemService(), una
instancia del gestor GPS utilizando la clase LocationManager, en la que se basa la API de
localizacin de Android, del mvil utilizando.
Una vez tenemos una instancia del gestor de GPS, vamos a obtener qu mtodos de localizacin
(Proveedor de localizacin o, en ingls, LocationProvider) posee el dispositivo; as conocemos
sus mdulos GPS disponibles. Como ya hemos comentado, los ms comunes son el GPS y la
localizacin mediante la red de telefona.
Para conocer los proveedores de localizacin disponibles en el dispositivo podemos usar
el mtodo getAllProviders() de la clase LocationManager.
Una vez obtenida la lista completa de proveedores disponibles, accedemos a las propiedades de ellos (precisin, consumo de batera, si puede obtener la altitud, la velocidad, etctera). Podemos obtener una referencia al provider mediante su nombre empleando el mtodo
getProvider(nombre) y, posteriormente, los mtodos disponibles para conocer sus propiedades.
Por ejemplo, para conocer la precisin podemos utilizar getAccuracy(), que devuelve
las constantes Criteria.ACCURACY_FINE para precisin alta y Criteria.ACCURACY_COARSE
para precisin media. El mtodo supportsAltitude() indica si el mdulo proporciona la altitud y el mtodo getPowerRequirement() indica el nivel de consumo de recursos del proveedor (batera). Puedes ver la lista completa de los mtodos con las caractersticas de un proveedor
en el enlace siguiente LocationProvider.

Atencin: el mtodo getAllProviders() devuelve todos los proveedores de localizacin disponibles en el dispositivo, incluso si stos no son accesibles por la
aplicacin (permisos) o no estn activados. En estos casos, la informacin no ser
accesible.

Sin embargo, qu proveedor de localizacin GPS es recomendable para seleccionarlo en


funcin de los requisitos de una aplicacin en concreto? Android proporciona una forma de
obtener los proveedores idneos que cumplen unos determinados requisitos. Para ello, debemos
definir un criterio de bsqueda mediante un objeto del tipo Criteria en el que sealamos las
caractersticas mnimas del proveedor que necesita la aplicacin.
Dicho Criterio est configurado para obtener la precisin de la medida mediante el mtodo
setAccuracy() y una de las constantes siguientes:
ACCURACY_FINE: mejor precisin del dispositivo.
ACCURACY_HIGH: precisin de localizacin alta (menor de 100 metros).
ACCURACY_MEDIUM: precisin entre 100 y 500 metros.
ACCURACYLOW: precisin mayor de 500 metros.
Lgicamente, cuanto menor sea la precisin, menos tiempo tardar el dispositivo en obtener la
localizacin.
Una vez hemos definido el criterio de precisin de la localizacin, dejamos que el sistema
operativo elija automticamente el proveedor de localizacin a travs del mtodo getBestProvider() de la clase LocationManager. Debemos indicar como primer parmetro el criterio de
precisin creado y, como segundo parmetro, si slo deseamos proveedores que estn activos
en ese momento.

287

Aula Mentor

Tambin podemos seleccionarlo manualmente utilizando las constantes de la clase


LocationManager que representan los proveedores siguientes:
- NETWORK_PROVIDER: determina la posicin del dispositivo utilizando la antena de telefona
mvil en la que se encuentre o el punto de acceso de la conexin WIFI.
- GPS_PROVIDER: precisa la posicin del dispositivo utilizando satlites GPS. Hay que tener en
cuenta que este proveedor puede tardar un tiempo ms o menos largo en fijar una posicin
ya que depende de las condiciones en las que se encuentre el dispositivo (espacios interiores
o exteriores).
- PASSIVE_PROVIDER: establece la posicin del dispositivo mediante la recogida de posiciones indirectamente de otras aplicaciones. Por ejemplo, si Google Latitude necesita conocer la
posicin GPS para enviarla a un servicio web, este proveedor se aprovecha indirectamente
de esa accin para obtener la coordenada. Si se necesita conocer la posicin exacta de un
usuario en un determinado momento, ste es el proveedor menos adecuado, pero es el que
menos batera emplea.

288

Hemos visto que podemos buscar dinmicamente los proveedores de localizacin segn un
determinado criterio de precisin. Sin embargo, normalmente una aplicacin est diseada para
utilizar un proveedor en concreto, como, por ejemplo, el mdulo GPS y, por lo tanto necesitamos
conocer si ste est activado o no en el dispositivo.
Para esto, la clase LocationManager dispone del mtodo isProviderEnabled(). Debemos pasarle como parmetro el tipo de provider que queremos consultar. En el cdigo
fuente anterior comprobamos si el mtodo seleccionado est activo, mostrando al usuario un
mensaje.

Una vez conocemos el proveedor de localizacin, vamos a definir un listener que monitoriza
el estado del GPS del dispositivo. Este listener debe implementarse a partir de la clase
LocationListener aadiendo los siguientes mtodos:
- onLocationChanged: este mtodo se lanza cada vez el sistema operativo refresca la posicin y la velocidad del dispositivo.
- onProviderEnabled: este mtodo se ejecuta al activarse el proveedor de localizacin.
- onProviderDisabled: este mtodo se lanza al desactivarse el proveedor.
- onStatusChanged: este mtodo se ejecuta cuando cambia el estado del proveedor. Puede
devolver las constantes siguientes: OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE, AVAILABLE.
Despus de definir el listener, debemos indicar al gestor LocationManager este listener
mediante el mtodo requestLocationUpdates() y, adems, establecer el tiempo mnimo en
milisegundos (15 segundos en el Ejemplo del curso) y la distancia mnima recorrida en la que el
listener desea obtener actualizaciones del proveedor de localizacin. Si estos parmetros son
puestos a cero, el listener recibir las actualizaciones de localizacin tan rpido como estn
disponibles por el proveedor de localizaciones.
Una vez que conocemos el proveedor de localizacin y est activado, podemos obtener
la localizacin actual. Para ello, debemos utilizar el mtodo getLastKnownLocation(String
provider) de la clase LocationManager que nos facilita la ltima posicin conocida del dispositivo de un provider determinado.

U3 Sensores y dispositivos de Android

Este mtodo NO devuelve la posicin actual ni solicita una nueva posicin al proveedor de localizacin, sino que se limita a indicar la ltima posicin obtenida. Por
lo tanto, es recomendable esperar a recibir la primera actualizacin a travs del
LocationListener y no leer los datos mediante este mtodo.

En el mtodo muestraPosicion() vamos mostrar los distintos datos de la posicin en las Vistas
de la interfaz de usuario, utilizando para ello los mtodos siguientes proporcionados por la
clase Location: getLatitude(), getAltitude() y getAccuracy() para obtener la latitud,
longitud y precisin respectivamente. Adems, esta clase dispone de otros mtodos para obtener
la altura, orientacin, velocidad, etctera.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso al
mdulo GPS del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las
siguientes etiquetas:
<uses-permission android:name=android.permission.INTERNET />
<uses-permission android:name=android.permission.ACCESS_FINE_LOCATION />

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (GPS) de la Unidad 3. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado su Mdulo GPS.
289
Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente aplicacin:

Aula Mentor

Para que funcione la deteccin por GPS en el AVD debemos configurar las opciones de
Localizacin abriendo sus Ajustes y configurando lo siguiente:

290

Si ejecutamos la aplicacin en el emulador de Eclipse ADT, veremos que, si pulsamos el botn


Activar localizacin, se rellena la interfaz de usuario con la informacin de la ltima posicin
conocida (si existe sta). Sin embargo, estos datos no cambian en ningn momento si pulsamos
de nuevo el botn Actualizar datos ya que no cambia en el AVD de Android.
Para simular la actualizacin de la posicin del dispositivo y comprobar que la aplicacin
responde correctamente, podemos usar una de las siguientes formas:
Envo manual de una nueva posicin al emulador de Android. Para ello, abrimos la perspectiva de DDMS de Eclipse ADT y, en la pestaa Emulator Control, en el apartado Location
Controls hacemos lo siguiente:

U3 Sensores y dispositivos de Android

Podemos enviar de forma manual al AVD en ejecucin nuevas coordenadas de posicin


para simular que el dispositivo ha cambia de localizacin. Si introducimos unas nuevas
coordenadas de longitud y latitud y pulsamos el botn Send, forzaremos la ejecucin del evento
onLocationChanged() de la aplicacin y aparecern estos mismos datos en la interfaz del
usuario.
Sin embargo, esta forma manual no resulta demasiado prctica ni automatizada para probar toda la funcionalidad de una aplicacin.
Por esto, Android proporciona otra forma de simular cambios frecuentes de posicin
que consiste en ofrecer una simulacin completa de cambios de posicin mediante un listado
de coordenadas que envan automticamente al AVD a una velocidad establecida. As, podemos
simular que el dispositivo se mueve constantemente. Este listado de coordenadas se puede proporcionar en dos formatos diferentes: GPX o KML. Ambos tipos de fichero son muy utilizados
por aplicaciones y dispositivos de localizacin, como GPS, aplicaciones de cartografa y mapas,
etctera.
Es posible crear ficheros KML a travs de la aplicacin Google Earth o manualmente con
cualquier editor de texto. En este ejemplo puedes encontrar el fichero prueba.gpx en formato
GPX (formato universal de intercambio de coordenadas de GPS) para que puedas experimentar
su uso.
Para utilizar este fichero como fuente de datos para simular cambios en la posicin del
dispositivo, accedemos a la pestaa anterior y a Location Controls. Despus, hacemos clic
sobre la pestaa GPX o KML, en funcin del formato del fichero de datos origen, que en este
ejemplo ser GPX. A continuacin, pulsamos el botn Load GPX y seleccionamos el fichero.
Despus, aparecer la lista de coordenadas tal y como aparece en la siguiente imagen:
291

Una vez cargado el archivo, debajo aparecen cuatro botones (de izquierda a derecha) que
permiten las operaciones siguientes:
- Avanzar automticamente por el listado enviando la coordenada al AVD.
- Ir a la posicin anterior del listado de forma manual.
- Ir a la posicin siguiente del listado de forma manual.
- Establecer la velocidad de cambio automtico.

Aula Mentor

A continuacin, puedes probar este listado en la aplicacin del Ejemplo 6 de esta Unidad
ejecutndolo en el AVD y pulsando el botn Activar localizacin para comenzar a detectar
cambios de posicin. Despus, pulsa el botn de avance automtico (botn verde).
Vers que la interfaz de la aplicacin se actualiza varias veces con nuevos valores de latitud y longitud cada poco tiempo. Sin embargo, con el mtodo requestLocationUpdates()hemos
definido que se muestren actualizaciones de posicin cada 15 segundos.
Si abres la Vista de LogCat de Eclipse ADT, advertirs que se han generado los mensajes de
depuracin siguientes (en tu caso deben ser muy parecidos):
06-16 21:48:34.058: I/Localizacin Android(2195): 40.46832333333334 -4.480451666666666
06-16 21:48:49.067: I/Localizacin Android(2195): 40.46773333333333 -4.482106666666667
06-16 21:49:04.088: I/Localizacin Android(2195): 40.46654 -4.483746666666667
06-16 21:49:19.098: I/Localizacin Android(2195): 40.46583833333333 -4.4860066666666665
06-16 21:49:34.108: I/Localizacin Android(2195): 40.46543 - -4.488525

292

Si observamos la fecha y hora vemos que el tiempo entre nueva posicin no coincide con los
15 segundos que ha definido el programador. Sin embargo, Android agrupada varias medidas
dentro de los 15 segundos indicados.

Definir una frecuencia entre actualizaciones de 15 segundos indica al sistema operativo que, al menos, la aplicacin necesita recibir una nueva posicin en ese periodo de tiempo; si bien el sistema puede dar varias posiciones adicionales si tiene
recursos disponibles dentro de ese periodo temporal mximo.

Adems, si investigas en el log de mensajes de Eclipse ADT, observars que cambia el estado
del proveedor a 1 (TEMPORARILY_UNAVAILABLE) despus de recibir un grupo de posiciones y,
antes de recibir de nuevo posiciones, el proveedor pasa a estado 2 (AVAILABLE).
Estos cambios en el estado de los proveedores de localizacin pueden ayudarnos a realizar diversas acciones. Por ejemplo, se puede utilizar el cambio de estado a 1 (se termina de
recibir un grupo de lecturas) para seleccionar la lectura ms precisa del grupo recibido. Esto es
muy importante cuando se estn utilizando varios proveedores de localizacin simultneamente
(GPS o WIFI) que, como sabes, tienen una precisin diferente.

U3 Sensores y dispositivos de Android

5. Uso de sensores en un juego

La popularizacin del mvil inteligente (del ingls, Smartphone) ha provocado tambin que
sus dueos y dueas jueguen con sus dispositivos en cualquier sitio. Android no es ajeno a esta
moda y dispone de una amplia y variada galera de juegos, como, por ejemplo, los ya clsicos
Trivial (Atrviate en su versin mvil), Apalabrados y Angry Birds.
Existen en Internet mltiples libreras y herramientas que ayudan al programador a desarrollar, de forma sencilla, juegos en Android. Veamos algunos de ellos.
Libreras de juegos en 3D
- Unity: evolucin del popular motor Unity 3D para dispositivos Android.
- Ogre 3D: evolucin a Android del motor 3D abierto y gratuito. Muy utilizado.
Libreras de juegos en 2D
- Marmalade SDK: potente motor 2D multiplataforma con el que se puede empezar a desarrollar juegos de forma totalmente gratuita.
- Corona SDK: otra opcin para realizar juegos y aplicaciones 2D multiplataforma.
- Cocos 2D x: librera capaz de abarcar una amplia gama de dispositivos, basada en extensiones. Totalmente gratuita.
- LibGDX: librera abierta y multiplataforma que combina grficos y audio. Muy interesante.
- AndEngine: joven motor de juegos 2D gratuito, rpido y divertido.
Estas libreras son muy tiles para desarrollar juegos de forma rpida; sin embargo, no ensean
realmente cmo hacerlo en Android de forma nativa, ya que sirven de pegamento entre el SDK
de Android y el programador.
Adems, hay que tener en cuenta que las libreras se pasan de moda o se quedan obsoletas. Por lo tanto, en este apartado vamos a estudiar el desarrollo de un juego sencillo utilizando
los mtodos disponibles en el SDK de Android.

5.1 Desarrollo de un Juego en Android


Como el movimiento se demuestra andando, el juego se demuestra jugando. En el Ejemplo 7
de esta Unidad 3 vamos a mostrar cmo desarrollar un juego en Android. Se utilizan en el mismo
los sensores del dispositivo para controlar las acciones y se aprovecha la vista SurfaceView, que
hemos estudiado en la Unidad 1, para dibujar la interfaz de usuario.
El juego consiste en una pelota que se mueve en funcin de la inclinacin del dispositivo
Android. Inicialmente, el jugador dispone de 50 puntos.
El objetivo del juego es llevar la pelota hasta la casa que aparece en la parte superior de
la pantalla ganando 50 puntos adicionales.
En los laterales de la pantalla aparecen unos muelles donde rebota la pelota y el jugador
pierde un punto. Adems, en la parte de abajo hay un muro de fuego donde, si acaba la pelota,
el jugador pierde todos los puntos y el juego termina.
Es recomendable abrir el Ejemplo 7 de esta Unidad para comprender mejor la explicacin siguiente.

293

Aula Mentor

Para empezar, el cdigo del layout main.xml incluye el diseo de la Actividad principal:
<FrameLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<es.mentor.unidad3.eje7.juego.JuegoView
android:id=@+id/areajuego
android:layout_width=fill_parent
android:layout_height=fill_parent/>
<RelativeLayout
android:id=@+id/gameLayout
android:layout_width=fill_parent
android:layout_height=fill_parent >
<LinearLayout
android:id=@+id/fuegoLayout

android:layout_width=fill_parent

android:layout_height=33px
android:orientation=vertical
android:gravity=bottom
android:background=@drawable/muromuellearriba

android:layout_alignParentTop=true/>

294

<ImageView
android:id=@+id/imageHome
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignTop=@+id/puntuacion
android:layout_centerHorizontal=true
android:layout_alignParentTop=true
android:src=@drawable/home />

<TextView
android:id=@+id/texto

android:text=@string/modo_preparado

android:visibility=visible
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:gravity=center_horizontal
android:textColor=#ff0000ff
android:textSize=40sp/>
<TextView
android:id=@+id/puntuacion
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:layout_marginTop=3dp
android:gravity=top
android:text=@string/puntuacion_texto
android:textColor=#ff0000ff
android:textSize=24sp
android:visibility=visible

U3 Sensores y dispositivos de Android

android:layout_below=@+id/texto/>
<LinearLayout
android:id=@+id/IzqLayout

android:layout_width=33px

android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=left
android:background=@drawable/muromuelleizq
android:layout_alignParentLeft=true

android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/DchaLayout

android:layout_width=33px

android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=right
android:background=@drawable/muromuelledcha
android:layout_alignParentRight=true

android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/fuegoLayout

android:layout_width=fill_parent
android:layout_height=45dp
android:orientation=vertical
android:gravity=bottom
android:background=@drawable/murofuego

android:layout_alignParentBottom=true/>

</RelativeLayout>
</FrameLayout>

Como puedes advertir, hemos incluido la Vista es.mentor.unidad3.eje7.juego.JuegoView que


implementa la superficie dinmica del juego y est heredada de SurfaceView.
Adems, para definir los laterales de la pantalla hemos usado un LinearLayout cuyo fondo
background es un drawable que, a su vez, es una imagen que se apila repetidamente
(tileMode=repeat):
<bitmap xmlns:android=http://schemas.android.com/apk/res/android
android:src=@drawable/fire
android:tileMode=repeat />

Igualmente, hemos definido el archivo JuegoActivity.java, que define la clase de la Actividad


principal con la lgica de la aplicacin. Veamos el aspecto de esta clase:
public class JuegoActivity extends Activity {
// Definimos las constantes del men del juego
private static final int MENU_RESUME = 1;
private static final int MENU_START = 2;
private static final int MENU_STOP = 3;
// Variables para definir el hilo del juego y su vista
private JuegoThread mJuegoThread;

295

Aula Mentor

private JuegoView mJuegoView;


// Variables para los efectos de sonido
public static SoundPool soundPool = null;
public static int idRebote;
public static int idGameOver;
public static int idWin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// La ventana de esta Actividad no tiene ttulo
requestWindowFeature(Window.FEATURE_NO_TITLE);
//Ocultamos la barra de notificaciones
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN);
// La pantalla no se apaga nunca
getWindow().addFlags(

WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Siempre se juega en vertical
setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Establecemos el layout de la Actividad.
// En este caso, s definimos un layout y luego dibujaremos el
// rea de juego con una Superficie superpuesta
setContentView(R.layout.main);
// Vista donde aparece el juego
mJuegoView = (JuegoView)findViewById(R.id.areajuego);
// Buscamos las Vistas de texto del juego
mJuegoView.setEstadoView((TextView)findViewById(R.id.texto));
mJuegoView.setPuntuacionView((TextView)

findViewById(R.id.puntuacion));
// Definimos el soundPool para los efectos de sonido
soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC , 0);
// Cargamos los efectos de sonido, al rebote le damos menos
// prioridad
idRebote = soundPool.load(this, R.raw.rebote, 0);
idGameOver = soundPool.load(this, R.raw.gameover, 1);
idWin = soundPool.load(this, R.raw.win, 1);

296

// Iniciamos el juego en esta Actividad


iniciarJuego(savedInstanceState);

// Mtodo que inicia el juego


private void iniciarJuego(Bundle savedInstanceState) {

// Si iniciamos la aplicacin por primera vez creamos
// la vista de Juego y el hilo que la gestiona
if (savedInstanceState == null) {
mJuegoThread = new JuegoThread(mJuegoView);
mJuegoView.setHilo(mJuegoThread);
mJuegoThread.setEstado(JuegoThread.ESTADO_PREPARADO);
}
else {
// Si ya existe el hilo entonces lo reactivamos
// pasndole los datos guardado de la instancia anterior
if(mJuegoThread != null) {

U3 Sensores y dispositivos de Android

mJuegoThread.restoreState(savedInstanceState);
// Si el juego estaba en el modo EJECUCION, lo pasamos
// a PAUSA para que el usuario pueda jugar
if(mJuegoThread.getEstado() ==JuegoThread.ESTADO_EJECUCION)
{

mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);

}
}
else {

// Si no existe el hilo pero ya hemos ejecutado la

// aplicacin, creamos un hilo nuevo.

JuegoThread jThread = new JuegoThread(mJuegoView);

mJuegoView.setHilo(jThread);

mJuegoThread = mJuegoView.getHilo();

// Recuperamos los datos anteriores

mJuegoThread.restoreState(savedInstanceState);

// El estado actual es preparado

mJuegoThread.setEstado(JuegoThread.ESTADO_PREPARADO);
}
}
// Accedemos al gestor de Sensores y se lo pasamos a la vista de
// Juego para que conecte el sensor que corresponda con su
// listener
mJuegoView.conectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
}
// Eventos de cambio de estado de la Actividad
@Override
protected void onPause() {
super.onPause();
// Si el juego est en ejecucin lo paramos
if(mJuegoThread.getEstado() == JuegoThread.ESTADO_EJECUCION) {

mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);
}
}
@Override
protected void onDestroy() {
super.onDestroy();

// Si se destruye la Actividad liberamos memoria de la Vista

// juego
mJuegoView.liberarTodo();
// Desconectamos del gestor de sensores
mJuegoView.desconectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
// Liberamos variables locales
mJuegoThread = null;
mJuegoView = null;

// Liberamos los sonidos guardados y la variable soundPool
soundPool.release();
soundPool = null;
}
@Override
// Mtodo que se lanza cuando es necesario guardar el estado de la

297

Aula Mentor

// Actividad
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Guardamos el estado del Hilo si ste existe
if(mJuegoThread != null) {

mJuegoThread.saveState(outState);
}
}
// Mtodo que asigna el men de la aplicacin
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_START, 0, R.string.menu_empezar);
menu.add(0, MENU_STOP, 0, R.string.menu_parar);
menu.add(0, MENU_RESUME, 0, R.string.menu_continuar);
return true;
}

298

// Si el usuario selecciona una opcin del men...


@Override
public boolean onOptionsItemSelected(MenuItem item) {

// Cambiamos el estado del Juego segn corresponda
switch (item.getItemId()) {
case MENU_START:
mJuegoThread.empezarJuego();
return true;
case MENU_STOP:

mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);
return true;
case MENU_RESUME:
mJuegoThread.continuarJuego();
return true;
}
return false;
} // end onOptionsItemSelected
} // end clase

En el evento onCreate() del cdigo anterior hemos utilizado la orden requestWindowFeature()


para ocultar el ttulo de la aplicacin y ampliar el espacio que ocupa la superficie. Posteriormente,
el mtodo setFlags() de la ventana (Window) establece los atributos FLAG_FULLSCREEN
y FLAG_KEEP_SCREEN_ON de la ventana para mostrarla en el modo pantalla completa (sin
barra de notificacin) y mantener la pantalla encendida si la aplicacin est en primer plano,
respectivamente. As, el usuario disfruta de toda la pantalla del dispositivo y puede jugar sin
distracciones. Tambin indicamos con la marca SCREEN_ORIENTATION_PORTRAIT que la
orientacin del dispositivo no cambie y sea siempre vertical (modo retrato).
En este caso, s que hemos definido un layout. Ms tarde, dibujaremos el rea de juego
con una Superficie superpuesta sobre la primera.
En esta clase hemos definido las variables del tipo JuegoView y JuegoThread, que
sirven de superficie de dibujo y de hilo de actualizacin (o de juego en este caso) de sta respectivamente, siguiendo el mismo esquema utilizado en el Ejemplo 8 de SurfaceView de la
Unidad 1.
Adems, puedes observar que hemos aadido efectos de sonido al juego mediante la

U3 Sensores y dispositivos de Android

clase SoundPool. En el Ejemplo 1 de la Unidad 1 puedes ver cmo se utiliza.

Es importante separar el cdigo en dos hilos: el hilo principal de la aplicacin y un


segundo hilo que se encarga de dibujar la superficie e interactuar con sta, para
que el usuario obtenga una sensacin ms fluida.

En el constructor de esta clase accedemos al gestor de Sensores (SENSOR_SERVICE) y lo pasamos


a la vista de Juego mediante el mtodo conectaSensor() para que conecte el sensor que
corresponda.
El mtodo iniciarJuego() se encarga de crear el Hilo y relacionarlo con su superficie
de juego teniendo en cuenta el estado del juego o el ciclo de vida de la aplicacin. Adems,
tambin accede al gestor de Sensores y lo conecta a la superficie del Juego para gestionar los
movimientos del dispositivo.
El mtodo onSaveInstanceState() guarda el estado del Hilo del juego, es decir, el
estado del juego propiamente dicho. El mtodo onPause() de la Actividad para el juego y onDestroy() libera las variables de memoria.
Finalmente, hemos definido el tpico Men de la aplicacin que gestiona las opciones del juego.
A continuacin, se muestra cmo se implementa la interfaz visual del rea de juego del usuario
mediante esta superficie definida en el fichero JuegoView.java:
// Vista que define el fondo del juego: hereda de SurfaceView e
// implementa su Callback
public class JuegoView extends SurfaceView implements
SurfaceHolder.Callback {
// Hilo que actualiza la SurfaceView
private volatile JuegoThread hilo;
// Listener del sensor
private SensorEventListener sensorListener;
// Utilizamos un Handler para la comunicacin entre la Vista del
// Juego y el Hilo. As recibimos mensajes del Hilo del juego.
private static Handler mHandler;
// Variables de las vistas de la interfaz usuario
private TextView mPuntuacionView;
private TextView mEstadoView;
// Constructor de la clase
public JuegoView(Context context, AttributeSet attrs) {

super(context, attrs);

// Obtenemos el Holder de la superficie y le asignamos los


// eventos definidos en esta clase

SurfaceHolder holder = getHolder();
holder.addCallback(this);

// Creamos el Handler que pasar mensajes del Hilo a la Vista


// del juego
mHandler = new Handler() {

299

Aula Mentor

300

@Override
// Definimos los mensajes
public void handleMessage(Message m) {

// Si el hilo indica el tipo de mensaje puntuacion

// actualizamos la Vista correspondiente

if(m.getData().getBoolean(puntuacion)) {

mPuntuacionView.setText(m.getData().getString(texto));

}

else // Si es otro tipo de mensaje entonces es un cambio de

// estado de la aplicacin

{
// Mostramos el TextView si el mensaje lo indica as
mEstadoView.setVisibility(m.getData().getInt(visible));
mEstadoView.setText(m.getData().getString(texto));

}

// Reproducimos un sonido si lo pide el mensaje
if (m.getData().getInt(sonido)>-1) {
if (m.getData().getInt(sonido)==R.raw.rebote)

JuegoActivity.soundPool.play(JuegoActivity.idRebote,
1, 1, 0, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.gameover)
JuegoActivity.soundPool.play(JuegoActivity.idGameOver,
1, 1, 1, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.win)

JuegoActivity.soundPool.play(JuegoActivity.idWin,
1, 1, 1, 0, 1);

}

}; // end Handler
} // end constructor
// Libera todos los recursos
public void liberarTodo() {

// Paramos el Hilo y limpiamos
this.hilo.setRunning(false);
this.hilo.liberarTodo();

// Quitamos el mtodo Callback de esta clase
this.removeCallbacks(hilo);

hilo = null;

// Quitamos el evento onTouch
this.setOnTouchListener(null);

// Liberamos el Listener del sensor

sensorListener = null;

// Quitamos el mtodo Callback de la superficie

SurfaceHolder holder = getHolder();
holder.removeCallback(this);
}
// Mtodo que establece el Hilo a la Vista de Juego
public void setHilo(JuegoThread elHilo) {

// Guardamos el hilo

hilo = elHilo;

// Delegamos el evento onTouch al Hilo

setOnTouchListener(new View.OnTouchListener() {

public boolean onTouch(View v, MotionEvent event) {
if(hilo!=null) {
return hilo.onTouch(event);

U3 Sensores y dispositivos de Android

}
else return false;
}
}); // end onTouch

// Definimos el listener del sensor y lo delegamos tambin al


// Hilo

this.sensorListener = new SensorEventListener() {

public void onAccuracyChanged(Sensor arg0, int arg1) {

// no es necesario
}

public void onSensorChanged(SensorEvent event) {
if(hilo!=null) {
if (hilo.isAlive()) {
hilo.onSensorChanged(event);
}
}
}
}; // end listener sensor


// Indicamos que el usuario puede hacer clic en la

// vista y que sta toma el foco
setClickable(true);
setFocusable(true);
}
// Devuelve el Hilo de esta Vista de juego
public JuegoThread getHilo() {

return hilo;
}
// Mtodos que devuelven o establecen las Vistas internas del juego
public TextView getEstadoView() {

return mEstadoView;
}

public void setEstadoView(TextView mEstadoView) {


this.mEstadoView = mEstadoView;
}

public TextView getPuntuacionView() {


return mPuntuacionView;
}

public void setPuntuacionView(TextView mPuntuacionView) {


this.mPuntuacionView = mPuntuacionView;
}

public Handler getmHandler() {


return mHandler;
}
public void setmHandler(Handler mHandler) {
JuegoView.mHandler = mHandler;
}

301

Aula Mentor

/*
* FUNCIONES DE PANTALLA
*/
// Si la superficie pierde el foco, entonces pausamos
// el juego. Esto ocurre, por ejemplo, si el usuario
// pulsa la tecla men de su dispositivo
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {

if(hilo!=null) {
if (!hasWindowFocus)
hilo.pausarJuego();
}
}

302

// Al crear la superficie iniciamos el hilo de sta


public void surfaceCreated(SurfaceHolder holder) {

if(hilo!=null) {
hilo.setRunning(true);

// Si el estado del hilo es nuevo entonces lo arrancamos

if(hilo.getState() == Thread.State.NEW){
hilo.start();
}
else {
// Si el hilo se ha terminado, lo reiniamos con el

// estado del anterior
if(hilo.getState() == Thread.State.TERMINATED){

hilo = new JuegoThread(this, hilo);
hilo.setRunning(true);
hilo.start();
}
}
}
} // end surfaceCreated
// Se lanza cuando cambia la superficie: indicamos al hilo el tamao
// de sta
public void surfaceChanged(SurfaceHolder holder, int format, int
width, int height) {

if(hilo!=null) {

// Indicamos el nuevo tamao de la superficie al hilo
hilo.setSurfaceSize(width, height);
}
}
// Si la superficie se destruye entonces hay que parar el hilo
public void surfaceDestroyed(SurfaceHolder arg0) {

if(hilo!=null) {

// Paramos el hilo
hilo.setRunning(false);

}

// Unimos el Hilo de actualizacin con el Hilo principal

// de la aplicacin ejecutando la orden join

// hasta que lo conseguimos. Hay que tener en cuenta que

// normalmente los juegos tienen varios hilos funcionando a la

// vez por lo que es necesario unirlos para destruirlos. En este

U3 Sensores y dispositivos de Android


// ejemplo hemos utilizado un nico hilo.

boolean reintentar = true;

while (reintentar) {
try {
if(hilo!=null) {
hilo.join();
}
reintentar = false;
}

catch (InterruptedException e) { }
}
} // end surfaceDestroyed
// Mtodos que conecta y desconecta el tipo de sensor
// TYPE_ORIENTATION con el SensorManager y el listener
// correspondiente
@SuppressWarnings(deprecation)
public void conectaSensor(SensorManager sm) {

// Es importante marcar con SENSOR_DELAY_GAME que el
// dispositivo debe indicar con el menor tiempo posible los

// cambios de orientacin para que el jugador note fluidez en

// las respuestas de sus movimientos.
sm.registerListener(this.sensorListener,
sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_GAME);
}
public void desconectaSensor(SensorManager sm) {
sm.unregisterListener(this.sensorListener);

this.sensorListener = null;
}
} // end class

Veamos el cdigo anterior que implementa la superficie que define el rea del juego.
Tal y como hemos estudiado en la Unidad 1, para utilizar una superficie debemos crear
una subclase que se extienda de la clase SurfaceView e implemente la interfaz SurfaceHolder.Callback para poder gestionar los mtodos relacionados con esta superficie:
En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
addCallback(this) para gestionar los eventos del SurfaceView y poder acceder a la citada
superficie.
Para actualizar las Vistas de esta superficie (hilo principal) desde el Hilo del juego es
recomendable utilizar un Handler de Java. Este mecanismo permite trasmitir informacin entre
hilos de una aplicacin.
Para ello, en el constructor de esta superficie definimos un Handler que comunica informacin desde el Hilo hacia la Vista del juego que recibe mensajes de ste, como actualizar la
puntuacin o cambiar el estado del juego. Para ello, implementamos el mtodo handleMessage() que gestiona los mensajes recibidos del hilo y actualiza las Vistas de la superficie.
En esta aplicacin hemos implementado algunos de los eventos que lanza una superficie.
Estudiemos las sentencias ms importantes de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos creado en el constructor de la clase JuegoActivity y que se ha recibido en la superficie mediante
el mtodo setHilo() desde la Actividad principal. Despus, establecemos el estado del hilo al
modo ejecucin y lo arrancamos con la orden start(). La funcin de este hilo es actualizar la

303

Aula Mentor

interfaz de usuario dibujndola cuando sea necesario.


El mtodo SurfaceDestroyed() se lanza cada vez que la aplicacin pasa a segundo
plano y el SurfaceView se va a destruir junto con todo su contenido. Por lo tanto, es necesario
parar el hilo. Para ello, unimos el Hilo del juego con el Hilo principal de la de la aplicacin invocando la orden join hasta que lo conseguimos. Si hubiera ms de dos hilos, sera necesario
unir todos estos hilos para destruirlos correctamente.
El evento onWindowFocusChanged() se invoca si la superficie pierde el foco y debemos
parar el juego. Esto ocurre, por ejemplo, si el usuario pulsa la tecla men de su dispositivo.
Como hemos comentado anteriormente, es imprescindible que la interaccin del usuario
con la aplicacin sea suave y los grficos se muestren sin parpadeos. Para conseguirlo, tenemos
que ejecutar dos hilos en la aplicacin: el hilo principal, que se encarga de gestionar la Actividad de la aplicacin, y el segundo hilo, que se encarga de dibujar la superficie, de gestionar la
interaccin del usuario con sta y los cambios del sensor.
La razn principal de esta divisin de tareas es que, como estudiamos en el curso de Iniciacin a Android, no debemos bloquear nunca el hilo principal de una aplicacin. Por ejemplo,
si el usuario presiona la pantalla tctil puede crear paradas en la ejecucin del cdigo.

Es muy importante separar siempre el cdigo en dos o ms hilos: el hilo principal


de la aplicacin y un segundo o tercer hilo que se encargan de dibujar las superficies, de gestionar la interaccin con stas y de obtener datos de los sensores, para
que el usuario obtenga una sensacin de juego ptima.
304

Hemos implementado el Hilo JuegoThread para actualizar el dibujo del objeto SurfaceView,
gestionar los toques sobre ste y obtener datos de los sensores. En el constructor de este hilo
pasamos como parmetro la referencia al objeto JuegoView, para poder modificar el contenido
de sta.
Por esto, en el mtodo surfaceChanged() del JuegoView obtenemos el tamao de la
pantalla del dispositivo Android y se lo pasamos al hilo JuegoThread.
No hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo el encargado de dibujarla.
En el mtodo setHilo() de esta clase hemos delegado el mtodo onTouchEvent() al
Hilo mediante la sentencia setOnTouchListener(). Tambin definimos, en este mtodo, el
listener del sensor y lo delegamos nuevamente al Hilo.
Este listener lo utilizamos en el mtodo conectaSensor(), donde mediante la orden
registerListener() del ServiceManager lo registramos indicando con SENSOR_DELAY_
GAME que el dispositivo debe comunicar con el menor tiempo posible los cambios de orientacin
(TYPE_ORIENTATION), para que el jugador note fluidez en las respuestas a sus movimientos.
Aunque el tipo de sensor TYPE_ORIENTATION es obsoleto (deprecated), lo hemos utilizado por sencillez en este Ejemplo. Si no queremos usar un sensor obsoleto, debemos utilizar una
combinacin de los sensores TYPE_ACCELEROMETER y TYPE_MAGNETIC_FIELD.
A continuacin, se muestra cmo se implementa este hilo en el archivo JuegoThread.java:
public class JuegoThread extends Thread{
// Estados del juego
public static final int ESTADO_PERDER = 1;
public static final int ESTADO_PAUSA = 2;
public static final int ESTADO_PREPARADO = 3;

U3 Sensores y dispositivos de Android

public static final int ESTADO_EJECUCION = 4;


public static final int ESTADO_GANAR = 5;
// Variable que almacena uno de los estados del juego anteriores
private int mEstado = 1;
// Indica si se debe ejecutar el Hilo
private boolean mRun = false;
// Superficie donde dibuja este hilo
private SurfaceHolder mSurfaceHolder;
// Handler para comunicar mensajes del Hilo a la Vista del Juego
private Handler mHandler;
// Contexto de la aplicacin Android
private Context mContexto;
// Vista del Juego (Superficie)
public JuegoView juegoView;
// Tamao del canvas de la superficie
protected int canvasWidth = 1;
protected int canvasHeight = 1;
// Tiempo de ejecucin desde la ltima actualizacin de la
// superficie
protected long tiempoEjec = 0;
// Imagen del fondo de pantalla
protected Bitmap mBackgroundImage;
// Puntuacin de la partida
private long puntuacion = 50;
// Imagen de la pelota
private Bitmap pelota;
// Posicin actual de la pelota
private float pelotaX = -1;
private float pelotaY = -1;
// Direccin de la pelota
private float pelotaVelocidadX = 0;
private float pelotaVelocidadY = 0;
// Constructor del Hilo
public JuegoThread(JuegoView eljuegoView) {
juegoView = eljuegoView;
// Obtenemos la superficie del juego, su Handler y el Contexto
mSurfaceHolder = eljuegoView.getHolder();
mHandler = eljuegoView.getmHandler();
mContexto = eljuegoView.getContext();
// Cargamos las imgenes de fondo del juego de la pelota
mBackgroundImage = BitmapFactory.decodeResource
(eljuegoView.getContext().getResources(),
R.drawable.background);
pelota = BitmapFactory.decodeResource

305

Aula Mentor

(eljuegoView.getContext().getResources(),
R.drawable.pelota);

}
// Constructor para cuando se desea jugar otra vez
public JuegoThread(JuegoView gameView, JuegoThread oldThread) {
// Recuperamos las variables anteriores
juegoView = gameView;
mSurfaceHolder = gameView.getHolder();
mHandler = gameView.getmHandler();
mContexto = gameView.getContext();
// Transferimos los valores del juego anterior
mEstado = oldThread.mEstado;
mRun = oldThread.mRun;
canvasWidth = oldThread.canvasWidth;
canvasHeight = oldThread.canvasHeight;
tiempoEjec = oldThread.tiempoEjec;
mBackgroundImage = oldThread.mBackgroundImage;
puntuacion = oldThread.puntuacion;

306

pelota = oldThread.pelota;
pelotaX = oldThread.pelotaX;
pelotaY = oldThread.pelotaY;
pelotaVelocidadX = oldThread.pelotaVelocidadX;
pelotaVelocidadY = oldThread.pelotaVelocidadY;

// Liberamos todas las variables


public void liberarTodo() {
this.mContexto = null;
this.juegoView = null;
this.mHandler = null;
this.mSurfaceHolder = null;
}
// Mtodo que inicia el juego
public void empezarJuego() {
// Sincronizamos estos cambios sobre la superficie
synchronized(mSurfaceHolder) {
// La pelota no se mueve (no tiene direccin)
pelotaVelocidadX = 0;
pelotaVelocidadY = 0;
// La centramos en la pantalla
pelotaX = canvasWidth / 2;
pelotaY = canvasHeight / 2;
// Tiempo de ejecucin
tiempoEjec = System.currentTimeMillis() + 100;
// Pasamos al estado de Ejecucin
setEstado(ESTADO_EJECUCION);
// La puntuacin es 0
setPuntuacion(50);
}
} // end empezarJuego
// Mtodos que guardan y recuperan el estado del Hilo

U3 Sensores y dispositivos de Android

public Bundle saveState(Bundle bundle) {


// Sincronizamos la superficie para ningn proceso pueda

// acceder a ella
synchronized (mSurfaceHolder) {
// A continuacin guardamos la puntuacin, estado
// y posicin/velocidad de la pelota para poder recuperar
// luego en el restore todos los datos y poder reiniciar

// el juego
if (bundle != null) {
bundle.putLong(puntuacion, puntuacion);
bundle.putInt(modoJuego, mEstado);
bundle.putFloat(mPelotaX, pelotaX);
bundle.putFloat(mPelotaY, pelotaY);
bundle.putFloat(mPelotaDX, pelotaVelocidadX);
bundle.putFloat(mPelotaDY, pelotaVelocidadY);

}
}
return bundle;
} // end saveState

public synchronized void restoreState(Bundle savedState) {


// Sincronizamos la superficie para que ningn proceso pueda

// acceder a ella
synchronized (mSurfaceHolder) {
// Pausamos el juego hasta que sepamos en qu estado se
// qued el juego
setEstado(ESTADO_PAUSA);
// Leemos las variables guardadas
setPuntuacion(savedState.getLong(puntuacion));
pelotaX = savedState.getFloat(mPelotaX);
pelotaY = savedState.getFloat(mPelotaY);
pelotaVelocidadX = savedState.getFloat(mPelotaDX);
pelotaVelocidadY = savedState.getFloat(mPelotaDY);
Integer modoJuego = savedState.getInt(modoJuego);
// Si el modo de juego era PERDER o GANAR cambiamos al

// modo final. Si no, lo dejamos en PAUSA para que el

// usuario haga clic en la pantalla y pueda continuar.
if (modoJuego == JuegoThread.ESTADO_PERDER |
modoJuego == JuegoThread.ESTADO_GANAR) {
setEstado(modoJuego);
}
}
} // end restoreState
// Mtodo principal del hilo
@Override
public void run() {
Canvas canvas;
// Mientras el hilo se ejecuta...
while (mRun) {
canvas = null;
try {
// Obtenemos el canvas de la superficie
canvas = mSurfaceHolder.lockCanvas(null);
// Sincronizamos la superficie para ningn proceso

307

Aula Mentor

308

// pueda acceder a ella


synchronized (mSurfaceHolder) {
// Si estamos jugando movemos la pelota
if (mEstado == ESTADO_EJECUCION) {
muevePelota();
}
// Dibujamos la superficie
doDraw(canvas);
}

}
finally {
// Acabamos y desbloqueamos la superficie
if (canvas != null) {
if(mSurfaceHolder != null)
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}

// Se usa para establecer el nuevo tamao de la superficie


public void setSurfaceSize(int width, int height) {
// Sincronizamos la superficie para que ningn proceso pueda

// acceder a ella
synchronized (mSurfaceHolder) {
// Guardamos el nuevo tamao
canvasWidth = width;
canvasHeight = height;
// Reescalamos el fondo de la superficie
mBackgroundImage =
Bitmap.createScaledBitmap(mBackgroundImage,

width, height, true);
}
}
// Mtodo que dibuja la superficie de juego
protected void doDraw(Canvas canvas) {
// Si no existe canvas no hacemos nada
if(canvas == null) return;
// Si tenemos la imagen de fondo, la aadimos al fondo de la
// superficie
if(mBackgroundImage != null)
canvas.drawBitmap(mBackgroundImage, 0, 0, null);

// Colocamos la pelota la primera vez que se dibuja la


// superficie
if (pelotaX<0) {
pelotaX = (canvasWidth-pelota.getWidth()) / 2;
pelotaY = canvasHeight / 3;
}
// Dibujamos la pelota en la posicin que corresponda
canvas.drawBitmap(pelota, pelotaX, pelotaY, null);

// Mtodo que mueve la pelota en la zona de juego.


// Habitualmente los desarrolladores de juegos a este mtodo

U3 Sensores y dispositivos de Android

// lo denominan Physics ya que simula la fsica (movimiento)


// del juego.
protected void muevePelota() {
// Obtenemos el tiempo actual
long tAhora = System.currentTimeMillis();
// Calculamos el tiempo que ha pasado desde la ltima
// ejecucin
float tPasado = (tAhora - tiempoEjec) / 1000.0f;
// Calculamos la posicin siguiente de la pelota en funcin
// del tiempo pasado y la direccin de la pelota
pelotaX += tPasado * pelotaVelocidadX;
pelotaY += tPasado * pelotaVelocidadY;

// Si la pelota da en los laterales de la pantalla aumentamos


// puntuacin en 1. Hay que tener en cuenta que el ancho de
// los muros de muelles es de 30 px
if((pelotaX <= 30 & pelotaVelocidadX < 0) ||
(pelotaX >= canvasWidth - pelota.getWidth()-30 &&
pelotaVelocidadX > 0) ) {
// Cambia la direccin de la pelota en el eje X
pelotaVelocidadX = -pelotaVelocidadX;
actualizaPuntuacion(-1);

// Reproducimos sonido rebote
sonidoRebote();
}
else
// Si la pelota se encuentra en la parte de arriba de la
// pantalla
if(pelotaY <= 25) {
// Si la pelota est en la imagen casa acabamos el juego.

// El tamao de la imagen 64
if (pelotaX >= canvasWidth / 2 - 64 &&

pelotaX <= canvasWidth / 2) {
// Aadimos 50 a la puntuacin
actualizaPuntuacion(50);
setEstado(JuegoThread.ESTADO_GANAR);
// Si no, rebota la pelota en el eje Y. Aumenta la

// puntuacin en 1
}else {
pelotaVelocidadY = -pelotaVelocidadY;
actualizaPuntuacion(-1);

// Reproducimos sonido rebote
sonidoRebote();
}
} else
// Si la pelota toca la parte de abajo, se acaba el juego
if(pelotaY >= canvasHeight - pelota.getHeight())

setEstado(JuegoThread.ESTADO_PERDER);
// Guardamos el ltimo tiempo de ejecucin
tiempoEjec = tAhora;
} // end doDraw

// Si el usuario toca la pantalla


public boolean onTouch(MotionEvent e) {
// Slo queremos detectar un toque en la pantalla. Por lo tanto,
// si no es un toque, no hacemos nada.

309

Aula Mentor

if(e.getAction() != MotionEvent.ACTION_DOWN) return false;


// Si estamos en un estado de final creamos un nuevo juego
if(mEstado == ESTADO_PREPARADO || mEstado == ESTADO_PERDER

|| mEstado == ESTADO_GANAR) {
empezarJuego();
return true;
}
else
// Si estamos en el estado PAUSA seguimos con el juego
if(mEstado == ESTADO_PAUSA) {
continuarJuego();
return true;
}
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {

this.actionOnTouch(e);
}
return false;
} // end onTouch

310

private void actionOnTouch(MotionEvent e) { }


// Evento que se lanza cuando hay un cambio en una medida del
// sensor. Aunque el tipo de sensor TYPE_ORIENTATION es obsoleto
//(deprecated), lo hemos utilizado por sencillez en el proyecto.
// Si no queremos usar un sensor obsoleto debemos utilizar una
// combinacin de los sensores TYPE_ACCELEROMETER y
// TYPE_MAGNETIC_FIELD.
@SuppressWarnings(deprecation)
public void onSensorChanged(SensorEvent event) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {

// La direccin de la pelota la da la posicin del sensor

// orientacin
if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
// Inclinacin alrededor del eje x (-90 a 90)
pelotaVelocidadX -= 1.5f*event.values[2];
// Rotacin sobre el eje Y (-180 a 180)
pelotaVelocidadY -= 1.5f*event.values[1];
}
}
} // end onSensorChanged
// Cambios de estado del juego
public void pausarJuego() {
synchronized (mSurfaceHolder) {
if (mEstado == ESTADO_EJECUCION) setEstado(ESTADO_PAUSA);
}
}
public void continuarJuego() {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {

// Actualizamos el tiempo de ejecucin para que no

// se produzca un salto
tiempoEjec = System.currentTimeMillis();

U3 Sensores y dispositivos de Android

}
setEstado(ESTADO_EJECUCION);

// Mtodo que establece el estado del juego tanto desde este Hilo
// como desde la Actividad juego
public void setEstado(int estado) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {

// Guardamos el nuevo estado
mEstado = estado;
// Si el estado cambia a ejecucin creamos un nuevo

// mensaje y lo enviamos al JuegoView para que actualice

// las Vistas en funcin del estado del juego
if (mEstado == ESTADO_EJECUCION) {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
// Ocultamos la etiqueta del estado del juego
b.putString(texto, );
b.putInt(visible, View.INVISIBLE);
// Enviamos el mensaje al JuegoView
msg.setData(b);
mHandler.sendMessage(msg);
}
// Si no estamos jugando debemos mostrar el mensaje

// correspondiente
else {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
Resources res = mContexto.getResources();
CharSequence str = ;
if (mEstado == ESTADO_PREPARADO)
str = res.getText(R.string.modo_preparado);
else
if (mEstado == ESTADO_PAUSA)
str = res.getText(R.string.modo_pausa)+ \n +

res.getText(R.string.mensaje_parado);
else
if (mEstado == ESTADO_PERDER) {
str = res.getText(R.string.modo_perder);

// Reproducimos sonido perder
b.putInt(sonido, R.raw.gameover);
} else
if (mEstado == ESTADO_GANAR) {
str =
res.getText(R.string.modo_ganar);

// Sonido de ganar
b.putInt(sonido, R.raw.win);
}
b.putString(texto, str.toString());
// Mostramos la etiqueta del estado del juego
b.putInt(viz, View.VISIBLE);
// Enviamos el mensaje al JuegoView

311

Aula Mentor

msg.setData(b);
mHandler.sendMessage(msg);

}
}
} // end detestado
// Mtodo que enva un mensaje al hilo principal
// para que reproduzca sonido de rebote
public void sonidoRebote() {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
b.putInt(sonido, R.raw.rebote);
msg.setData(b);
// Enviamos el mensaje del sonido
mHandler.sendMessage(msg);
} // end sonidoRebote

312

// Mtodo que establece la superficie


public void setSurfaceHolder(SurfaceHolder sh) {
mSurfaceHolder = sh;
}
// Indica si estamos ejecutando el hilo
public boolean isRunning() {
return mRun;
}
// Cambia el hilo a modo ejecucin
public void setRunning(boolean running) {
mRun = running;
}
// Obtiene el estado del juego
public int getEstado() {
return mEstado;
}
// Mtodo que enva mediante un Handler la puntuacin al JuegoView
public void setPuntuacion(long puntuacion) {
this.puntuacion = puntuacion;
// Sincronizamos la superficie para que ningn proceso pueda

// acceder a ella
synchronized (mSurfaceHolder) {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
// Creamos los datos del mensaje
b.putBoolean(puntuacion, true);
// Es necesario hacer un typecasting del campo puntuacin
b.putString(texto,
Long.toString(Math.round(this.puntuacion)).toString());
// Enviamos el mensaje al JuegoView
msg.setData(b);
mHandler.sendMessage(msg);
}
} // end setPuntuacion
// Actualiza la puntuacin actual sumando la actual a los nuevos
// puntos obtenidos
public void actualizaPuntuacion(long puntuacion) {
this.setPuntuacion(this.puntuacion + puntuacion);
}
} // end clase

U3 Sensores y dispositivos de Android

Empecemos por comentar que, en el cdigo anterior, hemos definido las constantes que
empiezan por ESTADO_ para controlar el estado del juego y actuar en consecuencia.
Puedes ver tambin que esta clase es un hilo tpico de Java con su correspondiente mtodo run(). En ste puedes ver que bloqueamos el canvas de la superficie para dibujarlo con
la orden mSurfaceHolder.lockCanvas() y, cuando es necesario, sincronizamos los mtodos
internos que mueven y dibujan la pelota. Si el usuario est jugando, entonces llamamos al
mtodo muevePelota(), que desplaza la pelota en la zona de juego. Habitualmente los desarrolladores de juegos a este mtodo lo denominan physics() ya que simula la fsica (movimiento)
del juego. Adems, en este bloque run() dibujamos la superficie con la orden doDraw(). Una
vez hemos ejecutado este bloque de cdigo, liberamos el canvas de la superficie desbloquendolo con la orden unlockCanvasAndPost(canvas).
Si te fijas en el mtodo doDraw() vers que nicamente pinta la superficie de juego con
la pelota en la posicin que corresponda.
Sin embargo, muevePelota() calcula la posicin siguiente de la pelota en funcin del
tiempo pasado (as simulamos el movimiento) y la direccin de la pelota. Tambin cambia el
estado del juego si la pelota acaba en ciertas posiciones de la pantalla.
El mtodo onTouch() sirve para interactuar con la pulsacin del usuario en la pantalla.
Este mtodo tiene como parmetro la clase MotionEvent que permite conocer la accin del
usuario mediante la orden getAction(). As, slo utilizamos la accin MotionEvent.ACTION_
DOWN cuando toca la pantalla para continuar o empezar el juego.
Para que el jugador controle el movimiento de la pelota, hemos utilizando el sensor de
orientacin y desarrollado el mtodo onSensorChanged() que nos proporciona en su parmetro SensorEvent la inclinacin alrededor del eje x (-90 a 90) y la rotacin sobre el eje Y (-180
a 180), que empleamos para obtener la nueva velocidad y sentido de la pelota.
Es importante notar que esta clase tiene dos constructores. El tradicional
JuegoThread(JuegoView eljuegoView) que obtiene la superficie del juego, su Handler
y el Contexto de la aplicacin. Y un segundo mtodo JuegoThread(JuegoView gameView,
JuegoThread oldThread) que utilizamos en el mtodo surfaceCreated() de JuegoView
para comprobar con la constante Thread.State.NEW si el hilo se ha ejecutado alguna vez. En
la documentacin de Android se indica que es obsoleto parar y reiniciar hilos. Ten en cuenta
que un usuario puede decidir abrir otra aplicacin mientras est jugando y esto pausa el juego
destruyendo la superficie, por lo que volver a recrearla.
Por lo tanto, una forma de recrear un hilo nuevo es definir un segundo mtodo JuegoThread() donde copiamos todos los atributos del hilo existente y, as, el nuevo hilo comienza
en el estado en el que se qued anteriormente garantizando la experiencia del usuario al continuar el juego en el estado donde se qued.
Te recomendamos que le eches un vistazo a los mtodos setPuntuacion() y setEstado() que establecen en la superficie JuegoView la puntuacin y el estado del juego,
respectivamente, desde este hilo utilizando un objeto de la clase Bundle. En esta clase vamos a
introducir la informacin que deseamos pasar al hilo principal, a aadirla al objeto Message con
setData(bundle) y a enviar este mensaje con la orden sendMessage(mensaje) de Handler.
Como no podemos bloquear el hilo del juego, hemos aprovechado estos mensajes para
notificar al hilo principal que debe reproducir sonidos con la clase asncrona SoundPool en
funcin del estado del juego o cuando la pelota rebota en los muelles.
Finalmente, hemos definido algunos mtodos auxiliares, como empezarJuego() que inicia el
juego, saveState() y restoreState() que guardan y recuperan el estado de ejecucin del
Hilo, respectivamente, y varios mtodos ms del tipo set() y get() necesarios en el hilo.

313

Aula Mentor

Cuando se utilizan hilos es imprescindible usar la orden synchronized en el objeto dinmico correspondiente para evitar accesos simultneamente a ste que
lo dejan en un estado incompleto, de forma que el usuario tendr la sensacin de
que la aplicacin se mueve a trompicones. En este caso, lo hacemos siempre que
vamos a modificar la superficie.

Aunque es posible utilizar el SensorSimulator para depurar la aplicacin, es lento y poco prctico;
por lo tanto, recomendamos probar esta aplicacin en un dispositivo real de Android.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

314

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (Juego) de la Unidad 3.


Estudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado
del programa anterior, en el que hemos utilizado un elemento SurfaceView y un
Sensor para desarrollar un juego.

Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente aplicacin:

Puedes probar a mover el dispositivo para constatar cmo cambia el sentido de la pelota y or
los efectos de sonido. Esperamos que el juego te resulte entretenido!

U3 Sensores y dispositivos de Android

6. Resumen
Hay que saber al final de esta unidad:
- La mayora de los dispositivos de Android incorporan sensores que miden su
movimiento, orientacin y otras varias magnitudes fsicas.
- Los tipos de sensores de Android son:
Sensores de Movimiento
Sensores del Medioambiente
Sensores de Posicin
- Android permite acceder a los sensores del dispositivo a travs de las clases:
Sensor: clase que representa a un sensor con todas sus propiedades.
SensorEvent: clase que almacena los datos medidos por el sensor.
SensorManager: gestor que permite acceder a los sensores del dispositivo.
SensorEventListener: interfaz utilizada para recibir las notificaciones del
SensorManager.
- Se pueden dividir los sensores en dos categoras ms: real y virtual. Real seala
que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de otros sensores o son medidas relativas.
- Para desarrollar aplicaciones que utilizan sensores, es necesario disponer de un
dispositivo fsico ya que el emulador de Android no permite depurarlas correctamente.
- Por defecto, en el emulador de dispositivos virtuales (AVD) del SDK de Android
no se pueden utilizar sensores para depurar aplicaciones.
- SensorSimulator es un simulador de sensores que permite depurar una aplicacin Android que los use.
- El SDK de Android dispone de la clase WifiManager que permite hacer uso del
mdulo WIFI, siempre y cuando el dispositivo disponga de l.
- El SDK de Android tambin ofrece soporte para la tecnologa Bluetooth que permite a un dispositivo intercambiar datos de forma inalmbrica con otros dispositivos cercanos.
- Las clases ms importantes del SDK Bluetooth de Android son:
BluetoothAdapter: permite realizar todo tipo de operaciones sobre este
mdulo bluetooth.
BluetoothDevice: representa un dispositivo bluetooth remoto con sus atributos y propiedades correspondientes.
BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin
de datos.
- Para utilizar este mdulo Bluetooth es necesario definir un receptor de mensajes
del tipo BroadcastReceiver para recibir los eventos del BluetoothAdapter cada
vez que encuentre un dispositivo.
- El AVD de Android no incluye los mdulos WIFI ni de Bluetooth; por lo tanto, es
necesario depurar aplicaciones que los usen en un dispositivo real de Android.
- Prcticamente todos los dispositivos Android integran una o varias cmaras de
fotos. El SDK de Android incluye las clases Java necesarias para gestionar cmaras de fotos.

315

Aula Mentor

- Existen dos formas de gestionar la cmara de fotos:


Utilizando un Intent que gestiona la cmara de fotos y devuelve la imagen a la
aplicacin que lo ha iniciado.
Integrando directamente la cmara en la aplicacin utilizando las libreras
(API) de Android.
- Las clases ms importantes de la API de la cmara de Android son:
Camera: clase bsica de la API para gestionar la cmara, tomar fotos y grabar
vdeos.
CameraInfo: clase que contiene la informacin de la cmara.
SurfaceView: clase que se usa para mostrar al usuario la previsualizacin de
la cmara de fotos.
MediaRecorder: clase utilizada para grabar vdeos.
- Es muy importante gestionar los mtodos del ciclo de vida de la Actividad que
contiene la cmara de fotos, ya que sta consume muchos recursos del sistema,
como CPU y batera, por lo que slo se deben utilizar cuando sea necesario.
- Muchos dispositivos Android integran el mdulo GPS de localizacin geogrfica
mundial. El SDK de Android incluye las clases Java necesarias para gestionarlo.
- La clase LocationManager de Android permite gestionar el mdulo GPS.
- Existen varios mtodos de obtener la localizacin de un dispositivo mvil: mediante el mdulo GPS, utilizando las antenas de telefona mvil o mediante los
puntos de acceso WI-FI cercanos.
316

U4 Bibliotecas, APIs y Servicios de Android

Unidad 4. Bibliotecas, APIs y Servicios de


Android

1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan bibliotecas de una aplicacin para ampliar su
funcionalidad.
Adems, usaremos la API de Telefona de Android para gestionar llamadas de telfono y
mensajes cortos (SMS) desde un dispositivo.
Despus, utilizaremos el Calendario de Android para administrar las citas y eventos de
ste.
Tambin veremos cmo aprovechar la funcionalidad del gestor de descargas (Download
manager) de Android.
Finalmente, estudiaremos cmo enviar un correo electrnico e implementaremos Servicios avanzados en Android.

2. Uso de Bibliotecas en Android


Una biblioteca (del ingls library) es un conjunto de funciones y utilidades que tienen una
interfaz bien definida para el comportamiento que se invoca. A diferencia de un programa
ejecutable, una biblioteca no se puede ejecutar de forma autnoma, pues su fin es ser utilizada
por otros programas, a veces, de forma simultnea.
Segn el tipo de sistema operativo, estas bibliotecas pueden tener distinta extensin: .SO
en Unix, .DLL en Windows, etctera.
Android, como sistema operativo completo, tambin dispone de este tipo de biblioteca
que ayuda a ampliar de forma sencilla las aplicaciones utilizando bibliotecas externas a stas, si
bien, la biblioteca de Android posee una serie de peculiaridades especiales que la diferencian
de las tpicas bibliotecas. La estudiaremos a continuacin.
El SDK de Android dispone de proyectos de tipo biblioteca (del ingls, library projects).
Un proyecto de tipo biblioteca es un conjunto de cdigo Java y recursos que parece un
proyecto normal Android, pero que nunca acaba en un archivo de tipo .apk en s mismo. Sin
embargo, cuando incluimos este cdigo y recursos en un proyecto Android, pasa a formar parte
de ste y se compilan en un archivo nico de tipo .apk. Es decir, las bibliotecas en Android son
funciones y recursos que podemos compilar conjuntamente en un proyecto Android.
Veamos las caractersticas de estas bibliotecas:
- Pueden tener su propio nombre de paquete Java.
- Se compila siempre como en un proyecto de Android que lo utiliza para ampliar su funcionalidad.
- Puede utilizar otros archivos JAR.
- No se puede convertir en un archivo JAR en s mismo, si bien Google lo va a permitir en
versiones futuras de Android.

317

Aula Mentor

- A excepcin de los archivos de Java, el resto de los archivos que pertenecen a una biblioteca,
por ejemplo, recursos de tipo imagen, se mantiene en el proyecto de tipo biblioteca.
- Si utilizamos una biblioteca en un proyecto Android, es necesario incluirla en ste como
dependencia del proyecto.
- A partir del SDK 15 de Android los IDs de recursos de la biblioteca no son finales (final).
- Tanto el proyecto de tipo biblioteca como el proyecto principal pueden acceder a los recursos internos de la biblioteca a travs de sus respectivas referencias a R.java.
- Es posible definir IDs de recursos repetidos en el proyecto principal y en la biblioteca. Los
IDs del proyecto principal tendrn prioridad sobre los de la biblioteca. Por esto, es recomendable distinguir los IDs de recursos entre los dos proyectos utilizando diferentes prefijos
de recursos, por ejemplo, anteponiendo el prefijo lib_ para los recursos de la biblioteca.
- Un proyecto principal puede hacer referencia a varias bibliotecas al mismo tiempo. Es posible establecer la prioridad de las bibliotecas para marcar qu recursos son ms importantes.
- Si queremos utilizar una Actividad (Activity) o un Servicio (Service) desarrollados en una
biblioteca, deben incluirse en el archivo manifest.xml del proyecto principal. Adems, es
necesario indicar el nombre del paquete completo de la biblioteca al incluir la Actividad o
el Servicio.
- Un proyecto de tipo biblioteca no puede hacer referencia a otra biblioteca, si bien en futuras
versiones del SDK de Android se podr hacer.

318

Es importante saber integrar bibliotecas en proyectos Android ya que existen muchas bibliotecas de cdigo libre disponibles en Internet que pueden simplificar la
programacin de un proyecto.

Para crear un proyecto de tipo biblioteca, debemos crear un proyecto normal de Android y
luego marcar la opcin biblioteca (library). Veamos de forma prctica cmo definir y utilizar una
biblioteca en Android.

2.1 Ejemplo de Biblioteca de Android


A continuacin, vamos a estudiar cmo crear e integrar una biblioteca en una aplicacin Android.
En el Ejemplo 1 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza una
biblioteca.
Lo primero que vamos a hacer es crear la biblioteca. Para ello, pulsamos en el men
principal de Eclipse ADT en la opcin siguiente:

U4 Bibliotecas, APIs y Servicios de Android

Aparecer esta ventana que debemos rellenar de la siguiente manera:

319

A continuacin, pulsamos el botn Next y aparecer esta ventana:

Aula Mentor

En la ventana anterior es muy importante marcar la opcin Mark this Project as a library.
Despus, hacemos clic de nuevo en el botn Next donde podramos cambiar el icono de la
biblioteca:

320
Volvemos a pulsar el botn Next para pasar a la siguiente pantalla:

U4 Bibliotecas, APIs y Servicios de Android

En esta ventana seleccionamos Blank Activity y pulsamos el botn Next:

321

En la ventana anterior rellenamos los campos tal y como aparecen en la captura y para acabar
la creacin de la biblioteca hacemos clic en el botn Finish.
Si abres el explorador de archivos en la carpeta bin del proyecto anterior (unidad4.
eje1.biblioteca) vers que Eclipse ADT ya ha compilado el proyecto en el formato .jar de
biblioteca Java:

Esta biblioteca consta de una Actividad secundaria que invocaremos desde la aplicacin principal
y de una imagen (robot.png) que usaremos en sta:

Aula Mentor

Si abres el archivo activity_actividad_secundaria.xml, vers que el diseo es muy sencillo


y contiene una etiqueta y dos botones:

322

<LinearLayout

xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:paddingBottom=@dimen/activity_vertical_margin
android:paddingLeft=@dimen/activity_horizontal_margin
android:paddingRight=@dimen/activity_horizontal_margin
android:paddingTop=@dimen/activity_vertical_margin
tools:context=.ActividadSecundaria >
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/etiqueta />

<LinearLayout

android:id=@+id/linearLayout

android:layout_width=fill_parent

android:layout_height=wrap_content

android:orientation=horizontal

android:gravity=top

android:layout_marginTop=10dp>
<Button
android:id=@+id/botonOK
android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Resultado OK
android:layout_weight=1

android:onClick=botonClick />
<Button
android:id=@+id/botonCANCEL
android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Resultado CANCEL
android:layout_weight=1

android:onClick=botonClick/>

U4 Bibliotecas, APIs y Servicios de Android


</LinearLayout>
</LinearLayout>

La lgica de la Actividad se define en el archivo ActividadSecundaria.java, que tiene este aspecto:


public class ActividadSecundaria extends Activity {
@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_actividad_secundaria);

// Recuperamos el parmetro y mostramos un mensaje

Bundle extras = getIntent().getExtras();

String parametro = extras.getString(parametro);

Toast.makeText(this, Parmetro recibido: +parametro,
Toast.LENGTH_LONG).show();
}

// Evento que se lanza cuando el usuario hace clic en un botn

public void botonClick(View v) {

Intent returnIntent = new Intent();

// Segn el botn que sea devolvemos un resultado distinto
if (v.getId()==R.id.botonOK)

setResult(RESULT_OK,returnIntent);
else
setResult(RESULT_CANCELED,returnIntent);

finish();
}
} // end clase

Se trata de una sencilla Actividad que devuelve RESULT_OK o RESULT_CANCELED segn el botn
donde pulse el usuario.
Ahora vamos a crear una aplicacin Android como viene siendo habitual. No se incluye
el proceso ya que el alumno o alumna debe saber crear el proyecto.
Despus, aadimos la biblioteca creada anteriormente haciendo clic con el botn derecho del ratn y seleccionando la opcin del men desplegable Properties. Accediendo a la
opcin Android, pulsamos el botn Add para aadir la biblioteca creada anteriormente:

323

Aula Mentor

Una vez importada la librera biblioteca, vers que aparecen dos archivos de recursos en la
carpeta gen: uno de la aplicacin que contiene la biblioteca (es.mentor.unidad4.eje1.
biblioteca_aplicacion) y el otro de la biblioteca (es.mentor.unidad4.eje1.biblioteca)
propiamente dicho:

Estudiemos ahora cmo utilizar los recursos (imagen robot.png) y clase (ActividadSecundaria)
que define esta biblioteca.

324

En el archivo layout activity_main.xml definimos la interfaz de usuario que consta de un


botn que lanza la Actividad de la biblioteca y de una imagen que obtenemos de sta:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=match_parent
android:layout_margin=2dp >
<TextView

android:layout_width=match_parent

android:layout_height=wrap_content

android:layout_marginBottom=5dp

android:text=@string/etiqueta />
<Button

android:id=@+id/boton

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Lanza actividad secundaria

android:layout_gravity=center

android:onClick=lanzaActSec />

<TextView

android:layout_width=match_parent

android:layout_height=wrap_content

android:text=@string/etiqueta2 />

<ImageView

android:layout_width=wrap_content

android:layout_height=wrap_content

U4 Bibliotecas, APIs y Servicios de Android


android:layout_gravity=center

android:layout_margin=10dp

android:src=@drawable/robot />
</LinearLayout>

Lo primero que observamos en el fichero anterior es que la imagen de la biblioteca est disponible
como recurso del proyecto y basta escribir @drawable/robot para acceder a ella.
En el archivo MainActivity.java desarrollamos la lgica de la aplicacin:
public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

// Mtodo onClick del botn que lanza la Actividad secundaria


public void lanzaActSec(View v) {
// Aadimos un parmetro a la invocacin
Intent intent = new Intent(this,ActividadSecundaria.class);

intent.putExtra(parametro, Texto del parmetro);

// Lanzamos el Intent esperando su respuesta
startActivityForResult(intent, 1);
}
// Evento que se lanza cuando la Actividad secundaria acaba
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
// Si respondemos a la actividad ejecutada
if (requestCode == 1) {
// Mostramos un mensaje en funcin del resultado de la

// Actividad
if (resultCode == RESULT_OK) {

Toast.makeText(this, Actividad secundaria OK,
Toast.LENGTH_LONG).show();
} else

if (resultCode == RESULT_CANCELED) {

Toast.makeText(this, Actividad secundaria
CANCELADA, Toast.LENGTH_LONG).show();
}
}
} // end onActivityResult
} // end clase

En las sentencias anteriores hemos utiliza la clase ActividadSecundaria definida en la biblioteca


como una clase del proyecto invocndola cuando el usuario hace clic en el botn correspondiente.
Para que la ActividadSecundaria est disponible en la aplicacin, debemos incluir en el archivo
manifest.xml del proyecto lo siguiente:
<application

325

Aula Mentor

android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/AppTheme android:allowBackup=true>
<activity
android:name=

es.mentor.unidad4.eje1.biblioteca_aplicacion.MainActivity>
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category

android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
<activity android:name=

es.mentor.unidad4.eje1.biblioteca.ActividadSecundaria
android:label=Actividad secundaria/>
</application>

Para que la ActividadSecundaria est disponible en el proyecto es muy importante incluir el nombre completo del paquete de la librera donde se define.

326

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 1 (Biblioteca) de la Unidad 4.


Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado una Biblioteca de Android.

Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente aplicacin:

U4 Bibliotecas, APIs y Servicios de Android

Si pulsas en el botn Lanza actividad secundaria aparecer la Actividad de la segunda captura


de la derecha.
Como puedes observar, utilizar bibliotecas es muy sencillo, bastante prctico desde el
punto de vista del programador y permite ampliar de forma rpida la funcionalidad de una aplicacin Android.

3. APIs del telfono: llamadas y SMS


Antes de estar disponible Android en otros tipos de dispositivos, este sistema operativo inici su
carrera en telfonos mviles. Por lo tanto, dispone de su propia API de telefona para gestionar
llamadas de telfono y mensajes cortos (SMS).
En este apartado, mediante un ejemplo prctico, se hace una explicacin detallada de las
funciones propias de esta API. Conocerlas te permitir realizar cualquier aplicacin que haga uso
de sus funcionalidades disponibles en el paquete android.telephony.
Esta API proporciona acceso a la informacin sobre los servicios de telefona disponibles
en el dispositivo y obtiene informacin sobre su estado. Adems, accede a algunos tipos de informacin del usuario: IMEI del dispositivo, nmero de telfono, operador, potencia de la red,
etctera.
Asimismo, las aplicaciones pueden registrar un listener para recibir notificaciones de
cambios del estado del telfono.
Si una aplicacin desea tener acceso a esta API, es necesario definir los permisos adecuados en el archivo manifest.xml del proyecto.
El SDK de Android dispone de clases que permiten hacer uso del telfono, siempre y
cuando el dispositivo disponga de este mdulo.
Las clases ms importantes de esta API de Android se denominan TelephonyManager y
SMSManager. Veamos sus caractersticas ms importantes.

3.1 TelephonyManager
TelephonyManager, gestor que permite determinar la siguiente informacin del servicio de
telefona (se incluye un listado bsico):
- IMEI del dispositivo
- N de Telfono
- Nombre del Operador en varios formatos
- Tipo y Potencia de la red conectada
- Datos de la tarjeta SIM
- N del buzn de mensajes de voz
- Datos de la Celda de Telefona (antena a la que est conectado)

Y del estado del telfono:


- LIBRE (IDLE)
- LLAMADA ENTRANTE (RINGING)
- DESCOLGADO (OFFHOOK)
Adems, es posible recibir notificaciones cuando este estado cambia.

327

Aula Mentor

3.2 SMSManager
SMSManager, este Gestor permite enviar mensajes cortos (SMS) y leer su formato interno PDU

que significa Protocol Description Unit, que es el formato estndar de los mensajes cortos SMS.

3.3 Ejemplo de utilizacin de la API de telefona


Estudiemos ahora, de forma prctica, cmo utilizar la API de telefona. Es recomendable abrir el
Ejemplo 2 de esta Unidad para seguir la explicacin que se expone a continuacin.
La aplicacin que desarrollamos muestra dos pestaas: Telfono y Mensajes cortos
(SMS). La primera pestaa permite realizar llamadas de telfono y recibir cambios en el estado
del telfono. La segunda, enviar y recibir mensajes SMS.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<android.support.v4.app.FragmentTabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@android:id/tabhost
android:layout_width=match_parent
android:layout_height=match_parent >

328

<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>

En el diseo anterior hemos incluido el elemento FragmentTabHost, que sirve de contenedor


de las pestaas TabWidget. Es decir, para establecer la interfaz del usuario hemos utilizado
Fragmentos de tipo pestaa.
Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity vemos el siguiente
contenido:
public class MainActivity extends FragmentActivity implements
TabHost.OnTabChangeListener
private FragmentTabHost mTabHost;

U4 Bibliotecas, APIs y Servicios de Android

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en

// el archivo activity_main.xml
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la

// pestaa
mTabHost.setup(this, getSupportFragmentManager(),
R.id.tabFrameLayout);
// Aadimos la pestaa Telfono utilizando el fragmento
// TelefonoFragmentTab
mTabHost.addTab(
mTabHost.newTabSpec(tab1).setIndicator(Telfono,
getResources().getDrawable(R.drawable.telefono)),
TelefonoFragmentTab.class, null);
// Aadimos la pestaa SMS utilizando el fragmento

// SMSFragmentTab
mTabHost.addTab(
mTabHost.newTabSpec(tab2).setIndicator(Mensajes
cortos (SMS),
getResources().getDrawable(R.drawable.sms)),
SMSFragmentTab.class, null);

// Establecemos el listener del cambio de pestaa
mTabHost.setOnTabChangedListener(this);
} // end onCreate()

// Evento que se lanza cada vez que se selecciona una pestaa nueva
@Override
public void onTabChanged(String tabId) {

// Mostramos un mensaje al usuario

Toast.makeText(this, Has cambiado a la pestaa + tabId,
Toast.LENGTH_SHORT).show();
}
} // end clase

En el cdigo anterior buscamos el contenedor FragmentTabHost de pestaas en la interfaz


de usuario definido en el archivo activity_main.xml. Mediante su mtodo setup()
establecemos la Vista donde debe aparecer el contenido de las pestaas. Despus, mediante el
mtodo addTab(TabSpec arg0, Class<?> arg1, Bundle arg2) aadimos las dos pestaas
indicando en el parmetro de tipo TabSpec el nombre, texto e imagen de la pestaa y en el
arg1 la clase que define el fragmento. El parmetro arg2 se usa en el caso de que sea necesario
pasar al fragmento informacin adicional.
Tambin hemos implementado en esta Actividad de tipo fragmento la interfaz TabHost.
OnTabChangeListener que lanza un evento cada vez que el usuario selecciona una nueva pestaa. En este caso, mostramos un mensaje sencillo de tipo Toast al usuario.
A continuacin, se muestra cmo se implementa el fragmento de la primera pestaa que
gestiona llamadas de telfono.
Si abres en Eclipse ADT el archivo telefono_fragment_layout.xml vers que contiene un diseo muy sencillo que consiste en una caja de texto y un botn para realizar llamadas
y una etiqueta para mostrar los eventos de la API de telefona:

329

Aula Mentor

<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical >

330

<LinearLayout
android:orientation=horizontal
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=5dp>
<Button

android:id=@+id/llamarBtn

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Llamar por telfono/>
<EditText

android:id=@+id/nTelefono

android:layout_width=wrap_content

android:layout_height=wrap_content

android:layout_weight=1

android:ems=10

android:text=091334455

android:inputType=phone >

</EditText>

</LinearLayout>

<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Datos Telfono:/>

<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />

<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>

U4 Bibliotecas, APIs y Servicios de Android

<TextView
android:id=@+id/Log
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceSmall
android:text=Log:/>
</ScrollView>
</LinearLayout>

En la clase TelefonoFragmentTab se define la lgica del fragmento:


public class TelefonoFragmentTab extends Fragment {

// Gestor de la API de telefona

private TelephonyManager tm;

// Listener para detectar cambios en el telfono

private MiPhoneStateListener miListener = null;

// Vista que muestra el log de eventos al usuario

private TextView log;

// Almacena el n de tfno que introduce el usuario

private EditText et;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Definimos un Intent del tipo nueva llamada saliente
String outgoing = android.intent.action.NEW_OUTGOING_CALL ;
IntentFilter intentFilter = new IntentFilter(outgoing);
// Registramos un receptor de mensajes para el Intent anterior
getActivity().registerReceiver(OutGoingCallReceiver,

intentFilter);
// Indicamos que el fragmento no debe destruirse
setRetainInstance(true);
}
// Como es un fragmento debemos buscar las Vistas en este evento y
// no en onCreate()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup
container, Bundle savedInstanceState) {

// Obtenemos las vistas del fragmento
View v = inflater.inflate(R.layout.telefono_fragment_layout,
container, false);
log = (TextView) v.findViewById(R.id.Log);
TextView tv = (TextView) v.findViewById(R.id.datosTfno);
et = (EditText) v.findViewById(R.id.nTelefono);
Button boton = (Button) v.findViewById(R.id.llamarBtn);
// Definimos el evento onClick de llamada tfno
boton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
// Limpiamos el log

log.setText(Log:);

// Tambin podramos definir ACTION_DIAL o

// ACTION_VIEW para ver el marcador de telfono pero

// sin iniciar la llamada

331

Aula Mentor

});

Intent intent = new Intent(Intent.ACTION_CALL,


Uri.parse(tel: +
et.getText().toString().trim()));
startActivity(intent);

tv.append(\nTipo telfono: );
// Obtenemos el gestor de la API del telfono
tm = (TelephonyManager)getActivity().getSystemService(
Context.TELEPHONY_SERVICE);
// Definimos el listener de llamadas
miListener = new MiPhoneStateListener();
// Obtenemos el tipo de telfono del dispositivo
int phoneType = tm.getPhoneType();
switch(phoneType){
case TelephonyManager.PHONE_TYPE_NONE:

tv.append(No hay telfono);

break;
case TelephonyManager.PHONE_TYPE_GSM:

tv.append(GSM);

break;
case TelephonyManager.PHONE_TYPE_CDMA:

tv.append(CDMS);

break;

332

case TelephonyManager.PHONE_TYPE_SIP:
tv.append(SIP);
break;

default:

tv.append(Desconocido);
}

// Mostramos ms informacin sobre el dispositivo:


// n IMEI, N telfono, Pas de la SIM, Nombre Operador y N
// del buzn de voz
tv.append(\nN IMEI: + tm.getDeviceId());
tv.append(\nN Telfono: + tm.getSubscriberId());
tv.append(\nPas SIM: + tm.getSimCountryIso());
tv.append(\nNombre Operador: + tm.getNetworkOperatorName());
tv.append(\nBuzn de voz: + tm.getVoiceMailNumber());
return v;
} // end onCreateView
@Override
public void onResume() {
super.onResume();
Log.d(Telfono, onResume);
// Indicamos al gestor de la API del telfono que escuche
// los eventos de ste con el listener que hemos creado
// anteriormente
tm.listen(miListener, PhoneStateListener.LISTEN_CALL_STATE);

U4 Bibliotecas, APIs y Servicios de Android

// Si deseamos dejar de controlar la llamadas cuando la Actividad


// pase a segundo plano deberamos descomentar el siguiente mtodo
/*@Override
public void onPause() {
super.onPause();
Log.d(Telfono, onPause);
tm.listen(miListener, PhoneStateListener.LISTEN_NONE);
}*/
// Al destruir el Fragmento es necesario dejar de escuchar los
// eventos del telfono
@Override
public void onDestroy() {
super.onDestroy();
Log.d(Telfono, onDestroy);
tm.listen(miListener, PhoneStateListener.LISTEN_NONE);
}

// Listener que escucha los eventos de la API del Tfno


public class MiPhoneStateListener extends PhoneStateListener
{
private String logText = ;
// Evento que se lanza cuando cambia el estado de llamada del
// telfono
@Override
public void onCallStateChanged(int state, String incomingNumber)
{
super.onCallStateChanged(state, incomingNumber);
// Almacenamos la informacin del nuevo estado del tfno
switch(state)
{
case TelephonyManager.CALL_STATE_IDLE:
logText = EN ESPERA...;
break;
case TelephonyManager.CALL_STATE_RINGING:

logText = SONANDO... n llamante = +
incomingNumber;
break;
case TelephonyManager.CALL_STATE_OFFHOOK:

logText = LLAMADA ACTIVA;

if (!incomingNumber.isEmpty())
logText += n llamante = + incomingNumber;
break;
default:

logText = SIN ESTADO [+state+] n llamante = +
incomingNumber;
break;
}
// Mostramos la informacin al usuario
if (!logText.trim().equals())

log.append(\nEstado: +logText);
}
} // end MiPhoneStateListener

333

Aula Mentor

// Receptor de mensajes para recibir el evento del sistema


// operativo cuando se produzca una nueva llamada
BroadcastReceiver OutGoingCallReceiver = new BroadcastReceiver()
{

// Cuando el usuario inicia una nueva llamada se lanza este

// evento
@Override
public void onReceive(Context context, Intent intent)
{
// Obtenemos el n de telfono que nos pasa el mensaje

// BroadCast
String str = LLAMADA ACTIVA n llamante = +

intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
// Mostramos el dato al usuario
log.append(\nEstado: + str);
}
};
} // end clase

En el cdigo anterior puedes observar que, al tratar con un Fragmento, hemos empleado el
mtodo onCreateView() para buscar las Vistas.
En este mismo mtodo hemos definido el evento onClick() del botn Llamar por telfono
que crea una Intencin de tipo Intent.ACTION_CALL e inicia la Actividad correspondiente.
Tambin podramos utilizar los tipos ACTION_DIAL o ACTION_VIEW para ver el marcador de
telfono pero sin iniciar la llamada.
334

Despus, hemos obtenido el gestor TelephonyManager de la API del telfono mediante la orden
getSystemService(Context.TELEPHONY_SERVICE) y utilizado sus mtodos extraemos los

datos siguientes:
- getPhoneType(): tipo de telfono del dispositivo.
- getDeviceId(): IMEI del dispositivo.
- getSubscriberId(): n de telfono.
- getSimCountryIso(): pas de la tarjeta SIM.
- getNetworkOperatorName(): nombre del operador de telefona.
- getVoiceMailNumber(): n de buzn de voz.

En este mismo mtodo creamos el listener que se extiende de PhoneStateListener, que recibir
los eventos del telfono y que se lanza cuando cambia el estado de llamada del telfono con
onCallStateChanged(). Cuando esto ocurre, simplemente aadimos un texto al Log de la
interfaz del usuario.
En los eventos onResume() y onDestroy() del fragmento indicamos al gestor de la API
del telfono que escuche o deje de escuchar respectivamente los eventos del telfono con el
listener que hemos creado anteriormente.
Para ello, utilizamos el mtodo listen(PhoneStateListener listener, int
events) del gestor TelephonyManager e indicamos en el segundo parmetro el tipo de evento
que queremos escuchar: LISTEN_CALL_STATE (cambios en el estado de llamada). Tambin es
posible escuchas otro tipo de eventos, como envo de datos, cambios en la potencia de la seal,
informacin de la celda, etctera.
Sin embargo, para detectar llamadas salientes es necesario definir en el mtodo onCreate() del fragmento un receptor de mensajes para el Intent del tipo android.intent.action.
NEW_OUTGOING_CALL mediante la orden registerReceiver(). Hemos incluido el receptor de
mensajes llamado OutGoingCallReceiver del tipo BroadcastReceiver para recibir el evento del

U4 Bibliotecas, APIs y Servicios de Android

sistema operativo cuando se produzca una nueva llamada y se lance el evento onReceive(). En
este caso, aadimos un nuevo mensaje de Log al usuario.
Veamos ahora cmo se implementa el fragmento de la segunda pestaa que gestiona
mensajes cortos (SMS).
Si abres en Eclipse ADT el archivo sms_fragment_layout.xml, vers que contiene
un diseo muy sencillo que consiste en dos cajas de texto (telfono y mensaje corto) con su
respectivo botn para enviar el mensaje y un listado ListView para mostrar los mensajes cortos
recibidos:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical
android:layout_margin=2dp >
<LinearLayout
android:orientation=horizontal

android:layout_width=fill_parent

android:layout_height=wrap_content>
<Button android:id=@+id/enviarBoton
android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Enviar SMS/>
<TextView android:layout_width=wrap_content
android:textAppearance=
?android:attr/textAppearanceMedium
android:layout_margin=4dp
android:layout_height=wrap_content

android:text=Telfono: />
<EditText

android:id=@+id/nTelefono

android:layout_width=wrap_content

android:layout_height=wrap_content

android:layout_weight=1

android:ems=10

android:text=091334455

android:inputType=phone >

</EditText>

</LinearLayout>
<LinearLayout

android:orientation=vertical

android:layout_width=fill_parent

android:layout_height=wrap_content>
<TextView android:layout_width=wrap_content
android:layout_height=wrap_content

android:text=Mensaje: />
<EditText android:id=@+id/mensajeET

android:layout_width=fill_parent
android:layout_height=wrap_content

android:text=prueba de mensaje />

</LinearLayout>
<TextView

android:layout_width=fill_parent

335

Aula Mentor

android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />


<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=
?android:attr/textAppearanceMedium
android:text=Mensajes recibidos:/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView
android:id=@+id/listado
android:layout_width=fill_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>

336

En la clase SMSFragmentTab se define la lgica del fragmento:


public class SMSFragmentTab extends Fragment implements

LoaderCallbacks<Cursor> {

// Almacena el n de tfno y el texto sms que escribe el usuario

private EditText telefono, textoSMS;

// Adaptador del listado de SMS recibidos

private SimpleCursorAdapter adapter;
private static final Uri SMS_INBOX =
Uri.parse(content://sms/inbox);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Definimos un Intent del tipo nueva llamada saliente
String outgoing = android.provider.Telephony.SMS_RECEIVED ;
IntentFilter intentFilter = new IntentFilter(outgoing);
// Registramos un receptor de mensajes para el Intent anterior
getActivity().registerReceiver(IncomingSMSReceiver,
intentFilter);
// Indicamos que el fragmento no debe destruirse
setRetainInstance(true);
// Definimos los campos de la BD de SMS que vamos a

// utilizar y las Vistas donde aparecer el texto
String[] columnas = new String[] { address, body };
int[] nombres = new int[] { R.id.telefono, R.id.sms };
// Indicamos el loader que va a gestionar las actualizaciones

// del listado mediante un Cursor
getActivity().getLoaderManager().initLoader(0, null, this);

U4 Bibliotecas, APIs y Servicios de Android

// No definimos un Cursor para acceder a la BD ya que el


// Loader se encargar de ello
adapter = new SimpleCursorAdapter(getActivity(),

R.layout.fila_listado, null, columnas, nombres, 0);
}

// Como es un fragmento debemos buscar las Vistas en este evento y


// no en onCreate()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup

container, Bundle savedInstanceState) {

// Buscamos las Vistas de la interfaz del usuario
View v = inflater.inflate(R.layout.sms_fragment_layout,

container, false);
telefono = (EditText) v.findViewById(R.id.nTelefono);
textoSMS = (EditText) v.findViewById(R.id.mensajeET);
ListView listado = (ListView) v.findViewById(R.id.listado);
Button boton = (Button) v.findViewById(R.id.enviarBoton);
// Definimos el evento onClick de envio SMS
boton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {

try {

// Buscamos el gestor por defecto de SMS

SmsManager smsMgr = SmsManager.getDefault();

// Enviamos un mensaje al telfono indicado
smsMgr.sendTextMessage(
telefono.getText().toString(), null,

textoSMS.getText().toString(), null, null);
// Mostramos un mensaje al usuario indicando que

// todo ha ido bien
Toast.makeText(getActivity(), SMS enviado,

Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(getActivity(), Error al enviar el
SMS, Toast.LENGTH_LONG).show();
}
}
}); // end onclick botn enviar SMS

// Establecemos el adaptador del listado


listado.setAdapter(adapter);
return v;

// Receptor de mensajes para recibir el evento del sistema


// operativo cuando se reciba un nuevo SMS
BroadcastReceiver IncomingSMSReceiver = new BroadcastReceiver()
{
@Override
// Evento que ocurre cuando se recibe un mensaje
public void onReceive(Context context, Intent intent)
{

// Obtenemos el contenido del mensaje SMS

Bundle bundle = intent.getExtras();

SmsMessage[] mensjs = null;

337

Aula Mentor

338


String str = ;

// Si el mensaje contiene algo

if (bundle != null)

{

// Obtenemos la informacin del SMS recibido.

// El campo PDU significa Protocol Description Unit

// y es el formato estndar de los mensajes cortos

// SMS

Object[] pdus = (Object[]) bundle.get(pdus);

// Pasamos todos los mensajes recibidos en formato

// pdu a una matriz del tipo SmsMessage que contendr

// los mensajes en un formato interno y accesible por

// Android.

// Definimos el tamao de la matriz con el n de

// mensaje recibidos

mensjs = new SmsMessage[pdus.length];

// Recorremos todos los mensajes recibidos

for (int i=0; i<mensjs.length; i++){

// Pasamos el elemento i a la matriz de mensajes
mensjs[i] =
SmsMessage.createFromPdu((byte[])pdus[i]);
// Guardamos en texto plano el mensaje
str += SMS de +
mensjs[i].getOriginatingAddress();
str += . Mensaje: ;
str += mensjs[i].getMessageBody().toString();
str += \n;

} // end for

// Mostramos un mensaje indicando que se ha recibido

// el mensaje

Toast.makeText(context, str,
Toast.LENGTH_SHORT).show();

} // end if bundle != null
} // end onReceive()
};

// Loader de tipo Cursor que se encargar de conectar a la BD y


// obtener los registros.

// Evento que se lanza cuando es necesario crear el Loader
@Override

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Indicamos los campos que deseamos obtener de la BD de

// mensajes SMS

String[] projection = { _id, address, body };
// Creamos el Loader indicando la URI y los campo que vamos

// a leer

CursorLoader cursorLoader = new CursorLoader(getActivity(),
SMS_INBOX, projection, null, null, null);

return cursorLoader;
}


// Evento que se lanza cuando termina la carga de datos
@Override

public void onLoadFinished(Loader<Cursor> arg0, Cursor result) {
// Actualizamos la interfaz del usuario indicando el nuevo

// Cursor al adaptador del listado con los datos

U4 Bibliotecas, APIs y Servicios de Android

adapter.swapCursor(result);
}


// Evento que se lanza cuando el Loader se destruye o se

// reinicia, es decir, ya no dispone de datos
@Override

public void onLoaderReset(Loader<Cursor> arg0) {
// Indicamos al adaptador que ya no debe utilizar ningn

// cursor
adapter.swapCursor(null);
}

} // end clase

En el cdigo fuente de este fragmento hemos empleado de nuevo el mtodo onCreateView()


para buscar las Vistas.
En este mismo mtodo hemos definido el evento onClick() del botn Enviar SMS que
busca el gestor SmsManager por defecto de SMS invocando el mtodo getDefault(). Despus,
utilizamos su mtodo sendTextMessage(String tfnoDestino, String tfnoCentroMsj,
String texto, PendingIntent msjEnviado, PendingIntent msjEntregado) para enviar
un mensaje corto. Debemos indicar como parmetros de este mtodo el telfono destino y el
texto del mensaje. Existen los parmetros opcionales: telfono del centro de mensajes SMS y dos
intenciones pendientes que permiten al programador gestionar los eventos que ocurren cuando
un mensaje es enviado y recibido por el destinatario.
Para detectar mensajes entrantes es necesario definir en el mtodo onCreate() del fragmento un receptor de mensajes para el Intent del tipo android.provider.Telephony.SMS_
RECEIVED mediante la orden registerReceiver(). Hemos incluido el receptor de mensajes
llamado IncomingSMSReceiver del tipo BroadcastReceiver para recibir el evento del sistema
operativo cuando se reciba un nuevo mensaje corto y se lance el evento onReceive(). En este
caso, obtenemos el contenido del mensaje SMS de la variable Bundle en formato PDU. El campo
PDU significa Protocol Description Unit y es el formato estndar de los mensajes cortos SMS.
Mediante el mtodo createFromPdu() de SmsMessage podemos transformar el formato PDU a
otro formato legible por Android.

Hay que tener en cuenta que es posible recibir simultneamente varios mensajes
SMS; por lo tanto, el campo pdus de la variable Bundle es de tipo matriz.

Para mostrar todos los mensajes SMS disponibles en el dispositivo, vamos a utilizar un listado de
tipo ListView con su respectivo adaptador del tipo SimpleCursorAdapter. Sin embargo, en lugar
de utilizar un Cursor para cargar los datos tal y como se estudia en el curso de Iniciacin de
Android de Mentor, vamos a emplear la clase Loader. Dada la importancia de esta clase vamos
a estudiarla en detalle.

3.3.1 Clase Loader


Esta clase Loader permite cargar datos asncronamente. Monitoriza los datos de la fuente y
muestra nuevos resultados cuando stos cambian. Adems, no es necesario conectar de nuevo

339

Aula Mentor

con los datos cuando hay cambios de configuracin en el dispositivo (cambio de orientacin).
Esta clase est disponible desde Android 3.0, aunque puede usarse mediante el paquete
de compatibilidad desde la versin 1.6.
Se puede utilizar tanto en un Actividad como en un Fragmento.
Para crear un objeto de tipo Loader debemos invocar el mtodo getLoaderManager().initLoader(0, null, this).

Este mtodo toma los siguientes parmetros:


- ID: identificador nico del Loader. En la sentencia anterior, el ID es 0.
- Bundle: argumentos opcionales que el Loader puede utilizar en el constructor (null en la
sentencia anterior).
- LoaderManager.LoaderCallbacks: interfaz con los mtodos que el LoaderManager invoca
para indicar que se producen eventos en el Loader. En la sentencia anterior, la clase local
implementa esta interfaz, por lo que pasa una referencia a s mismo con this. Es una buena
prctica implementar esta interfaz en la Actividad o el Fragmento.

340

La llamada a initLoader() inicia y activa el Loader y puede devolver lo siguiente:


Si ya existe el ID del Loader especificado, se reutiliza quedando este ltimo.
Por el contrario, el ID no existe, initLoader(), entonces se invoca el mtodo onCreateLoader() de LoaderManager.LoaderCallbacks donde se implementa el cdigo para el
acceso a los datos.
Una vez el Loader ha terminado de leer los datos asncronamente, entonces se lanza el
evento onLoadFinished() para indicar que podemos actualizar la interfaz del usuario.
Android proporciona una implementacin Loader para manejar conexiones de base de
datos SQLite mediante la clase CursorLoader, que permite indicar en los parmetros del constructor el origen de los datos, los campos que deseamos obtener, incluir criterios de filtrado y
ordenar el resultado.
A partir de Android 3.0, para acceder a un ContentProvider basado en una base de
datos SQLite, lo recomendable es utilizar esta clase CursorLoader. Este Loader realiza la
consulta de base de datos en un subproceso en segundo plano para que la aplicacin no se
bloquee.

A partir de Android 3.0, la clase CursorLoader sustituye al mecanismo de gestin


de cursores (Cursor) dentro de una Actividad que queda obsoleto.

Cuando el Cursor deja de ser vlido, entonces Android invoca el mtodo onLoaderReset()
para realizar las acciones pertinentes.
Uno de los retos del programador de Android en la gestin de bases de datos es que su
acceso es lento y que hay que tener en cuenta el ciclo de vida de la Actividad correctamente,
por ejemplo, para abrir y cerrar el Cursor si ocurre un cambio de configuracin.
En versiones anteriores a Android 3.0, para gestionar el ciclo de vida se puede utilizar
el mtodo managedQuery(). A partir de esta versin, este mtodo queda obsoleto y debemos
emplear la clase Loader para acceder a un ContentProvider.
La clase SimpleCursorAdapter, que se puede usar con ListView, dispone del mtodo swapCursor(), que el cargador puede utilizar para actualizar el Cursor en su mtodo
onLoadFinished() del Loader.

U4 Bibliotecas, APIs y Servicios de Android

Es importante crear el objeto del tipo Loader en onCreate() del Fragmento para
que est disponible en el mtodo OnCreateView() posteriormente.

Si te fijas en el cdigo fuente de este fragmento, vers que hemos creado un Loader de tipo
Cursor que se encargar de conectar a la BD y obtener los registros del ContentProvider.
Al crear el Loader se lanza el evento onCreateLoader() que, a su vez, crea un CursorLoader que accede al contenido content://sms/inbox. Es muy importante indicar en
su constructor los campos que deseamos obtener de la base de datos de mensajes SMS. Es
imprescindible obtener siempre el campo _id de la base de datos ya que Android lo asocia
automticamente al ListView.
Cuando el Loader finaliza la carga los datos, Android invoca el evento onLoadFinished() y es aqu donde actualizamos la interfaz del usuario indicando el nuevo Cursor al adaptador del listado mediante el mtodo swapCursor() de este ltimo.
En el caso de que el Loader se destruya o se reinicie, es decir, de que ya no disponga
de datos, Android invoca el evento onLoaderReset() y es necesario eliminar el Cursor del
adaptador.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga los permisos
necesarios para leer el estado del telfono, realizar llamadas, procesar las llamadas entrantes y
leer, recibir y enviar los mensajes cortos. Para ello, hay que incluir en el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_PHONE_STATE/>
<uses-permission android:name=android.permission.CALL_PHONE/>
<!-- Necesario para detectar la llamadas salientes -->
<uses-permission
android:name=android.permission.PROCESS_OUTGOING_CALLS/>
<uses-permission android:name=android.permission.READ_SMS/>
<uses-permission android:name=android.permission.RECEIVE_SMS/>
<uses-permission android:name=android.permission.SEND_SMS/>

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (API Telfono) de la Unidad
4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa anterior, en el que hemos utilizado la API de telefona de Android.

Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente aplicacin:

341

Aula Mentor

342

Para simular una llamada entrante de telfono o la recepcin de un mensaje corto SMS en el
AVD y comprobar que la aplicacin responde correctamente, abrimos la perspectiva de DDMS
de Eclipse ADT y, en la pestaa Emulator Control, en el apartado Telephony Status:

U4 Bibliotecas, APIs y Servicios de Android

Podemos crear una llamada entrante (botn Call) o un mensaje SMS recibido
(botn Send) de forma manual al AVD en ejecucin.

Si despliegas las distintas opciones, vers que es posible configurar el tipo de llamada
recibida y casi cualquier caracterstica de sta.

4. Calendario de Android

El sistema operativo Android dispone de Calendarios donde el usuario puede administrar citas
y recordatorios. Existen dos formas de gestionarlos:
- Mediante Intenciones (Intents), accediendo indirectamente al Calendario mediante Actividades.
- Utilizando la API oficial, que permite gestionar de forma directa el Calendario del sistema
operativo que Google public en la versin 4.0 y que permite insertar, actualizar y eliminar
calendarios, eventos, recordatorios, etctera. En versiones anteriores a la 4.0 era difcil desarrollar una aplicacin que usase directamente el Calendario, ya que hay que acceder a la
base de datos y sta puede cambiar en funcin del fabricante del dispositivo.
En este apartado vamos a estudiar la API de gestin del Calendario de Android.
343

4.1 API Calendario de Android


Esta API se basa en la utilizacin de ContentProvider (proveedor de contenidos) que utiliza la
clase principal CalendarContract para gestionar calendarios, citas, recordatorio, etctera.
Los proveedores de contenido (ContentProvider) hacen que determinados datos, almacenados en un conjunto de tablas de una base de datos relacional, sean accesibles por otras
aplicaciones, donde cada fila es un registro y cada columna contiene datos de un tipo y significado particular.
A travs del ContentProvider del Calendario las aplicaciones pueden obtener acceso
de lectura / escritura a estas tablas que contienen los datos de calendario del usuario.
Como sabes, un ContentProvider publica una URI (del ingls, Uniform Resource Identifier), que es una cadena de caracteres corta que identifica inequvocamente una fuente de
datos del sistema operativo. Todos los URI comienzan con la cadena content://. El proveedor
de contenidos del Calendario define los siguientes URI para cada una de sus tablas (y su clase
correspondiente):

Aula Mentor

Tabla (Clase)

Descripcin

Esta tabla contiene la informacin de un Calendario en


concreto, donde cada fila contiene los detalles de este
calendario nico, como el nombre, su color, la informacin
CalendarContract.Calendars
de sincronizacin, etctera.
Hay que tener en cuenta que un usuario puede tener
varios calendarios en el dispositivo al mismo tiempo.

CalendarContract.Events

Esta tabla almacena la informacin especfica de un


evento (cita o recordatorio), donde cada fila contiene, por
ejemplo, el nombre del evento, lugar, hora de inicio, hora
de finalizacin, etctera. Este evento puede ocurrir una
sola vez o repetirse varias veces. Cada evento se identifica
por el campo EVENT_ID, que hace referencia al campo
_ID de esta tabla.

344
Esta tabla contiene el tiempo de inicio y final para cada
evento, donde cada fila representa una nica ocurrencia
del evento. Para los eventos nicos existe una relacin
CalendarContract.Instances
1:1 entre esta tabla y la tabla de eventos. Sin embargo,
para eventos recurrentes se generan varias filas que
corresponden a las repeticiones de ese evento.

Esta tabla almacena los datos de los asistentes al evento,


donde cada fila representa un nico invitado de un evento
CalendarContract.Attendees
y guarda, entre otra informacin, la confirmacin de
asistencia del invitado al evento.

Esta tabla contiene los recordatorios mediante alertas/


notificaciones de un evento. Un evento puede tener varios
CalendarContract.Reminders
recordatorios y stos se especifican indicando cmo y
cundo se avisar al usuario.

U4 Bibliotecas, APIs y Servicios de Android

La siguiente representacin grfica muestra el modelo de datos del Calendario:

En los cuadros principales aparecen los campos que los vinculan entre s.
Para poder utilizar la API del Calendario es necesario que la aplicacin tenga los permisos
de lectura y escritura al Calendario del dispositivo. Para ello, hay que incluir en el fichero
AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_CALENDAR/>
<uses-permission android:name=android.permission.WRITE_CALENDAR/>

Veamos ahora las tablas que forman la estructura del modelo de datos del calendario:

4.2 Tabla Calendarios


La tabla CalendarContract.Calendars contiene los detalles de los calendarios. Entre sus
campos ms importantes, podemos citar los siguientes:
Nombre

Descripcin

NAME

Nombre del calendario.

CALENDAR_DISPLAY_NAME

Texto que se muestra al usuario.

VISIBLE

Indica si el calendario es visible al usuario.

SYNC_EVENTS

Indica si se deben sincronizar los eventos.

Veamos unos ejemplos sobre cmo interactuar con esta tabla.

345

Aula Mentor

Por razones de simplicidad, en los ejemplos siguientes las consultas se realizan en el proceso
principal de la aplicacin. En la prctica, estas consultas deben hacerse en un hilo asncrono
mediante Loaders o mediante la clase AsyncQueryHandler para modificar datos.
Por ejemplo, para consultar todos los calendarios dados de alta en el dispositivo podemos
escribir las sentencias:
// Cursor que usamos para la consulta
Cursor cur = null;
// Obtenemos un objeto del tipo ContentResolver
ContentResolver cr = getContentResolver();
// URI con los datos de la tabla Calendarios
Uri uri = Calendars.CONTENT_URI;
// Campos que deseamos obtener de la tabla
public static final String[] CAMPOS = new String[] {
CalendarContract.Calendars._ID,
Calendars.ACCOUNT_NAME,

CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}
// Indicamos los criterios de seleccin de los datos
String seleccion = ( + Calendars.ACCOUNT_TYPE + = ?);
String[] seleccionArgs = new String[] {com.google};
// Obtenemos los datos en un cursor.
cur = cr.query(uri, CAMPOS, seleccion, seleccionArgs, null);

346

Hay que tener en cuenta que el nombre de la cuenta del usuario Calendars.
ACCOUNT_NAME (por ejemplo, usuario@gmail.com) se puede repetir en
diferentes tipos de cuentas ACCOUNT_TYPE.

Existe tambin el tipo de cuenta especial ACCOUNT_TYPE_LOCAL que se usa para crear
calendarios no asociados con la cuenta de Google del dispositivo. Y que no se sincroniza.
Para actualizar un calendario hay que buscarlo primero indicando un criterio de seleccin como, por ejemplo, su campo _ID. Fjate en el siguiente ejemplo:
// Indicamos el ID del calendario que deseamos modificar
long calendarioID = 2;
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Modificamos el nombre del calendario
values.put(Calendars.CALENDAR_DISPLAY_NAME, Nueva descripcin del
calendario);
// Aadimos a la Uri del calendario el ID de ste
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI,
calendarioID);
// Actualizamos el nombre del calendario y obtenemos las filas insertadas en
la base de datos de Calendarios
int filas = getContentResolver().update(updateUri, cv, null, null);

Los Calendarios genricos estn pensados para ser manipulados principalmente mediante un
adaptador de sincronizacin (Sync Adapter); por lo tanto, otras aplicaciones slo pueden hacer
cambios superficiales, como cambiar el nombre de visualizacin (ver ejemplo anterior).
Para crear un calendario local desde una aplicacin en el dispositivo debemos indicar
el tipo de cuenta account_type de ACCOUNT_TYPE_LOCAL. Como hemos comentado, los ca-

U4 Bibliotecas, APIs y Servicios de Android

lendarios de este tipo no se sincronizan con un servidor externo. Fjate en el siguiente ejemplo
de creacin de un calendario local:
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Definimos las caractersticas del calendario
cv.put(Calendars.ACCOUNT_NAME, Nombre de la cuenta usuario);
cv.put(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL);
cv.put(Calendars.NAME, Nombre del calendario);
cv.put(Calendars.CALENDAR_DISPLAY_NAME, Nombre que muestra calendario);
cv.put(Calendars.CALENDAR_COLOR, 0xEA8561);
// Los usuarios solo pueden leer el calendario
cv.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
cv.put(Calendars.OWNER_ACCOUNT, Nombre de la cuenta usuario);
// El calendario es visible y se sincroniza
cv.put(Calendars.VISIBLE, 1);
cv.put(Calendars.SYNC_EVENTS, 1);
// Actualizamos el nombre del calendario y obtenemos las filas
// insertadas en la base de datos de Calendarios
int filas = getContentResolver().update(Calendars.CONTENT_URI, cv, null,
null);

Para borrar un calendario permanentemente as como todos sus eventos asociados podemos
ejecutar las siguientes sentencias:
// Seleccionamos la URI del calendario con el ID que queremos borrar
Uri calUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, CAL_ID);
// Borramos el calendario
getContentResolver().delete(calUri, null, null);

4.3 Tabla Eventos/Citas


La tabla CalendarContract.Events contiene los detalles de los eventos de los calendarios.
Entre sus campos ms importantes, vemos que contiene:
Nombre

Descripcin

CALENDAR_ID

Campo_IDdel calendario al que pertenece el evento.

ORGANIZER

Correo electrnico (Email) del organizador del evento.

TITLE

Ttulo del evento.

EVENT_LOCATION

Lugar donde se celebrar el evento.

DESCRIPTION

Descripcin del evento.

DTSTART

Hora UTC de inicio del evento en milisegundos.

DTEND

Hora UTC de fin del evento en milisegundos.

347

Aula Mentor

Nombre

348

Descripcin

EVENT_TIMEZONE

Zona horaria del evento.

EVENT_END_TIMEZONE

Zona horaria de finalizacin del evento.

DURATION

Duracin del evento en el formato RFC5545. Por ejemplo,


PT1Hindica que el evento tiene una duracin de 1 hora
y P2W significa dos semanas.

ALL_DAY

Valor de tipo lgico que indica si el evento dura todo el


da.

RRULE

Regla de repeticin de un evento. Por ejemplo,


FREQ=WEEKLY;COUNT=10;WKST=SU, significa que se
repite 10 veces semanalmente todos los domingos.

RDATE

Fecha de repeticin de un evento. Normalmente se utiliza


conjuntamente con el campo anterior.

AVAILABILITY

Indica si este evento marca el calendario como tiempo


ocupado.

GUESTS_CAN_MODIFY

Indica si lo invitados pueden modificar el evento.

GUESTS_CAN_INVITE_OTHERS

Indica si otros invitados pueden aadir nuevos invitados


al evento.

GUESTS_CAN_SEE_GUESTS

Indica si los invitados pueden ver el listado completo de


invitados.

Si tenemos en cuenta que para aadir un evento al calendario hay que rellenar muchos campos,
vemos que es recomendable utilizar un Intent para hacerlo. Estudiaremos cmo hacerlo un poco
ms adelante en este apartado.
Sin embargo, si es necesario insertar un evento directamente en el calendario, podemos utilizar
esta API teniendo en cuenta lo siguiente:
- Es imprescindible indicar los campos CALENDAR_IDyDTSTART, es decir, _ID del calendario
y fecha de inicio del evento.
- Tambin debemos sealar la zona horaria del evento EVENT_TIMEZONE.
- Si el evento no se repite, entonces debes incluir el campo DTEND (fecha final evento).
- Para eventos repetitivos, hay que incluir el campo duracin DURATIONadems de los camposRRULEoRDATE.
Veamos un ejemplo que aade un evento al calendario con sentencias Java:
// Creamos la variable donde vamos a almacenar los valores del evento
ContentValues cv = new ContentValues();
// Definimos el Evento
cv.put(Events.CALENDAR_ID, CAL_ID);
cv.put(Events.TITLE, Ttulo del evento);
cv.put(Events.DTSTART, fecha_inicio_en_milisegundos);
cv.put(Events.DTEND, fecha_fin_en_milisegundos);
cv.put(Events.EVENT_LOCATION, Lugar: mi casa);

U4 Bibliotecas, APIs y Servicios de Android

cv.put(Events.DESCRIPTION, Descripcin del evento);


cv.put(Events.EVENT_TIMEZONE, Europe/Paris);
// Insertamos el evento
getContentResolver().insert(CalendarContract.Events.CONTENT_URI, cv);

Para actualizar un evento hay que buscarlo primero indicando un criterio de seleccin, como,
por ejemplo, su campo Events._ID. Fjate en el siguiente ejemplo:
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Definimos las modificaciones del Evento
cv.put(Events.TITLE, Ttulo del evento);
cv.put(Events.DESCRIPTION, Descripcin del evento);
cv.put(Events.EVENT_LOCATION, Nuevo lugar del evento);
// Definimos el criterio de bsqueda del evento y su argumento
String seleccion = (+Events._ID+ = ?);
String[] seleccionArgs = new String[]
{String.valueOf(id_del_evento_a_actualizar)};
// Modificamos el evento
getContentResolver().update(CalendarContract.Events.CONTENT_URI, cv,
seleccion, seleccionArgs);

Para borrar un evento podemos ejecutar las siguientes sentencias:


// Seleccionamos la URI del evento con el ID que queremos borrar
Uri eventoUri = ContentUris.withAppendedId(
CalendarContract.Events.CONTENT_URI, id_evento_a_borrar);
// Borramos el evento
getContentResolver().delete(eventoUri, null, null);

Las siguientes sentencias son equivalentes a las anteriores:


// Definimos el criterio de seleccin del evento
String seleccion = (+Events._ID+ = ?);
String[] seleccionArgs = new String[]
{String.valueOf(id_evento_a_borrar)};
// Borramos el evento con los criterios anteriores
getContentResolver().delete(CalendarContract.Events.CONTENT_URI, seleccion,
seleccionArgs);

En el Ejemplo 3 de esta Unidad, descrito ms adelante, vemos cmo buscar eventos de un


calendario.

349

Aula Mentor

4.4 Tabla Invitados


La tabla CalendarContract.Attendees contiene los detalles de los invitados a un evento del
calendario. Entre sus campos ms importantes, vemos que contiene:
Nombre

Descripcin

EVENT_ID

_ID del evento.

ATTENDEE_NAME

Nombre del invitado.

ATTENDEE_EMAIL

Correo electrnico del invitado.

Calidad del invitado respecto al evento. Puede tomar uno de


estos valores:
RELATIONSHIP_ATTENDEE: simple invitado
ATTENDEE_RELATIONSHIP RELATIONSHIP_NONE: ninguna
RELATIONSHIP_ORGANIZER: organizador
RELATIONSHIP_PERFORMER: artista
RELATIONSHIP_SPEAKER: ponente

350

ATTENDEE_TYPE

Tipo de invitado:
TYPE_REQUIRED: requerido
TYPE_OPTIONAL: opcional

ATTENDEE_STATUS

Respuesta del invitado a la invitacin:


ATTENDEE_STATUS_ACCEPTED: aceptada
ATTENDEE_STATUS_DECLINED: denegada
ATTENDEE_STATUS_INVITED: slo invitado
ATTENDEE_STATUS_NONE: ninguna
ATTENDEE_STATUS_TENTATIVE: quizs

De igual forma que hemos hecho en los casos anteriores, es muy sencillo aadir un invitado al
evento con el _ID eventoID:
// Creamos la variable donde vamos a almacenar los valores
ContentValues cv = new ContentValues();
// Definimos los datos del invitado
cv.put(Attendees.ATTENDEE_NAME, Nombre);
cv.put(Attendees.ATTENDEE_EMAIL, usuario@ejemplo.com);
cv.put(Attendees.ATTENDEE_RELATIONSHIP,
Attendees.RELATIONSHIP_ATTENDEE);
cv.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
values.put(Attendees.ATTENDEE_STATUS,
Attendees.ATTENDEE_STATUS_INVITED);
cv.put(Attendees.EVENT_ID, eventoID);
// Aadimos el invitado
getContentResolver().insert(Attendees.CONTENT_URI, cv);

U4 Bibliotecas, APIs y Servicios de Android

4.5 Tabla Recordatorios


La tabla CalendarContract.Reminders contiene los detalles de los recordatorios de un evento
del calendario. Entre sus campos ms importantes, vemos que contiene:
Campo

Descripcin

EVENT_ID

_ID del evento.

MINUTES

Minutos anteriores a la hora del evento en los que se debe iniciar el recordatorio.

METHOD

Mtodo del recordatorio:


METHOD_ALERT: alerta
METHOD_DEFAULT: por defecto
METHOD_EMAIL: correo electrnico
METHOD_SMS: mensaje corto

Es muy sencillo aadir un recordatorio 15 minutos antes al evento con el _ID eventoID:
// Creamos la variable donde vamos a almacenar los valores
ContentValues cv = new ContentValues();
// Definimos los datos del recordatorio
cv.put(Reminders.MINUTES, 15);
cv.put(Reminders.EVENT_ID, eventoID);
cv.put(Reminders.METHOD, Reminders.METHOD_ALERT);
// Aadimos el recordatorio
getContentResolver().insert(Reminders.CONTENT_URI, cv);

4.6 Tabla de instancias


La tabla CalendarContract.Instances contiene los detalles de los eventos nicos del
calendario, es decir, aqu se separan los eventos recurrentes en eventos individuales (instancias).
Esta tabla es de solo lectura. Entre sus campos ms importantes, vemos que contiene:
Campo

Descripcin

BEGIN

Hora de inicio de la instancia del evento en milisegundos.

END

Hora de fin de la instancia del evento en milisegundos.

END_DAY

Da de finalizacin de la instancia del evento.

END_MINUTE

Minuto medido desde la media noche de finalizacin de la instancia del


evento.

EVENT_ID

_IDdel evento.

START_DAY

Da de inicio de la instancia del evento.

START_MINUTE

Minuto medido desde la media noche de inicio de la instancia del evento.

351

Aula Mentor

4.7 Intenciones de Calendario de Android


Como hemos comentado, es posible utilizar Intenciones del sistema operativo para gestionar el
calendario. Aunque el uso de estas Intenciones es limitado en comparacin con un Proveedor
de contenido (ContentProvider), tiene las ventajas de que el usuario ya conoce la aplicacin
Calendario de su dispositivo y de que, desde el punto de vista del programador, ya estn
desarrolladas y automatizadas las alarmas, la configuracin de los asistentes, etctera. Adems.
Para utilizar las Intenciones, la aplicacin no necesita permisos adicionales.
Con las Intenciones podemos realizar las siguientes operaciones con el calendario:
- Abrir la aplicacin Calendario en una fecha especfica.
- Ver eventos.
- Crear nuevos eventos.
- Editar eventos existentes.
Veamos las intenciones disponibles:
Accin

352

VIEW

URI

Descripcin

Extras

content://com.android.
calendar/time/
Abre el calendario
Tambin podemos utilizar la especificado en una Ninguno.
constante: CalendarContract. fecha.
CONTENT_URI.

content://com.android.
calendar/events/

VIEW

Visualiza un evento en CalendarContract._


Tambin podemos utilizar la concreto.
ID
constante: Events.CONTENT_
URI.
content://com.android.
calendar/events/

EDIT

E D I T

Edita un evento en CalendarContract._


Tambin podemos utilizar la concreto.
ID
constante: Events.CONTENT_
URI.
content://com.android.
calendar/events

Cualquiera
de
los
Crea
o
edita
un
evento.
campos
del
siguiente
Tambin podemos utilizar la
INSERT
listado.
constante:
Events.CONTENT_URI.

U4 Bibliotecas, APIs y Servicios de Android

Campos extra al dar de alta o editar un evento:


Campo Extra Intent

Descripcin

Events.TITLE

Nombre del evento.

CalendarContract.EXTRA_EVENT_BEGIN_TIME

Fecha en milisegundos del inicio


del evento

CalendarContract.EXTRA_EVENT_END_TIME

Fecha en milisegundos del final


del evento

CalendarContract.EXTRA_EVENT_ALL_DAY

Indica si el evento ocurre todos los


das

Events.EVENT_LOCATION

Lugar del evento

Events.DESCRIPTION

Descripcin del evento

Intent.EXTRA_EMAIL

Correos electrnicos de todos los


invitados o participantes

Events.RRULE

Regla de repeticin

Events.ACCESS_LEVEL

Indica si el evento es pblico o


privado

Events.AVAILABILITY

Indica si el evento es en tiempo de


trabajo o no.
353

Si deseamos abrir el calendario por una fecha especfica (por ejemplo, dos meses desde la fecha
actual), podemos escribir las siguientes sentencias Java:
// Obtenemos los milisegundos pasados de la fecha actual + 2 meses
Calendar cal = new GregorianCalendar();
cal.setTime(new Date());
cal.add(Calendar.MONTH, 2);
long time = cal.getTime().getTime();
// Generamos la URI correspondiente
Uri.Builder builder =
CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath(time);
builder.appendPath(Long.toString(time));
Intent intent =
new Intent(Intent.ACTION_VIEW, builder.build());
// Iniciamos la Actividad del Intent
startActivity(intent);

Por ejemplo, para lanzar un Intent que cree un evento en el calendario podemos escribir las
sentencias:
// En algunos dispositivos no funciona bien la accin ACTION_INSERT
// y se debe utiliza en su lugar Intent.ACTION_EDIT
Intent intent = new Intent(Intent.ACTION_EDIT);
// URI con los Eventos del Calendario
intent.setData(CalendarContract.Events.CONTENT_URI);

Aula Mentor

// Incluimos los datos del evento


intent.setType(vnd.android.cursor.item/event);
intent.putExtra(Events.TITLE, Estudiar curso Android Avanzado);
intent.putExtra(Events.EVENT_LOCATION, En casa);
intent.putExtra(Events.DESCRIPTION, Repasar ejemplos);
// Iniciamos la Actividad
startActivity(intent);

Para visualizar un evento en concreto, podemos escribir las siguientes sentencias:


// Definimos la URI con el ID del Evento que queremos editar
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, idEventoAct);
// Con ACTION_VIEW visualizamos el evento
Intent intent = new Intent(Intent.ACTION_VIEW).setData(uri);
// Iniciamos la actividad
startActivity(intent,);

Si es necesario editar el evento, hay que usar la accin ACTION_EDIT.

4.8 Diferencias entre Intents y la API del Calendario


En funcin de las necesidades de la aplicacin, es mejor utilizar Intents o la API del calendario.
Veamos los pros y los contras de cada forma de enfocar el desarrollo:
354

Tipo

Ventajas

Desventajas

Intents

No son necesarios permisos


adicionales.
El usuario decide qu calendario
utiliza para almacenar los eventos.
El usuario est familiarizado con
la aplicacin calendario.
La mayora de las aplicaciones
funcionan de esta forma.

Las operaciones disponibles con el


calendario son reducidas y pueden
tener limitaciones en aplicaciones
complejas.

API Calendario

Se puede modificar cualquier


campo del calendario.
Se puede ejecutar operaciones en Necesidad de permisos adicionales
segundo plano sin que el usuario en la aplicacin desarrollada.
se percate de ello.
Permite sincronizar el calendario.

4.9 Ejemplo de uso de Intents de la API del Calendario


Estudiemos ahora, de forma prctica, cmo utilizar la API del Calendario y sus Intents. Es
recomendable abrir el Ejemplo 3 de esta Unidad para seguir la explicacin que damos.
La aplicacin que desarrollamos permite buscar y borrar eventos de un calendario en
una fecha concreta utilizando la API del calendario. Adems, mediante Intents se pueden aadir
y Editar estos eventos.

U4 Bibliotecas, APIs y Servicios de Android

En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:


<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android

android:layout_width=fill_parent

android:layout_height=fill_parent

android:orientation=vertical >
<TextView

android:layout_width=fill_parent
android:layout_height=wrap_content

android:text=Selecciona la fecha y busca o gestiona un
evento en el Calendario
android:gravity=center

android:padding=10dip/>
<Spinner
android:id=@+id/spinner1
android:layout_width=match_parent
android:layout_height=wrap_content />
<LinearLayout

android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal

android:gravity=center >

<TextView android:id=@+id/fecha
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center
android:text=Sin fecha

android:padding=10dip/>

<Button

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Seleccionar fecha

android:onClick=showDatePickerDialog />

</LinearLayout>

<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />


<TextView android:id=@+id/evento

android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center

android:padding=10dip/>

<LinearLayout

android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal

355

Aula Mentor

356


android:gravity=center >

<Button android:id=@+id/anterior
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Anterior

android:padding=10dip/>

<Button android:id=@+id/siguiente
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Siguiente

android:padding=10dip/>

</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />


<LinearLayout

android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal

android:gravity=center >


<Button

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Alta evento

android:onClick=altaEvento />


<Button android:id=@+id/borrar

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Borrar evento

android:onClick=borrarEvento />


<Button android:id=@+id/editar

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Editar evento

android:onClick=editarEvento />

</LinearLayout>

</LinearLayout>

Como puedes observar, la interfaz de usuario es muy sencilla y consta de un selector donde el
usuario elije un calendario. Hay, adems, una serie de botones para ver el siguiente/anterior
evento, dar de alta uno nuevo, editarlo o borrarlo.
Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el
siguiente contenido con la lgica de la aplicacin:
// Clase que almacena el _id y nombre del Calendario
class MiCalendario {

U4 Bibliotecas, APIs y Servicios de Android


public String nombre;

public String id;

public MiCalendario(String _name, String _id) {

nombre = _name;

id = _id;
}
@Override

public String toString() {
return nombre;
}
}
// Clase principal de la Actividad que implementa el evento onClick de los
botones
public class MainActivity extends Activity implements OnClickListener {














// Cursor para el acceso a los eventos del calendario


private static Cursor mCursor = null;
// Etiqueta con la fecha seleccionada por el usuario
private static TextView fechaLabel;
// Da, Mes y Ao seleccionado por el usuario
private int diaAct, mesAct, anioAct;
// ID del evento que se muestra en pantalla
private int idEventoAct = -1;
// ID del Calendario seleccionado
private long idCalendarioAct = -1;
// Botones para gestionar eventos
private Button botonAnt, botonSig, botonEditar, botonBorrar;
// Etiqueta donde se muestran los datos del Evento
private TextView tvEvento;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Al iniciar la aplicacin, buscamos la fecha actual
Time t = new Time();
t.setToNow();
diaAct=t.monthDay;
mesAct=t.month;
anioAct=t.year;
// Buscamos las Vistas de la interfaz del usuario
tvEvento = (TextView)findViewById(R.id.evento);
fechaLabel = (TextView)findViewById(R.id.fecha);
botonEditar = (Button)findViewById(R.id.editar);
botonBorrar = (Button)findViewById(R.id.borrar);
botonSig = (Button)findViewById(R.id.siguiente);
botonSig.setOnClickListener(this);
botonAnt = (Button)findViewById(R.id.anterior);
botonAnt.setOnClickListener(this);
Spinner calendariosSp = (Spinner)findViewById(R.id.spinner1);

//
//
//
//

Buscamos con un Cursor todos los Calendarios disponibles en


el dispositivo y seleccionamos los campos _ID y
DISPLAY_NAME. No incluimos el campo
Calendars.ACCOUNT_TYPE ya que queremos obtener todos los

357

Aula Mentor

// calendarios independientemente de su tipo.


Cursor cursor = getContentResolver().query(

CalendarContract.Calendars.CONTENT_URI, new String[]

{ CalendarContract.Calendars._ID,
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME }
, null, null, null);

// Matriz donde guardamos los Calendarios
MiCalendario[] calendarios = new

MiCalendario[cursor.getCount()];
// Si el cursor devuelve datos...
if (cursor!=null) {

cursor.moveToFirst();

// Recorremos todos los resultados con un bucle

for (int i = 0; i < cursor.getCount(); i++) {


// Aadimos un nuevo calendario a la matriz


calendarios[i] = new MiCalendario(
cursor.getString(1), cursor.getString(0));


cursor.moveToNext();

} // end for i
} // end cursor!=null
// Cerramos el cursor
cursor.close();
// Creamos un adaptador de tipo matriz para asociarlos al
// Spinner con los nombres de los Calendarios
ArrayAdapter<MiCalendario> l_arrayAdapter =

new ArrayAdapter<MiCalendario>(this,
android.R.layout.simple_spinner_item, calendarios);
// Slo se puede seleccionar una opcin

l_arrayAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);

// Asociamos el adaptador al Spinner y elegimos el elemento 0

calendariosSp.setAdapter(l_arrayAdapter);

calendariosSp.setSelection(0);

// Definimos el evento OnItemSelected del Spinner

calendariosSp.setOnItemSelectedListener(new
AdapterView.OnItemSelectedListener() {
@Override

public void onItemSelected(AdapterView<?> p_parent,
View p_view, int p_pos, long p_id) {
// Obtenemos el objeto correspondiente a la

// opcin seleccionada

// y hacemos typecasting al MiCalendario
MiCalendario calen =
(MiCalendario)p_parent.getSelectedItem();

// Obtenemos el ID del Calendarios seleccionado
idCalendarioAct=Long.valueOf(calen.id);
Log.d(Calendario, Seleccionado: +
Long.toString(idCalendarioAct));

// Buscamos los eventos del da actual


cargaEventos();
}
@Override

public void onNothingSelected(AdapterView<?> arg0) {}
});

358

U4 Bibliotecas, APIs y Servicios de Android

} // end onCreate()
// Mtodo que busca todos los eventos del Calendario y da
// seleccionados
private void cargaEventos() {
// Creamos dos fechas de inicio y fin de la bsqueda de eventos
GregorianCalendar fechaIni = new GregorianCalendar(anioAct,
mesAct, diaAct);
GregorianCalendar fechaFin = new GregorianCalendar(anioAct,
mesAct, diaAct+1);
// Actualizamos la Etiqueta con la fecha seleccionada
fechaLabel.setText(Integer.toString(diaAct) + / +

Integer.toString(mesAct+1) + / +

Integer.toString(anioAct));
// Seleccionamos los campos que deseamos mostrar: _ID, ttulo,

// fecha inicio/fin y ubicacin

String[] campos = new String[] {
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.EVENT_LOCATION };

// En el criterio de seleccin indicamos el ID del Calendario

// y la fecha de inicio/final de los Eventos

String seleccion = CalendarContract.Events.CALENDAR_ID +
= ? and + CalendarContract.Events.DTSTART +
>= ? and + CalendarContract.Events.DTEND + <= ? ;

// Argumento de la seleccin anterior

String[] seleccionArgs = { String.valueOf(idCalendarioAct),
String.valueOf(fechaIni.getTimeInMillis()),
String.valueOf(fechaFin.getTimeInMillis()) };

// Buscamos los Eventos del Calendario en esas fechas y los

// ordenamos decrecientemente por fecha de inicio

mCursor = getContentResolver().query(
CalendarContract.Events.CONTENT_URI, campos,
seleccion, seleccionArgs,
CalendarContract.Events.DTSTART + ASC);

// Nos movemos al primer registro

mCursor.moveToFirst();
// Actualizamos los botones simulando un clic en el botn

// anterior registro

onClick(findViewById(R.id.anterior));
}
// Mtodo que muestra un selector de fecha a partir de la clase
// DateDialogFragment
public void showDatePickerDialog(View v) {
// Creamos una variable de tipo Calendar con la fecha
// actual para pasrsela al DateDialogFragment
Calendar cal = Calendar.getInstance();
cal.set(anioAct, mesAct, diaAct);
// Creamos el objeto del tipo DateDialogFragment
DateDialogFragment ddf = DateDialogFragment.newInstance(this,
Selecciona la fecha, cal);
// Definimos su evento dateDialogFragmentDateSet
ddf.setDateDialogFragmentListener(new
DateDialogFragmentListener() {

359

Aula Mentor

@Override
public void dateDialogFragmentDateSet(int year, int
monthOfYear, int dayOfMonth) {
diaAct=dayOfMonth;
mesAct= monthOfYear;
anioAct=year;
// Buscamos los eventos del nuevo da seleccionado
cargaEventos();
}
}); // end setDateDialogFragmentListener









// Mostramos el selector de fecha
ddf.show(getFragmentManager(), date picker dialog fragment);
}

360

// Mtodo que da de alta un nuevo Evento


public void altaEvento(View v) {
// En algunos dispositivos no funciona bien la accin
// ACTION_INSERT y se debe utiliza en su lugar

// Intent.ACTION_EDIT
Intent intent = new Intent(Intent.ACTION_EDIT);
// URI con los Eventos del Calendario
intent.setData(CalendarContract.Events.CONTENT_URI);
// Incluimos los datos del evento
intent.setType(vnd.android.cursor.item/event);
intent.putExtra(Events.TITLE, Estudiar curso Android
Avanzado);
intent.putExtra(Events.EVENT_LOCATION, En casa);
intent.putExtra(Events.DESCRIPTION, Repasar ejemplos de la
Unidad 4 del curso);
// Descomentando las siguientes lneas aadimos informacin

// extra Fecha de inicio y fin del Evento
/*GregorianCalendar calDate = new GregorianCalendar(2013,10,02);
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,


calDate.getTimeInMillis());
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME,


calDate.getTimeInMillis());
// El evento dura todo el da
intent.putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true);
// El Evento es recurrente
intent.putExtra(Events.RRULE,
FREQ=WEEKLY;COUNT=11;WKST=SU;BYDAY=TU,TH);

// El Evento es privado y marca el Calendario como ocupado


intent.putExtra(Events.ACCESS_LEVEL, Events.ACCESS_PRIVATE);
intent.putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY);
*/

// Iniciamos la Actividad esperando el resultado para Actualizar


// los datos
startActivityForResult(intent, 1);
}
// Mtodo que borra un Evento
public void borrarEvento(View v) {

U4 Bibliotecas, APIs y Servicios de Android

// Seleccionamos el ID del Calendario y del Evento


String seleccion = (+Events._ID+ = ? and +

Events.CALENDAR_ID +=? );
// Argumentos del criterio de seleccin
String[] seleccionArgs = new String[]
{String.valueOf(idEventoAct),
String.valueOf(idCalendarioAct)};
// Borramos los Eventos que cumplan el criterio de seleccin
// Anterior
// CUIDADO! Si intentas borrar un Evento del Calendario local
// de algunos dispositivos Samsung (aplicacin S Planner) vers
// que no desaparece el Evento.
// Esto ocurre por un bug en esta aplicacin. Si usas el
// calendario de Google, entonces todo funciona correctamente.
int rows = getContentResolver().delete(
CalendarContract.Events.CONTENT_URI, seleccion,
seleccionArgs);
Log.d(Calendario, Evento borrado + idEventoAct+ fila: +
rows + calendario+ String.valueOf(idCalendarioAct));
// Recargamos los eventos
cargaEventos();
}
// Mtodo que edita un Evento mediante un Intent
public void editarEvento(View v) {
// Definimos la URI con el ID del Evento que queremos editar
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI,
idEventoAct);
// Con ACTION_VIEW visualizamos el evento
Intent intent = new Intent(Intent.ACTION_EDIT).setData(uri);
// Iniciamos la actividad esperando su resultado
startActivityForResult(intent, 1);
}

@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
// Siempre actualizamos los datos independientemente del

// resultado del Intent
if (requestCode == 1) {

cargaEventos();
}
}//onActivityResult
// Mtodo que se lanza cuando el usuario hace clic en los botones
// Siguiente/Anterior Evento
@Override
public void onClick(View v) {

// Si no hay eventos deshabilitamos los botones

if (mCursor.getCount()==0) {
botonSig.setEnabled(false);
botonAnt.setEnabled(false);
botonBorrar.setEnabled(false);
botonEditar.setEnabled(false);

tvEvento.setText(No hay eventos este da!);
return;

361

Aula Mentor

362

}
// En funcin del botn sobre el que hace clic el usuario

// avanzamos o retrocedemos un registro del cursor
switch(v.getId()) {
case R.id.siguiente:
if(!mCursor.isLast())
mCursor.moveToNext();

break;
case R.id.anterior:
botonSig.setEnabled(false);
if(!mCursor.isFirst())
mCursor.moveToPrevious();
break;
} // en case

// Habilitamos los botones de bsqueda
botonSig.setEnabled(!mCursor.isLast());
botonAnt.setEnabled(!mCursor.isFirst());

// Formateador de fechas

Format df = DateFormat.getDateFormat(this);

Format tf = DateFormat.getTimeFormat(this);


// Variables temporales

String titulo = ;

String ubicacion = ;

Long inicio = 0L;

idEventoAct = -1;

// Lee
try {
idEventoAct = mCursor.getInt(0);
titulo = mCursor.getString(1);
inicio = mCursor.getLong(2);

ubicacion = mCursor.getString(3);

} catch (Exception e) {

Log.d(Calendario, Error la leer registro de la BD
de Eventos);
}
// Deshabilitamos los botones Editar y Borrar si no hemos

// encontrado un registro

botonBorrar.setEnabled(idEventoAct > -1);

botonEditar.setEnabled(idEventoAct > -1);

// Mostramos la informacin del Evento al usuario

tvEvento.setText(idEventoAct+: +titulo+ el da +

df.format(inicio) + desde las +
tf.format(inicio) + en + ubicacion);

} // end onClick
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Tambin utilizamos un Cursor para buscar todos los Calendarios
disponibles en el dispositivo y seleccionamos los campos _ID y DISPLAY_NAME. En este caso no
incluimos el campo Calendars.ACCOUNT_TYPE ya que vamos a mostrar todos los calendarios
independientemente de su tipo. A continuacin, recorremos todos los registros del Cursor y

U4 Bibliotecas, APIs y Servicios de Android

pasamos la informacin a una matriz del tipo MiCalendario, que definimos al inicio de esta
clase principal. Finalmente, creamos un adaptador de tipo matriz para asociarlos al Spinner con
los nombres de los Calendarios y definimos su evento OnItemSelected(), que obtiene el objeto
correspondiente cada vez que el usuario selecciona otro calendario.
El mtodo cargaEventos() busca todos los eventos del Calendario y da seleccionados
por el usuario. Para ello, seleccionamos los campos que deseamos mostrar: _ID, ttulo, fecha
inicio/fin y ubicacin. Definimos el criterio de seleccin indicamos el _ID del Calendario y la
fecha de inicio/final de los Eventos. Despus, buscamos los Eventos del Calendario mediante el
mtodo query() del objeto ContentResolver y los ordenamos decrecientemente por fecha
de inicio.
El mtodo showDatePickerDialog() muestra una ventana de dilogo con un selector
de fecha a partir de la clase DateDialogFragment. Adems, definimos su evento dateDialogFragmentDateSet() que se lanza cuando el usuario elige una fecha.
El mtodo altaEvento() y editarEvento() ejecuta un Intent que sirve para dar de
alta un nuevo Evento o editarlo, tal y como hemos visto en la teora anterior. En este caso, lo
ejecutamos con la orden startActivityForResult() para recibir el resultado de su ejecucin
en la actividad principal.
El evento borrarEvento() usa la API del calendario para borrar el evento actual.

Atencin. Si intentas borrar un Evento del Calendario local de algunos dispositivos


Samsung (aplicacin S Planner), vers que no desaparece el Evento. El calendario
se denomina My Calendar y esto ocurre por un error en esta aplicacin. Si usas
el calendario de Google, entonces todo funciona correctamente.

Finalmente, el mtodo onClick() se lanza cuando el usuario hace clic en los botones Siguiente/
Anterior Evento. Aqu buscamos el evento correspondiente, mostramos su informacin y
habilitamos o deshabilitamos los botones correspondientes cuando ya no se encuentren ms
registros.
En el archivo DateDialogFragment.java hemos definido un selector de fecha a partir
de la clase DialogFragment. De nuevo volvemos a utilizar un fragmento en lugar de la clsica
ventana de tipo Dialog para mostrar una ventana de dilogo. Si abres este archivo, observars
que tiene este aspecto:
// Clase de tipo DialogFragment que muestra un selector de fecha
public class DateDialogFragment extends DialogFragment {

// Variable para almacenar el contexto, fecha y el listener

static Context sContext;

static Calendar sDate;

static DateDialogFragmentListener sListener;


// Constructor del fragmento

public static DateDialogFragment newInstance(Context context,

String titulo, Calendar date){

// Creamos el nuevo objeto el tipo DateDialogFragment

DateDialogFragment dialog = new DateDialogFragment();

// Guardamos los datos de las variables

sContext = context;

sDate = Calendar.getInstance();

sDate = date;

363

Aula Mentor

// Obtenemos el ttulo del selector y se lo



// pasamos a la ventana de dilogo

Bundle args = new Bundle();
args.putString(title, titulo);
dialog.setArguments(args);

// Devolvemos la ventana de dilogo
return dialog;
}
@Override

public Dialog onCreateDialog(Bundle savedInstanceState) {

// Devolvemos la ventana de dilogo

return new DatePickerDialog(sContext, dateSetListener,
sDate.get(Calendar.YEAR),
sDate.get(Calendar.MONTH),
sDate.get(Calendar.DAY_OF_MONTH));
}

364

// Mtodo que establece el listener del Fragmento


public void setDateDialogFragmentListener(
DateDialogFragmentListener listener){

sListener = listener;
}


// Mtodo que define el listener onDateSet que se lanza cuando

// el usuario selecciona una fecha

private DatePickerDialog.OnDateSetListener dateSetListener =
new DatePickerDialog.OnDateSetListener() {
@Override

public void onDateSet(DatePicker view, int year, int
monthOfYear, int dayOfMonth) {

// Obtenemos la nueva fecha

Calendar newDate = Calendar.getInstance();
newDate.set(year, monthOfYear, dayOfMonth);

// Invocamos el listener con la nueva fecha
sListener.dateDialogFragmentDateSet(year,
monthOfYear, dayOfMonth);
}
};

//DateDialogFragment listener interface

public interface DateDialogFragmentListener{

// Interfaz del nuevo listener

public void dateDialogFragmentDateSet( int year, int
monthOfYear, int dayOfMonth);
}
} // end clase

Observa que el cdigo anterior es sencillo: el evento newInstance() sirve de constructor de la


ventana de dilogo.
En el evento onCreateDialog(), tpico de las ventanas de dilogo, creamos una ventana
del tipo DatePickerDialog que ser la que mostraremos al usuario para que seleccione una fecha.
El resto del cdigo define el listener onDateSet, que se lanza cuando el usuario selecciona una
fecha. As, se detecta que en la Actividad principal ocurre este evento.
Finalmente, ten en cuenta que para poder ejecutar esta aplicacin es necesario que se

U4 Bibliotecas, APIs y Servicios de Android

tenga permisos de lectura y escritura al Calendario del dispositivo. Para ello, hay que incluir en
el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_CALENDAR/>
<uses-permission android:name=android.permission.WRITE_CALENDAR/>

Adems, para poder depurar la aplicacin en un dispositivo real de Android, es necesario indicarlo
en el archivo manifest en la etiqueta <application> mediante el atributo android:debuggable=true.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (Calendario) de la Unidad 4.


Estudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado
del programa anterior, en el que hemos utilizado su Calendario.

Para poder gestionar calendario en un AVD del SDK de Android es necesario poder dar de alta
una cuenta de Google; sin embargo, el SDK no permite hacerlo de forma sencilla ni compatible
entre todas las versiones. Por lo tanto, es mejor probar esta aplicacin en un dispositivo real de
Android.
Si no lo haces, aparecer esta ventana y no podrs dar de alta una cuenta de Google (por
lo menos en las versiones que existen ahora mismo del SDK de Android):

365

Aula Mentor

Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:

366

Recomendamos que pruebes las distintas opciones de Alta y Edicin para ver cmo funcionan
al ejecutarse el Intent correspondiente.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

5. Gestor de descargas (Download manager)


En este apartado vamos a estudiar el gestor de descargas de Android denominado, en ingls,
DownloadManager y disponible desde la versin Android 2.3. (API 9). Se trata de un servicio
en segundo plano que permite gestionar descargas de larga duracin y, una vez finalizada la
descarga, se notifica a la aplicacin origen de la descarga mediante un mensaje de tipo broadcast.
Para obtener una instancia a esta clase se debe utilizar la orden getSystemService(DOWNLOAD_

U4 Bibliotecas, APIs y Servicios de Android

SERVICE). Para que la aplicacin detecte cundo finaliza la descarga del archivo, hay que definir
un BroadcastReceiver y buscar la accin ACTION_DOWNLOAD_COMPLETE para tratar el archivo.

Hay que tener en cuenta que para utilizar esta clase, la aplicacin debe tener el permiso
de acceso a Internet.

5.1 Ejemplo de utilizacin del Gestor de descargas


Estudiemos ahora, de forma prctica, cmo utilizar el Gestor de descarga. Es recomendable abrir
el Ejemplo 4 de esta Unidad para seguir la explicacin que damos.
La aplicacin que desarrollamos muestra una caja de texto donde el usuario puede introducir una URL de una imagen y dos botones: Iniciar descarga y Mostrar descargas.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView

android:layout_width=fill_parent
android:layout_height=wrap_content

android:text=Gestor de descargas de Android
android:gravity=center

android:padding=10dip/>
<EditText
android:id=@+id/urlET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text= http://www.aulamentor.picanya.org/imatges/
horizontal_cometa_mentor.png
android:ems=10 >
<requestFocus />
</EditText>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:gravity=center >
<Button
android:id=@+id/boton1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=descargarImagen
android:text=Iniciar descarga />
<Button
android:id=@+id/boton2
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=verDescargas
android:text=Mostrar descargas />
</LinearLayout>

367

Aula Mentor

<TextView

android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Imagen descargada:
android:gravity=center

android:padding=10dip/>
<ImageView android:layout_height=wrap_content

android:id=@+id/imagenView

android:src=@drawable/ic_launcher

android:layout_width=fill_parent

android:gravity=center/>
</LinearLayout>

En la clase MainActivity se define la lgica de la aplicacin:


// Clase principal de la Actividad
public class MainActivity extends Activity {


// Identificativo de la descarga

private long idPeticion;

// Variable del gestor de descargas
private DownloadManager dm;
private EditText urlET;

368

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
urlET = (EditText)findViewById(R.id.urlET);
// Definimos el BroadcastReceiver para detectar cundo

// finaliza la descarga
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String accion = intent.getAction();
// Si la accin es ACTION_DOWNLOAD_COMPLETE entonces
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.
equals(accion)) {
// Creamos un objeto del tipo Query para poder

// consultar el Gestor de descargas
Query query = new Query();
// Filtramos por el ID de peticin
query.setFilterById(idPeticion);
// Filtramos en el gestor de descarga la consulta

// anterior
Cursor c = dm.query(query);
// Si hay algn registro entonces...
if (c.moveToFirst()) {

// Obtenemos la columna STATUS de la descarga
int columnIndex = c.getColumnIndex(
DownloadManager.COLUMN_STATUS);
// Si el STATUS de la descarga es CORRECTO
// entonces
if (DownloadManager.STATUS_SUCCESSFUL ==

U4 Bibliotecas, APIs y Servicios de Android

c.getInt(columnIndex)) {
// Localizamos la imagen de la interfaz

// del usuario
ImageView view = (ImageView)

findViewById(R.id.imagenView);
// Obtenemos la URI del archivo en local
String uriString = c.getString(

c.getColumnIndex(
DownloadManager.COLUMN_LOCAL_URI));
// Cargamos la nueva imagen
view.setImageURI(Uri.parse(uriString));
}
} // end if hay algn registro
}
} // end onReceive
}; // end BroadcastReceiver
// Registramos el BroadcastReceiver
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
} // end onCreate
// Mtodo que se lanza cuando el usuario pulsa el botn descargar
public void descargarImagen(View view) {

// Obtenemos referencia al servicio DOWNLOAD_SERVICE
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
// Creamos la peticin con la URL de la imagen
Request peticion = new Request(
Uri.parse(urlET.getText().toString()));
// Arrancamos la descarga y guardamos su identificativo
idPeticion = dm.enqueue(peticion);
}
// Mtodo que se lanza cuando el usuario pulsa el botn Mostrar
// descargas
public void verDescargas(View view) {
// Utilizamos un Intent del tipo ACTION_VIEW_DOWNLOADS y lo
// ejecutamos
Intent i = new Intent();
i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS);
startActivity(i);
}
} // end clase

Como puedes observar, el cdigo fuente es muy sencillo: simplemente utilizamos un objeto
del tipo DownloadManager y definimos el BroadcastReceiver para recibir el mensaje
correspondiente cuando acaba la descarga de la imagen y mostrarla en la interfaz de usuario.
Para mostrar todas las descargas del gestor, utilizamos un Intent del tipo ACTION_VIEW_
DOWNLOADS y lo ejecutamos.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a Internet. Para ello, hay que incluir en el fichero AndroidManifest.xml la siguiente
etiqueta:
<uses-permission android:name=android.permission.INTERNET/>

369

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Gestor de descargas) de la


Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado el Gestor de descargas de Android.

Si ejecutas en Eclipse ADT este Ejemplo 4 en el AVD, vers que se muestra la siguiente aplicacin:

370

Si pulsas el botn Mostrar descargas vers que aparecen todas las descargas del gestor:

U4 Bibliotecas, APIs y Servicios de Android

Es recomendable instalar esta aplicacin en un dispositivo real de Android para ver su


funcionalidad completa.

6. Cmo enviar un correo electrnico


En este apartado vamos a estudiar cmo enviar un correo electrnico en Android. Existen tres
formas de enviar un mensaje:
- OAuth 2.0 de Gmail.
- Intent del tipo message/rfc822.
- Biblioteca externa JavaMail API.
Veamos en qu consiste cada mecanismo.

6.1 OAuth 2.0 de Gmail


OAuth (Open Authorization) es un protocolo abierto, que permite a un usuario del sitio A
compartir la informacin de este sitio (proveedor de servicio) con el sitio B (llamado consumidor)
sin compartir toda su identidad compartiendo un token o palabra clave.
Google dispone de soporte para la autenticacin de Gmail utilizando OAuth 2.0. Esto
evita la necesidad de disponer del nombre de usuario y contrasea de la cuenta.
Funciona de la siguiente manera: el usuario autoriza que una aplicacin que implementa
este protocolo acceda a Gmail; entonces, Gmail le transmite un token (palabra clave) a esta
aplicacin para que pueda acceder al correo durante cierto tiempo. La ventaja de este mtodo
es que la aplicacin no necesita conocer el usuario y la contrasea de la cuenta, por lo que es
un mtodo ms seguro de autenticacin.
Puedes encontrar ms informacin sobre este tipo de autenticacin en la ayuda de Android en Internet en este enlace.

6.2 Intent del tipo message/rfc822


Se trata de un mecanismo intuitivo y muy sencillo de desarrollar que, utilizando un Intent,
permite al usuario utilizar el cliente de correo electrnico local del dispositivo.
Para desallorarlo, creamos un Intent con la accin ACTION_SEND, aadimos los campos
del correo electrnico e indicamos el tipo message/rfc822.
Ms adelante veremos un ejemplo de aplicacin de este mecanismo.

6.3 Biblioteca externa JavaMail API


Como has visto, Android permite enviar mensajes de correo electrnico desde una aplicacin
local; sin embargo, a veces, es necesario que la aplicacin pueda enviar mensajes sin la
intervencin del usuario del dispositivo.
El SDK de Android no incluye de forma nativa una API que permita enviar correos electrnicos. No obstante, podemos utilizar la API JavaMail, que est desarrollada especficamente
para Android.
En el siguiente ejemplo veremos cmo se utiliza.

371

Aula Mentor

6.4 Ejemplo sobre cmo envar un correo electrnico


Estudiemos ahora, de forma prctica, cmo utilizar la API JavaMail y/o un Intent para
enviar correos electrnicos. Es recomendable abrir el Ejemplo 5 de esta Unidad para seguir la
explicacin que se ofrece.
La aplicacin que desarrollamos permite enviar un sencillo correo electrnico utilizando
uno de los dos mecanismos.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal
que muestra tres cajas de texto (destinatario, asunto y mensaje) y dos botones (enviar por Intent y enviar por JavaMail):
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/linearLayout1
android:layout_width=fill_parent
android:layout_height=fill_parent
android:orientation=vertical >
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Destinatario:
android:textAppearance=?android:attr/textAppearanceLarge />

372

<EditText
android:id=@+id/destinatarioET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:inputType=textEmailAddress >
<requestFocus />
</EditText>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Asunto:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/asuntoET
android:layout_width=fill_parent
android:layout_height=wrap_content />
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Mensaje:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/mensajeET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=top
android:inputType=textMultiLine

U4 Bibliotecas, APIs y Servicios de Android

android:lines=5 />
<Button
android:id=@+id/enviarIntent
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Enviar x Intent />
<Button
android:id=@+id/enviarSMTP
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Enviar x JavaMail />
</LinearLayout>

Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity vemos el siguiente


contenido con la lgica de la aplicacin:
// Clase principal de la Actividad
public class MainActivity extends Activity {

// Variables de la interfaz del usuario
EditText destinatarioET;
EditText asuntoET;
EditText mensajeET;
MailSender m;

// Constantes que definen el usuario y contrasea cuenta gmail
static String usuario=usuario@gmail.com;
static String clave=contrasea;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
final Button enviarSMTP = (Button)

this.findViewById(R.id.enviarSMTP);
final Button enviarIntent = (Button)

findViewById(R.id.enviarIntent);
destinatarioET = (EditText) findViewById(R.id.destinatarioET);
asuntoET = (EditText) findViewById(R.id.asuntoET);

mensajeET = (EditText) findViewById(R.id.mensajeET);

destinatarios = new String[]{

destinatarioET.getText().toString()};


// Definimos el evento onClick de los botones

enviarSMTP.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

// Creamos el objeto que enva el email

m = new MailSender(usuario, clave);

// Cargamos la matriz de destinatarios

String[] destinatarios = {

destinatarioET.getText().toString().trim()};

m.setDestinatarios(destinatarios);

// Indicamos quin enva el correo

m.setDe(usuario);

373

Aula Mentor


// Aadimos el asunto y el cuerpo

m.setAsunto(asuntoET.getText().toString());

m.setCuerpo(mensajeET.getText().toString());


// Es necesario utilizar un hilo separado para enviar

// el correo. Si no, aparece la excepcin

// android.os.NetworkOnMainThreadException

// ya que a partir de la versin 3.0 de Android no

// podemos utilizar la conexin de red en el hilo

// principal porque lo bloquea.

new AsyncTask<Void, Void, Void>() {

// Variable para guardar el resultado

String resultado=;
@Override
protected void onPreExecute()
{

// Deshabilitamos el botn de enviar

enviarSMTP.setEnabled(false);
Log.d(Debug, onPreExecute());
}

374

@Override
protected Void doInBackground(Void... params)
{
Log.d(Debug, doInBackground() -- Enviamos

el mensaje por SMTP);
try {

// Si quisiramos aadir un fichero

// adjunto descomentaramos la siguiente
// sentencia

//m.addAttachment(/sdcard/imagen.jpg);

// Si enviamos el mensaje...


if(m.send()) {


Log.d(Debug, Enviado OK);


resultado= El correo se ha enviado
correctamente.;


} // Si no lo enviamos...


else {


Log.d(Debug, Enviado Error);


resultado=Ha ocurrido un error al

enviar el mensaje!;


}

} catch(Exception e) {


// Si ocurre una excepcin, guardamos su

// informacin


Log.d(Debug, Excepcin +

e.getMessage().toString());


resultado= Error al enviar el mensaje:
+ e.getMessage().toString();

}
return null;
}
@Override
protected void onPostExecute(Void res)

U4 Bibliotecas, APIs y Servicios de Android

Log.d(Debug, onPostExecute());
// Habilitamos el botn de enviar y mostramos

// un mensaje con el resultado

enviarSMTP.setEnabled(true);
Toast.makeText(MainActivity.this, resultado,
Toast.LENGTH_LONG).show();
}
}.execute(); // end AsyncTask

}
}); // end onClick botn

enviarIntent.setOnClickListener(new OnClickListener() {
@Override

public void onClick(View v) {

// Utilizamos un Intent para crear el mensaje

Intent email = new Intent(Intent.ACTION_SEND);

// Cargamos la matriz de destinatarios

String[] destinatarios = {

destinatarioET.getText().toString().trim()};

email.putExtra(Intent.EXTRA_EMAIL, destinatarios);
// Podemos incluir en copia o en copia oculta a ms

// destinatarios

//email.putExtra(Intent.EXTRA_CC,new String[]{to});
//email.putExtra(Intent.EXTRA_BCC,

new String[]{to});
email.putExtra(Intent.EXTRA_SUBJECT,

asuntoET.getText().toString());
email.putExtra(Intent.EXTRA_TEXT,

mensajeET.getText().toString());
// Indicamos que vamos a enviar un correo

// electrnico
email.setType(message/rfc822);

// Iniciamos el Intent

startActivity(Intent.createChooser(email, Elige el

cliente de correo electrnico:));
}
}); // End onClick botn
} // end onCreate()
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para definir el evento
onClick() de ambos botones.
El botn enviarSMTP enva un correo electrnico mediante la API JavaMail. Para ello,
utilizamos un objeto de la clase local MailSender que se define en otro archivo y que sirve de
interfaz a esta biblioteca.

375

Aula Mentor

Para enviar un correo mediante la API JavaMail hay que utilizar un hilo separado,
pues si no lo hacemos, salta la excepcin android.os.NetworkOnMainThreadE
xception, ya que a partir de la versin 3.0 de Android no podemos utilizar la conexin de red en el hilo principal porque lo bloquea.

El botn enviarIntent enva un correo electrnico creando un Intent del tipo message/
rfc822 con la accin ACTION_SEND, en la que completamos los datos EXTRA_EMAIL (destinatario),
EXTRA_SUBJECT (asunto) y EXTRA_TEXT (cuerpo del mensaje). Posteriormente, con la orden
Intent.createChooser() mostramos un ventana de dilogo donde el usuario puede elegir la

aplicacin con la que desea completar la accin.


Como hemos comentado, en el fichero MailSender.java hemos definido una clase que
sirve de interfaz con la API MailJava y que se extiende de la clase javax.mail.Authenticator.
Conozcamos su contenido:

376

// Clase principal de la Actividad que se extiende de


// javax.mail.Authenticator
public class MailSender extends javax.mail.Authenticator {

// Variables internas de la clase

private String usuario;

private String clave;


private String[] destinatarios;

private String de;


private String puerto;

private String puertoOrigen;


private String servidor;


private String asunto;

private String cuerpo;


private boolean autenticado;


private boolean debug;


private Multipart multipart;


// Constructor bsico de la clase

public MailSender() {
// Por defecto, enviamos mensajes al servidor de google.

// Aqu estn sus caractersticas

servidor = smtp.gmail.com;

puerto = 465;

puertoOrigen = 465;


usuario = ;

clave = ;

de = ;

asunto = ;

cuerpo = ;

// Indica si deseamos recibir los mensajes de debug

U4 Bibliotecas, APIs y Servicios de Android


debug = false;

// Indica si autenticamos por SMTP

autenticado = true;

// Variable utilizada para cargar los ficheros adjuntos

multipart = new MimeMultipart();

// Sentencias necesarias para que se puedan cargar ficheros

// adjuntos. Se trata de algo interno de

// la biblioteca javax.mail.Authenticator

MailcapCommandMap mc = (MailcapCommandMap)

CommandMap.getDefaultCommandMap();
mc.addMailcap(text/html;; x-java-content
handler=com.sun.mail.handlers.text_html);
mc.addMailcap(text/xml;; x-java-content
handler=com.sun.mail.handlers.text_xml);
mc.addMailcap(text/plain;; x-java-content
handler=com.sun.mail.handlers.text_plain);
mc.addMailcap(multipart/*;; x-java-content
handler=com.sun.mail.handlers.multipart_mixed);
mc.addMailcap(message/rfc822;; x-java-content
handler=com.sun.mail.handlers.message_rfc822);
CommandMap.setDefaultCommandMap(mc);
}


// Contructor donde aadimos el usuario y la clave

public MailSender(String usuario, String clave) {

// Invocamos al constructor genrico
this();


this.usuario = usuario;

this.clave = clave;
}


// Mtodo que enva un mensaje y crear excepciones

public boolean send() throws Exception {

// Establecemos las propiedades de la sesin

Properties props = setProperties();

// Si tenemos cuenta de usuario, su clave, destinatarios,

// asunto y cuerpo del mensaje...

if(!usuario.equals() && !clave.equals() &&

destinatarios.length > 0 && !de.equals() &&
!asunto.equals() && !cuerpo.equals()) {

// Creamos una sesin con las propiedades anteriores

Session session = Session.getInstance(props, this);

// Creamos un eMail en el objeto siguiente

MimeMessage msg = new MimeMessage(session);

// Aadimos los campos De
msg.setFrom(new InternetAddress(de));

// Indicamos los destinatarios

InternetAddress[] addressTo = new

InternetAddress[destinatarios.length];

for (int i = 0; i < destinatarios.length; i++) {

addressTo[i] = new

InternetAddress(destinatarios[i]);
}

377

Aula Mentor

msg.setRecipients(MimeMessage.RecipientType.TO,

addressTo);

// Indicamos asunto y fecha de envo
msg.setSubject(asunto);
msg.setSentDate(new Date());


// Cargamos el cuerpo del mensaje en formato MIME

BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(cuerpo);
multipart.addBodyPart(messageBodyPart);
msg.setContent(multipart);


// Enviamos el mensaje
Transport.send(msg);
return true;

} else {
return false;
}
}

378

// Es imprescindible definir el mtodo de autenticacin que se


// usa en Session.getInstance con this. Si no, la API no sabe
// cmo autenticar con el servidor
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(usuario, clave);

}


// Mtodo que aade un fichero adjunto

public void addAttachment(String filename) throws Exception {
// Usamos la variable multipart para aadirlo en formato

// MIME

BodyPart messageBodyPart = new MimeBodyPart();

DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));

messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
}


// Mtodo que establece las propiedades de la sesin con el

// servidor

private Properties setProperties() {

Properties props = new Properties();
props.put(mail.smtp.host, servidor);
if(debug) {
props.put(mail.debug, true);
}
if(autenticado) {
props.put(mail.smtp.auth, true);
}
props.put(mail.smtp.port, puerto);
props.put(mail.smtp.socketFactory.port, puertoOrigen);
props.put(mail.smtp.socketFactory.class,

javax.net.ssl.SSLSocketFactory);
props.put(mail.smtp.socketFactory.fallback, false);
return props;
}

U4 Bibliotecas, APIs y Servicios de Android


// Mtodo que establece el cuerpo del mensaje

public void setCuerpo(String cuerpo) {

this.cuerpo = cuerpo;
}

// Mtodo que establece los destinatarios del mensaje

public void setDestinatarios(String[] destinatarios) {

this.destinatarios = destinatarios;
}

// Mtodo que establece el remitente del mensaje

public void setDe(String de) {

this.de = de;
}

// Mtodo que establece el asunto del mensaje

public void setAsunto(String asunto) {

this.asunto = asunto;
}
} // end clase

En el cdigo anterior puedes ver que, en el mtodo setProperties(), establecemos las


propiedades de la sesin con el servidor SMTP (smtp.gmail.com) utilizando la clase tpica
Properties de Java de duplas.
El mtodo que realiza todo el trabajo enviando un mensaje y creando las excepciones (cuando
es necesario) es send(). Aqu puedes observar que utilizamos las clases de JavaMail siguientes:
- Session: crea una sesin con las propiedades anteriores.
- MimeMessage: compone un mensaje de tipo MIME. Sus mtodos setFrom() indica el remitente, setRecipients() establece los destinatarios, setSubject(), el asunto del mensaje,
setSentDate(), la fecha del envo y setContent() aade el cuerpo del mensaje y los
ficheros adjuntos en formato multipart.
- MimeMultipart: gestiona el cuerpo del mensaje y los ficheros adjunto.
- InternetAddress: genera una direccin de correo electrnico vlida.
- BodyPart: se usa para crear el cuerpo del mensaje con setText() y/o adjuntar un archivo
con setFileName().
- Transport: sirve para iniciar la conexin y enviar el correo electrnico.
- DataSource: se utiliza para leer archivos del dispositivo que se adjuntarn al mensaje.
- PasswordAuthentication: clase que implementa el mtodo de autenticacin.

Es imprescindible definir el mtodo de autenticacin con la clase PasswordAuthentication que se usa en Session.getInstance con this para que la
API sepa cmo autenticar la sesin con el servidor de correo.

Para poder utilizar las clases anteriores es necesario copiar los archivos .jar que forman la
biblioteca JavaMail en el directorio libs del proyecto e importarlas al mismo, tal y como ya
hemos hecho anteriormente en el curso:

379

Aula Mentor

Despus, aparecen ya en el proyecto como bibliotecas referenciadas:

380

Finalmente, acabamos la explicacin de todo el cdigo indicando que, para poder ejecutar esta
aplicacin, es necesario que tenga permisos de acceso a Internet. Para ello, hay que incluir en
el fichero AndroidManifest.xml la siguiente etiqueta:
<uses-permission android:name= android.permission.INTERNET/>

Adems, para poder depurar la aplicacin en un dispositivo real de Android, es necesario


indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 5 (Enviar Correo electrnico) de la Unidad 4. Estudia el cdigo fuente y ejectalo en un dispositivo fsico de
Android para ver el resultado del programa anterior.

Para poder enviar correos electrnicos, es necesario tener acceso directo a Internet; por lo tanto,
es mejor probar esta aplicacin en un dispositivo real de Android.

U4 Bibliotecas, APIs y Servicios de Android

Si ejecutas en Eclipse ADT este Ejemplo 5 en un dispositivo real, vers que se muestra la
siguiente aplicacin:

381
Si pulsas en el botn Enviar x Intent, vers que el sistema te ofrece un listado con aplicaciones
para completar la accin (dependiendo del dispositivo aparecern ms o menos):

Aula Mentor

Te recomendamos que pruebes las distintas opciones de envo de correo electrnico para ver
cmo funcionan.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

Para que funcione esta aplicacin con tu cuenta de Gmail es necesario que, en su configuracin,
tengas habilitado el Acceso IMAP. Mira cmo puedes configurarlo.

382

7. Servicios avanzados de Android


Un Servicio (Services) es un componente de software que se ejecuta en segundo plano y
que realiza operaciones cada cierto tiempo. Un servicio no proporciona una interfaz grfica al
usuario. Por ejemplo, un servicio puede reproducir msica en segundo plano mientras el usuario
est en otra aplicacin, o puede obtener informacin de Internet sin la interaccin del usuario.
Otros componentes, como una actividad, pueden iniciar un servicio e interactuar con l si es
necesario.
Aunque en el curso de iniciacin de Android de Mentor ya estudiamos los servicios
y en la Unidad 2 hemos empleado un servicio para actualizar un Widget, vamos a repasar las
caractersticas de estos servicios para que sirva de recordatorio.

7.1 Teora sobre servicios de Android


Como ya sabes, la plataforma Android ofrece una gran cantidad de servicios predefinidos en
el sistema a los que podemos acceder a travs de las clases de tipo Manager. En una Actividad
podemos acceder a estos servicios a travs del mtodo getSystemService().
Sin embargo, si una aplicacin Android define sus propios Servicios, stos deben declarase en

U4 Bibliotecas, APIs y Servicios de Android

el fichero AndroidManifest.xml del proyecto.


Un componente de una aplicacin Android puede iniciar un servicio que seguir funcionando en segundo plano, incluso si el usuario cambiara a otra aplicacin.
Adems, un componente de la aplicacin puede unirse (en ingls bind) al servicio para
interactuar con l e incluso realizar comunicaciones entre procesos. Por ejemplo, un servicio
podra conectarse a Internet en un segundo plano para descargar noticias, reproducir msica,
etctera.
Un servicio puede funcionar de dos modos:
- Autnomo: cuando un componente de la aplicacin, por ejemplo, una actividad, inicia el
servicio mediante el mtodo StartService(). Una vez arrancado, el servicio puede ejecutarse en segundo plano de forma indefinida, incluso si el componente que lo inici se
destruye. Normalmente, un servicio iniciado de esta forma realiza una nica operacin y no
devuelve el resultado al componente que lo inicia. Por ejemplo, puede descargar de Internet
un archivo o cargarlo. Cuando la operacin finaliza, el servicio debe detenerse.
- Dependiente o Ligado (en ingls a este modo se le denomina bind): cuando un componente de la aplicacin se une al servicio mediante el mtodo bindService(). Un servicio
ligado ofrece una interfaz de tipo cliente-servidor que permite a los componentes de una
aplicacin interactuar con l enviando peticiones y recibiendo su resultado. Un servicio ligado slo se ejecuta mientras otro componente de la aplicacin est unido a l. Es posible unir
un mismo servicio a varios componentes de una o de varias aplicaciones al mismo tiempo;
sin embargo, cuando todos ellos se desligan, el servicio se destruye.
Un servicio puede funcionar de las dos formas anteriores simultneamente, es decir, se puede
arrancar en modo Autnomo (de manera indefinida) y tambin en modo Ligado. Simplemente
hay que implementar los mtodos onStartCommand() para el modo Autnomo y onBind()
para el modo Ligado.
Cualquier componente de una aplicacin puede iniciar un servicio. Incluso un componente de otra aplicacin distinta a la que define el servicio tambin puede iniciarlo de la misma
forma que iniciaramos una Actividad de otra aplicacin mediante Intenciones.
Tambin se puede declarar un servicio como privado en la aplicacin, en el archivo AndroidManifest.xml, y bloquear el acceso desde otras aplicaciones.
Los servicios tienen que ser declarados en el archivo AndroidManifest.xml con la etiqueta
<service android:name=nombreClase> </service> y la implementacin de la clase
debe heredarse de la clase Service.

Importante: los servicios propios de una aplicacin, por defecto, se ejecutan en


el hilo principal de su proceso. Por lo tanto, para no bloquear el hilo principal o
de la interfaz, debemos ejecutar estos servicios con hilos de ejecucin (Thread)
diferentes.

7.2 Servicios propios


Una aplicacin puede declarar su propio servicio para llevar a cabo operaciones que tarden en
ejecutarse y no necesiten interactuar con el usuario, o para suministrar una nueva funcionalidad
a otras aplicaciones.
A continuacin, se muestra un esquema con los mtodos que invoca Android cuando

383

Aula Mentor

lanzamos un servicio segn su modo de funcionamiento:

384

Una Actividad puede iniciar un servicio en modo Autnomo a travs del mtodo StartService()
y detenerlo mediante el mtodo StopService(). Cuando lo hacemos, Android invoca su mtodo
onCreate(); despus, se invoca el mtodo onStartCommand() con los datos proporcionados
por la Intencin de la actividad.
En el mtodo startService() tambin podemos indicar como parmetro el comportamiento
del ciclo de vida de los servicios:
- START_STICKY: se utiliza para indicar que el servicio debe ser explcitamente iniciado o
parado.
- START_NOT_STICKY: el servicio termina automticamente cuando el mtodo onStartCommand() finaliza su ejecucin.
- START_REDELIVER_INTENT: el sistema tratar de volver a crear el servicio.
- START_STICKY_COMPATIBILITY: versin compatible de START_STICKY, que no garantiza
que onStartCommand() sea llamado despus de que el proceso sea matado.
Si la actividad quiere interactuar con un servicio (modo Dependiente o Ligado) para, por
ejemplo, mostrar el progreso de una operacin, puede utilizar el mtodo bindService(). Para
esto, hay que usar el objeto ServiceConnection, que permite conectarse al servicio y devuelve
un objeto de tipo IBinder, que la actividad puede utilizar para comunicar con el servicio. Ms
adelante veremos en detalle cmo definir servicios en modo Ligado dentro de las aplicaciones
Android.
Una vez repasados los conceptos de servicios, vamos a aprender un nuevo tipo de servicio: Intent Service.

U4 Bibliotecas, APIs y Servicios de Android

7.3 Intent Service


Hemos estudiado que, normalmente, la clase base para crear Servicios es la Service. Sin
embargo, existe tambin la clase IntentService que podemos utilizar para crear Servicios.
Podemos utilizar la clase IntentService para realizar ciertas tareas en segundo plano, como,
por ejemplo, enviar un mensaje de correo electrnico. La diferencia respecto a la clase Service
es que la instancia del IntentService termina automticamente una vez finaliza la
ejecucin de su tarea.
La clase IntentService dispone del mtodo onHandleIntent(), que ser invocado de
forma asncrona por el sistema y es donde debemos implementar la funcionalidad de este tipo
de servicio.

Lo ms interesante de todo es que este tipo de servicio se ejecuta siempre en un


hilo distinto al principal de la aplicacin.

En este apartado vamos a ver qu ocurre cuando se bloquea el hilo principal con un servicio y
cmo podemos solucionarlo mediante la clase IntentService.

7.4 Ejemplo de uso de IntentService


Este ejemplo muestra una caja de texto donde el usuario puede introducir un nmero entero y
la aplicacin, utilizando un servicio, le indica si el nmero introducido es primo.
En matemticas, un nmero primo es un nmero natural mayor que 1 que tiene nicamente dos
divisores distintos: l mismo y el 1. Los nmeros primos se contraponen as a los compuestos,
que son aquellos que tienen algn divisor natural aparte de s mismos y del 1. El nmero 1, por
convenio, no se considera ni primo ni compuesto.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content >
<EditText
android:id=@+id/entrada
android:layout_width=0dip
android:layout_height=wrap_content
android:layout_weight=1
android:inputType=numberSigned
android:text=2013 >
<requestFocus/>
</EditText>

385

Aula Mentor

<Button
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=calcularOperacion
android:text=Es un nmero primo?/>
</LinearLayout>
<TextView
android:id=@+id/salida
android:layout_width=match_parent
android:layout_height=match_parent
android:text=
android:textAppearance=?android:attr/textAppearanceMedium/>
</LinearLayout>

Como puedes observar, la interfaz de usuario es muy sencilla.


Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente
contenido con la lgica de la aplicacin:
// Clase principal de la Actividad
public class MainActivity extends Activity {
// Vistas de la Actividad
private EditText entrada;
public static TextView salida;

386

// Clase que se utiliza para recibir mensajes del sistema operativo


public class receptorServicio extends BroadcastReceiver {

// Contante que define el mensaje de respuesta

public static final String ACTION_RESP=


es.mentor.unidad4.eje6.intent_service.action
+.RESPUESTA_SERVICIO;


// Cuando recibimos un mensaje nuevo...

@Override

public void onReceive(Context context, Intent intent) {

// Obtenemos el dato interno del Intent

Boolean esPrimo = intent.getBooleanExtra(resultado,

false);

// Actualizamos la interfaz del usuario

salida.append(El nmero +entrada.getText().toString());

if (esPrimo)


salida.append( ES primo + \n);

else


salida.append( NO ES primo + \n);

}
} // end clase receptorServicio
@Override
public void onCreate(Bundle savedInstanceState) {

// Mtodo tpico que busca las Vistas que vamos a utilizar

// despus

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// Buscamos las Vistas de la interfaz del usuario

U4 Bibliotecas, APIs y Servicios de Android

entrada= (EditText) findViewById(R.id.entrada);


salida= (TextView) findViewById(R.id.salida);
// Definimos un IntentFilter para recibir los mensajes
IntentFilter filtro = new
IntentFilter(receptorServicio.ACTION_RESP);
filtro.addCategory(Intent.CATEGORY_DEFAULT);
// Registramos su receptor de mensajes
registerReceiver(new receptorServicio(), filtro);
} // end onCreate
// Mtodo que se lanza cuando el usuario hace clic en el botn de
// la Actividad
public void calcularOperacion(View view) {
// Obtenemos el n que ha escrito
int n = Integer.parseInt(entrada.getText().toString());
// Creamos el Intent para lanzar el servicio
Intent i = new Intent(this, ServicioOperacion.class);
i.putExtra(numero, n);
// Lanzamos el servicio
startService(i);
}
// end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, registramos un receptor de mensajes cuando utilizamos
el servicio del tipo IntentService.
El mtodo calcularOperacion() se lanza cuando el usuario hace clic en el botn de
la Actividad y crea un Intent para lanzar el servicio que calcula si el nmero introducido es
primo o no.
Tambin utilizamos la clase receptorServicio del tipo BroadcastReceiver para recibir mensajes del servicio IntentService.
En el archivo ServicioOperacion.java hemos definido el servicio que utilizamos en
la Actividad principal:
// Clase de tipo Servicio
public class ServicioOperacion extends Service {

// extends IntentService {


// Contructor de la clase IntentService

/*public ServicioOperacion() {

super(Nombre del servicio);
}*/


//Mtodo que se ejecuta cuando arranca el servicio (Service)
@Override
public int onStartCommand(Intent intencion, int flags, int
idArranque){
// Obtenemos el nmero
int n = intencion.getExtras().getInt(numero);

// Actualizamos la interfaz del usuario.
// OJO! Podemos hacer esto porque el servicio se ejecuta
// en el mismo hilo de ejecucin, el principal de la Actividad.
if(esPrimo(n)) MainActivity.salida.append(El nmero + n +

387

Aula Mentor


NO es primo + \n);
else MainActivity.salida.append(El nmero + n + ES primo +
\n);
// Paramos un tiempo la ejecucin
SystemClock.sleep(5000);
// Si queremos que la parada dure ms, entonces descomenta la

// siguiente lnea
//SystemClock.sleep(20000);
// El servicio es del siguiente tipo
return START_STICKY;
}
// No definimos este mtodo ya que no es necesario
@Override
public IBinder onBind(Intent arg0) {
return null;
}

388

// Mtodo invocado cuando arranca el IntentService


/*@Override

protected void onHandleIntent(Intent intencion) {

// Obtenemos el nmero
int n = intencion.getExtras().getInt(numero);

// Paramos mucho el tiempo la ejecucin
SystemClock.sleep(25000);

// OJO! El servicio se ejecuta
// en distinto hilo ejecucin.
//if(esPrimo(n)) MainActivity.salida.append(El nmero + n +

NO es primo + \n);
//else MainActivity.salida.append(El nmero + n + ES primo
+ \n);

// Devolvemos el resultado en un Intent del tipo correspondiente
Intent i = new Intent();
i.setAction(receptorServicio.ACTION_RESP);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.putExtra(resultado, esPrimo(n));
sendBroadcast(i);
}
*/

// Mtodo que indica si un nmero es primo


private static Boolean esPrimo(int n) {

// Algoritmo que comprueba si el n de primo

int a=0;

for(int i=1; i<(n+1) ;i++){

if(n % i==0){

a++;

}
} // end for

return (a!=2);
}
} // end clase servicio

U4 Bibliotecas, APIs y Servicios de Android

Si te fijas en el cdigo fuente anterior, hemos definido el servicio a partir de la clase Service e
implementado su mtodo onStartCommand() que realiza el clculo para verificar si el nmero
es primo con el algoritmo del mtodo esPrimo(). Ms adelante veremos para qu sirve el resto
del cdigo.
Finalmente, acabamos la explicacin de todo el cdigo indicando que, para poder ejecutar el servicio, hay que incluirlo en el fichero AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (Intent Service) de la Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Intent Service de Android.

Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente
aplicacin:

389

Veamos ahora el resto del cdigo fuente del servicio. Como sabes, a la hora de desarrollar
aplicaciones en Android hay que tener en cuenta que todos los componentes (actividades,

Aula Mentor

servicios y receptores de mensajes) se van a ejecutar en el hilo principal de la aplicacin. Este


hilo debe estar siempre disponible para atender los eventos generados por el usuario y, por lo
tanto, no se debe bloquear. Es decir, cualquier proceso que requiera un tiempo importante para
ser ejecutado debe hacer su tarea desde un nuevo hilo, As dejar libre el hilo principal para que
ste pueda seguir procesando nuevos eventos.
Si en el cdigo fuente anterior descomentas la lnea 40 de ServicioOperacion.java:
SystemClock.sleep(20000) para que el tiempo de ejecucin del servicio sea ms largo, vers
que en el AVD la aplicacin se bloquea y aparece el siguiente mensaje de error:

390

Es decir, como el servicio anterior se est utilizando en el hilo principal, se est parando este
hilo y el sistema operativo indica que la aplicacin no responde.
Veamos lo que ocurre si utilizamos un servicio IntentService que, recordamos, se ejecuta en
un hilo distinto al principal.
Para ello, basta con cambiar el tipo de servicio extendiendo su clase de IntentService
y descomentando los mtodos ServicioOperacion() (contructor de la clase) y onHandleIntent() (invocado cuando arranca el IntentService). Adems, hay que comentar el mtodo
onStartCommand().

U4 Bibliotecas, APIs y Servicios de Android

Cuidado! Como este servicio se ejecuta en distinto hilo de ejecucin, no podemos


acceder directamente la interfaz del usuario como en el caso anterior. Si descomentas, las lneas 68 y 69 vers que aparece este error:

Tampoco es posible utilizar la clase Toast de mensajes flotantes desde otros hilos.

Como el nuevo hilo del IntentService que hemos creado pertenece al mismo proceso que el
hilo principal, podemos compartir entre ellos todas las variables. Es decir, podramos implementar
un nuevo mtodo o una variable pblica para devolver el valor de la funcin esPrimo(), tanto
en la clase del servicio como en la clase de la actividad. Sin embargo, hemos resuelto este
inconveniente utilizando un mecanismo ms elegante: receptores de mensajes.
Si modificamos el tiempo de retardo a 25 segundos para este IntentService y ejecutamos de nuevo la aplicacin, veremos que el sistema no muestra ningn error (aunque s tarda
en dar el resultado):
391

Aula Mentor

7.5 Comunicacin con servicios


A continuacin, vamos a ampliar los conocimientos sobre servicios estudiando las distintas
formas de comunicarnos con ellos.
Existen varias maneras de comunicacin bidireccional entre una actividad y un servicio. En esta
seccin veremos las diferentes formas y daremos recomendaciones sobre su uso. A continuacin,
se muestra el listado:
- Actividad ligada (bind) al servicio
Ya hemos comentado anteriormente que, si el servicio se inicia en el mismo proceso que la
actividad, sta puede conectarse directamente al servicio. Esta es una manera relativamente
sencilla y eficiente para la comunicacin. Es importante no realizar operaciones complejas que bloqueen el hilo principal.
- Utilizando un receptor de mensajes (Receiver)
Tambin puedes definir receptores de mensajes registrados por la Actividad para recibir
mensajes del servicio, tal y como hemos hecho en el Ejemplo 6 de esta Unidad.
- Enviando informacin va Intent al arrancar servicios
Al arrancar un servicio, ste recibe datos desde el componente que lo ejecuta dentro de
bundle de datos del Intent.

392

- Handler y Messenger
Si el Servicio debe comunicar informacin a la Actividad, sta puede recibirla mediante un
objeto de tipo Message mediante un Intent. En el Ejemplo 7 ( Juego) de la Unidad 3
hemos visto cmo utilizar este mecanismo de comunicacin.
Si es la Actividad la que quiere comunicarse con un servicio, podemos usar la clase
Messenger. Esta clase es parcelable, se puede delegar en otro proceso, es decir, tambin
sirve para enviar objetos del tipo Messages al Handler de la Actividad.
Messenger dispone del mtodo getBinder(), que permite a la Actividad enviar
mensajes al servicio.
- Utilizacin de AIDL para servicios en distintos procesos
Para comunicar con un Servicio que se ejecuta en un proceso diferente, podemos hacer uso
de IPC (Inter Process Communication) para enviar datos entre diferentes procesos.
AIDL (Android Interface Definition Language) es similar a otros IPC existentes en el
diseado especficamente para Android.
Para ello, debemos crear un fichero AIDL que, aparentemente, es similar a un interfaz
(interface) de Java pero el fichero tiene la extensin .aidl y slo permite extender a
otros ficheros AIDL.

Este mecanismo se debe emplear slo si es necesario que el servicio est disponible a otras aplicaciones; de lo contrario deberamos utilizar un servicio definido
localmente.

Para hacerlo, debemos dar los siguientes pasos:


Definir la interfaz AIDL del servicio en un archivo con extensin .aidl.
Implementar el Stub para el servicio remoto. Un Stub es un bloque de cdigo usado

U4 Bibliotecas, APIs y Servicios de Android

como sustituto de alguna otra funcionalidad. En este caso se implementa la interfaz


definida en el archivo .aidl.
Conectarnos al servicio desde cualquier aplicacin Android.

7.6 Ejemplo de uso de AIDL


Este ejemplo muestra dos cajas de texto donde el usuario puede introducir dos nmeros enteros
y la aplicacin, utilizando un servicio de tipo AIDL, le indica el resultado de multiplicar ambos
nmeros.
En cdigo del layout activity_main.xml se incluye el sencillo diseo de la Actividad principal:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent
android:gravity=center_horizontal >

<TextView android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Ejemplo 7: Servicio AIDL
android:textSize=22sp/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<EditText
android:id=@+id/valor1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:hint=Nmero 1 />
<TextView android:layout_width=wrap_content
android:gravity=center
android:layout_height=wrap_content android:text=x
android:textSize=36sp/>
<EditText
android:id=@+id/valor2
android:layout_width=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:layout_height=wrap_content
android:hint=Nmero 2/>
<Button android:id=@+id/botonCalc
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text==/>

393

Aula Mentor


<TextView

android:id=@+id/resultado

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Resultado

android:textSize=22sp />

</LinearLayout>

Despus, vamos a definir el archivo IMultiplicacionServicio.aidl que especifica las


funciones primitivas disponibles por el servicio y a las que deseamos conectarnos mediante una
Actividad:
// Declaramos la interfaz.
interface IMultiplicacionServicio {
// Los tipos de datos utilizados pueden ser del tipo int, boolean,
// etc.
int multiplicar(in int valor1, in int valor2);
}

394

Como puedes observar, simplemente definimos la interface del servicio con los mtodos
disponibles. En este caso, el mtodo multiplicar.
Los parmetros de los mtodos definidos en la interfaz tienen una etiqueta que marca el cometido
del mismo. Puede contener in (entrada), out (salida) o inout (entrada/salida).
- Por defecto, AIDL soporta los siguientes tipos de datos:
- Todas las primitivas del lenguaje de programacin Java (como int, long, char, boolean,
etctera).
- String
- CharSequence
- List (todos los elementos de la lista deben ser uno de los tipos de datos soportados anteriores).
- Map (todos los elementos de la lista deben ser uno de los tipos de datos soportados anteriores).
- Todas las clases que sean parceables (que se puede partir o serializar).
Ahora vamos a definir el Stub del servicio, es decir, el bloque del cdigo que implementa la
funcin anterior. Si abrimos el archivo ServicioOperacion.java vemos que contiene:
// Clase de tipo Servicio que se ejecuta en segundo plano
// cuando arranca la Actividad
public class ServicioOperacion extends Service {

@Override

public void onCreate() {
super.onCreate();
}


// Definimos el evento onBind que se usa para ligar la Actividad

// al servicio
@Override

public IBinder onBind(Intent intent) {
// Devuelve el resultado de multiplicar dos nmeros

// implementando el mtodo multiplicar definido en

// IMultiplicacionServicio.aidl

U4 Bibliotecas, APIs y Servicios de Android


return new IMultiplicacionServicio.Stub() {

public int multiplicar(int valor1, int valor2) throws
RemoteException {

return valor1 * valor2;

}

};
}
@Override

public void onDestroy() {

super.onDestroy();
}

} // end clase servicio

Puedes observar en el cdigo anterior que la clase se extiende de Service e implementa el


evento onBind(), que se lanza cuando un componente de Android se liga al servicio.
En este caso, se devuelve el resultado de multiplicar dos nmeros implementando el
mtodo multiplicar definido en IMultiplicacionServicio.aidl.
A continuacin, si abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente
contenido con la lgica de la aplicacin:
// Clase principal de la Actividad
public class MainActivity extends Activity {
// Vistas de la Actividad
private static TextView resultado;
private EditText valor1;
private EditText valor2;
private Button boton;
// Servicio definido en el archivo AIDL
IMultiplicacionServicio servicio;
// Conexin al servicio anterior
ServicioOperacionConexion conexion;
// Esta clase representa la conexin al servicio a partir de
// la clase ServiceConnection que se usa para conectarse a
// un servicio de otro proceso
class ServicioOperacionConexion implements ServiceConnection {
// Evento que se lanza cuando se conecta al servicio
public void onServiceConnected(ComponentName name, IBinder
boundService) {
// Buscamos el servicio haciendo un typecasting del servicio

// definido en el archivo IMultiplicacionServicio.aidl
servicio = IMultiplicacionServicio.Stub.asInterface((IBinder)
boundService);
// Mostramos mensaje al usuario

Log.d(AIDL, onServiceConnected() conectado!!!);

Toast.makeText(MainActivity.this, Servicio remoto conectado,

Toast.LENGTH_LONG).show();
}
// Evento que se lanza cuando nos desconectamos del servicio
public void onServiceDisconnected(ComponentName name) {
// Liberamos la variable servicio

395

Aula Mentor

servicio = null;
// Mostramos un mensaje al usuario
Log.d(AIDL, onServiceDisconnected() desconectado);
Toast.makeText(MainActivity.this, Service remoto
desconectado, Toast.LENGTH_LONG).show();

}
} // end clase ServicioOperacionConexion

// Mtodo que liga la Actividad con el servicio


private void iniciarServicio() {

// Definimos la conexin del servicio

conexion = new ServicioOperacionConexion();

// Mediante un Intent le pasamos la clase que define

// el servicio

Intent i = new Intent();

i.setClassName(es.mentor.unidad4.eje7.aidl,

es.mentor.unidad4.eje7.aidl.ServicioOperacion.
class.getName());

// Ligamos la actividad al servicio

boolean res = bindService(i, conexion,

Context.BIND_AUTO_CREATE);

Log.d(AIDL, initService() ligado (BIND) con resultado +

res);
} // end iniciarService()

396

// Desliga la Actividad del servicio


private void pararServicio() {
// Desligamos la Actividad
unbindService(conexion);
conexion = null;
Log.d(AIDL, releaseService() desligado.);
}
@Override
public void onCreate(Bundle savedInstanceState) {
// Mtodo tpico que busca las Vistas que vamos a utilizar

// despus
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
resultado = (TextView) findViewById(R.id.resultado);
valor1 = (EditText) findViewById(R.id.valor1);
valor2 = (EditText) findViewById(R.id.valor2);
boton = (Button) findViewById(R.id.botonCalc);
// Definimos el onClick del botn
boton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int v1, v2, res = -1;
// Validamos que el usuario ha introducido los operandos
if (valor1.getText().toString().isEmpty()

|| valor2.getText().toString().isEmpty()) {

Toast.makeText(MainActivity.this,

ERROR: debes introducir al menos dos
nmero enteros.,

Toast.LENGTH_SHORT).show();

return;

U4 Bibliotecas, APIs y Servicios de Android

}
// Buscamos el valor de los operandos
v1 = Integer.parseInt(valor1.getText().toString());
v2 = Integer.parseInt(valor2.getText().toString());
// Invocamos el servicio
try {
res = servicio.multiplicar(v1, v2);
} catch (RemoteException e) {
Log.d(AIDL, Error al invocar el servicio: + e);
e.printStackTrace();
}
// Mostramos el resultado
resultado.setText(String.valueOf(res));

}
}); // end evento onClick botn
// Iniciamos el servicio
iniciarServicio();
} // end onCreate()


@Override

protected void onDestroy() {
// Al acabar la Actividad liberamos el servicio

pararServicio();

super.onDestroy();

}
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, definimos el evento onClick() del botn de la
aplicacin y conectamos con el servicio AIDL.
El mtodo iniciarServicio() liga la Actividad con el servicio. Para ello, definimos la
conexin del servicio mediante la clase local ServicioOperacionConexion y un Intent en el
que indicamos la clase que define el servicio en la orden bindService().
La clase ServicioOperacionConexion se implementa a partir de la clase ServiceConnection y sus mtodos onServiceConnected() y onServiceDiconnected(), que obtiene
el cdigo Stub del servicio remoto haciendo un typecasting del servicio definido en el archivo
IMultiplicacionServicio.aidl. As, ya tenemos acceso al mtodo multiplicar del citado
servicio.

Una aplicacin que quisiera conectarse a este servicio debe contar con el archivo
.aidl correcto e implementar su clase ServiceConnection correspondiente.

Finalmente, y como es habitual, para poder ejecutar el servicio hay que incluirlo en el fichero
AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>

397

Aula Mentor

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (Servicio AIDL) de la Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Servicio AIDL de Android.

Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente
aplicacin:

398

8. Servicios SOAP en Android


Un Servicio SOAP (siglas de Simple Object Access Protocol) es un protocolo estndar que
define cmo un cliente y un servidor pueden comunicarse por medio de intercambio de datos
en formato XML.
En este apartado vamos a describir cmo desarrollar en Eclipse ADT un servidor de aplicaciones SOAP en Java y, posteriormente, vamos a acceder a l desde una aplicacin Android.
El alumno debe tener en cuenta que los servicios SOAP pueden ser complejos. El
objetivo de este apartado es demostrar cmo usarlos en una aplicacin Android.

U4 Bibliotecas, APIs y Servicios de Android

8.1 Instalacin de bibliotecas SOAP en Eclipse ADT


Lo primero que vamos a exponer es cmo incluir las bibliotecas necesarias en Eclipse ADT
para poder desarrollar un servidor de aplicaciones SOAP en Java.
Para ello, hacemos clic en la opcin del men Help -> Install New Software de
Eclipse ADT. En la ventana que aparece a continuacin hay que seleccionar la opcin --All
Available Sites-- en el campo Work with:

399

Debemos seleccionar las opciones siguientes:


- Eclipse Java EE Developer Tools
- Eclipse Web Tools Platform
- Eclipse Java Web Developer Tools
- JAX-WS Tools
- JST Server Adapters
- JST Server Adapters Extensions
- JST Server UI
- Web Page Editor

Aula Mentor

A continuacin, pulsamos el botn Next y aparecer la siguiente ventana:

400

Pulsamos de nuevo el botn Next y aceptamos la licencia de uso en la ventana siguiente:

U4 Bibliotecas, APIs y Servicios de Android

A continuacin, al pulsar el botn Finish, se instala el nuevo software:

Una vez acabada la instalacin, Eclipse ADT solicitar reiniciarse:

Pulsamos el botn Yes para que Eclipse ADT se reinicie.


Una vez se haya reiniciado Eclipse ADT, vamos a configurar el servidor Web de aplicaciones Tomcat. Para ello, hacemos clic en la opcin del men Windows -> Preferences y
seleccionamos el elemento Server-Runtime Environment:

401

Aula Mentor

Si pulsamos el botn Add aparece una ventana donde tenemos que seleccionar Apache
Tomcat v7.0; despus, pulsamos el botn Next:

402
En la siguiente ventana indicamos el nombre del servidor y el directorio de su instalacin, y lo
descargamos pulsando el botn Download and Install:

U4 Bibliotecas, APIs y Servicios de Android

A continuacin, aparece una nueva ventana superpuesta sobre la anterior. Hay que pulsar el
botn Finish para instalar el servidor Tomcat:

Una vez acaba la instalacin, aparecer el nuevo servidor Tomcat como disponible en las
opciones de Eclipse ADT:

403

Aula Mentor

Y en Eclipse ADT aparece tambin el nuevo servidor como si se tratara de un proyecto ms:

8.2 Desarrollo de un servidor SOAP en Eclipse ADT


Seguidamente, vamos a crear el servicio SOAP en el servidor de aplicaciones Tomcat que hemos
creado en el paso anterior.
Hacemos clic en la opcin File > New -> Other del men principal de Eclipse ADT y
seleccionamos la creacin de un nuevo proyecto del tipo Dynamic Web Proyect:

404

U4 Bibliotecas, APIs y Servicios de Android

Pulsamos el botn Next y escribimos el nombre del proyecto:

405

Pulsamos el botn Next de nuevo dejando las opciones como estn:

Aula Mentor

En la siguiente ventana podemos modificar el directorio donde se almacena los contenidos Web
e indicar que se cree el archivo web.xml que describe el servidor:

Finalmente, pulsamos el botn Finish y Eclipse ADT ofrecer cambiar la perspectiva a Java
EE. Es recomendable hacerlo para gestionar el servidor de forma ms sencilla.

406

Ahora que ya hemos creado el proyecto del servicio SOAP, hacemos clic con el botn derecho
del ratn sobre el proyecto y seleccionamos la opcin New -> Package:

U4 Bibliotecas, APIs y Servicios de Android

Rellenamos la ventana anterior con el nombre del paquete y pulsamos Finish para crearla.
Dentro del paquete anterior, con la opcin New -> Class creamos la clase que implementar el
servicio SOAP. Completamos la ventana tal y como aparece en la siguiente imagen:

407

Abrimos el archivo creado y desarrollamos el mtodo siguiente:


public class WebService {
// Mtodo que devuelve los grados en unidades Celsius de un
// valor en fahrenheit
public int getGradosCelsius(int grados)
{
return (int) ((grados-32) / 1.8);
}
} // end WebService

Como puedes ver, se trata de un sencillo mtodo que cambia un valor temperatura de las
unidades Fahrenheit a Celsius.
Despus, vamos a crear el servicio SOAP haciendo clic con el botn derecho del ratn
sobre el proyecto y en la opcin File > New -> Other del men principal de Eclipse ADT
seleccionamos Web Service:

Aula Mentor

A continuacin, aparece la ventana siguiente:

408

U4 Bibliotecas, APIs y Servicios de Android

En esta ventana es muy importante escribir, en el campo Service Implementation, el nombre


ntegro (en programacin, se denomina tambin nombre completamente calificado) de la clase
anterior, es decir, su paquete + nombre.
Tambin debemos mover la primera barra de desplazamiento hasta Start service para
que se inicie el servicio y la segunda hasta Test Client para que se cree automticamente un
cliente sencillo y poder probar el servicio.
Si pulsamos el botn Finish, aparecer la siguiente ventana donde podemos iniciar el servicio:

409

Una vez se ha creado el servicio SOAP, podemos ver su definicin en el archivo ServidorSoap.
wsdl del proyecto:

Aula Mentor

En el caso de que no hayamos iniciado el servicio con el botn Start Server anterior, podemos
hacerlo desde el proyecto con el botn derecho del ratn en la opcin Run as > Run on
Server:

410

Aparecer esta ventana donde debemos pulsar el botn Next:

U4 Bibliotecas, APIs y Servicios de Android

En la siguiente ventana podemos seleccionar si queremos ejecutar el servicio SOAP/ Cliente


SOAP:

411

Al ejecutar el servicio, el sistema operativo Windows puede mostrar ventanas de este tipo
indicando que es necesario desbloquear Eclipse ADT para que funcione como servidor.

Aula Mentor

Pulsaremos el botn Desbloquear y veremos en Eclipse ADT que el servicio SOAP se ejecuta
correctamente:

Adems, observars que se abre en Eclipse ADT una ventana interna con un navegador
donde podemos probar el servicio SOAP anterior, este es el cliente de pruebas (http://

localhost:8080/unidad4.eje8.soapClient/sampleWebServiceProxy/TestClient.
jsp):

412

8.3 Ejemplo de uso de servidor SOAP en Android


En la fecha de redaccin del curso el SDK de Android no incluye ningn soporte para el acceso
a servicios Web de tipo SOAP.
Para poder utilizarlo, vamos a emplear la popular biblioteca externa ksoap2-android.
Esta biblioteca, que es un fork (migracin) de la biblioteca kSOAP2, especialmente adaptada para
Android, nos permitir acceder a servicios Web que utilicen el estndar SOAP.
La ltima versin de esta biblioteca en el momento de escribir este texto es la 3.0.0. Puedes
descargarla deeste enlace.
Este ejemplo muestra una caja de texto donde el usuario puede introducir una temperatura en unidades Fahrenheit y la aplicacin, utilizando un servicio SOAP, obtiene su valor en
unidades Celsius.

U4 Bibliotecas, APIs y Servicios de Android

Estudiemos ahora, de forma prctica, cmo utilizar esta biblioteca SOAP. Es recomendable abrir
el Ejemplo 8 de esta Unidad para entender la explicacin siguiente.
Ya hemos estudiado en el curso cmo incluir bibliotecas externas en proyectos Android. Basta
con copiar su archivo .jar en la carpeta libs del proyecto e importarla al proyecto.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent
android:gravity=center_horizontal >

<TextView android:layout_width=fill_parent

android:layout_height=wrap_content android:text=Ejemplo 8:
Servicio SOAP

android:textSize=22sp/>


<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />


<EditText

android:id=@+id/grados

android:layout_width=wrap_content

android:layout_height=wrap_content

android:gravity=center_horizontal|center

android:inputType=numberSigned

android:hint=Grados fahrenheit />


<Button android:id=@+id/botonCalc

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Obtener grados Celsius/>


<TextView

android:id=@+id/resultado

android:layout_width=wrap_content

android:layout_height=wrap_content

android:text=Resultado

android:textSize=22sp />
</LinearLayout>

Como puedes observar, la interfaz de usuario es muy sencilla.


Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente
contenido con la lgica de la aplicacin:
// Clase principal de la Actividad
public class MainActivity extends Activity {
// Vistas de la Actividad
private static TextView resultado;
private EditText grados;
private Button boton;

413

Aula Mentor

414

@Override
public void onCreate(Bundle savedInstanceState) {

// Mtodo tpico que busca las Vistas que vamos a utilizar

// despus

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// Buscamos las Vistas de la interfaz del usuario

resultado = (TextView) findViewById(R.id.resultado);

grados = (EditText) findViewById(R.id.grados);

boton = (Button) findViewById(R.id.botonCalc);

// Definimos el onClick del botn

boton.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

int valor = -1;

// Validamos que el usuario ha introducido los operandos

if (grados.getText().toString().isEmpty()) {


Toast.makeText(MainActivity.this,


ERROR: debes introducir los grados.,


Toast.LENGTH_SHORT).show();


return;

}

// Buscamos los grados introducidos

valor = Integer.parseInt(grados.getText().toString());

// Invocamos el servicio a travs de una tarea asncrona

// (Puede tardar en responder el servidor y no queremos

//
bloquear el hilo principal de la aplicacin!)

TareaWebService tarea = new TareaWebService();

// Le pasamos como parmetro los grados introducidos por

// el usuario

tarea.execute(valor);

}

}); // end evento onClick botn


} // end onCreate()
// Tarea Asncrona para llamar al WebService de consulta en
// segundo plano que admite enteros como prametros
private class TareaWebService extends

AsyncTask<Integer,Integer,Boolean> {

// Aqu guardamos el resultado de la ejecucin del servicio
String resultadoTxt=;
protected Boolean doInBackground(Integer... params) {

// Variable que usamos para saber si la ejecucin termina

// bien

boolean resul = true;
// El nombre del espacio del servicio SOAP
final String NAMESPACE =
http://soap.eje8.unidad4.mentor.es;

// La URL a la que debemos conectarnos

// ES NECESARIO CAMBIAR LA IP A TU ORDENADOR!!!!

final String URL=http://10.X.X.X:8080/unidad4.eje8.soap/
services/WebService?wsdl;

// Mtodo que vamos a invocar

final String METHOD_NAME = getGradosCelsius;

U4 Bibliotecas, APIs y Servicios de Android

// Accin invocada = NAMESPACE + METHOD_NAME


final String SOAP_ACTION =
http://soap.eje8.unidad4.mentor.es/getGradosCelsius;



// Definimos el objeto HTTP que se usa para conectar al

// servidor de URL

HttpTransportSE transporte = new HttpTransportSE(URL);

// Definimos el objeto de tipo SOAP con el nombre del

// espacio y mtodo

SoapObject request = new SoapObject(NAMESPACE,

METHOD_NAME);

// Aadimos el parmetro que necesita el mtodo anterior

request.addProperty(grados, params[0]);

// Para poder enviar la peticin al servidor es necesario

// comprimirlo todo en un bloque (sobre)

SoapSerializationEnvelope envelope =
new SoapSerializationEnvelope(SoapEnvelope.VER11);

// Se usa para indicar que se debe utilizar compatibilidad

// si nos vamos a conectar a servicios Net-Services

// (Microsoft).

// Aqu el servidor es Apache-Tomcat as que lo dejamos

// comentado

// envelope.dotNet = true;

// Aadimos la peticin

envelope.setOutputSoapObject(request);

// Controlamos los errores de ejecucin

try

{


// Hacemos la peticin al servidor


transporte.call(SOAP_ACTION, envelope);


// Obtenemos el resultado


SoapPrimitive resSoap
=(SoapPrimitive)envelope.getResponse();


// Lo guardamos en la variable temporal


resultadoTxt=resSoap.toString();

}

catch (Exception e)

{


// Si hay un error, lo mostramos en el Logcat


resul = false;


Log.d(Debug, e.getMessage().toString());

}

return resul;
} // end doInBackground
// Al acabar mostramos el resultado de la tarea
protected void onPostExecute(Boolean result) {

if (result)

resultado.setText(resultadoTxt+ Celsius);

else

resultado.setText(Error al ejecutar la llamada!);
}
} // end TareaWebService
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las

415

Aula Mentor

Vistas de la interfaz del usuario y definimos el onClick del botn Obtener grados Celsius, con
el que invocamos el servicio SOAP a travs de una tarea asncrona.

Importante: la respuesta del servicio SOAP puede tardar bastante tiempo y no


debemos bloquear el hilo principal de la aplicacin; por lo tanto, llamamos a este
servicio con una tarea asncrona (AsyncTask) que se ejecuta en otro hilo.

Si analizamos esta clase TareaWebService, extendida de AsyncTask, que implementa la


llamada al servicio SOAP, vemos que definimos cuatro constantes que servirn para acceder al
citado servicio:
- NAMESPACE: espacio de nombres utilizado en el servicio Web.
- URL: direccin URL de la conexin del servicio Web.
- METHOD_NAME: nombre del mtodo Web que vamos a ejecutar.
- SOAP_ACTION: accin SOAP que se define como NAMESPACE + METHOD_NAME.
Podemos conocer estos valores del archivo WebService.wsdl del servidor SOAP accediendo
desde el navegador de Internet a la pgina http://localhost:8080/unidad4.eje8.soap/services/
WebService?wsdl. Veremos lo siguiente:

416

U4 Bibliotecas, APIs y Servicios de Android

En la imagen anterior se muestran resaltados en amarillo los valores de tres de las cuatro
constantes que debemos utilizar en la aplicacin Android. La cuarta constante se define a partir
de las dos anteriores.

IMPORTANTE: en la URL del navegador hay que sustituir el nombre de la mquina


localhost por su direccin IP, para que las aplicaciones Android ejecutadas en
el AVD se conecten a la direccin correcta, ya que la direccin 127.0.0.1 (localhost)
corresponde al propio dispositivo virtual que no dispone del servidor SOAP.

Para obtener la direccin IP en Windows podemos ejecutar el comando IP config y ver el


resultado:

417

En los sistemas operativos de tipo Unix podemos ejecutar el comando ifconfig:

Antes de continuar, vamos a entender el formato de una peticin de este servidor SOAP en
concreto. Veamos la estructura que tendra si la capturamos:
Send: Return Code: 0x00000000
POST /unidad4.eje8.soap/services/WebService?wsdl HTTP/1.1

Aula Mentor

User-Agent: ksoap2-android/2.6.0+
SOAPAction: http://soap.eje8.unidad4.mentor.es/getGradosCelsius
Content-Type: text/xml;charset=utf-8
Connection: close
Accept-Encoding: gzip
Host: 10.17.19.63:8080
Content-Length: 398
<v:Envelope xmlns:i=http://www.w3.org/2001/XMLSchema-instance

xmlns:d=http://www.w3.org/2001/XMLSchema

xmlns:c=http://schemas.xmlsoap.org/soap/encoding/

xmlns:v=http://schemas.xmlsoap.org/soap/envelope/>
<v:Header />
<v:Body>
<n0:getGradosCelsius id=o0 c:root=1
xmlns:n0=http://soap.eje8.unidad4.mentor.es>

<grados i:type=d:int>110</grados>
</n0:getGradosCelsius>
</v:Body>
</v:Envelope>
Receive: Return Code: 0x00000000
HTTP/1.1 200 OK

418

Puedes ver en la captura anterior varias zonas marcadas correspondientes a las tres partes
principales de una peticin SOAP.
- Primero, en color rojo, se encuentra la CABECERA HTTP ya que el envo de una peticin
SOAP al servidor se realiza mediante este protocolo http.
- Despus, en color azul, se incluye otra serie de etiquetas y datos a modo de CONTENEDOR
estndar que recibe el nombre de Envelope. La informacin indicada en este contenedor no
es especfica de la llamada al servicio, pero s contiene informacin sobre formatos y esquemas de validacin del estndar SOAP.
- Finalmente, en color verde, en la parte interna del XML, encontramos los datos de la PETICIN (Request) en s, que contiene el nombre del mtodo invocado y los nombres y
valores de los parmetros en entrada.
Todo esto junto har que el servidor sea capaz de interpretar correctamente la peticin SOAP,
llame al mtodo interno correcto y devuelva el resultado en un formato similar al anterior:
Send: Return Code: 0x00000000
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Fri, 06 Sep 2013 08:19:05 GMT
Connection: close
198
<?xml


version=1.0 encoding=utf-8?><soapenv:Envelope
xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance>

U4 Bibliotecas, APIs y Servicios de Android

<soapenv:Body>
<getGradosCelsiusResponse xmlns=http://soap.eje8.unidad4.mentor.es>

<getGradosCelsiusReturn>43</getGradosCelsiusReturn>
</getGradosCelsiusResponse>
</soapenv:Body>
</soapenv:Envelope>
Send: Return Code: 0x00000000
0

Una vez estudiada la estructura y funcionamiento general de una peticin SOAP y su respuesta,
veamos cmo incluirla en una aplicacin Android.
Si continuamos examinando la clase TareaWebService, observamos que crea la peticin
SOAP al servicio Web, la enva y recibe su respuesta correspondiente.
Primero creamos el objeto del tipo HttpTransportSE que se encargar de realizar la comunicacin
HTTP con el servidor SOAP. A este objeto le pasaremos la URL de la conexin al servicio web.
Posteriormente, creamos la peticin (request) al mtodo correspondiente mediante un
nuevo objeto SoapObject indicando como parmetros el NAMESPACE y el nombre del mtodo
Web. Mediante el mtodo addProperty() de esta clase incluimos los parmetros de entrada del
mtodo anterior indicando el nombre del parmetro y su valor.
Despus, definimos el contenedor SOAP (envelope) mediante su mtodo SoapSerializationEnvelope indicando la versin de SOAP que vamos a utilizar (versin 1.1).
Si vamos a conectar a un servicio SOAP de Net-Services (Microsoft), debemos indicar dotNet =
true del contenedor. Como ste no es el caso, en el cdigo fuente original lo hemos comentado.
A continuacin, asociamos la peticin creada anteriormente a este contenedor mediante el
mtodo setOutputSoapObject().
Finalmente, para invocar al mtodo remoto del servidor SOAP. usamos el mtodo call()
del objeto del tipo HttpTransportSE.
Despus de la llamada al servicio SOAP, vamos a recibir su resultado mediante el mtodo
getResponse(). Dependiendo del tipo de resultado que devuelva el mtodo invocado debemos
convertir la respuesta a un tipo u otro.
En este caso, como el resultado que esperamos es un valor simple (un nmero entero),
convertimos la respuesta a un objeto del tipo SoapPrimitive que, directamente, transformamos
a una cadena de tipo cadena invocando toString().
Finalmente, y como es habitual, para poder acceder a Internet es necesario indicarlo en
el fichero AndroidManifest.xml:
<uses-permission android:name=android.permission.INTERNET/>

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 8 (Servicio SOAP) de la


Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado un Servicio SOAP de Android.

419

Aula Mentor

Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:

420

Para que este ejemplo funcione, debes definir tu servidor Tomcat en Eclipse ADT e importar el
cdigo fuente del servidor SOAP y ejecutarlo. Con el servidor SOAP lanzado debes ejecutar la
aplicacin Android. No te olvides de escribir la direccin IP de tu ordenador para que funcione
bien!

8.4 Peticin / Respuesta compleja SOAP en Android


Si la respuesta del servidor fuera un objeto complejo, debemos convertir el resultado de
getResponse() al tipo SoapObject. Fjate en el siguiente ejemplo de cdigo que devuelve una
matriz del tipo Dato (que tiene sus propiedades y mtodos correspondientes):
// Obtenemos la respuesta del servidor
SoapObject resSoap =(SoapObject)envelope.getResponse();
// Creamos la matriz con los datos obteniendo el n de propiedades del
// objeto
Dato[] listado = new Dato[resSoap.getPropertyCount()];
// Recorremos todos los elementos del resultado
for (int i = 0; i < listado.length; i++)
{

// Obtenemos el elemento i
SoapObject ic = (SoapObject)resSoap.getProperty(i);

// Creamos un objeto de tipo Dato
Dato dato = new Dato();

// Asignamos las propiedades de este objeto
dato.id = Integer.parseInt(ic.getProperty(0).toString());
dato.nombre = ic.getProperty(1).toString();
dato.edad = Integer.parseInt(ic.getProperty(2).toString());

// Aadimos el dato al listado
listado[i] = dato;
}

U4 Bibliotecas, APIs y Servicios de Android

Como el resultado devuelto por el servicio es una matriz del tipo Dato, lo primero que hemos
hecho es crear la matriz local con su misma longitud, que se obtiene mediante su mtodo
getPropertyCount().
Despus, recorremos los distintos elementos de la matriz devuelta mediante el mtodo
getProperty(indice). A su vez, cada uno de estos elementos es otro objeto del tipo SoapObject que representa a un Dato.
Para acceder a las propiedades de cada elemento, invocamos el mtodo
getProperty(indice) con el ndice de cada atributo.
Si deseamos invocar un mtodo Web que recibe como parmetro algn objeto complejo.
debemos modificar la clase Dato de forma que sea serializable.

La serializacin (o marshalling en ingls) consiste en un proceso de codificacin de


un objeto para ser tratado de forma automtica. En este caso, el protocolo SOAP
requiere que los datos intercambiados entre el cliente y servidor mantengan ciertas reglas y formatos.

Para ello, implementamos la interfaz KvmSerializable en esta clase Dato que obliga a
desarrollar los siguientes mtodos:
- getProperty(int indice): devuelve el valor de cada atributo de la clase a partir de su
ndice de orden. Por ejemplo, el ndice 0 retorna el valor del atributo Id, el ndice 1 el del
atributo Nombre, etctera:
@Override
public Object getProperty(int arg0) {
switch(arg0) {
case 0:
return id;
case 1:
return nombre;
case 2:
return edad;
}
return null;
}

- getPropertyCount(): indica el nmero de atributos de la clase Dato, que en nuestro caso


ser 3 (Id, Nombre y Edad):
@Override
public int getPropertyCount() {

return 3;
}

- getPropertyInfo(int indice, HashTable ht, PropertyInfo info): en funcin del


ndice indicado como parmetro, informa del tipo y del nombre del atributo correspondiente. El tipo de atributo se seala mediante un valor de la clase PropertyInfo:
@Override
public void getPropertyInfo(int ind, Hashtable ht, PropertyInfo info) {

421

Aula Mentor

switch(ind) {
case 0:
info.type =
info.name =
break;
case 1:
info.type =
info.name =
break;
case 2:
info.type =
info.name =
break;
default:break;

PropertyInfo.INTEGER_CLASS;
Id;
PropertyInfo.STRING_CLASS;
Nombre;
PropertyInfo.INTEGER_CLASS;
Edad;

- setProperty(int indice, Object valor): asigna el valor de cada atributo segn su


ndice y el valor recibido como parmetro:

422

@Override
public void setProperty(int ind, Object val) {

switch(ind) {
case 0:
id = Integer.parseInt(val.toString());
break;
case 1:
nombre = val.toString();
break;
case 2:
edad = Integer.parseInt(val.toString());
break;
default:
break;
}
}

Si deseamos invocar un mtodo pasndole un objeto Dato como parmetro podemos escribir
las siguientes sentencias de ejemplo:
// Objeto de peticin al servidor
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
// Objeto de tipo Dato que vamos a enviar en la peticin
Dato dato = new Dato();
dato.nombre = txtNombre.getText().toString();
dato.edad = Integer.parseInt(txtEdad.getText().toString());
PropertyInfo pi = new PropertyInfo();
pi.setName(dato);
pi.setValue(dato);
pi.setType(dato.getClass());
// Aadimos a la peticin el objeto Dato
request.addProperty(dato);
// Contenedor de la peticin
SoapSerializationEnvelope envelope = new

U4 Bibliotecas, APIs y Servicios de Android


SoapSerializationEnvelope(SoapEnvelope.VER11);
// Aadimos la peticin al contenedor
envelope.setOutputSoapObject(request);
// Asociamos al espacio de nombres la clase Dato con la clase real //
Java
envelope.addMapping(NAMESPACE, Dato, dato.getClass());
// Aqu van el resto de sentencias de la llamda

Como puedes ver, con esta biblioteca se puede desarrollar una conexin completa con un
servidor de aplicaciones de tipo SOAP.

9. Resumen
Hay que saber al final de esta unidad:
- Una biblioteca (del ingls library) es un conjunto de funciones y utilidades que
tienen una interfaz bien definida y que no se puede ejecutar de forma autnoma:
Su fin es ser utilizada por otros programas.
- Las bibliotecas en Android son funciones y recursos que podemos compilar conjuntamente en un proyecto Android.
- Existen muchas bibliotecas de cdigo libre disponibles en Internet que pueden
simplificar la programacin de un proyecto.
- Android dispone de una API de telefona para gestionar llamadas de telfono y
mensajes cortos (SMS) que se encuentra en el paquete android.telephony.
- Las clases ms importantes de esta API de Android se denominan TelephonyManager y SMSManager.
- La clase Loader permite cargar datos asncronamente, monitoriza la fuente y
muestra nuevos resultados cuando cambia.
- Android dispone de Calendarios donde el usuario puede administrar citas y recordatorios. Existen dos formas de gestionarlos:
Mediante Intenciones (Intents), accediendo indirectamente al Calendario
mediante Actividades.
Utilizando la API oficial que permite gestionarlo de forma directa.
- La API del Calendario de Android se basa en la utilizacin de ContentProvider
(proveedor de contenidos) que utiliza la clase principal CalendarContract.
- Mediante Intenciones podemos realizar las siguientes operaciones con el calendario:
Abrir la aplicacin Calendario en una fecha especfica.
Ver eventos.
Crear nuevos eventos.
Editar eventos existentes.
- El gestor de descargas de Android denominado DownloadManager es un servicio en segundo plano que permite gestionar descargas de larga duracin.
- Existen 3 formas de enviar un correo electrnico en Android:
OAuth 2.0 de Gmail
Intent del tipo message/rfc822
Biblioteca externa JavaMail API

423

Aula Mentor

424

- OAuth (Open Authorization) es un protocolo abierto, que permite a un usuario del


sitio A compartir la informacin de este sitio (proveedor de servicio) con un sitio
B (llamado consumidor) sin compartir toda su identidad.
- El SDK de Android no incluye de forma nativa una API que permita enviar correos
electrnicos. No obstante, podemos utilizar la API JavaMail, que est desarrollada especficamente para Android.
- Un Servicio (Services) es un componente de software que se ejecuta en segundo plano y que realiza operaciones cada cierto tiempo.
- La clase IntentService realiza ciertas tareas en segundo plano en un hilo distinto
del principal y acaba automticamente una vez finaliza la ejecucin de su tarea.
- Existen varias maneras de comunicacin bidireccional entre una actividad y un
servicio:
Actividad ligada (bind) al servicio
Receptor de mensajes (Receiver)
Enviando informacin va Intent al arrancar servicios
Handler y Messenger
AIDL para servicios en distintos procesos.
- IPC (Inter Process Communication) se usa para comunicar con un Servicio que se
ejecuta en un proceso diferente.
- AIDL (Android Interface Definition Language) es similar a otros IPC existentes en
el mercado diseado para Android.
- Un Stub es un bloque de cdigo usado como sustituto de alguna otra funcionalidad.
- Un Servicio SOAP (siglas de Simple Object Access Protocol) es un protocolo
estndar que define la comunicacin entre un cliente y un servidor mediante
intercambio de datos en formato XML.

U5 Utilidades avanzadas

Unidad 5. Utilidades avanzadas

1. Introduccin
En esta Unidad vamos a explicar cmo acceder al portapapeles de Android. Adems,
implementaremos Drag and Drop (arrastrar y soltar) y gestionaremos los toques de usuario
sobre la pantalla del dispositivo.
Tambin, repasaremos las recomendaciones de diseo en funcin del tamao y de la
resolucin de la pantalla del dispositivo Android.
Estudiaremos cmo desarrollar aplicaciones multi-idioma mediante un ejemplo prctico.
Finalmente, repasaremos herramientas que ayudan al desarrollo rpido de cdigo en
Android.

2. Portapapeles de Android
Android proporciona un potente portapapeles para copiar y pegar contenidos, que es compatible
tanto con tipos de datos simples como complejos, incluyendo cadenas de texto, estructuras de
datos complejas, datos binarios e, incluso, recursos de aplicaciones.
Los datos de tipo cadena se almacenan directamente en el portapapeles, mientras que en
el caso de los datos complejos lo que se almacena es una referencia que luego utiliza la aplicacin que lee el portapapeles mediante el proveedor de contenidos correspondiente.
La clase principal que se usa para acceder al portapapeles se denomina ClipboardManager, que es
un gestor que obtiene la referencia a la misma invocando la orden getSystemService(CLIPBOARD_
SERVICE).
Para aadir informacin al portapapeles debemos crear un objeto del tipo ClipData, que
contiene la descripcin de la informacin (objeto ClipDescription de tipo matriz) y los datos
en s mismos (uno o ms objetos del tipo ClipData.Item).
El portapapeles slo puede contener un nico dato del tipo ClipData al mismo tiempo.
El objeto del tipo ClipDescription contiene la descripcin de los datos guardados en
una matriz. Estos datos son de tipo MIME. As, las otras aplicaciones pueden gestionar tambin
estos datos del portapapeles en funcin de su tipo MIME.
El objeto del tipo ClipData.Item contiene los datos almacenados y puede ser uno de los
siguientes objetos:
- Cadena de texto.
- URI: se usa para copiar datos complejos de un proveedor de contenidos.
- Intencin: as se copia accesos directos a aplicaciones.
Es posible aadir al portapapeles varios objetos del tipo ClipData.Item al mismo tiempo.
De esta forma, podemos incluir selecciones mltiples en l, por ejemplo, podemos seleccionar
varias opciones de un listado y copiarlas todas al mismo tiempo. Cada una de estas opciones

425

Aula Mentor

estar representada por un objeto del tipo ClipData.Item.


En el portapapeles slo se puede almacenar un objeto del tipo ClipData a la vez. Cuando una aplicacin pega un nuevo objeto ClipData al portapapeles, se borra el objeto anterior.
La clase ClipData dispone de los siguientes mtodos bsicos para aadir un nico dato del tipo
ClipData.Item:
- newPlainText(descripcin, texto): aade un objeto del tipo ClipData.Item que
contiene una cadena de texto. El tipo MIME que parecer en ClipDescription ser MIMETYPE_TEXT_PLAIN.
- newUri(resolver, descripcin, URI): pega un objeto del tipo ClipData.Item que
contiene una cadena de tipo URI. El mtodo utiliza el resolver como proveedor de contenidos para resolver el tipo MIME que parecer en ClipDescription. Si la URI no se resuelve,
el tipo MIME ser MIMETYPE_TEXT_URILIST.
- newIntent(label, intent): incorpora un objeto del tipo ClipData.Item que contiene
una cadena con una Intencin. El tipo MIME que parecer en ClipDescription ser MIMETYPE_TEXT_INTENT..

2.1 Ejemplo de portapapeles

426

Estudiemos ahora, de forma prctica, cmo utilizar el Portapapeles de Android. Es recomendable


abrir el Ejemplo 1 de esta Unidad para seguir la explicacin siguiente.
La aplicacin que desarrollamos permite copiar y pegar los distintos tipos de datos que
hemos nombrado anteriormente.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal
que muestra cuatro botones con las distintas opciones de copia y una caja de texto para pegar
el dato copiado:
<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/texto_estilo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal
android:textSize=20sp />
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:orientation=horizontal>
<Button
android:id=@+id/copiar_texto_estilo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarTextoEstilo
android:text=@string/copiar_texto_estilo />
<Button

U5 Utilidades avanzadas

android:id=@+id/copiar_texto_plano
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarTextoPlano
android:text=@string/copiar_texto />
</LinearLayout>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:orientation=horizontal>
<Button android:id=@+id/copiar_intent
android:layout_width=wrap_content

android:layout_height=wrap_content
android:onClick=copiarIntent
android:text=@string/copiar_intent />
<Button android:id=@+id/copiar_uri
android:layout_width=wrap_content

android:layout_height=wrap_content
android:onClick=copiarUri
android:text=@string/copiar_uri />
</LinearLayout>

<TextView
android:id=@+id/tipo_contenido_portapapeles
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal
android:textStyle=normal
android:textSize=16sp/>
<EditText
android:id=@+id/clip_text
android:layout_width=match_parent
android:layout_height=0px
android:layout_weight=1
android:textStyle=normal/>

</LinearLayout>

Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente


contenido con la lgica de la aplicacin:
public class MainActivity extends Activity {
// Gestor del portapapeles
ClipboardManager mClipboard;
// Vista donde se muestra el contenido del portapapels
TextView contenidoPortapapeles;
EditText mEditText;
// Variables para guardar el texto que se copia
CharSequence textoEstilo;
String textoPlano;

427

Aula Mentor

// Listener que salta cuando hay cambios en el portapapeles


ClipboardManager.OnPrimaryClipChangedListener
mPrimaryChangeListener
= new ClipboardManager.OnPrimaryClipChangedListener() {
public void onPrimaryClipChanged() {

actualizaPortapapeles();
}
};

428

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las vistas de la interfaz del usuario
setContentView(R.layout.activity_main);
contenidoPortapapeles =

(TextView)findViewById(R.id.tipo_contenido_portapapeles);
mEditText = (EditText)findViewById(R.id.clip_text);
// Buscamos el texto con estilo del fichero /res/strings.xml
// y obtenemos su texto plano
textoEstilo = getText(R.string.texto_estilo);
textoPlano = textoEstilo.toString();
// Lo pasamos a la etiqueta
TextView tv = (TextView)findViewById(R.id.texto_estilo);
tv.setText(textoEstilo);
// Obtenemos referencia al gestor del portapapeles
mClipboard =

(ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
// Aadimos el listener al portapapeles
mClipboard.addPrimaryClipChangedListener(

mPrimaryChangeListener);
// Actualizamos el estado del portapapeles
actualizaPortapapeles();
} // end onCreate
@Override
protected void onDestroy() {
super.onDestroy();
// Hay que liberar el listener!
mClipboard.removePrimaryClipChangedListener(

mPrimaryChangeListener);
}
// Mtodo que copia el texto con estilo
public void copiarTextoEstilo(View button) {
mClipboard.setPrimaryClip(ClipData.newPlainText(Texto con

estilo, textoEstilo));
}
// Mtodo que copia el texto plano
public void copiarTextoPlano(View button) {
mClipboard.setPrimaryClip(ClipData.newPlainText(Texto plano,

textoPlano));
}
// Mtodo que copia un Intent
public void copiarIntent(View button) {

// Creamos el intent
Intent intent = new Intent(Intent.ACTION_VIEW,

U5 Utilidades avanzadas

Uri.parse(http://www.mentor.educacion.es/));
// Lo copiamos al portapapeles
mClipboard.setPrimaryClip(ClipData.newIntent(VIEW intent,
intent));
}
// Mtodo que copia una URI
public void copiarUri(View button) {
mClipboard.setPrimaryClip(ClipData.newRawUri(URI,
Uri.parse(http://www.mentor.educacion.es/)));
}
// Mtodo que actualiza la interfaz del usuario mostrando el
// contenido del portapapeles
void actualizaPortapapeles() {

// Obtenemos los datos del portapapeles
ClipData clip = mClipboard.getPrimaryClip();
// Mostramos la informacin en funcin del contenido
if (clip == null) {

contenidoPortapapeles.setText(Error: sin acceso al

portapapeles);
mEditText.setText();
} else if (clip.getItemAt(0).getText() != null) {

contenidoPortapapeles.setText(El portapapeles contiene

texto del tipo: );

mEditText.setText(clip.getItemAt(0).getText());
} else if (clip.getItemAt(0).getIntent() != null) {

contenidoPortapapeles.setText(El portapapeles contiene un

Intent: );

mEditText.setText(clip.getItemAt(0).getIntent().toUri(0));
} else if (clip.getItemAt(0).getUri() != null) {

contenidoPortapapeles.setText(El portapapeles contiene una
URI: );

mEditText.setText(clip.getItemAt(0).getUri().toString());
} else {

contenidoPortapapeles.setText(Sin contenido en el
portapapeles);
mEditText.setText(Sin datos...);
}
// Ahora obtenemos los tipos mime contenidos en el

// portapapeles
String[] mimeTypes = clip != null ?
clip.getDescription().filterMimeTypes(*/*) : null;
if (mimeTypes != null) {

// Pegamos todos los tipos encontrados
for (int i=0; i<mimeTypes.length; i++) {

contenidoPortapapeles.append(mimeTypes[i]);

contenidoPortapapeles.append(\n);
} // end for i
}
} // end actualizaPortapapeles
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para obtener referencia
al gestor del portapapeles y establecer su listener que salta cuando hay cambios en el

429

Aula Mentor

portapapeles.
En el mtodo onDestroy()liberamos este listener. Es muy importante no olvidarse
de liberar siempre esta variable.
A continuacin, definimos distintos mtodos que copian diferentes tipos de contenidos
mediante el mtodo setPrimaryClip() del portapapeles.
Finalmente, el mtodo actualizaPortapapeles() se encarga de actualizar la interfaz
del usuario mostrando el contenido del portapapeles. Para ello, obtiene los datos del portapapeles con el mtodo getPrimaryClip(), que devuelve un objeto del tipo ClipData para, posteriormente, utilizar este ltimo objeto y leer su descripcin con el mtodo getDescription()
y el contenido en s mismo del primer elemento con getItemAt(0) y aplicar el mtodo que
corresponda en funcin del dato almacenado.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 1 (Portapapeles) de la Unidad 5. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado el Portapapeles de Android.

Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente
aplicacin:

430

U5 Utilidades avanzadas

3. Drag and Drop (Arrastrar y soltar)


Arrastrar y soltar (del ingls, drag and drop) es una expresin informtica que se refiere a la
accin de mover objetos de la interfaz del usuario entre dos partes visuales de aplicaciones. Los
objetos arrastrados dependen de la funcin del programa.
Android, como sistema operativo completo que es, permite a los usuarios arrastrar contenidos de una Vista y soltarlos en otra.
Una operacin de arrastrar y soltar comienza cuando el usuario realiza un gesto en una
Vista que se desea arrastrar, como un clic largo sobre ella. Mientras el usuario arrastra con el
dedo el contenido parece una sombra sobre el layout de la Actividad.
Para que otra Vista se convierta en receptora del elemento arrastrado, debemos indicarlo
implementando un listener View.OnDragListener, que, a su vez, define el mtodo callback
onDragEvent().
Podemos invocar el mtodo setOnDragListener() para establecer el listener anterior de una Vista receptora.
Una vez que el usuario suelta el dedo de la pantalla sobre una Vista receptora, el sistema
finaliza la operacin de arrastre y realiza la operacin que hayamos definido en esta Vista.

3.1 Proceso de Arrastrar y soltar


A continuacin, vamos a ver los diferentes estados por los que puede pasar el proceso de
arrastrar y soltar:
- Iniciado (Started): ocurre cuando un usuario hace un gesto sobre una Vista, como, por
ejemplo, un clic normal o un clic largo sobre ella, podemos iniciar su arrastre invocando
su mtodo startDrag(). Para definir los gestos citados sobre la Vista, podemos utilizar los
listeners OnTouchListener o LongClickListener.
El mtodo se define as: startDrag(ClipData datos, View.DragShadowBuilder sombra, Object vista, int flags). Como puedes observar, al iniciar el arrastre
de una Vista podemos indicar cuatro parmetros: un objeto opcional del tipo ClipData
(Portapapeles) para pasar informacin en la operacin, la sombra creada del tipo DragShadowBuilder, la Vista que se arrastra y el parmetro flags, que no tiene aplicacin hasta
ahora y debe contener el valor 0.
Despus, el sistema enva un evento de arrastre con el tipo de accin ACTION_DRAG_
STARTED a los listeners OnDragListener de todos las Vista del layout activo. Estos listeners deben devolver true para indicar que sus Vistas estn interesadas en ser receptoras
de la Vista arrastrada. En caso contrario, se debe devolver false.
- Arrastrando (Dragging): el usuario sigue arrastrando una Vista. Si su sombra se superpone con una Vista receptora, que tiene definido el listener OnDragListener, esta ltima
puede reaccionar al evento cambiando su apariencia para indicar que es una Vista activa
y est disponible para aceptar la Vista arrastrada. En este caso, el sistema enva el tipo de
accin ACTION_DRAG_ENTERED.
- Soltado (Dropped): el usuario suelta la Vista arrastrada encima de la Vista receptora que
acepta el objeto. En este caso, el sistema enva el tipo de accin ACTION_DROP. Este evento
contiene los datos pasados en el mtodo startDrag(). En este listener debemos indicar
con true o false si aceptamos la Vista.
- Terminado (Ended): ocurre cuando el usuario suelta la Vista arrastrada, tanto si la ha soltado en una Vista receptora como si renuncia a la operacin. Este evento se enva a todos los
listeners registrados para que puedan volver a dejar sus Vistas en su estado visual normal,
es decir, ya no est activas para recibir una Vista.

431

Aula Mentor

Como puedes observar, la teora es bastante sencilla. Vamos a experimentar con un ejemplo
prctico cmo se aplica.

3.2 Ejemplo de Arrastrar y soltar


Estudiemos ahora, de forma prctica, cmo utilizar el mecanismo de Arrastrar y soltar. Es
recomendable abrir el Ejemplo 2 de esta Unidad para seguir la explicacin que se da.
La aplicacin que desarrollamos permite arrastrar frutas a una cesta de la compra (imgenes de tipo ImageView) y muestra en una etiqueta el nmero de frutas que vamos dejando
en esta cesta.
En cdigo del layout activity_main.xml se incluye este sencillo diseo de la Actividad
principal:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=match_parent
android:orientation=vertical >

432

<LinearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
android:orientation=vertical
android:id=@+id/cestaIV >
<ImageView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_margin=15dp
android:src=@drawable/cesta
android:layout_gravity=top|center_horizontal />
</LinearLayout>
<TextView android:id=@+id/mensaje
android:text=No hay ninguna en la cesta
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
android:textSize=17sp />

<LinearLayout

android:layout_width=fill_parent

android:layout_height=match_parent

android:orientation=horizontal >





<ImageView
android:id=@+id/manzanaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/manzana
android:layout_marginLeft=5dp
android:layout_gravity=bottom />

U5 Utilidades avanzadas

<ImageView
android:id=@+id/naranjaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/naranja
android:layout_marginLeft=5dp
android:layout_gravity=bottom />

<ImageView
android:id=@+id/peraIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/pera
android:layout_marginLeft=5dp
android:layout_gravity=bottom />

<TextView

android:text=Arrastra frutas a la cesta!

android:layout_width=wrap_content

android:layout_height=wrap_content

android:layout_gravity=bottom

android:layout_marginLeft=5dp

android:layout_marginBottom=3dp

android:textSize=17sp />

</LinearLayout>
</LinearLayout>

Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente


contenido con la lgica de la aplicacin:
public class MainActivity extends Activity {

// Etiqueta donde se muestra el n de frutas de la cesta

TextView mensaje;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Establecemos la interfaz del usuario
setContentView(R.layout.activity_main);
// Buscamos las vistas y establecemos su evento onTouch
findViewById(R.id.manzanaIV).setOnTouchListener(new

MiTouchListener());
findViewById(R.id.naranjaIV).setOnTouchListener(new

MiTouchListener());
findViewById(R.id.peraIV).setOnTouchListener(new

MiTouchListener());
// Buscamos la vista de destino y establecemos su evento

// onDrag
findViewById(R.id.cestaIV).setOnDragListener(new

MiDragListener());
// Vista que muestra el mensaje al usuario con el n de frutas
mensaje= (TextView) findViewById(R.id.mensaje);
} // end onCreate()
// Listener que establece el evento onTouch de una Vista

433

Aula Mentor

private final class MiTouchListener implements OnTouchListener {



public boolean onTouch(View view, MotionEvent motionEvent)

{

// Si el evento es pulsado

if (motionEvent.getAction() ==

MotionEvent.ACTION_DOWN) {

// No pasamos informacin en el portapapeles

ClipData data = ClipData.newPlainText(, );
// Construimos la sombra del arrastre de la
// Vista

DragShadowBuilder shadowBuilder = new
View.DragShadowBuilder(view);

// Iniciamos el arrastre de la vista

view.startDrag(data, shadowBuilder, view, 0);

// Indicamos que hemos gestionado el evento
return true;

} else {
return false;
}

}

} // end class MiTouchListener

434

// Listener que establece el evento onDrag de una Vista


class MiDragListener implements OnDragListener {

// Forma que toma la vista cuando entramos en ella

Drawable enterShape = getResources().getDrawable(
R.drawable.shape_droptarget);

// N de frutas en la cesta

private int nFrutas = 0;
@Override

public boolean onDrag(View v, DragEvent event) {

// Obtenemos la accin

int accion = event.getAction();
switch (accion) {

// Si se ha iniciado el DRAG no hacemos nada
case DragEvent.ACTION_DRAG_STARTED:
break;
// Si entramos en la cesta

// haciendo drag cambiamos su fondo con la forma

// enterShape
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackground(enterShape);
break;
// Al salir del drag quitamos la forma del fondo de

// la cesta
case DragEvent.ACTION_DRAG_EXITED:
// A partir de la API 16 ya no hay que usar
// setBackgroundDrawable()
v.setBackground(null);
break;

// Si dejamos caer la fruta en la cesta
case DragEvent.ACTION_DROP:
// Una fruta ms!!!!
nFrutas++;

if (nFrutas==0) mensaje.setText(No hay ninguna

U5 Utilidades avanzadas


en la cesta);

else if (nFrutas==1) mensaje.setText(1 fruta

en la cesta);

else mensaje.setText(nFrutas + frutas en la

cesta);
break;
// Si acaba el drag de la cesta, quitamos el fondo en

// la vista
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(null);
default:
break;
}

// Indicamos que hemos gestionado el evento
return true;
}
} // end MiDragListener
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para establecer los
listeners OnTouchListener de las frutas que son las Vistas que se arrastran y OnDragListener
de la cesta que las recibe.
Adems, hemos definido el listener MiTouchListener que establece el evento onTouch() de las frutas para la accin ACTION_DOWN (pulsado). En este evento definimos un objeto portapapeles sin datos, construimos la sombra del arrastre de la Vista e iniciamos el arrastre
de la vista con startDrag().
Tambin, hemos definido el listener MiDragListener, que establece el evento onDrag()
de la cesta para las acciones siguientes:
- ACTION_DRAG_STARTED: se inicia el arrastre, en cuyo caso no hacemos nada.
- ACTION_DRAG_ENTERED: el usuario entra en la cesta haciendo drag con una fruta y cambiamos el fondo de esta cesta con la forma enterShape.
- ACTION_DRAG_EXITED: el usuario saca la fruta de la cesta y quitamos la forma del fondo de
la cesta.
- ACTION_DROP: el usuario deja caer la fruta en la cesta y aumentamos el contador de frutas
en la cesta y lo mostramos en la etiqueta correspondiente. Aqu podramos haber cambiado
de sitio la Vista hacindola desaparecer del layout original y pasndola al nuevo. Para ello
basta con escribir el siguiente cdigo sencillo:
// Obtenemos la vista arrastrada
View view = (View) event.getLocalState();
// Obtenemos la vista que contiene a la Vista arrastrada
ViewGroup contenedor = (ViewGroup) view.getParent();
// Quitasmos la vista del contenedor original
contenedor.removeView(view);
// Buscamos la vista que contiene a la Vista receptora
LinearLayout contenedorDestino = (LinearLayout) v;
// Aadimos la Vista arrastrada al contenedor destino
contenedorDestino.addView(view);
// La hacemos visible si la habamos ocultado
// onDrag() anterior
view.setVisibility(View.VISIBLE);

en

el

evento

435

Aula Mentor

- ACTION_DRAG_ENDED: si se acaba el arrastre, quitamos el fondo de la cesta.


En el cdigo anterior utilizamos la forma (shape) enterShape que toma la vista cuando
entramos en ella. Esta forma se define en el archivo shape_droptarget.xml que contiene
un crculo de color amarillo:
<shape xmlns:android=http://schemas.android.com/apk/res/android
android:shape=oval >
<solid android:color=#DDFFFF00/>
</shape>

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Drag and Drop) de la
Unidad 5. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado el mecanismo de Arrastrar y soltar
de Android.

Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente
aplicacin:

436

4. Gestin del toque de pantalla


Una pantalla tctil es una pantalla que, mediante toques sobre su superficie, permite la entrada
de datos y rdenes al dispositivo.
Desde la aparicin de las pantallas tctiles en los telfonos mviles hemos cambiado la

U5 Utilidades avanzadas

forma de utilizarlos haciendo su uso mucho ms intuitivo y sencillo.


La caracterstica ms importante de una pantalla tctil es que sustituye al ratn de un
ordenador de sobremesa. As, podemos seleccionar, arrastrar y soltar cualquier elemento de la
pantalla fcilmente. Adems, suele utilizarse tambin para sustituir al teclado en aquellos dispositivos que no disponen de uno fsico.
Como sabrs, la pantalla tctil es imprescindible en muchos videojuegos de Android.
Desde el punto de vista del desarrollador, la gestin bsica de la pantalla tctil se basa en
implementar el mtodo OnTouchEvent() de una clase View. Tambin podemos implementar
la interfaz OnTouchListener en otras clases. Este mtodo devuelve un parmetro de la clase
MotionEvent con toda la informacin del toque del usuario.
Los mtodos ms importantes de la clase MotionEvent son:
- getAction(): tipo de accin realizada por el usuario. Puede devolver los siguientes valores:
ACTION_DOWN: dedo apoyado en la pantalla.
ACTION_MOVE: dedo en movimiento.
ACTION_UP: dedo que se levanta de la pantalla.
ACTION_CANCEL: el gesto se cancela.
ACTION_OUTSIDE: el usuario toca fuera del rea de la interfaz del usuario.
- getX(), getY(): posicin X, Y de la pulsacin.
- getDownTime(): indica el tiempo en milisegundos que el usuario presion por primera vez
en una cadena de eventos de posicin.
- getEventTime(): tiempo en milisegundos del evento actual.
- getPressure(): estima la presin de la pulsacin. El valor 0 es el mnimo y el valor 1 representa una pulsacin normal.
- getSize(): estima el grosor de la pulsacin y su valor puede variar entre 0 y 1.
Tambin debemos conocer el concepto de multitctil que permite detectar una entrada de
varios dedos en la pantalla. Hay que tener en cuenta que esta funcionalidad est disponible
a partir de Android 2.0 y que la pantalla del dispositivo fsico debe ser compatible con esta
tecnologa.
Desde el punto de vista del programador, para averiguar si el dispositivo tiene esta capacidad puedes utilizar la siguiente sentencia:

boolean multiTouch = getPackageManager().hasSystemFeature(



PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);

Cada vez que el usuario toca la pantalla con un dedo, el sistema define un punto con la clase
PointF, que almacena la posicin (X, Y) del dedo, el Id para identificarlo y si ndice Index.
Fjate en el siguiente esquema visual:

437

Aula Mentor

En este caso, un objeto de la clase MotionEvent contendr informacin de todos estos puntos.
Un punto estar activo desde que el usuario pulsa sobre la pantalla hasta que deja de presionar.
Se puede obtener el nmero de puntos activos llamando al mtodo getPointerCount()
de la clase anterior.
A partir de Android 2.0, esta clase MotionEvent ampla la lista de constantes para identificar
las acciones posibles de multi-touch. Veamos los nuevos valores y cmo afectan a las acciones
anteriores:
- ACTION_POINTER_DOWN: aparece un nuevo punto (dedo) distinto de los existentes.
- ACTION_POINTER_UP: un punto se levanta (se quita el dedo), pero sigue quedando alguno
activo.
- ACTION_DOWN: se pulsa en la pantalla sin que haya otro punto activo.
- ACTION_UP: se deja de presionar el ltimo punto activo
- ACTION_MOVE: se mueve cualquiera de los puntos activos.
- ACTION_CANCEL: se cancela un gesto.
- ACTION_OUTSIDE: el punto se sale de la vista.
Es posible gestionar tambin las pulsaciones del usuario mediante gestos (del ingls, gestures).
Esta funcionalidad est disponible desde Android 1.6 y veremos un ejemplo muy sencillo al final
de este apartado.

4.1 Ejemplo de gestin de toque de pantalla


438

Estudiemos ahora, de forma prctica, cmo gestionar los toques de usuario en la pantalla
de un dispositivo Android. Es recomendable abrir el Ejemplo 3 de esta Unidad para seguir la
explicacin que se ofrece.
La aplicacin que desarrollamos muestra tres pestaas:
- Pestaa 1 (clase UnToqueView.java): presenta una pizarra donde el usuario puede dibujar
con el dedo.
- Pestaa 2 (clase MultitouchView.java): permite al usuario tocar la pantalla con dos o ms
dedos y muestra en la pantalla crculos que se mueven con los dedos
- Pestaa 3 (clase ZoomImageView.java): muestra una imagen cuyo tamao puede cambiar el
usuario haciendo el gesto pitch (dos dedos pegados a la pantalla que se acercan o se alejan).
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal que
muestra 3 pestaas:
<android.support.v4.app.FragmentTabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@android:id/tabhost
android:layout_width=match_parent
android:layout_height=match_parent >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs

U5 Utilidades avanzadas

android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>

Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente


contenido con la lgica de la aplicacin:
public class MainActivity

extends FragmentActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en

// el archivo activity_main.xml
FragmentTabHost mTabHost = (FragmentTabHost)
findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la

// pestaa
mTabHost.setup(this, getSupportFragmentManager(),

R.id.tabFrameLayout);
// Aadimos las pestaas que son fragmentos
mTabHost.addTab(
mTabHost.newTabSpec(tab1).setIndicator(Pizarra,
getResources().getDrawable(R.drawable.pizarra)),

PizarraFragmentTab.class, null);
mTabHost.addTab(
mTabHost.newTabSpec(tab2).setIndicator(Multi touch,
getResources().getDrawable(R.drawable.touch)),

MultiTouchFragmentTab.class, null);
mTabHost.addTab(mTabHost.newTabSpec(tab3).setIndicator(

Cambio Tamao,
getResources().getDrawable(R.drawable.resize)),

ResizeFragmentTab.class, null);
} // end onCreate()
} // end clase

Como puedes observar, en el cdigo anterior hemos implementado con fragmentos las pestaas.
Como ya hemos utilizado este tipo de estructuras en la Unidad 2 no vamos a explicar su sintaxis.
Si abres el archivo PizarraFragmentTab.java, vers que hemos desarrollado el fragmento

439

Aula Mentor

que dibuja la pizarra:


public class PizarraFragmentTab extends Fragment {

// Como es un fragmento debemos buscar las Vistas en este evento
//y no en onCreate()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup

container, Bundle savedInstanceState) {
// Creamos la Vista interna a partir de la clase

// correspondiente
View v = new UnToqueView(getActivity());
return v;
} // end onCreateView
} // end clase

Como puedes observar, simplemente devolvemos la Vista correspondiente a la funcionalidad


requerida por el fragmento.
En el resto de fragmentos puedes ver que el cdigo fuente es exactamente igual.
Veamos ahora la funcionalidad real que gestiona los toques de pantalla del usuario.
La clase UnToqueView se encarga de dibujar la pizarra y contiene las siguientes sentencias:

440

// Clase de tipo Vista que simula una pizarra


public class UnToqueView extends View {

// Pincel que usamos para pintar

private Paint paint = new Paint();

// Camino que guarda el trazo del dibujo

private Path path = new Path();

// Constructor de la clase

public UnToqueView(Context context) {
super(context);

// Definimos el pincel de dibujo con el

// borde suave, de ancho 6, color negro,

// trazo slido y uniones redondeadas

paint.setAntiAlias(true);

paint.setStrokeWidth(6f);

paint.setColor(Color.BLACK);

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeJoin(Paint.Join.ROUND);
} // end constructor

// Evento que se lanza cuando es necesario dibujar la Vista
@Override

protected void onDraw(Canvas canvas) {

// Dibujamos el camino

canvas.drawPath(path, paint);
}

// Evento que salta cuando el usuario toca la pantalla
@Override

public boolean onTouchEvent(MotionEvent event) {

// Obtenemos la posicin del toque

float eventX = event.getX();

float eventY = event.getY();

U5 Utilidades avanzadas


// En funcin del tipo de toque

switch (event.getAction()) {

// Si el dedo toca la pantalla

case MotionEvent.ACTION_DOWN:

// Movemos el camino a la posicin X, Y

path.moveTo(eventX, eventY);

return true;

// Si el dedo se mueve

case MotionEvent.ACTION_MOVE:

// Creamos el camino hasta X, Y mediante una lnea

path.lineTo(eventX, eventY);

break;

// Si se levanta el dedo

case MotionEvent.ACTION_UP:

// No hacemos nada

break;

default:

return false;

} // end case

// Indicamos que se debe redibujar la Vista

invalidate();

return true;
} // end onTouchEvent
} // end clase

Como puedes ver, esta clase se extiende de la clase bsica View, que sobrescribe sus mtodos
onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear
para dibujar en esta Vista.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos el camino (clase Path) que se va creando segn el usuario toca la pantalla y desplaza
el dedo por ella.
Finalmente, en el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla,
obtenemos la posicin del dedo y ejecutamos las siguientes sentencias en funcin de la accin:
- ACTION_DOWN: si el dedo toca la pantalla, entonces movemos el camino a la posicin X, Y.
- ACTION_MOVE: si el dedo se mueve, entonces creamos el camino hasta X, Y mediante una
lnea.
Al final de este mtodo indicamos que se debe redibujar la Vista cada vez que ocurre un evento
onTouch.

La clase MultitouchView se encarga de mostrar crculos cuando el usuario toca la pantalla con
varios dedos a la vez y contiene las siguientes sentencias:
// Clase de tipo Vista que muestra crculos cuando
// el usuario toca la pantalla con varios dedos a la vez
public class MultitouchView extends View {

// Radio de los crculos

private static final int RADIO = 50;

// Matriz que guarda los puntos de la pantalla

private SparseArray<PointF> puntosActivos;

// Pincel para dibujar

private Paint paint;

// Matriz con colores para dibujar crculos

441

Aula Mentor

442


private int[] colores = { Color.GREEN, Color.BLUE, Color.RED,

Color.MAGENTA, Color.BLACK, Color.CYAN, Color.GRAY,

Color.DKGRAY, Color.LTGRAY, Color.YELLOW };


// Constructor de la clase

public MultitouchView(Context context) {

super(context);

// Creamos la matriz de puntos

puntosActivos = new SparseArray<PointF>();

// Inicializamos el pincel

paint = new Paint(Paint.ANTI_ALIAS_FLAG);

paint.setColor(Color.RED);

// Indicamos que se debe rellenar todo lo que dibuja

// para que el crculo se pinte por dentro

paint.setStyle(Paint.Style.FILL_AND_STROKE);
} // end constructor


// Evento que se lanza cuando es necesario dibujar la Vista
@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// Dibujamos todos los puntos en crculo con un bucle

int size = puntosActivos.size();

for (int i = 0; i < size; i++) {

// Obtenemos el punto i

PointF punto = puntosActivos.valueAt(i);

// Si obtenemos el punto...

if (punto != null)


// Elegimos un color

paint.setColor(colores[i % 9]);


// Dibujamos el crculo


canvas.drawCircle(punto.x, punto.y, RADIO, paint);

} // end for i
} // end onDraw()

// Evento que salta cuando el usuario toca la pantalla
@Override

public boolean onTouchEvent(MotionEvent event) {

// Mostramos el contenido del evento


Log.d(Debug, event.toString());

// Obtenemos el ndice del punto del objeto del evento


int pointerIndex = (event.getAction() &

MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;

// Obtenemos el ID del punto


int pointerId = event.getPointerId(pointerIndex);

// Obtenemos la mscara de la accin


int mascaraAction = event.getAction() &
MotionEvent.ACTION_MASK;

switch (mascaraAction) {
// En el caso de que el usuario tenga el dedo en la pantalla

U5 Utilidades avanzadas

case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
// Cuando aparece un nuevo punto entonces lo guardamos en

// la matriz

PointF punto = new PointF();

punto.x = event.getX(pointerIndex);

punto.y = event.getY(pointerIndex);

puntosActivos.put(pointerId, punto);

break;

}

// Si se mueve el dedo

case MotionEvent.ACTION_MOVE: {

// Recorremos todos los puntos y actualizamos su nueva

// posicin

for (int size = event.getPointerCount(), i = 0; i < size;

i++) {

PointF punto = puntosActivos.get(event.getPointerId(i));

if (punto != null) {


punto.x = event.getX(i);


punto.y = event.getY(i);

}

}

break;

}

// Si el dedo se levanta o se cancela

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_POINTER_UP:

case MotionEvent.ACTION_CANCEL: {

// Quitamos el punto activo

puntosActivos.remove(pointerId);

break;

}

} // end switch

// Redibujamos la Vista

invalidate();

// Indicamos que gestionamos el evento

return true;
} // end onTouchEvent
} // end clase

Como puedes observar, de nuevo esta clase se extiende de la clase bsica View, que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear
para dibujar los crculos en esta Vista. Tambin creamos una matriz del tipo SparseArray del
tipo PointF que almacenar las posiciones de los dedos en la pantalla.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos los crculos de todos estos puntos que representan dedos en la pantalla utilizando un
bucle de la matriz de puntos.
Finalmente, el evento onTouchEvent() se lanza cuando el usuario toca la pantalla.
Por motivos pedaggicos, hemos incluido en este mtodo un Log del objeto MotionEvent para
ver el valor que toma el objeto cuando se ejecuta la aplicacin y se desplazan los dedos por la
pantalla y se reciben varios eventos. El resultado puede ser similar al siguiente:

443

Aula Mentor

D/Debug(18932): MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=232.78922,


y[0]=221.5654, toolType[0]=TOOL_TYPE_FINGER, buttonState=0,
metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0,
eventTime=129379232, downTime=129379232, deviceId=1, source=0x1002 }
D/Debug(18932): MotionEvent { action=ACTION_POINTER_DOWN(1), id[0]=0,
x[0]=232.78922, y[0]=221.5654, toolType[0]=TOOL_TYPE_FINGER, id[1]=1,
x[1]=536.5508, y[1]=830.43646, toolType[1]=TOOL_TYPE_FINGER, buttonState=0,
metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0,
eventTime=129379266, downTime=129379232, deviceId=1, source=0x1002 }
...
D/Debug(18932): MotionEvent { action=ACTION_MOVE, id[0]=0, x[0]=233.92477,
y[0]=235.19922, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=536.5508,
y[1]=830.43646, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=358.83606,
y[2]=194.72021, toolType[2]=TOOL_TYPE_FINGER, id[3]=3, x[3]=575.1597,
y[3]=335.8001, toolType[3]=TOOL_TYPE_FINGER, id[4]=4, x[4]=85.734566,
y[4]=382.0652, toolType[4]=TOOL_TYPE_FINGER, buttonState=0,
metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=5, historySize=1,
eventTime=129379707, downTime=129379232, deviceId=1, source=0x1002 }
...
D/Debug(18932): MotionEvent { action=ACTION_POINTER_UP(0), id[0]=0,
x[0]=233.92477, y[0]=235.84473, toolType[0]=TOOL_TYPE_FINGER, id[1]=1,
x[1]=536.5508, y[1]=830.43646, toolType[1]=TOOL_TYPE_FINGER, buttonState=0,
metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0,
eventTime=129379975, downTime=129379232, deviceId=1, source=0x1002 }

444

D/Debug(18932): MotionEvent { action=ACTION_UP, id[0]=1, x[0]=536.5508,


y[0]=830.43646, toolType[0]=TOOL_TYPE_FINGER, buttonState=0,
metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0,
eventTime=129379975, downTime=129379232, deviceId=1, source=0x1002 }

Como puedes ver, cuando hay ms de un punto en la pantalla, el evento resulta complejo de
interpretar. Estas acciones las ha podido hacer cualquier punto de los activos o uno nuevo.
Se codifica simultneamente el cdigo de la accin (8 bits menos significativos) e ndice del
punto (siguientes 8 bits). Para extraer la informacin interna por separado, puedes realizar una
operacin lgica utilizando las mscaras de bits correspondientes. Fjate en el siguiente cdigo:
// Utilizando las mscaras de bits siguientes obtenemos el ndice del //
punto en concreto
int pointerIndex = (event.getAction() &

MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
// Para obtener la accin que est ocurriendo podemos utilizar la
// mscara de bits siguientes
int mascaraAction = event.getAction() & MotionEvent.ACTION_MASK;

El mtodo getPointerCount()permite conocer el nmero de puntos. As, podemos recorrer


con un bucle todos los puntos activos y averiguar la informacin sobre cada punto con los
mtodos getX() y getY().
El mtodo getPointerId(int indice) proporciona el identificador del punto. No hay
que confundir el ndice del punto con su identificador. El ndice se asigna en funcin del orden
en que se pulsaron. El ndice cero siempre es el ms antiguo y decrece a medida que los puntos
anteriores a l dejan de estar activos. Sin embargo, el identificador de un punto se asigna cuando
se crea y permanece constante durante toda su vida. Nos ser muy til para seguir la pista de

U5 Utilidades avanzadas

un determinado punto. El mtodo getPointerId(int id) permite averiguar el identificador


de un punto a partir de su ndice.
Una vez conocemos la estructura de la informacin del objeto del tipo MotionEvent y obtenemos
las posiciones de los dedos, ejecutamos las siguientes sentencias en funcin de la accin:
- ACTION_DOWN o ACTION_POINTER_DOWN: si uno o varios dedos tocan la pantalla, entonces
lo guardamos en la matriz de puntos.
- ACTION_MOVE: si el dedo se mueve, entonces recorremos con un bucle todos los puntos y
actualizamos su nueva posicin en la matriz de datos.
- ACTION_UP o ACTION_POINTER_UP o ACTION_CANCEL: si uno o ms dedos se levantan o se
cancela la operacin, quitamos el punto de la matriz de datos.
Al final de este mtodo indicamos que se debe redibujar la Vista cada vez que ocurre un evento
onTouch.
La clase ZoomImageView se encarga de escalar una imagen mediante el mecanismo pitch (dos
dedos que se separan o juntas para agrandar o disminuir la imagen respectivamente). Contiene
las siguientes sentencias:
// Clase de tipo Vista que permite escalar una imagen
public class ZoomImageView extends View {

// Imagen que aparece en la pantalla

private Drawable imagen;

// Factor de escalado

private float factorEscalado = 1.0f;

// Clase usada para detectar cambios de escala

private ScaleGestureDetector scaleGestureDetector;

// Constructor de la clase

public ZoomImageView(Context context) {
super(context);

// Obtenemos la imagen que es el icono de la aplicacin

imagen = context.getResources().getDrawable(

R.drawable.ic_launcher);

// Indicamos que esta Vista puede obtener el foco de la

// aplicacin

setFocusable(true);

// Establecemos los bordes de la imagen

imagen.setBounds(0, 0, imagen.getIntrinsicWidth(),


imagen.getIntrinsicHeight());

// Definimos el detector de cambios de escala con su

// listener correspondiente

scaleGestureDetector = new ScaleGestureDetector(context, new
ScaleListener());
} // end constructor


// Evento que se lanza cuando es necesario dibujar la Vista
@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// Guarda el estado del lienzo. As evitamos que parpadee

canvas.save();

// Escalamos el lienzo en Ancho y Alto

canvas.scale(factorEscalado, factorEscalado);

445

Aula Mentor

446


// Dibujamos la imagen

imagen.draw(canvas);

// Restauramos el lienzo

canvas.restore();
} // end onDraw()


// Evento que salta cuando el usuario toca la pantalla
@Override

public boolean onTouchEvent(MotionEvent event) {

// Pasamos el evento al detector de escala

scaleGestureDetector.onTouchEvent(event);

// Redibujamos la Vista

invalidate();

return true;
}


// Listener del ScaleGestureDetector

private class ScaleListener extends

ScaleGestureDetector.SimpleOnScaleGestureListener {

// Si se lanza el evento onScale...

@Override

public boolean onScale(ScaleGestureDetector detector) {
// El nuevo factor de escalado es el anterior

// multiplicado por el valor de escala del evento

factorEscalado *= detector.getScaleFactor();

// No dejamos que el objeto sea muy pequeo o muy grande.

factorEscalado = Math.max(0.2f, Math.min(factorEscalado,

5.0f));

// Indicamos que se debe redibujar la Vista

invalidate();

return true;

} // end onScale()
} // end ScaleListener
} // end clase

Como puedes observar, de nuevo esta clase se extiende de la clase bsica View que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el detector de cambios de escala con su listener correspondiente (clase ScaleGestureDetector).
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, se guarda el
estado del lienzo y as evitamos que parpadee, escalamos el lienzo en ancho y alto, dibujamos
la imagen y restauramos el lienzo.
En el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla, pasamos
el evento al detector de la escala anterior y redibujamos la Vista.
Finalmente, definimos listener del ScaleGestureDetector del tipo SimpleOnScaleGestureListener que lanza el evento onScale. En este bloque de cdigo fijamos el nuevo
factor de escalado teniendo en cuenta el valor de escala del evento e indicamos que se debe
redibujar la Vista.
El multi-touch no est disponible en el AVD; por lo tanto, es necesario probar esta aplicacin en un dispositivo real de Android.
Adems, para poder depurar la aplicacin en un dispositivo real de Android es necesario indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.

U5 Utilidades avanzadas

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (Toques pantalla) de la


Unidad 5. Estudia el cdigo fuente y ejectalo en un dispositivo fsico de Android
para ver el resultado del programa anterior.

Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:

447

Aula Mentor

Recomendamos que pruebes las distintas ventanas para ver el comportamiento de la aplicacin.

Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.

5. Tamaos de pantalla de los dispositivos Android


Este apartado describe cmo desarrollar aplicaciones para diferentes tamaos de pantalla de los
dispositivos en Android, de forma que presenten el mismo aspecto al usuario.
Android permite definir el tamao de las Vistas de la interfaz de usuario mediante dimensiones fijas o relativas. Sin embargo, los dispositivos Android disponen de pantallas con
diferentes tamaos, resoluciones y densidad en pxeles.
Si se utilizan dimensiones fijas para definir un botn de la interfaz del usuario, por ejemplo, mediante pxeles, este botn podra verse correctamente en un determinado dispositivo,
pero puede aparecer demasiado pequeo en otros dispositivos con una mayor densidad de
pxeles y el usuario no podra hacer clic sobre l.
448

Si se usan pxeles para definir las dimensiones de una Vista en un dispositivo cuya pantalla tiene
una densidad de pxeles baja, obtendramos el siguiente resultado visual:

U5 Utilidades avanzadas

Sin embargo, en un dispositivo cuya pantalla tiene una resolucin alta, el mismo diseo tendra
este aspecto visual:

449

Como puedes ver, la percepcin del usuario sera muy distinta y, realmente, la aplicacin
cambiara tanto que su experiencia sera cambiante.
Los mltiples dispositivos con Android disponibles en el mercado hacen que el diseo
de interfaces visuales en este sistema operativo sea ms compleja. En este apartado vamos a dar
una serie de recomendaciones sobre este asunto.

5.1 Android y tamaos de pantalla


El primer dispositivo Android que estuvo disponible en el mercado tena una pantalla del tipo
HVGA de 320 x 480 pxeles. HVGA significa la mitad de la matriz de grficos de video (o VGA
de medio tamao).
Android clasifica las pantallas, tomando en cuenta la longitud de la diagonal que va desde la esquina superior izquierda a la esquina inferior derecha de la pantalla del dispositivo, en
cuatro tamaos generales: pequeo, normal, grande y extra grande.

Aula Mentor

A continuacin, se muestra una tabla con todos los tamaos de pantalla estndar de Android y
su densidad de pixeles:

Densidad baja
(120) ldpi

450

Densidad media
(160) mdpi

Densidad alta
(240) hdpi

Densidad
muy alta
(320) xhdpi

Pantalla
pequea

QVGA: 240x320

480x640

Pantalla
normal

WQVGA400 :240x400
HVGA: 320x480
WQVGA: 240x432

WVGA: 480x800
WVGA854:
640x960
480x854
600x1024

Pantalla
grande

WVGA: 480x800
WVGA854: 400x854

Pantalla muy
1024x600
grande

WVGA800: 480x800
WVGA854: 480x854
600x1024
WXGA: 1280x800
1024x768
1280x768

1536x1152
1920x1152
1920x1200

2048x1536
2560x1536
2560x1600

5.2 Densidades de pantalla


Desde el punto de vista del desarrollador, no es tan importante la resolucin de la pantalla como
su densidad. Android define los siguientes trminos en relacin con este concepto:
- Resolucin de pantalla: nmero total de pixeles fsicos en una pantalla.
- Densidad de la pantalla: cantidad de pixeles en un rea fsica fija de la pantalla, normalmente se conoce como DPI (puntos por pulgada).

U5 Utilidades avanzadas

- Pixeles independientes de la densidad (DP): es una unidad de pixel virtual que se utiliza
en el diseo de la interfaz de usuario con el fin de expresar dimensiones o la posicin de
una Vista de una manera independiente de la densidad de la pantalla. En una pantalla de
160 DPI, 1 DP equivale a un pxel fsico, que es la densidad de referencia asumida por el
sistema como la densidad media de la pantalla. En tiempo de ejecucin, Android modifica,
de forma transparente, cualquier ampliacin de las unidades de DP segn sea necesario, en
base a la densidad real de la pantalla que se est utilizando. La conversin de las unidades
de DP a los pxeles de la pantalla es simple: N de pxeles = DP * (DPI / 160). Por ejemplo,
en una pantalla de 240 DPI, 1 DP es igual a 1,5 pxeles fsicos.

Es recomendable utilizar siempre DP como unidad para definir los diseos de la interfaz de usuario para asegurarse de que se mostrar correctamente en pantallas
con diferentes densidades.

Si se especifica el tamao en dp, Android automticamente escalar la interfaz del usuario


teniendo en cuenta la pantalla del dispositivo.
Puedes utilizar dp en archivos de recursos, como, por ejemplo, los layouts.
En la tabla anterior, puedes ver que Android divide las densidades de pantalla en cuatro
categoras: LDPI (baja), MDPI (media), HDPI (alta), y XHDPI (muy alta).
Si abres un proyecto en Eclipse ADT puedes ver que en la carpeta res existen varias
subcarpetas drawable-xxxx donde podemos incluir las imgenes con distinta resolucin que
debe utilizar la aplicacin en funcin de la densidad de la pantalla:

Es recomendable incluir siempre las imgenes MDPI y HDPI para cualquier aplicacin Android
que desarrolles.
Si utilizas un icono de tamao 100100 pixeles en la interfaz del usuario en un layout
cuyo dispositivo tiene una pantalla de 320480 pxeles, este mismo icono se vera demasiado
pequeo en un dispositivo con una pantalla HDPI, que tiene ms puntos por pulgada:

451

Aula Mentor

Las distintas densidades de pantalla de Android tienen las siguientes proporciones


aproximadamente: 3 : 4 : 6 : 8.
Por lo tanto, para ajustar las imgenes a estas densidades de pantalla de los dispositivos,
necesitamos seguir esta relacin de escala. Es decir, podemos crear las siguientes cuatro versiones
diferentes de nuestra imagen:
7575 para pantallas de baja densidad (x0.75);
100100 para pantallas de media densidad (imagen bsica);
150150 para pantallas de alta densidad (x1.5);
200200 para pantallas de muy alta densidad (x2.0)

452

200 x 200

150 x 150

100 x 100

75 x 75

Se recomienda que los objetos que pueda pulsar el usuario con el dedo tengan un
tamao, al menos, de 45x45 dp.

5.3 Buenas prcticas de diseo de interfaces de usuario


Para asegurar que las aplicaciones desarrolladas se muestren correctamente en diferentes
pantallas debemos tener en cuenta las siguientes recomendaciones:
- Usar wrap_content, fill_parent o unidades en dp cuando especifiquemos dimensiones en
los ficheros XML de Layouts.
- Para definir tamaos de texto deberamos optar por unidades en sp (scale-independent pixel).
- No recurrir a AbsoluteLayout, ya obsoleto, pues obliga a utilizar posiciones fijas y puede
provocar que no se muestren bien las Vistas en distintas pantallas. Como sustitucin, debemos utilizar RelativeLayout, que maneja posiciones relativas en su lugar.
- Suministrar distintos bitmaps en funcin de la densidad, tal y como hemos visto anteriormente.

U5 Utilidades avanzadas

- No utilizar pxeles reales a la hora de trabajar con bitmaps en el cdigo de la aplicacin.


- Android utiliza pxeles como unidad estndar para expresar los valores de dimensiones o de
coordenadas. Esto significa que los mtodos devolvern los resultados en pxeles teniendo
en cuenta la densidad actual de la pantalla.
Por ejemplo, si en un dispositivo, la sentencia miVista.getWidth() devuelve 10,
en otro dispositivo con una pantalla de alta densidad, el valor devuelto puede ser de 15. Es
decir, a la hora de manipular mapas de bits debemos referirnos a los pixeles teniendo en
cuenta la densidad de la pantalla en tiempo de ejecucin.
Para ello, puedes utilizar el siguiente mtodo para obtener el nmero de pxeles a
partir de una dimensin especificada en dp:
public int obtenerPixelesDesdeDp(int dpDato) {
// Obtenemos la escala de la densidad de pantalla
final float escala =
getResources().getDisplayMetrics().density;
// Convertimos los ps a pixels teniendo en cuenta la escala de
// densidad
return (int) (dpDato * escala + 0.5f);
}

6. Internacionalizacin de aplicaciones Android


Internacionalizacin (tambin se denomina abreviadamente i18n) es el proceso de agregar
diferentes literales traducidos de distintos idiomas a un programa.
En un mundo cada vez ms globalizado, los posibles usuarios de una aplicacin de Android publicada en Google Play pueden proceder de distintos pases. En este caso, es interesante
que la aplicacin tenga la capacidad de traducir automticamente los literales de la misma.
Incluso si la aplicacin no est pensada para una audiencia global, vale la pena definir
todos los literales en un nico idioma. As es ms sencillo y rpido revisar la ortografa de los
mensajes y garantizar la coherencia entre todos ellos. Tambin permite a los no programadores
cambiar los mensajes para corregir errores gramaticales sin tener que modificar el cdigo fuente.
La forma ms sencilla y recomendada por Google de desarrollar aplicaciones multiidioma en Android consiste en crear una carpeta llamada res\values-sufijo que contenga el
archivo strings.xml que corresponda a los literales de un idioma. Por ejemplo, la carpeta res\
values-en contendr en su interior el fichero con los literales en ingls.
De igual manera, podemos definir la carpeta res\drawable-en que albergar las imgenes relacionadas con el idioma ingls.
Como puedes observar, la internacionalizacin de una aplicacin Android es muy sencilla e intuitiva de implementar. De todas formas, en la documentacin oficial puedes encontrar
ms informacin. Adems, en el enlace siguiente se dan ms recomendaciones en cuanto a la
utilizacin de recursos.

Puedes encontrar un listado de los Cdigos ISO de pases en este enlace.

La clase Locale se usa para obtener informacin del idioma activo en el sistema operativo y
para formatear fechas y nmeros.

453

Aula Mentor

6.1 Ejemplo del uso de Internacionalizacin


Veamos ahora, mediante un sencillo ejemplo, cmo aplicar la internacionalizacin de aplicaciones
Android.
Es recomendable abrir el Ejemplo 2 de esta Unidad para entender la explicacin siguiente.
La aplicacin que desarrollamos cambia los literales automticamente en funcin del
idioma del dispositivo donde se ejecuta.
En cdigo del layout activity_main.xml se incluye este sencillo diseo de la Actividad
principal:

454

<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical

android:layout_width=fill_parent

android:layout_height=fill_parent>

<TextView

android:id=@+id/etiqueta
android:text=@string/texto_etiqueta
android:layout_width=match_parent
android:layout_height=wrap_content
android:gravity=center
android:layout_margin=20dp
android:isScrollContainer=true

android:background=#AAAAAA

android:textColor=#333333/>

<Spinner

android:layout_height=wrap_content

android:layout_width=match_parent

android:id=@+id/spinnerOfCharacterRaces

android:entries=@array/opciones/>

<Button

android:layout_width=match_parent
android:layout_height=wrap_content
android:id=@+id/boton
android:text=@string/texto_boton

android:gravity=center/>
</LinearLayout>

Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente


contenido con la lgica de la aplicacin:
public class MainActivity extends Activity implements OnClickListener {

private TextView etiqueta;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las Vistas de la interfaz del usuario

U5 Utilidades avanzadas

setContentView(R.layout.activity_main);
etiqueta = (TextView) findViewById(R.id.etiqueta);
Button button = (Button) findViewById(R.id.boton);
button.setOnClickListener(this);
// Obtenemos el idioma local
Locale locale = Locale.getDefault();
Toast.makeText(this, getString(R.string.idioma_actual) +

+ locale.getLanguage() + ,Toast.LENGTH_LONG).show();
}
@Override

public void onClick(View v) {

if (v.getId() == R.id.boton) {

// Cambiamos el texto de la etiqueta con un literal
etiqueta.setText(getString(R.string.texto_boton2));
}
}
} // end clase

En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para mostrar un mensaje
de tipo Toast con el cdigo ISO del idioma actual del dispositivo mediante la clase Locale con
el mtodo getLanguage().
En el evento onClick() del botn cambiamos el texto de la etiqueta con un literal.
Como puedes observar, en ningn momento seleccionamos un idioma en concreto, es Android
el que lo hace automticamente por nosotros.
Para definir los idiomas de la aplicacin, primero debemos rellenar los archivos de la
carpeta res\values que establecen los literales del idioma por defecto. En las carpetas con la
estructura de nombre res\values-cdigo_ISO_idioma se incluyen la misma informacin que
la carpeta anterior traducida.
En este ejemplo, hemos aadido el castellano como idioma extra. En Eclipse ADT, la estructura
de carpetas tiene el siguiente aspecto:

455

Aula Mentor

Si accedes al primer archivo \res\values\strings.xml vers que contiene los literales en


ingls:
<?xml version=1.0 encoding=utf-8?>
<resources>
<string name=app_name>Internationalization and

Localization</string>

<string name=texto_etiqueta>Label Text Sample</string>

<string name=texto_boton>Button</string>

<string name=texto_boton2>Button pushed</string>

<string name=idioma_actual>Current language ISO code

is</string>
</resources>

Si abres en Eclipse ADT el fichero extra \res\values-es\strings.xml, observars que se


marca con una bandera del pas correspondiente y los literales estn traducidos:

456

Fjate que en las carpetas values hemos incluido tambin el fichero arrays.xml,
que se traduce en funcin del idioma y se usa para rellenar las opciones del Spinner de la aplicacin.

Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Internacionalizacin) de


la Unidad 5. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa anterior, en el que hemos desarrollado una aplicacin Android
multi-idioma.

U5 Utilidades avanzadas

Si ejecutas en Eclipse ADT este Ejemplo 4 en el AVD, vers que se muestra la siguiente
aplicacin en ingls:

457
Para cambiar el idioma directamente desde el AVD, debes cambiar la configuracin del dispositivo
virtual. Fjate en las siguientes capturas de pantalla:

Aula Mentor

Si abres de nuevo el ejemplo anterior, vers que ya aparece en castellano la aplicacin:


458

Tambin puedes cambiar el idioma del AVD desde la ventana de comandos mediante la siguiente
orden:
setprop persist.sys.language [cdigo idioma];setprop persist.sys.country
[cdigo pas];stop;sleep 5;start

U5 Utilidades avanzadas

En el directorio: adt-bundle-windows-x86\sdk\platform-tools debes ejecutar estos


comandos:
adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached
emulator-5554
device
adb shell
root@android:/ # setprop persist.sys.language es;setprop persist.sys.
country ES; stop;sleep 5;start;setprop persist.sys.country ES;stop;sleep
5;start
root@android:/ #

Nota: a los 5 segundos de ejecutar estos comandos, el AVD se reiniciar para volver a arrancar
los nuevos idioma y pas indicados como parmetros.

Para depurar aplicaciones en distintos idiomas recomendamos arrancar dos AVD


configurados con el idioma correspondiente y ejecutar la aplicacin en ambos
para ver el resultado.
459

7. Desarrollo rpido de cdigo Android

A todos los programadores nos gusta mejorar una aplicacin reutilizando mtodos y funciones
sencillos, eficientes y ya probados por otro programador que realizan un cometido especfico.
Cuando juntamos todos estos trocitos de cdigo fuente, obtenemos una aplicacin compleja con
mucha funcionalidad adicional.
Para esto, podemos utilizar Snippets. Un snippet es una porcin de cdigo Android que
puedes aadir a un programa y que realiza una funcin especfica.
Existen multitud de pginas en Internet donde puedes encontrarlos. Por ejemplo, Wikicode incluye snippets y tutoriales de muchsimos lenguajes de programacin. Adems de Android,
encontramos snippets de PHP, JS, HTML, CSS, SQL, CSV, JAVA, C++, IPHONE, PERL, etctera.
En el apartado de Android hay unos 50 snippets; entre otros, podemos encontrar:
- Obtener versin API Android.
- ImageButton con fondo transparente.
- Activar el Vibrador del movil en Android.
- Crear un men de opciones en Android.
- Detectar conexin de datos en android.
- Hacer consulta con cursores desde Android.

Aula Mentor

- Hacer una peticin HTTP POST en Android.


- Obtener el alto y el ancho de la pantalla en android.
Por ejemplo, si abrimos el snippet Hacer una peticin HTTP POST en Android, vers que
contiene una explicacin clara sobre cmo desarrollar esta funcionalidad:

460
Te recomendamos que busques en Internet snippets que te permitan mejorar las aplicaciones
de forma rpida y sencilla, si bien no recomendamos abusar de ello ya que el programador o
programadora debe entender cmo funciona su cdigo fuente.

U5 Utilidades avanzadas

8. Resumen
Hay que saber al final de esta unidad:
- Android dispone de un potente portapapeles para copiar y pegar contenidos.
- La clase principal que se usa para acceder al portapapeles es ClipboardManager.
- El portapapeles slo puede contener un nico dato del tipo ClipData al mismo
tiempo.
- Arrastrar y soltar (drag and drop) es una expresin informtica que se refiere a la
accin de mover objetos de la interfaz del usuario entre dos partes visuales de
las aplicaciones.
- Para que otra Vista se convierta en receptora del elemento arrastrado, debemos
indicarlo implementando su listener View.OnDragListener.
- La gestin bsica de la pantalla tctil se basa en implementar el mtodo OnTouchEvent() de la clase View.
- Las pantallas multitctil permiten detectar una entrada de varios dedos. Esta funcionalidad est disponible a partir de Android 2.0.
- Tambin es posible gestionar las pulsaciones del usuario sobre la pantalla mediante gestos (del ingls, gestures). Est disponible desde Android 1.6.
- Internacionalizacin (abreviadamente i18n) es el proceso de agregar diferentes
literales traducidos de distintos idiomas a un programa.
- Para internacionalizar una aplicacin en Android hay que crear una carpeta llamada res\values-sufijo que contenga el archivo strings.xml con los literales de
un idioma.
- Un snippet es una porcin de cdigo Android que puedes aadir a un programa
y que realiza una funcin especfica.

461

Você também pode gostar