Está en la página 1de 332

grupo editorial

Técnico Superior en Desarrollo de Aplicaciones Multiplataforma

Android
Programación multimedia y de dispositivos móviles

Iván López Montalbán


Manuel Martínez Carbonell
Juan Carlos Manrique Hernández
INDICE

CAPÍTULO 1. INTRODUCCIÓN................................................................................................................ 1

1.1 ¿Qué es Android?.................................. 2


1.2 Programación para A ndroid................................................................................................................. 4
1.3 No tengo un teléfono Android, ¿Puedo seguir este texto?..................................................................5
1.4 ¿Qué necesito saber?............................. 6
1.5 ¿Se desarrolla igual para un equipo de escritorio (Desktop) que para un
dispositivo Android?............................... 6
1.6 Prerrequisitos... ¿Por dónde empezamos?...........................................................................................7
1.7 Primer contacto: Instalando Android Studio.......................................................................................8
1.8 Nuestro primer proyecto......................................................................................................................15
1.9 Primer contacto con el código. ¿Dónde está el Java?.........................................................................19
1.10 Programando sin tirar líneas de código............................................................................................. 23
1.11 Introduciendo un poco de código...................................................................................................... 24
1.12 Probando, probando............................ 30
1.13 Entendiendo un poco más el código............................................. 32
1.14 Los emuladores..................... ...33
PRÁCTICA 1. La calculadora de los números prim os....................................................................... 34

CAPÍTULO 2. DESARROLLO DE APLICACIONES PARA MÓVILES....................................... 37


2.1 Arquitectura de A ndroid.......... ............................................................................................................38
2.1.1 Los componentes de una aplicación................................. 38
2.1.2 Comunicación entre componentes.................................... -.40
vi Programación en Android

2.1.3 El fichero de manifiesto............................................................................................................40


2.1.4 La pila de software de Android................................................................................................ 42
2.2 Profundizando en el desarrollo: tipos de widgets para Android......................................................42
2.2.1 Los controles de entrada...........................................................................................................43
2.2.2 Las API’s de A ndroid................................................................................................................45
2.2.3 La clase TextView......................................................................................................................46
2.2.4 La clase B utton...........................................................................................................................48
2.2.5 Las clases RadioGroup y RadioButton.....................................................................................50
2.2.6 La clase Checkbox......................................................................................................................53
2.2.7 Las clases ToggleButton y Switch.............................................................................................55
2.2.8 Las clases ImageView e ImageButton......................................................................................55
2.2.9 La clase EdiText..........................................................................................................................58
2.3 Internalización de aplicaciones mediante el archivo string.xml......................................................61
2.3.1 Creación de aplicaciones multiidioma.....................................................................................62
2.3.2 Probando la internalización en el emulador........................................................................... 65
2.4 Layouts y contenedores........................................................................................................................67
2.4.1 Propiedades de los Layouts....................................................................................................... 68
2.4.2 Relative Layouts ........................................................................................................................69
2.4.3 Linear Layouts...........................................................................................................................70
2.4.4 Layouts tabulares.......................................................................................................................72
2.4.5 Trabajando programáticamente con contenedores................................................................75
2.5 Los diálogos y los fragmentos.............................................................................................................. 79
2.6 Los widgets de selección.......................................................................................................................83
2.6.1 Seleccionando múltiples elementos...........................................................................................86
2.6.2 Los Spinners.............................................................................................................................. 87
2.6.3 Las secciones personalizabas.................................................................................................... 89
2.6.4 Los selectores de fecha/hora..................................................................................................... 92
2.7 Construcción de m enús.......................................................................................................................96
2.7.1 Dotando de acción a los elementos de m enú.................................................................. 100
2.7.2 Menús contextúales................................................................................................................ 101
2.8. ElActionBar........................................................................................................................................ 106
2.9. Otros widgets para tu IU ....................................................................................................................107
2.10 Más funciones de callback.................................................................................................................. 108
PRÁCTICA 2. Encuentra hipotenochas..................................................................................................... 109

CAPÍTULO 3. COMUNICACIONES.......................................................................................................113
3.1 Ejecución de proyectos en dispositivos reales................................................................................... 114
índice vii

3.1.1 Depuración de aplicaciones en dispositivos móviles............................................................116


3.2 Comunicación con otros componentes............................................................................................118
3.2.1 Llamando a otras actividades..................................................................................................118
3.2.2 Más sobre Intents: compartir datos........................................................................................ 122
3.2.3 Los filtros de Intents................................................................................................................ 128
3.3 Solicitud de permisos..........................................................................................................................130
3.4 Los servicios........................................................................................................................................ 135
3.5 Conexiones a Internet........................................................................................................................142
3.5.1 Ejemplo: establecer una conexión http a través de AsyncTask.............................. 143
3.6 Las notificaciones................................................................................................................................146
3.6.1 Notificaciones avanzadas........................................................................................................ 148
3.7 Recibiendo Broadcasts.................................. 149
3.7.1 Lista completa de Broadcast Receivers..................................................................................150
3.7.2 Broadcast personalizados........................................................................................................ 151
3.8. Los mensajes de texto. Enviar y recibir SMS a través de código......................................... 151
3.9. Los proveedores de contenido (Content Provider)......................................................................... 155
3.10 Acceso abases de datos SQLite............................................................................................. ....159
3.11 Las conexiones Bluetooth.................................................................................................................. 161
3.12 Las alarmas................... 170
3.13 Publicación de aplicaciones en Google Play Store........................................................................... 171
PRÁCTICA 3. El Birthday y el Helper............................................................................................ 175

CAPÍTULO 4. PERSISTENCIA DE LOS DATOS Y CONTENIDO MULTIMEDIA..............................179


4.1 La persistencia de los datos............................................... ....180
4.1.1 Preferencias.................................................................................................................... 180
4.1.2 Ficheros estáticos..................................................................................................................... 194
4.2 Contenido multimedia....................................................................................................................... 195
4.3 Reproducción de audio y vídeo. La clase MediaPlayer........................................................ ...195
4.3.1 Inicialización del reproductor................ 197
4.3.2 Preparación del contenido.......................................................................................................197
4.3.3 Controlando la reproducción................................................................................................... 198
4.3.4 Finalización de la reproducción.................................................................................. 198
4.3.5 Añadiendo controles estándar. La clase MediaController...................................... 201
4.3.6 Reproducción de vídeo...................... 203
4.3.7 Streaming de audio y vídeo.................................................................................................... 204
4.4 Captura de fotos.............................. -............206
4.4.1 Obtención de Thumbnails......................................................................................................207
viii Programación en Android

4.4.2 Guardar fotos a tamaño completo.........................................................................................207


4.5 Tratamiento de imágenes escaladas................................................................................................. 210
4.6 Captura de vídeo................................................................................................................................ 212
4.6.1 Cómo anunciar que nuestra aplicación depende de la cámara........................................... 214
4.7 Almacenamiento de contenido multimedia(en la galería)..............................................................214
PRÁCTICA 4. Elige de entre dos ejercicios................................................................................................ 215

CAPÍTULO 5. PROGRAMACIÓN DE VIDEOJUEGOS........................................................................... 217


5.1 Introducción....................................................................................................................................... 218
5.2 La arquitectura de un videojuego..................................................................................................... 219
5.3 El canvas de Android......................................................................................................................... 220
5.4 Los dibujables (Drawables)................................................................................................................222
5.4.1 Los Sprites................................................................................................................................ 223
5.5 El framework de animaciones de Android.......................................................................................224
5.5.1 Las Property Animations........................................................................................................ 224
5.5.2 View Animations....................................................................................................................225
5.5.3 Drawable Animations.............................................................................................................228
5.6 Las animaciones en tiempo real: el bucle deun videojuego................................................ 230
5.6.1 Un ejemplo de bucle de juego................................................................................................ 235
5.7 La interacción con el jugador............................................................................................................237
5.7.1 Los eventos Touch................................................................................................................. 237
5.7.2 Los eventos Multitouch.................................................................................... ......................240
5.7.3 Los gestos................................................................................................................................242
5.8. Creación de un videojuego sencillo: the Xindi Invasion..................................................................244
5.8.1 Diseñando el videojuego......................................................................................................... 244
5.8.2 El modo de pantalla.................................................................................................................245
5.8.3 Ajustando tu videojuego al tamaño de la pantalla............................................................... 246
5.8.4 El escenario.............................................................................................................................. 248
5.8.5 La nave del jugador..................................................................................................................250
5.8.6 Los controles........................................................................................................................... 251
5.8.7 Los enemigos.......................................................................................................................... 254
5.8.8 El disparo................................................................................................................................. 259
5.8.9 Las colisiones..........................................................................................................................262
5.8.10 La música..............................................................................................................................265
5.8.11 El nivel de dificultad........................................................................................................... 267
5.8.12 El fin del juego......................................................................................................................268
5.8.13 Adaptando la velocidad de los objetos..................................................................................271
índice ix

5.8.14 Libera recursos...................................................................................................................... 273


5.9. Los motores para programación de videojuegos...............................................................................274
5.9.1 Motores para videojuegos en 2D ............................................................................................275
5.9.2 Motores para videojuegos en 3D ............................................................................................276
PRÁCTICA 5. Videojuego o trabajo..................................................................................... 277

CAPÍTULO 6. LOCALIZACIÓN GEOGRÁFICA........................................................................................217


6.1 Localización geográfica....................................................................................................................... 280
6.2 ¿Cómo se ha trabajado con la localización geográfica desde el comienzo de Android?.............. 280
6.2.1 Especificación de permisos de aplicación para gestionar la ubicación del dispositivo.....281
6.2.2 Proveedores de localización....................................................................................................282
6.2.3 Recepción de la última ubicación........................................................................................... 284
6.2.4 Recepción de actualizaciones de ubicación........................................................................... 287
6.3 Las nuevas APIs de los servicios de localización............................................................................. 292
6.3.1 Google Play Services................................................................................................................ 293
6.3.2 Obtención de la última ubicación...........................................................................................298
6.3.3 Recepción de actualizaciones de ubicación........................................................................... 301
6.3.4 Visualización de la dirección de una ubicación..................................................................... 305
6.4 Sensores............................................................................................................................................... 314
6.4.1 Sistemas de coordenadas........................................................................................................ 315
6.4.2 Tipos de sensores. Clasificación.............................................................................................316
6.4.3 Las clases Sensor y SensorManager........................................................................................318
6.4.4 Motorizando sensores............................................................................................................. 320
PRÁCTICA 6. Geolocalización.................................................................................................................... 323
PROLOGO

Si estás leyendo este prólogo no necesitas que te expliquemos qué es Android, o la


importancia que tiene en el mundo tecnológico actual ¿verdad? Entonces, ¡vamos al
grano!
¿Cómo surge la idea de este libro?
De la inquietud de tres amigos que no estaban nada satisfechos con los contenidos
que se estaban impartiendo en una determinada asignatura.
¿A quién va dirigido este libro?
Pues a toda persona que desee sumergirse en el maravilloso mundo de la
programación multimedia de dispositivos móviles con Android.
¿Se requieren conocimientos previos?
Para abordar este libro no se requiere ningún conocimiento previo de Android. Sin
embargo, sí son necesarios conocimientos de programación en Java o, al menos, en algún
lenguaje de programación orientado a objetos.
¿Cómo está enfocado este libro? ¿Qué me voy a encontrar dentro de él?
Nuestra intención ha sido dar un enfoque totalmente práctico y didáctico. Como
docentes que somos, sabemos que el conocimiento que realmente se aprende, se asimila y
se llega a dominar, es aquel que permite al estudiante jugar con él, que le da la
oportunidad de equivocarse y aprender de sus propios errores. Esto solo se consigue si se
prioriza la parte práctica sobre la teórica. Así es como trabajamos día a día con nuestros
alumnos. Entonces... ¿no contiene nada de teoría este libro? Pues claro que contiene
teoría, pero la justa y necesaria para poder abordar las actividades y las prácticas
propuestas. Para que quede claro, NO somos de la escuela de la “lección magistral”.
Además, hemos tratado de evitar formalismos, intentando escribir en un lenguaje directo
y claro.
x ii Programación en Android

De modo que, lo que NO te vas a encontrar en este libro es un “Manual de referencia


de Android” o “La Biblia de Android”. Es decir, desde el primer capítulo vas a crear tu
primer proyecto de Android. Cada capítulo contiene una serie de actividades, que te
encontrarás después de ciertos apartados, para que vayas asentando tus conocimientos
conforme los estudies. Actividades de las que tendrás a tu disposición la solución, para
que puedas examinar el código, jugar con él o ejecutarlo en tu tablet o móvil. Y al final
de cada capítulo se sugiere un proyecto o práctica final que unifica todo lo estudiado en
dicho capítulo.
Como es imposible y nunca hemos pretendido encerrar en un libro todo el
conocimiento de Android, constantemente te sugerimos sitios a visitar para que puedas
profundizar allá donde te plazca.
El libro está estructurado para que alguien que no tenga ningún conocimiento de
Android acabe diseñando y programando sus propias aplicaciones. Por ello, se
recomienda una lectura ordenada hasta el capítulo 3 (Comunicaciones). A partir de ahí,
puedes disfrutar jugando con la geolocalización, con el streaming de vídeo/audio o con el
maravilloso capítulo de videojuegos, donde aprenderás a programar uno partiendo de
cero.
Deseamos que te diviertas aprendiendo, pero sobre todo que el estudio de este libro te
estimule a nuevas creaciones.

Los autores,
trabajando en equipo en Talavera de la Reina y Alcázar de San Juan
Junio de 2015
UNIDAD 1

INTRODUCCIÓN

CONTENIDOS
1.1 ¿Qué es Android?
1.2 Programación para Android
1.3 No tengo un teléfono Android, ¿Puedo seguir este texto?
1.4 ¿Qué necesito saber?
1.5 ¿Se desarrolla igual para un equipo de escritorio (Desktop) que para un
dispositivo Android?
1.6 Prerrequisitos... ¿Por dónde empezamos?
1.7 Primer contacto: Instalando Android Studio
1.8, Nuestro primer proyecto
1.9. Primer contacto con el código. ¿Dónde está el Java?
1.10 Programando sin tirar líneas de código
1.11 Introduciendo un poco de código
1.12 Probando, probando...
1.13 Entendiendo un poco más el código
1.14 Los emuladores

V
2 Programación en Android

1.1 ¿QUÉ ES A N D R O ID ?
Android es un Sistema Operativo de última generación basado en Linux y creado por
Google para sus dispositivos. Parte de su éxito radica en su interfaz de usuario basada
en la tecnología DMI (Direct Manipulation Interface), un tipo de interacción hombre-
ordenador que representa objetos gráficos de interés en
pantalla de manera rápida, reversible y de acciones A ft 6 12 I

increméntales y que proporcionan feedback, es decir,

W
l ■
APPS WIDGETS

proporcionan en todo momento un resultado que el usuario


puede interpretar fácilmente. Dicho de otro modo, utiliza + ”

0#
metáforas del mundo real para utilizar los objetos API D em os Browser Calculator C alendar I

representados en pantalla, que ayudan de manera muy _

fácil al usuario a interpretar lo que tiene en pantalla, cómo

&it s
Camera Clock Custom De» Settm qs 1
lo c ale
utilizarlo y los resultados obtenidos. Acciones como el
m
swiping (pasar la mano por la pantalla) o tapping
De» Tools Downloads Gallery G estures I
(presionar ligeramente para seleccionar un objeto) son muy Builder j

naturales para los usuarios, y hasta los niños más pequeños C Jr-
Q m ♦

de manera intuitiva pueden utilizar los dispositivos con M essaging Music People Phone

Android.
Android está diseñado principalmente para dispositivos
con pantallas táctiles, desde Smartphones y Tablets hasta
las nuevas Smart Tv o televisiones inteligentes. También
se usa en cámaras digitales y otros muchos dispositivos
electrónicos.
Android está programado en C/C+-1- y tiene licencia open source, aunque muchos de
los dispositivos que usan Android funcionan con una combinación de software libre y
propietario.
La primera versión de Android se publicó en 2008 y la última, a fecha de la creación
de este texto, se publicó en Noviembre de 2014 con la versión 5.0 (LollyPop). Hay
versiones compiladas para plataformas ARM, MIPS y x86.
Cada versión de Android tiene un nombre en clave y un nivel de API. Este nivel de
API especifica un conjunto de librerías que se utilizan para desarrollar una aplicación.
Por ejemplo, la versión 4.4 tiene el nombre en clave de Kitkat, y su nivel de API es el
nivel 19.
Una de las mayores complicaciones que encontrarás cuando programes para Android
es intentar dar soporte al mayor número posible de APIs. Cuando desarrolles, deberás
especificar una versión mínima de API para la que funcionará tu aplicación
android:minSdkVersion y una versión máxima android:maxSdkVersion. Estos dos
parámetros definirán el rango de versiones para las que tu App funciona, y por tanto,
los dispositivos sobre los que va a funcionar:
Unidad 1. Introducción 3

Versiones de Android:
V e rs ió n N iv e l d e N o m b re A lg u n a s d e lets n o v e d a d e s
API
1.0 1 Apple Pie Prim era versión comercial
1.1 2 B anana Resolvió los problem as de la 1.0
Bread
1.5 3 Cupcake Núcleo de Linux 2.6.27
1.6 4 Donut Incluye la Galería, y el m otor Text-to-speech
2.0/2.1 5/7 Eclair GUI renovada, calendario y Google Maps renovado.
Fondos de pantalla animados
2.2.x 8 Froyo Soporte p ara pantallas de HD y optimización de memoria
y rendimiento
2.3.x 9/10 Gingerbread Actualizaciones variadas del diseño de la interfaz,
mejoras en audios y gráficos, soporte de video y voz con
Google Talk, m ejora en el software de la cám ara y
eficiencia de la batería
3.x 11/13 Honeycomb Añadida la A ctionBar y la b arra de sistema, teclado
rediseñado, mejoras en el H TTPS, posibilidad de acceso a
tarjetas SD. M ultitarea simplificada, soporte para
procesadores multinúcleo
4.0.x 14/15 Ice Cream Numerosas optimizaciones y corrección de errores.
Sandwich Carpetas con Drag & Drop, captura de pantalla
integrada, cám ara mejorada, corrector ortográfico del
teclado mejorado, mejoras en gráficos y bases de datos
4.1 16 Jelly Bean Interfaz de usuario rem odelada con triple buffer y 60 fps.
Mejoras en la redimensión de widgets. Inclusión de la
barra de notificaciones y gestos
4.2 17 Jelly Bean Soporte m ultiusuario, foto esfera y acceso rápido a la
(Gummy barra de notificaciones
Bear)
4.3 18 Jelly Bean Soporte de B luetooth de baja energía, Open GL 3.0 y
mejoras en la seguridad. Autocom pletar en el teclado de
marcación. Nueva interfaz p ara la cám ara
4.4 19 K itK at Solucionados numerosos errores, diseño renovado p ara el
m arcador de teléfono, aplicación de contactos, MMS y
otros elementos de la IU. Optim izado para sistemas con
poca RAM y procesador
5.0 21 Lollipop Nuevo diseño de la interfaz de usuario (M aterial Design).
Nuevas formas de controlar las notificaciones, ahorro de
batería mejorado. Mejoras en la m ultitarea y soporte
para 64 bit
4 Programación en Android

A C T IV ID A D 1.1. Es muy buena idea que te registres en el canal de YouTube de los


desarrolladores de Android íhttps: / / www .voutube.com /user/androiddevelopers) p ara estar al
tan to de las novedades que ocurren en el m undo “androide”. En especial, puedes buscar uno de
los videos más vistos llamado Android Demo, donde se presentan los teléfonos móviles que
funcionan con Android.

1.2 P R O G R A M A C IÓ N P A R A A N D R O ID
En este texto utilizaremos como recursos
para programar un paquete de herramientas
llamado Android Studio. Android Studio
incorpora un IDE propio.
Aunque también se puede programar con
i r i r i i ANDR0ID i-
Android a través de Netbeans, usando un
plugin llamado NBPlugin, o a través de las
ADT de Google (Android Developer Tools)
'S1
'studio
con el IDE Eclipse, Google recomienda
utilizar Android Studio, que, aunque en el
momento de escribir este curso, estaban en
versión Beta, probablemente en la actualidad
sea la única herramienta mantenida por Google para el desarrollo de aplicaciones en
Android.

*- C T~”£- <tev*ioper,af»dro<<Uom - .
# Developers ■ Design Distribute a ¡
Training API Guides Reference Googkr Services Samples

Download

Android Stud» * j
Android Studio
BETA
Migrating from 1
C dgnc Android Studio t% a new Andicd devetopmen»
environment based on intefaJIDtA rt provides new
{seat ing a Project
features and improvements over fclipse APT and vnfi
Tips and Tucks be the official Android IOC once t f i ready On top of
the capabilities you expect from mteRu. Android
Using the Android
ñojecivbw Stud» offers

Using dw Layout • Flexible Gradle-based build m te m


tddof
• Bury vanants and muftipte APK gtmeralwr
Quitting Your Prefect 1 • f-xpanded template support for Google services
with Oradle
and vam os device types
Debuggingvnth
Android Stud»
I
f
• R sh Layout edrtor with support lo> theme ed&ng
• Lmt tools to catch performance, usability version
Workflow companbihty and other problems
• ProOuad and R esign in g capabilities
Support Library
•- Download Android Studio Beta voa 6
making fl easy to integrate Google Cloud
Toots Help »I with the Android s o t tor wmdows
Messaging and App Engine
Revisions * I I Cauiion: Android Stud>o it currently in beta Some
Thrs download «Kfudes
I features are not yet implemented and you may
NDK
Unidad 1. Introducción 5

Android Studio incorpora los siguientes componentes:


• El entorno integrado de desarrollo
(propiamente llamado Android
Studio).
• Android SDK Tools: Es un
componente que incluye un conjunto
de herramientas de desarrollo y de
depuración de programas.
• Android Platform Tools: Las
herramientas para poder compilar y
generar paquetes (apk) para el
sistema operativo Android.
• La imagen del sistema operativo Android.

A C T IV ID A D 1.2. M ientras sigues leyendo, pon a descargar el Android Studio. Te hará falta
en unos momentos. ¡Ah! Y si no tienes el JD K de Jav a (deberías tenerlo del módulo de
program ación) descárgalo también.

1.3 NO TENG O U N TELÉFONO A N D R O ID ,


¿PU ED O SEG U IR ESTE TEXTO?
Por supuesto que sí.
No necesitas gastarte un solo euro en comprar un
dispositivo para poder probar los programas que
aprendas a hacer aquí. Utilizarás un emulador
proporcionado por el SDK de Android que funciona con
una imagen del software de Android. Este emulador
monta una imagen del sistema operativo, es decir, el
mismo software que lleva cualquier dispositivo Android,
se monta en una máquina virtual formando el entorno
de emulación. Sobre este entorno, se puede ejecutar
prácticamente cualquier programa que hayas realizado
(a excepción de algunas funcionalidades):
6 Programación en Android

1.4 ¿QUÉ N ECESITO SABER?


• ¿Sabes programar? Necesitas saber un poco de programación en Java. O al
menos, saber cómo programar en un lenguaje de programación con sintaxis
estilo C.
• ¿Sabes lo que es un Entorno Integrado de Desarrollo? Sí conoces eclipse o
netbeans, la desesperación del paso a Android Studio te durará 5 minutos.
• ¿Sabes de bases de datos? Seguro que sí....aunque no es estrictamente
necesario.
• ¿Sabes lo que es una máquina virtual? ¡Pues claro!
Pues entonces, eso es lo que necesitas saber, programar en Java y manejar un IDE.
El resto son añadidos que te vendrán bien.

1.5 ¿SE DESARRO LLA IGUAL PARA UN


EQ UIPO DE ESCRITO R IO (D E SK T O P ) QUE
P A R A U N D ISPO SIT IV O A N D R O ID ?
Pues va a ser que no.
Tienes que tener en cuenta unas cuantas reglas:
• Un dispositivo Android generalmente es un dispositivo portable y pequeño, con
capacidad de procesamiento mucho más limitada, además:
Las pantallas son pequeñas
Los teclados si existen, son pequeños
Los dispositivos con los que se señalan los objetos son torpes, molestos e
imprecisos, por ejemplo, unos dedos gordos y pantallas LCD “multitouch”
• Los dispositivos tienen conexión a redes de datos (4G, Internet), pero esa
conectividad es limitada o de ancho de banda pequeño, e incluso a veces
intermitente.
Por tanto, tienes que aceptar en tu mente de programador el concepto de que un
Smartphone no es un ordenador, es un teléfono, y que una Tablet no es un ordenador, es
una Tablet.
No hay nada que peor siente a un usuario que una “App” convierta a su teléfono
móvil en un “NO TELEFONO”, es decir, crear un App que le tome el control de tantos
recursos que bloquee el acceso a otras aplicaciones y por ejemplo, no le permita coger el
teléfono cuando está recibiendo una llamada.
Unidad 1. Introducción 7

Por tanto, tienes que tener en cuenta todos estos aspectos cuando desarrolles en
Android.

1.6 PR ER R E Q U ISITO S... ¿PO R D Ó N D E


EM PEZAM OS?
1. Android Studio funciona compilando Java (además de otros muchas cosas, como
por ejemplo XML), por tanto, necesitas el JDK de Java para poder compilar.
Si no lo tienes, descarga la última versión de JDK e instálalo de la página
siguiente:

E !*** » tVewfüoMh | Or? * 1 (hi mniyiii jfc ■•■ftiT*

<- e S i? r
Cour<»yv i#m « . v I ímbm ? . v S e a rc h Q.

Product» Solution» Download* Stor* Support Training Partnar* About OTN

a » Dwmioafl* vacvmrum- ¿na 'irono**** Im m Java SDK* ana Toot»

Java SE Downloads

*■»»»£*«
WBean t JtíSüaüíili
i ¿iiU&üír. í.g,iw

||jt2222SOB Java RcKmrces

awnan»untase JO* & £ £ 4 ? 0%


tieaoBaüSfcaB
l&CT.r^lvwi
Java SE ta if i * E sasa
**a*r
9* iOt t nr*a»« nr» t u t n c . s«ars» tr< t t ^ r w x i «srraa&oo* inoritc f * n*
nm* MSi E raras» j« £ msaKrr * » or» ^ u a ra rs uanaptm m vac ss>*■ ans JMC 5 «
L«itrtr«e8* I:Jav*o«
«CssegaasL
JDK
1ijgirjiB
■ # .aaragniB
i O o o iic m
■ j* » iE f * w W 3 *
> T f t r w t í ÍSW=1**Í

• fcr»ws»F*s

h ttp : / / www.oracle.com / technetw ork/java/javase / downloads / index.htm l

(si cuando leas esto, el enlace ya no es válido, busca en google Jav a JD K y descárgalo).

2. ¿Has descargado ya el Android Studio? Si no, vete a la página


https: / / developer.android.com/sdk/ installing/studio.html y descárgalo.
Tardará un buen rato, así que es buen momento para que te tomes un café y
reflexiones sobre todo lo que has leído hasta ahora.
8 Programación en Android

1.7 P R IM E R CO NTACTO : IN ST A L A N D O
A N D R O ID STUDIO
“’SÉyjCi^... ' -í_j

► Equipo ►IVAN_PROF16 (H:) ► androidst ▼ ij B.::.......... i Pj

Organizar Compartir con w Grabar Nueva carpeta a== ES #


* Nombre Fecha de modifica... Tipo

android-studio-bundle-135.16411B6.exe 19/02/2015 10:25 Aplicación

Abre la carpeta donde hayas descargado el Android Studio y ejecuta el fichero .exe
para comenzar la instalación:
Sigue los pasos de la instalación básica, con el asistente que te guiará mediante un
proceso fácil de tipo “clic-siguiente-clic-siguiente....”:
Android Studio Setup ■n-im -L iiaiiltT - .......................... ' M T p

Welcome to the Android Studio


Setup

Setup will guide you through the installation of Android


Studio.

It is recommended that you dose all other applications


before starting Setup. This wl make it possible to update
relevant system files without having to reboot your
computer.

Cldc Next to continue.

Android
Studio

; <£ack \fn S > | I Cancel I

Elige los componentes que vas a instalar (por defecto, todos):


Android Studio Setup

Choose Components
Choose which features of Android Studio you want to install.

Check the components you want to install and uncheck the components you don't want to
install. Click Next to continue.

Description
Select components to install:
Position your mouse
0 Android SDK over a component to
0 Android Virtual Device see its description.
0 Performance (Intel® HAX

Space required: 3.8GB

□ [B a c k Ii Next > j j Cancel


Unidad 1. Introducción 9

Pulsa en Next un par de veces:


Selecciona la ubicación donde instalarás el entorno y donde instalarás el SDK (deben
ser ubicaciones diferentes):

T77Í L -c’o r
Android S tudio S etu p I *—11
Configuration Settings
Install Locations

Android Studio Installation Location

The location spedfied m ust have a t least 500MB of free space,


dick Browse to customize:
C:'program Rles\Android\Andn>id Studio Browse.,

Android SDK Installation Location

The location spedfied must have a t least 3,2GB of free space.


Click Browse to customize;
C:\android_studio _^dk Browse.,

< Back Next > Cancel

Si dispones de un procesador Intel, podrás usar el acelerador de hardware:

Android Studio Setup

Configuration Settings
Emulator Setup

We have detected that your system can run the Android emulator in an accelerated
performance mode.

Please set the maximum amount of RAM available for the Intel Hardware Accelerated
Manager (HAXM) to use for all x86 emulator instances.

You can change these settings a t any time. Please refer to the Intel HAXM Documentation
for more information.

9 Recommended: 512 MB
Q Custom: 512 MB ▼
*This value must be between 512 MB and 1 GB

Note: Setting aside a large memory reservation may cause other programs to run slowly
when using the x86 Android emulator with HAXM.

< Back |i Next > j Cancel |


10 Programación en Android

Pulsa otra vez en “Next” y comenzará la instalación:

Android Studio Setup | « S f I' 'I1' — 'I


Installing
Please wait while Android Studio is being installed.

Execute: *C:^JsersV1rofiesor\AppData'iocahJemp\vCTedist_x64_2008.exe’ /q:a

Show details

Después de un pequeño rato, ha concluido la instalación y podemos empezar a


configurar el entorno.

■------------------------------------------------------------------
Android Studio Setup

Completing the Android Studio


Setup
Android Studio has been installed on your computer.

Clide Finish to dose Setup.

E l Start Android Stiidto!

Android
Studio

< Back Finish Cancel

J
Unidad 1. Introducción 11

Al terminar, se descargará el SDK:

_Ej - •;
Android Studio Setup

Update Info
A n e w version o f Android S tu d io is available?

Dow nloading Android SDK Tools, revision 2 4.0 2

Show Details

Cancel Bnisr:

A continuación arrancamos Android Studio. Si es la primera vez que lo instalamos


hay que indicarlo en la siguiente pantalla, si tuviéramos versiones anteriores de Android
Studio, existe la posibilidad de importar las preferencias que hubiéramos configurado en
versiones anteriores. En nuestro caso, indicamos que no queremos importar nada y
pulsamos ok:

r-1 C o m p le te In stallation

You can import your settings from a previous version o f Android Studio.

I w ant to import my se ttings from a custom location:

Specify config folder or installation home o f the previous v erso n o f Android Studio:

I in
o I do not have a previous version o f Android Studio or I do not w ant to import my settings

OK
12 Programación en Android

La primera pantalla con la que nos encontramos es la de bienvenida:

Desde esta pantalla podemos crear un nuevo proyecto, importarlo, abrir uno que
existe, personalizar la gestión de configuración...

....un momento, ¿qué es eso de gestión de configuración?


Amplia información leyendo aquí: http://es.w ikipedia.org/w iki/C ontrol_de_versiones
P ara calentar contaremos que Android Studio perm ite seleccionar diferentes herram ientas para
controlar las versiones y los cambios en los ficheros que componen tu proyecto.

Por supuesto también permite configurar y acceder a la documentación.


Antes de inclinar el cuello, meter la cabeza en el teclado y ponernos a tirar líneas de
código como locos, necesitamos primero realizar unas configuraciones: Así que pulsa en
“Configure”:
Unidad 1. Introducción 13

La primera configuración importante es utilizar el SDK Manager para descargar los


componentes que nos van a hacer falta para poder compilar los proyectos, probarlos y
depurarlos. Pulsa en SDK Manager y verás la siguiente pantalla:

*
£2 Android SDK Manager

Packages Tools
SDK Path: D: Android Studio Tools'-sdk

Packages

s§|* Name API Rev, Status


D l Tool!
p y * ' Android SDK Tools 23.02 Hf Installed
O Android SDK Platform-tools 20 US' Installed J
¡3 Android SDK Build-tools 20 ¡¡I Installed
Android SDK Build-tools 19.1 H Not installed
p! ^ Android SDK Build-tools 19.0.3 j I Not installed
[fj ^ ' Android SDK Build-tools 19,0.2 i Not installed
p V ' Android SDK Build-tools 19.01 \ Not installed
13 n Android SDK Build-tools 19 \ j Not installed
(fj Android SDK Build-tools m il f 1 Not installed
F~~ ^ Android SDK Build-tools m i [ j Not installed
IF Android SDK Build-tools 18.01 i...1 Not installed
j n -4 Android SDK Build-tools 17 [~~1 Not insta lled
* 1m £ 2 Android L (API 20, L preview)

Show: E Updates/New fE3 Installed p Obsolete Select New or Updates


c Install 9 packages... 3
Sort by: ® API level ) Repository Deselect All
* c Delete packages...
J
O
Done loading packages.

Si desplazas la lista de paquetes verás que hay paquetes instalados y otros que
necesitamos instalar. Verás que hay una serie de paquetes que pide instalar (en este
caso 9). Desplaza la lista hasta el final y en el paquete de Extras, selecciona “Intel x86
emulator accelerator (HAMX installer) y verás que ahora hay 10 paquetes a instalar:

Q Android SDK Manager

Packages Tools
SDK Path: DAAndroid Studio Tools'\sdfc

Packages

j -S: Name API Reve Status -


4 0 ! _1 Extras
FI ÉS Android Support Repository 6 Installed
E n Android Support Library 20 ;P j Not installed
F ] C Google Play services for Fit Preview 1 ; ] Not installed
F j B Google Play services for Froyo 12 □ Not installed
j n n Googie Play services 19 [“1 Not installed
F j Q Googie Repository 11 Pj; Not installed
O O Googie Play APK Expansion Library 3 Not installed
O ¿ 5 Googie Play Billing Library 5 I i Not installed
D Q Googie Play Licensing Library 2 □ Not installed
fyl B Googie USB Driver 10 n Not installed
F B Googie Web Driver 2 Not installed
V. B ■*&>Emulator Accelerator (HAXM installer) 4 P j Not installed J:

Show: VJ Updates/New E Installed £3 Obsolete Select New or Updates I Install 10 packages...

Sort by: ® API level Repository Deselect All Delete packages.

Done loading packages.


- O «
14 Programación en Android

Este paso de instalar los paquetes del SDK es muy importante, si no lo haces, cuando
ejecutes tu primer proyecto puedes encontrarte con un molesto mensaje de “No system
images installed for this target”, queriendo decir que no has instalado la imagen del
sistema operativo que tendrán tus dispositivos Android emulados.
¿Qué es una “System image” o imagen de sistema?
Aparte del sistema operativo que contiene tu dispositivo, contiene iconos, apps,
sonidos, configuraciones, ficheros, propiedades y un montón de características más. Por
eso es necesario descargarlas antes de poder crear el emulador de móvil/tablet con el que
vas a trabajar.
En cuanto al último paquete, se tra ta de un acelerador para que el emulador vaya
ligeramente más rápido. Si buscas en google “My Android emulator is too slow” verás
que es un problema común y recurrente entre los programadores que se inician con
Android.
Pues todo listo entonces, pulsa el botón “Install 10 packages”, lee y acepta la licencia
y espera un rato... Otro café es demasiado, pero estaría bien cualquier otra actividad que
puedas realizar ahora, como por ejemplo, registrarte en stackoverflow.com o github.com
y explorar las posibilidades de estas dos páginas webs para desarrolladores.

f j Android SDK Manager I El ip á w i


Packages Tools
SDK Path: D: .Android Studio Tools' sdk

Packages

Name API Rev. Status


ffl ü A n d r o id TV ¡ntei x S ñ A to m S y s te m Im a g e 20 1 N o t in s ta lle d
W \ ÍU A R M EABI v7a S y s te m Im a g e 20 1 F I N o t in s ta lle d
E S I JtoteZx3 6 A to m S y s te m Im a g e 20 1 f j N o t in s ta lle d
Q C Android 4.4W (API20)
jPJ ijji SDK Platform 20 1 (¡R- Installed
|Q ¿ S a m p le s fo r SD K 20 2 N o t in sta lle d
g ] H A n d r o id W e a r A R M EABI v7 a S y s te m Im a g e 20 1 N o t in sta lle d
g ] ü A n d r o id W e a r In te l xB6 A to m S y s te m Im a g e 20 1 P j N o t in sta lle d
¡ 3 ITT Sources fo r A n d r o id SD K 20 1 N o t in sta lle d
[FjCZ Android 4.4.2 (API 19)
□ C2 Android 4.3 (API 18)
[ Q I S Android 4.2.2 (API 17)
D C * Android 4.12 (API 16)
F lP g , Android 4.0,3 (API 15)

Show S I Updates/New S Installed EH Obsolete Select New or Updates Install 10 packages..

Sort by: t jAWIarej Repository Deselect All Delete packages.,

©
Downloading Android Wear ARM EABIv7a System Image, Android API 20, revision 1 (61%, 462 KiB/s, 88 seconds left)

Como hay referencias entre paquetes, al terminar de instalar algunos paquetes, te


pedirá instalar otros cuantos paquetes más. Instala hasta que no te pida más y ya has
terminado (por ahora).
Unidad 1. Introducción 15

Asegúrate de que todo lo que has marcado está instalado:

------------------------------------------------------------------------ ----------
* j Android SDK Manager 1o . a M 'i .

Packages Tools
; SDK Path: D:\Android Studio TooLv.sdk
Packages

<fl¡' Name API Rev. Status . a


D , Extras
O Ü Android Support Repository
3 Android Support Library
6 1 pjf Installed
1
ü O Google Ploy services for fit Preview I Not installed
H Ó Google Play sen-ices for froyo 12 Not installed
P I H Google Play services 19 Not installed
¡P Ü Google Repository 11 Not installed
LJ Ü Google Play APK Expansion Library 3 Not installed
Q B Google Play Billing Library 5 .. Not installed
L j O Google Play Licensing Library 2 Not installed
O ÜS Soogie USB Driver 10 ¡j L Installed [
2 instance li
I j Q Google Web Driver
LJ £3 b»tel xB6 Emulator Accelerator (HAXM installer) 4 | ‘^R?TTs?allle3l,l,l|
a
Show. E l Updates/New i?j Installed Q Obsolete Select New or Updates Install packages...

Sort by: ® API level . Repository Deselect All Delete packages...

\
Done loading p ackages^^^

Si te aparece el mensaje “Done loading packages” bien, si no, tendrás que volver a
intentarlo.

1.8. N U E ST R O P R IM E R PR O Y EC T O
Para calentar y conocer el entorno un poco más, vamos a realizar nuestro primer
proyecto, para ello, en la pantalla de bienvenida de Android Studio, pulsamos en “New
Project”:

A continuación, ponemos el nombre de la aplicación (incluyendo el dominio de la


compañía) y la ubicación donde irá almacenado en nuestro disco duro:
16 Programación en Android

New Project

Configure your new project

Application name: Mi primera App

Company Domain: ilm .m isprcyectcs|ccm

Package name: co m .m s p ro y e c tc s .iim .m i prim era app

Project location: ¡0\Users\ILM \A ndroid5tudioProjects\M iprim eraApp2

Previous Cancel

Pulsamos en “Next” y seleccionamos el tipo de plataforma para los que será


compilada nuestra aplicación. Hay que escoger el mínimo SDK con el que funcionará
nuestra app.

S e le c t th e fo rm facto rs y o u r a p p w ill ru n on

Different platforms require separate SDKs

0 Phone and Tablet

Minimum SDK API 16: Android 41 (Jelly Bean)

Lower API levels target more device, but have fewer feature available. By targeting API 16 and later, your app will
run on approximately 78,3% of the device that are active on the Google Play Store. Help me choose.
□ TV

Minimum SDK

0 Wear

Minimum SDK

Q Glass (Not Installed)

Minimum SDK

Previous
Unidad 1. Introducción 17

Esto dependerá de los requisitos de nuestra aplicación, pero para empezar, puedes
escoger una que es bastante compatible con todos los móviles y Tablets que hay, por
ejemplo, la API 16 (Jelly Bean):

Pulsas en next, y verás que aparece otra pantalla pidiendo que selecciones el tipo de
actividad. Aquí nos detenemos un momento, debemos primero saber qué es una
actividad:
Una actividad es nuestro programa en sí mismo, contiene la interfaz de usuario de
nuestra App, pero vamos a investigar un poco más sobre el concepto de actividad:

Add an activity to Mobile

Add No Activity

Blank Activity Blank Activity with Fragment

WL
t
Fullscreen Activity Google Maps Activity Google Play Services Activity

£revious Cancel

Cuando no sabemos o no conocemos un concepto (y con la nueva tecnología es


totalmente normal que nos bombardeen constantemente con nueva terminología),
debemos buscar en una fuente fiable la definición de ese concepto que no sabemos. Por
ejemplo, si buscamos en la documentación de Android el concepto de actividad, en
inglés, activity, obtenemos:
18 Programación en Android

An activity is a single, focused thing that the user can do. Almost all activities
interact with the user, so the Activity class takes care of creating a window for you in
which you can place your UI with setContentView(View).
Traducido vagamente sería algo así como “Una cosa simple y concreta que el usuario
puede hacer y que contiene la interfaz de usuario”. Dicho de otro modo, y teniendo en
cuenta la definición de Activity, y puesto que los móviles están preparados para ejecutar
muchas apps pequeñas a la vez, se puede afirmar que una actividad es un programa
pequeño y ligero, controlado por Android y sometido a las normas de funcionamiento de
Android. De esta manera, evitaremos convertir el dispositivo móvil en un ordenador
común.

De momento, escogeremos una actividad en blanco “Blank Activity” y, aunque hay


varios tipos, de momento trabajaremos con actividades en blanco. Pulsa en Next y elige
el nombre de tu actividad y pulsa en Finish:

Comenzará el proceso de creación del proyecto y configuración del entorno para que
comiences a programar:

Building 'MiprimeraApp' Gradle project info

Gradle: Build
P— --------
[ B B B B B B B B B B B B B B B B B B B B B B S B B B B ^ Cancel

Una vez creado el proyecto, Android Studio nos da la bienvenida amablemente con
consejos sobre cómo utilizarlo:
Unidad 1. Introducción 19

j" _ Did you know.

TODO tool window Sets you preview each of th e encountered TODO item s ■
ju s t click th e preview button on th e toolbar.

TODO: Current File Scope Based Default Changelist


Found 1,829 TODO items in 1,252 files f t B É 3.f e -tP
/ / filew atch
, j¡_ ▼ C5 community (21 items in 17 files)
i f s td a fx .oh
t Cj native <Í3 Hems in11 files)
▼ D fileWatcher (2 items in 2 files) #include st
rzri

0 Show Tips on Startup

Es importante leerse estos consejos porque a la larga facilitan el aprendizaje del uso
de la herramienta y pueden proporcionar trucos para efectuar operaciones que a priori
pueden parecer no triviales.

1.9. P R IM E R C O N T A C TO CO N EL CÓ DIG O.
¿D Ó N D E E ST Á EL JA V A ?
Nada más abrir el primer proyecto, aparece esta pantalla:

X** íi*V*9*« ' i gctecNMÉ¡«SÍ %s |ccfe v£| Hunks* Í)m


a uo - - tt .
earn »■
; ». «. »ctws>.trv.pívm€f«.»<tivai»d>ml «
«*» 9 - t" Q,. ?. NUM4- -

.i
a- e &
.ilwe*ri*í'<Sí*
J t*n*wU^tesa!Plrtivt)
JtjfrltUyevl
Tílsteftsw

1f§KtU$v«Uy«uc

La pantalla aparece dividida en dos partes


El explorador de proyectos, a la izquierda, donde aparecen todas las carpetas
ficheros que componen el proyecto. Presta especial atención a la carpeta si
20 Programación en Android

donde están todos los ficheros que modificarás para dar vida a tu App. Las
otras carpetas, en principio, hasta que no veamos cosas más avanzadas no nos
interesan, pero conviene saber qué contienen:
build: contienen los archivos binarios resultado del proceso de compilación
(tanto generados como intermedios)
libs: Inicialmente vacía, contendrá referencias a librerías de código
programadas por nosotros.
gradle: Gradle es el plugin que utiliza android studio para compilar,
construir y depurar tus programas.
Un fichero importante: El AndroidManifest.xml, que es un fichero XML
que contiene toda la descripción de la aplicación que estamos creando y
qué componentes (servicios, actividades, imágenes, etc.) están incluidas.

• El panel central, con dos pestañas o tabs en la parte inferior, “Design” y


“Text”. Con esta vista tendrás una visión de todos los componentes o Widgets,
que puedes ir insertando para configurar tu interfaz gráfica.
pestañas superiores:
JAVA Y XML

c miPnmeraActividad.java * £ artrv¿ty_mi_primera_actrvidadjtml *

Palette I* Q - Nexus 4 » Q ' i AppTheme miPrimeraArfividad» 0 » 21»

DLiiouU a -s e | - - 1 0 «t
bH FrameLayout
LinearLayout (Horizontal)
i LinearLayout (Vertical)
ÜJ TableLayout
P^TableP.ow
GridLayout
[líj Relat'rveLayout
Widgets
Plain TextView
|Ab¡ Large Text
¡Abj Medium Text
¡Ab| Small Text
Button
Small Button
¿ RadioButton
Q CheckBox
o Switch
— ToggleButton
- ImageButton
ImageView
“• ProgressBar (Large)
" • ProgressBar (Normal)
*** ProgressBar (Small)
*■ ProgressBar (Horizontal)

Design | Text j
1>
pestañas inferiores:
Diseño y Texto

Si pulsas en “Text”, verás que aparece con una ventana con código XML
autogenerado por Android Studio. Sí, has leído bien, XML. La interfaz gráfica
de tu app se puede y se recomienda definir con definiciones en XML. Si te
fijas en los tabs superiores, por un lado, tienes el fichero XML y por otro lado,
Unidad 1. Introducción 21

un fichero Java. Pues en XML se declaran todos los componentes y en el


fichero Java se programan los comportamientos.

c miPrimeraActividad.java * « activity_m¡_pr¡mera_actividad.xml x !
; ..[c R e la tiv e L a y o u t j * l a * : a n d r o i d - " h t t p : //s c h e m a s . a n d rc i ri y- r e ; a n d ro id "
x m ln s: to o ls = " h tt p : //s c h e m a s . a n d r o id . c o m /to o ls 11 android: la y o u tj,r id th = ”match parent"
a n d ro id :la y o u t_ h e ig h t= ”m a tch jp a ren t" a n d r o id :paddxngLe f t = "16dp"
andró i d :paddingR ight="16dp"
a n d r o id :paddingTop="16dp"
android:paddingBottoffl="16dp" *■
t o o l s : c o n t e x t * " .m iPrim eraA ctividad">
A'>WxWvWW..-WvWvVvWvWvV
-ÜXD..

■CTextView a n d r o id :te x t= " H e llo w orld !" an d roid :layou t_w id th =" w rap _con ten t"
android: layout_height="w rap__content" />

/Relative Layout>

A la derecha encuentras la vista previa de cómo quedará tu App en el


dispositivo Android. Si modificas el archivo XML verás cómo cambia. Puedes
experimentar a cambiar alguna cadena de texto para ver cómo cambia el
diseño. Por ejemplo, el fragmento de código:

cTextView

android:text="@string/hello_world"

a n d r o i d :l a y o u t _ w i d t h = "w r a p _ c o n t e n t "

a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "

/>

corresponde a un campo de texto que se


presenta en pantalla y que contiene la cadena de
texto “Helio W orld”. Si pruebas a cambiar la
cadena de caracteres por “Elearning rocks!”,
verás el efecto en el emulador.

Por último, si seleccionas el fichero con extensión Java


“MiPrimeraActivity”, verás código en Java. Éste es el código Java que se
genera automáticamente cuando creas el proyecto y que define el
comportamiento de la actividad:
22 Programación en Android

c miDrimeraActiv¡dad.java x <> art¡vity_mi_pr¡mera_art¡v¡dad.xml x

package ccm.misprcyectcs.ilm.miprimeraapp;

[j|import ,.,

;public class HdPriaera&ctiyidad extends ActicnBsrActivity {

@O v e r r id e
*1 ^ protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView (R. layout.activi ty_mi_priiaera_actividad);
Ó }

i @Override
®T $ public boolean onCreateOptionsMenu(Menu menu) {
/ / I n f l a t e the mena; th is a d d s item s to the a ctio n bar i f i t i s p r e s e n t .
getKenuInflater(). inflate (R.menu.menu_mi_primera_actividad, menu);
return true;
Í }

gOverride
#T É public boolean onQptionsItemSelected(KenuItem item) {
1=) / / Handle a ctio n bar it e m c lic k s h ere. The a ctio n bar w ill
/ / a u to m a tica lly handle c lic k s on the Home/Op b u tto n , so long
É / / as you s p e c if y a parent a c t i v i t y in AndroidMamf e s t . xml.
int id = item, getItemld( ) •

/ / n om spection S im p lifia b lelfS ta te m e n t


if (id = R,id.action_settings) {
return true;
}

También te habrás dado cuenta de que ha desaparecido la pantalla de vista


previa y que solo está disponible cuando modificas el archivo XML. Aunque es
perfectamente posible cambiar la interfaz de usuario a través de código fuente,
solo verás los cambios en la interfaz reflejados en la vista previa cuando
cambies el archivo XML.
Unidad 1. Introducción 23

1.10. PR O G R A M A N D O SIN T IR A R LÍNEAS DE


CÓDIGO
Una de las características del desarrollo de Android es que se puede llegar a diseñar
muchas cosas sin apenas tocar código, de hecho, ya habrás comprobado la cantidad de
código en XML y Java que genera Android Studio con tan solo trastear un poco con los
menús.
Para ilustrarlo, haz la siguiente prueba: v MíPrimeraActivity.java * « act¡vity_mi_pnmera.xml >

Palrtte I*" ; Q ,» £ Nexus 4^ O * Cl-AppTheme MrPrimera


O wwgets
En vista diseño, arrastra un widget de tipo jA§í Plain TextView S ESI* I
¡A§ Large Text
botón a la pantalla del móvil que muestra la vista [tó Medium Ted
jAbiSmall Ted
previa y sitúalo justo debajo del TextView que •:l- Button
s* Small Button
viene por defecto. i,§0 RadioButton
fvjCheckBox
3® Switch

Si cambias a la vista de texto “Text”, verás que m. ToggleButton


Si ImageButton
se ha añadido en XML el siguiente código: 2 ImageVsew
*■* ProgressBar (Large)
ProgressBar (Normal)
ProgressBar (Small)
“• ProgressBar (Horizontal)
•©• SeekBar
<Button

a n d r o i d :l a y o u t _ w i d t h = "w r a p _ c o n t e n t "
a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d r o i d : t e x t = " N e w Button"
android:id="@+id/button"
a n d r o i d :1 a y o u t _ b e 1 o w = "@+ i d / 1 e x t V i e w "
a n d r o i d :l a y o u t _ a l i g n P a r e n t L e f t = "t r u e "
android:layout_alignParentStart="true" />

Para cambiar el texto del botón puedes hacerlo editando el código pulsando dos veces
sobre el propio botón y abriendo las propiedades:

Puedes ponerle en el campo text una cadena de caracteres con el nuevo texto del
botón y el id lo usarás para referenciarlo después desde el código. Puedes ponerle, por
ejemplo, “Pulsa aquí”.
24 Programación en Android

1.11. IN T R O D U C IE N D O U N POCO DE
CÓDIGO
Vamos a darle un poco de funcionalidad a nuestro primer proyecto. ¿Adivinas cuál?
Pues claro, vamos a hacer que cuando pulsemos el botón, cambie el texto del TextView.
Para empezar, ¿Te acuerdas de lo que era un EventListener o un ActionListener?
Efectivamente, eran métodos que había que implementar para responder a los eventos
que “escuchábamos” y así dar una funcionalidad a un componente. Eso sí, previamente
había que registrarlo para que cuando el componente causara el evento, el programa
ejecutara el método que responde al evento del componente. Por ejemplo, si te acuerdas
de la asignatura de programación de primero, con los componentes de Swing, si
queremos responder al evento acción de un botón debemos primero crear una clase (o
aprovechar la que estuviéramos codificando) que implementara la interfaz
ActionListener. Esta implementación nos obligaba a codificar un método llamado
actionPerformed que respondía a la acción de pulsar en el botón (en inglés esto se llama
método o función Callback). Y al componente había que registrarle el objeto que
implementa ActionListener mediante el método addActionListener. Pues en Android, no
es muy distinto, hay que implementar la interfaz OnClickListener, registrar el objeto
que implementa la interfaz mediante el método setOnClickListener y programar un
método llamado OnClick que hace de Callback.
Lo primero de todo que debes saber es que para poder referencia en tu código a las
clases de los componentes que has incluido en el XML y que van a ser parte de tu
interfaz de usuario, debes importar las clases. Dentro del paquete Android, subpaquete
widget tienes las clases TextView y Button, que son las dos que has agregado en tu
primer proyecto.
i m p o r t a n d r o i d . w i d g e t .TextView;
i m p o r t a n d r o i d .w i d g e t .B u t t o n ;

¿Cómo referencio en mi código Java los componentes que he agregado mediante el


código XML de la actividad?
Muy fácil, solo tienes que creqr una referenr-ia al obieto de la clase que quieras^-nor
ejemplo botón (Button) y llamar a la función findViewByldí ... )

B utton miBoton;
m i B o t o n = ( B u t t o n ) f i n d V i e w B y l d (R.i d . b u t t o n ) ;

A partir de aquí puedo acceder a un sinfín de propiedades y métodos para programar


mi botón como me apetezca.
Lo siguiente es saber dónde ubicar mi código. Si buscas una función main, que sepas
que no la vas a encontrar. De hecho, no solo no existe como tal, sino que cada actividad
tiene un ciclo de vida, que va sucediendo llamadas a funciones callback según la
Unidad 1. Introducción 25

actividad experimente una interacción por parte del usuario, por ejemplo, arrancar una
actividad, abandonar una actividad, retomar una actividad. A continuación puedes ver
el gráfico extraído de la página de desarrolladores de Android, que ilustra perfectamente
el ciclo de vida de una actividad y la transición de llamadas a funciones callback según
van pasando por diferentes estados:

Resum ed
(visible)

onResumeQ ~ T onPauseO
onResumeQ

r Started Paused
(visible) (partially visible)

onStartQ T
ortStartQ
onSwpO

S topped
Created -onRestartQ-
(nidaen)
onDestroyQ

D estroyed

Figure 1. A simplified illustration of the Activity lifecycle, expressed as a step pyramid. This shows how, for every
callback used to take the activity a step toward the Resumed state at the top, there's a callback method that takes the
activity a step down. The activity can also return to the resumed state from the Paused and Stopped state.

Imagen: Ciclo de vida de una actividad de developer.android.com

De esta manera, con la arquitectura de actividad de Android aseguramos que nuestra


aplicación será una app adaptada a un dispositivo móvil y no un programa típico para
un ordenador de tipo Desktop, es decir, evitamos:

• Que la actividad se bloquee o deje de funcionar cuando el usuario recibe una


llamada o cambia a otra app mientras está usando la tuya.
• No consume recursos valiosos del sistema cuando el usuario no está usándola
activamente.
• No se pierde el progreso del usuario si abandonan la app y luego vuelve a ella.
• No se bloquea cuando el usuario, por ejemplo, cambia la posición de la pantalla
de vertical a horizontal.
• Etc.

No es necesario implementar todas las funciones callback, aunque conforme tus apps
sean más completas y más complejas, seguro que acabas peleándote con todas y cada
una de ellas.
26 Programación en Android

De momento nos centraremos en la primera acción que el ciclo de vida ejecuta


cuando el sistema operativo arranca la App que estás desarrollando. Esta función
callback es “onCreate” , y si, si quieres puedes pensar en ella como en la función main,
pero teniendo en cuenta las diferencias técnicas.

Pues manos a la obra, en primer lugar has de añadir la implementación de la clase


View.OnClickListener, después añadir el código para acceder a los widgets (button y
text View) que has agregado en el fichero XML y finalmente conseguir acceso a ellos.
Después añade en el código de la función onCreate el código para poder referenciar a los
componentes textView y button, y registra el listener “OnClick”. A continuación te
señalamos las líneas de código que hemos añadido a “MiPrimeraActivity.java”:

public class MiPrim e r a A c t i v i t y extends Action B a r A c t i v i t y implements


View.OnClickListener{

Button miBoton; //referencias a los widg e t s añadidos


TextView miTexto;

@Override
protected void onCreate(Bundle savedlnstanceState) {
s u p e r .o n C r e a t e ( s a v e d l n s t a n c e S t a t e ) ;
s e t C o n t e n t V i e w ( R .l a y o u t .a c t i v i t y _ m i _ p r i m e r a ) ;

miBoton=(Button)findViewByld(R.id.button);
miBoton.setOnClickListener(this);

}
p u b l i c v o i d o n C l i c k ( V i e w view) {
/ / r e s p o n d e al e v e n t o C l i c k
m i T e x t o = ( T e x t V i e w ) f i n d V i e w B y l d ( R .i d .t e x t V i e w ) ;
m i T e x t o .s e t T e x t (" p u l s a d o " );
}
}

Y a compilar...

A nalyte Refactor Run T ools VCS Wine

Cbi+F9
Make M odule 'app'

Rebuild Project
Clean Project
wdrcidStudioPrcjec
Generate Signed APK...
Unidad 1. Introducción 27

Si tuvieras errores saldría algo del estilo:

Messages Gradle Build

jy„ :app:generateDebugSources UP-TO-DATE


JfL ¡appxompileDebugJava
X ‘ 1 G\Users\ILM\AndroidStud¡oPro¡ects'vMiprimeraApp\app\src\.main
t i error: cannot find sym bol class kick
Execution failed fortask‘:app:compileDebugJava',
* E > Compilation failed; seethe compiler error output for details.
U T, 0 BUILD FAILED
? 0 Total time: 8.576 secs
© 2 errors

Y si has escrito exactamente lo que te hemos propuesto no tendrás errores, podrás


ejecutar tranquilamente
Ejecutamos...
Refactor Build Tools VCS Window Help
Mayus+FM
Í : 4* Er *1‘ ^
.. . Ufe Debug 'app' Mayús+F9
m .: java Er :‘r
► Run.., Alt+Mayús+FlO -
C
: Debug... Alt+Mayús+F9
jdioPrcjecU
1 Edit Configurations...
Stop Ctrl-#- F2

Rplnar) iChannpH CIjkjgpc

Y... ¡anda! ¡No tenemos ejecutando el emulador! No hay problema...

* Choose Device -
1 . 1 = ^

O Choose a running device

Device il • -_ ^ - tu -_

(•) paunch emulator

Android virtual device: | .[none]


m 3
1 1 Use same device for future launches

U ! Cancel
..
28 Programación en Android

Es hora de crear el emulador, es decir, el teléfono virtual donde poder ejecutar y


depurar nuestros programas. Para crearlo, sacamos el Android Virtual Device Manager
(AVD Manager):

Refactor Build Window Help


Jasks & Contexts
St ❖ 4 »8 [ ¥ ( S. D w ?
Save File as Template...
ain java É3 co m , raapp c MiPrim eraActivity
Generate JavaDcc...
1*- c MiP ni_primera.xml x
Save Project as Template...
udioProjectsMv
Éimf Manage Project Templates...

O Groovy Console..,
p u l #» Navigation Editor

Google Cloud Tools ^ + Sync Project with Gradie Files.

S Open Terminal.,. Android Device Monitor


*í 0 4. \ f w W U rU VV X U w U I< U •,LA

super.onCreate(savedln
q SDK Manager

/■■S8ÉÍM&.3MMÍÉ9;. Pci r V Enable ÁDB Integration

El AVD Manager es muy sencillo de usar:

* AVD Manager

E ] $
Virtual devices allow you to test your application without having to
own the physical devices.

Create a virtual device

To prioritize which devices to test your application on, visit the


Android Dashboards, where you can get up-to-date information on
which devices are active in the Android and Google Play ecosystem.

m m Cancel
Unidad 1. Introducción 29

Podemos crear tantos dispositivos virtuales como queramos, de momento solo nos
hace falta uno:

[3 ] Nexus One
Category Name * Size Resolution Density
I Phone | Nexus S 4.0* 480x800 hdpi

Tablet Nexus One (Edited) 3,7' 480x£00 hdpi


Sue normal
Ratio, long
Wear | Nexus One 3,r I 430x800 hdpi Density hdpi

j TV Nexus 6 5.96' 1440x2560 560dpi

Nexus 5 4S5- 1050x1920 xxhdpi

Nexus 4 4.7“ 768x1280 xhdpi

MiAndroid 5,0- 1080x1920 xxhdpi

Galaxy Nexus 4.65' 720x1280 xhdpi

Android Wear Square 1.65' 280x280 hdpi

New Hardware Profile Import Hardware Profiles


E i

• ................... ......................- l i e ^ W B M ^ IIIS ifc lW IB tr • ii m m - - ~


. A .

S y s te m Im a g e

Select a system image

Reiease Name API Level * A3! Target


KitKat Wear L armeabi-v7a Android L (Preview)
B «U < W =. i l m Android I (Preview)
Lollipop 21 armeabi-vTa Android 5X11
lollipop 21 xS5 Android 5XL1
LoKpop 21 x86_64 Android 5.0.1
Lollipop 21 armeabi-v7a Google APIs (Google Inc.) • google.apis [( #
Lollipop 21 xS6 Google APIs (Google Inc.) - google.apis [C :
Android Open Source
Lollipop 21 x86_64 Google APIs (Google Inc.) - google.apis [( j
Project
KitKat 19 armeabi-v7a Android 44 3
KitKat Android 4.422 Syitem Image
19 xS6
x86
KitKat 19 armeabi-v7e Google APIs (Google Inc.)
KitKat 19 xS£ Google APIs (x86 System Image) (Google I
j .telly Bean Download IS armeabi-¥7s Andmirf SDK Platform 43

3 Show downloadable system images [a ~ l

En el pantallazo verás que hemos creado, por ejemplo, un dispositivo virtual basado
en un teléfono Nexus One, con pantalla pequeña, con Android L (ten cuidado, no escojas
las acabadas en W, que son de Android Wear y funcionan de manera diferente). La
CPU es Intel Atom (x86). Si tu procesador es Intel, asegúrate de seleccionar esta opción
si no quieres que tu emulador sea una tortuga virtual. Obviamente, esto solo lo tienes
que hacerlo una vez, por cierto, el emulador es lento y consume muchos recursos,
advertido quedas. Si utilizas para ayudar al emulador la GPU del ordenador mejor.
30 Programación en Android

• Virtual Device Configuration e. i, 4 ^ a i

iilPlIiff
Android Virtual Device (AVD)
'-i ti ■ si

VerifyConfiguration fl - '*

AVD Name MiAndroid

Li"J Nexus One 3,7” 480x800 hdpi Change.,, j

A KitKat Wear Android L (Preview) x86 Change...

Startup size
and
orientation
Scale 1 Nothing Selected

Emulated 0 Use Host GPU


Performance
□ Store a snapshot for faster startup

You can either use Host GPU or Snapshots

Show Advanced Settings

.Previous Cancel

Pulsa el botón “Finish” para finalizar, y tendrás disponible tu emulador.

1.12, P R O B A N D O , P R O B A N D O ...
Ahora sí, compilado el código y creado el emulador, volvemos a lanzar la ejecución de
la app y esta vez, podemos seleccionar el dispositivo creado.

|. 07 I
* Choose Device

O Choose a running device

De, cr ‘ Ser
di t i t ‘

'U U u rg ic -;h «

( • ) Launch emulator

Android virtual device: miAndroide API 21 an


1 1 Use same device for future launches

a S 3 B Cincel
Unidad 1. Introducción 31

Tardará un poco en arrancar, pero una vez arrancado no es necesario arrancarlo de


nuevo entre ejecución y ejecución de tu app.

Pulsa aquí

Después, al pulsar, ocurrirá lo que le hemos programado:

Pulsa aquí
32 Programación en Android

1.13. E N T E N D IE N D O U N PO CO M ÁS EL
CÓDIGO
Varios aspectos fundamentales deben quedarte claro desde este ejemplo:

A: La necesidad de conseguir una referencia a los widget de la interfaz de usuario.

Button miBoton;
m i B o t o n = ( B u t t o n ) findViewByld (R.i d . b u t t o n ) ;

La primera instrucción declara la referencia, la segunda, consigue el acceso al widget


y a partir de ahí, ya podemos operar con él. Ten en cuenta que este código debe ser
situado después de la instrucción set Content ViewfR.layout, activity mi primera): Si no
lo haces, el resultado de la llamada a findViewByld() será nulo y no podrás acceder al
widget.

B: La necesidad de implementar la interfaz para responder mediante callback al evento


del clic:
public class MiPrimeraActivity extends ActionBarActivity
implements View.OnClickListener{

C: El registro de la función callback:

miBoton.setOnClickListener(this);

esta es la referencia al objeto creado de la clase actual, que como implementa la función
de callback OnClick, pues se puede pasar como parámetro.

D: La programación de la función OnClick():

p u b l i c v o i d o n C l i c k ( V i e w view) {
m i T e x t o = ( T e x t V i e w ) f i n d V i e w B y l d ( R .i d .t e x t v i e w ) ;
m i T e x t o .s e t T e x t (" p u l s a d o " );
}

Consistente en obtener la referencia al objeto de texto y establecer el valor pulsado


(método setText)
Unidad 1. Introducción 33

1.14. LOS EM U LA D O R ES
Habrás visto que el emulador que trae Android Studio es muy lento. Si tienes un
procesador Intel, la instalación por defecto de Android Studio te habrá instalado un
acelerador de hardware. Puedes comprobar que está instalado desde el SDK Manager:

Ptckages Tool*
SDKPíth: D:\imdroid.5di:
Packages
i Name Rev' Status
. GoogltAPls 2 No?installed
• O. OE*t*AndfOíd
« Support Repository
11 m Update available rev. 12
J £3 Android Support Library 21.0.3 I* Update available: rev. 22
m O Gcogle Play services 22 Ü. Update available rev. 23
1. j
O Googíe Repository 16 Nat installed
r]¿3 Google Play APK Expansion Library 3 Not instaliec
ClH Google Play Biffing Library 5 t ~ Not instated
B Google Ploy licensing library 2 Nat installed
0 3 Android Avto API Simulators 1 [3 Not installed
BÉá Google USB Driver 11 installed
n &W
K‘MSn.y 2 Not installed
V- Intel Emulator Accelerator (HAXM installer) 5.1 i Update available: rev. 53

Show: •<£ Updates/N«w J] Installed Select tie* e install 24 packages..

Obsolete Deselect Ail Delee 11 packages...

Dene loading packages.

Desafortunadamente, esta utilidad solo funcionará para aquellos privilegiados que


dispongan de un procesador Intel y con tecnología de virtualización. Para el resto de los
mortales, nuestros emuladores llevarán una imagen arm que defraudará bastante en el
tem a de velocidad.
O tra opción muy extendida para aquellos con problemas de recursos a la hora de
ejecutar un emulador es la de una empresa llamada Genymotion, que proporciona un
emulador muy potente y rápido. Tiene una versión gratis, funciona a través de máquinas
virtuales Virtual Box, y aunque exige registro, es muy completa:
34 Programación en Android

PRÁCTICA 1. LA CALCULADORA DE NÚMEROS


PRIMOS
Crea una nueva App con Android Studio para calcular el enésimo número primo. El
programa tendrá la siguiente interfaz:

▼■¿57 Funcionará de la siguiente forma:


PrimersTarea

Calculadora de números Cuando se introduzca un número N en el campo posición y el


primos:
Introduce qué número primo usuario pulse en calcular, el programa mostrará en un campo
quieres calcular. Por ejemplo, si
d eseas saber el sexto número
primo, pon el número 6 y dale a
de texto, a la derecha del botón, el enésimo número primero.
calcular. Te dirá que el sexto
número primo es el 11

<1 O □

Ejemplos de ejecución:

• Si el usuario introduce el número 1, la aplicación mostrará “el primo número 1


es el 1”.
• Si el usuario introduce el número 2, la aplicación mostrará “el primo número 2
es el 2”.
• Si el usuario introduce el número 6, la aplicación mostrará “el primo número 6
es el 11”
• Si el usuario introduce el número 888, la aplicación mostrará:

33 '• a SO * *£■^ ..????


Q P rim e » a T a i e a

Calculadora de números
primos:
In tro d u ce q u é n ú m e ro p rim o
q u ie re s c alcu la r. P o r e je m p lo , si
d e s e a s s a b e r el s e x to n ú m e ro
prim o, p o n el n ú m e ro 6 y d a le a
c a lc u la r Te d irá q u e el s e x to
n ú m e ro p rim o e s el 11

Calcular'
O pom o num ero 888 e e el 6899
Unidad 1. Introducción 35

R eq uisitos:
* Recuerda que la tarea está puesta para que empieces a practicar desarrollando
aplicaciones profesionales, y que por tanto, debes ser lo más profesional posible desde el
principio, esto incluye tener buenas prácticas de programación:

• Cada cadena de caracteres que uses, insértala como recurso en el fichero


strings.xml y luego referénciala por su id @string/cadena
• Cambia el icono de la App, no dejes el que viene por defecto, y para todas las
resoluciones de pantalla (hdpi, mdpi, xhdpi, xxhdpi). Lo puedes hacer
trasteando en la carpeta res.
• Valida el campo posición con un número entero p o s itiv o =0, por ejemplo de
hasta 6 dígitos.
• Los números primos son muy “caros” de calcular en términos de procesador,
por eso, guárdate los números primos que ya hayas calculado. Puedes utilizar
un array por ejemplo.
• No apliques toda la lógica del cálculo en la misma clase Activity, crea una
clase que tenga la responsabilidad de calcular los números primos.

C riterios d e corrección:
• La aplicación funciona en el emulador sin errores de compilación ni de
construcción y la interfaz de usuario está construida como en la imagen del
ejemplo. (3 puntos)
• La aplicación calcula correctamente el número primo en orden. (2 puntos)
• La aplicación valida correctamente el campo posición (tanto cuando tiene un
valor, como cuando no lo tiene). (2 puntos)
• La aplicación optimiza el cálculo almacenando los números primos ya
calculados (Criba de Eratóstenes). (2 puntos)
• La lógica del cálculo está separada en otra clase. (1 punto)
• TOTAL 10 puntos

C onsejos:
• Esta primera práctica es tan solo una toma de contacto con el entorno, por
tanto, tiene más de programación en Java pura y dura que de pelearse con
Android. Así que te recomendamos:
• No agaches la cabeza y empieces a programar sin antes pensar en todas las
implicaciones: siéntate lejos del ordenador antes y valora toda la estrategia que
vas a seguir.
36 Programación en Android

• Plantea un esquema de las clases y recursos que vas a utilizar y escribe en


papel un pseudocódigo con los algoritmos que creas que te van a dar
quebraderos de cabeza (por ejemplo, para calcular los números primos).
• Después, puedes comenzar a diseñar la IU de tu App con Android Studio.
• Hacer todo el código de una vez y luego probar no es buena idea: haz pruebas
cada cierto tiempo para asegurarte de que todo lo que vas codificando es útil y
funciona. (Cada 10 líneas o así).
• Antes de liarte a programar con algo sobre lo que tienes dudas, escribe en el
foro tus dudas. Quizá el difícil problema que buscas resolver no lo es tanto.
• Ten en cuenta que el acelerador de Hardware para el emulador solo te
funcionará si tu procesador es Intel, de otra manera, tendrás que tener
instalada una imagen ARM del sistema operativo en el SDK.
UNIDAD 2

DESARROLLO DE APLICACIONES
PARA MÓVILES

r CONTENIDOS
2.1 Arquitectura de Android
2.2 Profundizando en el desarrollo: tipos de widgets para Android
2.3 Internalización de aplicaciones mediante el archivo string.xml
2.4 Layouts y contenedores
2.5 Los diálogos y los fragmentos
2.6 Los widgets de selección
2.7 Construcción de menús
2.8. El ActionBar
2.9. Otros widgets para tu IU
2.10 Más funciones de callback
38 Programación en Android

2.1. A R Q U IT E C T U R A DE A N D R O ID
En el caso práctico de la primera unidad de trabajo creaste tu primera actividad.
Esta actividad la definíamos como una tarea simple que el dispositivo móvil realiza.
Además de actividades hay otros tres tipos de componentes que pueden formar parte de
tu App.
Todos estos componentes se programan en Java con las siguientes reglas:
• El Sistema Operativo Android es un sistema multiusuario en el que cada App
es un usuario diferente.
• Por defecto, el sistema asigna un identificador a cada usuario (User ID) que es
solo conocido por la aplicación y el sistema operativo. El sistema establece
permisos para todos los ficheros de la App de tal manera que solo la App
puede acceder a ellos.
• Cada proceso tiene su propia Máquina Virtual (VM), de tal manera que el
código de cada aplicación se ejecuta aislado de otras aplicaciones, evitando así
potenciales problemas de seguridad.
Pese a estas restricciones hay formas para intercambiar información entre las
distintas aplicaciones. Además, para que una App pueda acceder a los recursos del
dispositivo, por ejemplo, contactos, cámara, bluetooth, etc. se le deben dar permisos en
el momento de la instalación.

2.1.1. LOS COMPONENTES DE UNA APLICACIÓN


Cada componente de una aplicación es un punto de entrada a través del cual el
sistema Android puede comunicarse con tu App. Cada tipo de componente tiene un
propósito y un ciclo de vida propio que dicta cuando un componente se crea y se
destruye.
Hay cuatro tipos de componentes:

LA S A C T IV ID A D E S : Son las que conoces hasta ahora. Como viste en la Unidad 1,


una actividad representa una pantalla simple con una interfaz de usuario. Por ejemplo,
una App para gestión de pedidos puede tener una actividad para mostrar el listado de
clientes, otra actividad para mostrar un producto determinado y una tercera actividad
para mostrar los datos de un pedido en concreto. Además de ser programas
independientes, cada actividad puede ser ejecutada por otra App (si tu App lo permite).
Por ejemplo, la cámara podría activar la actividad de mostrar un producto determinado
para poder compartir la foto y ponerle foto al producto. Para poder crear una actividad
debes crear una subclase de la clase A c tiv ity . Esta clase Activity tiene su propio ciclo
de vida, es decir, recuerda que no tiene la función “main” típica de Java, sino que se van
invocando en cadena a una serie de métodos. Esta figura que ves a continuación, que ya
Unidad 2. Desarrollo de aplicaciones para móviles 39

has visto con otra forma en la Unidad 1, es la típica que se pone en todas las
bibliografías, pero que ilustra perfectamente el ciclo de vida de la actividad:

The activity is finishing or


being destroyed by the system

on Destroy!)

Activity
shut down

Imagen de developer.android.com

Fíjate en la imagen anterior, y a partir de ahora, verás que la primera función


onCreateQ vamos a introducir bastante código en las próximas secciones.

LOS SE R V IC IO S: Un servicio, en inglés service, es un programa que no tiene interfaz


de usuario y que se ejecuta en background. Por ejemplo, un servicio podría estar
conectándose a la red para obtener los precios de los productos de tu App en tiempo
real. Para poder crear un servicio debes crear una subclase de la clase Service.
40 Programación en Android

LOS P R O V E E D O R E S D E C O N T E N ID O S : Un proveedor de contenido manipula


un conjunto compartido de datos de una aplicación. Gracias a este proveedor de
contenido, una App publica esta información para otras Apps de tal manera, que estas
pueden acceder al contenido e incluso, si tienen permisos, modificarlas. Por ejemplo,
Android tiene un content provider para manejar la información de los contactos del
usuario. De esta manera, nuestra App de gestión de pedidos, podría actualizar la
información de los clientes o de los proveedores directamente en la agenda de contactos.
Para poder crear una ContentProvider debes crear una subclase de la clase
C o n ten tP ro v id er.

R E C E P T O R E S D E B R O A D C A S T : Utilizando la nomenclatura de redes, un


Broadcast Receiver es un programa que recibe un mensaje de multidifusión de alguna
App. Por ejemplo, una cámara que acaba de capturar una foto, una App que publica
que ha cambiado el precio de algún producto, etc.

2.1.2. COMUNICACIÓN ENTRE COMPONENTES


Cuando quieras que tu aplicación haga fotos con la cámara del dispositivo móvil, no
es necesario que programes una aplicación que haga fotos. En su lugar, puedes utilizar
mensajes a Apps ya creadas para que hagan esas cosas por ti. Se tra ta de no reinventar
la rueda una y otra vez, y en este aspecto Android nos proporciona su sistema de
comunicación entre componentes.
Cuando un componente intenta hacer una llamada a otro componente, por ejemplo,
una actividad invoca a otra actividad, se crea un mensaje del componente origen al
componente destino, declarando la intención de solicitar algún servicio. Esta intención,
en inglés, in ten t, es la forma asincrona que tienen los componentes de comunicarse en
tiempo de ejecución. La comunicación a través de intents están en contraposición a la
comunicación a través de las llamadas a las APIs que, pudiendo estas ser síncronas o
asincronas se enlazan en tiempo de compilación.
El intent activa el componente invocado, enlazando los componentes. Para
actividades y servicios, el intent define la acción a realizar. Para los receptores de
broadcast, el intent simplemente define el anuncio a realizar (por ejemplo, baja batería).
El componente content provider, no es activado por intents.

2.1.3. EL FICHERO DE MANIFIESTO (AndroidManifest.xml)


Antes de ejecutar una App, el sistema operativo lee un fichero XML llamado el
Fichero de manifiesto o Android Manifest File que se encuentra ubicado en el directorio
raíz de tu proyecto. En este fichero se declaran todos los componentes que forman parte
de tu App.
Unidad 2. Desarrollo de aplicaciones para móviles 41

Además en este fichero se declaran:


• Los permisos que requiere una App.
• Mínimo nivel de API de Android requerido por tu App.
• Características hardware y software que necesita tu App (p.ej. bluetooth,
cámara).
• Librerías que necesitan ser enlazadas a nuestra App. (p.ej. librerías de google
maps).
• Otras cosas...
El formato del fichero de manifiesto es muy sencillo:

IP Project .7 I_________ O # ■ i*~ c MiPrimeraActivity.java x j « AndroidManifest.xml x f o 2 ctivity_mi_primeraj<ml x


f B MiprimeraApp |c?ml version®”1. 0" encoding®’ut f-8 ?>
► D .¡dea E^Xnanifest xmlns¡android®"http://schemas.android.coas/api/res/android8
▼H»PP pa elage®"com.example.ila .Mgrj^raapp" >
► D build
R <application
D libs
android:allowBackup® "trué"
W D s rc android:icon®" fjdrawable/ic_launcher ”
► t 3 androidTest android: label®"Mi primera App"
V D main android: theme®*istyle/AppTheme" >
▼ (ÜHjava P? <activity
android:name® ".MiPrj^raAct ívxty*
'W S3 com.example.ilm.miprimeraapp
android:label*"Mi primera App" >
§1 © MiPrimeraActivity
£) <intent-filter>
' 1 res <action android:name®"android.intent.action.MAIN" />
<> AndroidManifestJcml
El .gitignore <category android:name®"andróid.intent.category .LAUNCHER" />
m app,¡mi £j </intent-filter>
É1 < /a c tiv ity >
0 build.gradle
él < /application>
0 proguard-rules.pro

Como puedes ver en el ejemplo de la Unidad 1, tu fichero AndroidManifest declara la


actividad que creaste con el asistente de Android Studio mediante el uso de la etiqueta
< activity >.
El atributo androidmame establece el nombre de la atividad y el atributo
androiddabel la etiqueta que se mostrará. Se debe declarar, de esta manera todos los
componentes que vaya a usar tu App:
• < activity> componentes de tipo actividad
• <service> componentes de tipo servicio
• < receiver> componentes de tipo receptores de broadcast
• <provider> componentes de tipo proveedor.

Como hemos visto antes, se pueden utilizar intents para iniciar actividades, servicios
y receptores de broadcast. Aunque es posible iniciar un componente simplemente
escribiendo su nombre y utilizando la clase Intent, si lo declaramos en el archivo de
manifiesto, el sistema podría seleccionar nuestra aplicación como una posible App que
fuera capaz de responder a ese Intent.
42 Programación en Android

2.1.4. LA PILA DE SOFTWARE DE ANDROID


La pila de software en un reflejo gráfico de cómo organiza Android sus diferentes
partes software. La primera capa, o capa de Aplicación, ilustra las Apps que se ejecutan
en el SSOO. La segunda capa, la Application Framework es la que garantiza que las
aplicaciones que se crean para Android siguen el esquema que dicta Android y no otro.
La tercera capa, de librerías, ilustra todos los paquetes de librerías de las que podemos
hacer uso para elaborar nuestra App. La última, muestra lo que es el propio núcleo de
Linux, con sus controladores hardware incluidos.

Applications

H orne D ialer SMS/MMS IM Browser Camera Alarm Calculator

Media
Contacts Voice Dial Email Calendar AJbums Clock
Player

Application Framework
Notification
Activity Manager Window Manager Content Providers View System
Manager

Package Manager Tele ph ony Ma nag er Resource Manager Location Manager XMPP Service

Libraries Android Runtime


----- *
Surface Manager Media Framework SQLrte Core Libraries

Ope nG L|ES FreeType LibWebCore Daivk Virtual Machine

SGL SSL Libe

Linux Kernel
Flash Memory
Display Driver Camera Driver Bluetooth DrK/er Binder (IPC) Driver
Driver

Power
USB Driver Keypad Driver WiFi Driver Audio Drivers
Management

Figura: Android software stack ('https: //source.android.com/devices/tech/seciiritv/index.htmll

2.2 P R O F U N D IZ A N D O EN EL DESARROLLO:
TIPOS DE W ID G ETS P A R A A N D R O ID
En esta sección se presentan los controles más populares en las Apps de Android. Los
controles o widgets que vas a estudiar a continuación tienen muchísimas variantes, así
que ármate de paciencia y procura probar todas y cada una de las características de
cada widget.
Unidad 2. Desarrollo de aplicaciones para móviles 43

2.2.1. LOS CONTROLES DE ENTRADA

En la Unidad 1 ya trabajaste con algunos tipos de controles de entrada, entre ellos


los controles de tipo texto y de botón. Es el momento de profundizar en los controles de
entrada de información para nuestra App.

Button

Text field
te . -i- i» ,» J

¿C O N T R O L O W ID G E T ? Digamos que Control es el término usado en los


comienzos de la programación visual y que heredamos todos aquellos que empezamos
allá por los años 90 con los primeros lenguajes visuales. Se refiere al componente visual
mediante el cual el usuario de alguna manera proporciona información a nuestra App,
ya sea a través de texto, de números, de casillas que se pueden marcar o desmarcar y de
eventos controlados al tocar la pantalla. Android los llama widget, aunque como todo en
informática tiende a tener varios nombres, es válido tanto uno como otro.
Controles Típicos: Aunque hay muchos tipos de widgets, aquí te mostramos los más
típicos:
44 Programación en Android

Tipo de Descripción Clases Imagen


Control
Botón Un botón que puede ser Button
“apretado” por el usuario para
P u lsa aquí
realizar una acción
Texto El texto puede ser no editable TextView,
(etiqueta o TextView*), o editable EditText,
(Campo de Texto o EditText). AutoCompleteT ext
Este último, se puede filtrar tipo y View
longitud del valor que introduce el
| i
usuario
Casilla de Un campo que puede ser CheckBox
verificación encendido o apagado por el
usuario. Se presentan al usuario
como opciones no mutuamente
excluyentes entre sí
Botón de Igual que los checkboxes, pero RadioButton,
Opción redondos y representan opciones RadioGroup o
mutuamente excluyentes
Switches Representan una opción booleana, ToggleButton
con valores de encendido/apagado

Spinners En otros entornos llamados Spinner


“Combo boxes” o listas
Q Selecciona tu idioma
desplegables. Permiten al usuario
seleccionar un valor de entre
varios
Selectores Diálogos de usuario que permiten DatePicker,
al usuario seleccionar un valor TimePicker
(generalmente fecha/hora)
mediante flechas o con gestos con
la mano
Imágenes Se pueden utilizar imágenes para ImageView,
producir acciones al igual que con
los botones
ImageButton
*•5 ^ 1
*TextView se puede configurar para ser editable.

Otros tipos de controles importantes para tu aplicación:

Layouts: Son agrupaciones de controles para organizar los en algún tipo de formación.
Unidad 2. Desarrollo de aplicaciones para móviles 45

T ab u lad ores o Tabs: Son un tipo de contenedores que organizan la información por
diversos criterios.

M en ú s y Subm enús: Permiten seleccionar al usuario varias opciones (agrupadas) para


producir diferentes acciones.

D iálogos d e alertas: Permite al usuario responder a una pregunta espontánea de la


aplicación.

B arras de D esp lazam ien to: Permite al usuario desplazar una parte de la interfaz de
usuario para examinar una parte que no estaba visible.

ACTIVIDAD 2.1. Examina el cuadro “Palette” dentro de Android estudio y localiza los
posibles tipos de controles que has visto hasta ahora:

P ale tte I- E l Text Fields u Date & Time


C j W idgets Plain Text (SlTextClock
lAbl Plain TextView 0 AnalogClock
n E 3 Person Name
[Abl Large Test ESI Dig'rtalCleck
1 D Password
Iflbl Medium Text ^ Chronometer
I J Password (Numeric)
(Ab| Small Text
DatePicker
Button Ó ] E-mail
™ TimePicker
• Small Button il l Phone
CalendarView
• ) RadioButton 1*1 Postal Address
C Expert
0 CheckBox □ Multiline Text
ill Switch
!.J Space
1 Time
s ToggleButtcn RJ CheckedTextView
:Lj Date ■*= QuickContactBadge
Ü ImageButton
M ImageView
I : Number Ia9 ExtractEditT ext
ProgressBar (Large) I J Number (Signed) ■= AutoCompleteTextView
ProgressBar (Normal) I Number (Decimal) “ MultiAutoCpmpleteTextView

2.2.2. LAS API’s de Android


A partir de ahora, todo lo que irás estudiando a continuación, tendrá como base lo
expuesto en el propio manual de referencia para programadores de Android. Como no
puede ser de otra manera, es la fuente oficial, y cualquier otra fuente, incluido este
texto, debe ser secundario. Tu principal guía de referencia, donde viene todo (o al menos
prácticamente todo) documentado, será la documentación de las API que puedes
encontrar en los siguientes enlaces:
46 Programación en Android

Guía de la API de Android h ttp : / /developer.a n d ro id .c o m /e u id e /index, htm l


Tutoriales de entrenamiento h ttp : / /developer.android.com /traininK /index.htm l
Referencias de Paquetes h ttp ://d ev e lo p e r.a n d ro id .c o m /re fe re n ce /p a ck a e es.h tm l

La documentación de las APIs además de ser sencilla, es muy versátil, pudiendo


seleccionar en todo momento la versión de la API que quieras consultar. Por ejemplo,
para ver la documentación sobre los widgets que explicaremos a continuación puedes
entrar en el enlace “android.widget” desde la referencia de los paquetes:
h ttp : / / developer.android.com / reference/android / w idget / package-siim m ary.htm l

$ a
'Ir Developers - Oewgn :a a
Raining APIftádes ■' - too»® 6óo#e Services

*.ÓM ■»API kwell


«táto0 * ‘ls u n lm i 20‘
android text method
android.widget
The wklget pacxspt « mhm» í<f#st* ««**»» üi«feB*cnts t t w e * your scwcn *>u can also design your twwt.

uié Te crean* your own widget, ««end , ¡r« « a sateeHs® IB use p u r séáget in lays*» XMLlh«* are two additional files far yo» to e m it Here is a h a of files yotfS
andf&id view »s>d íq a ta w is implement a custom widget
m&má.'emN accfs&tbárty
♦ java imféementaíion tii« - This t$ she hi# shat It you can msianttafe the object ttsm i*you? Mat, yoi, will atte have»
l^o*d.v«w animation
z M t a cm witcitit that «írteves a i the «arfe»» values Stem the layout X?*t fie
&r«#o¡d¥tevr
amlroMÍWHi Uortsmicc ♦ XML definetse file - m XUL file in w*/vates/ sha? ¡teímes the *Mi element m&i to instantiate your vwdget and the attributes that it supports Other
appfepsiitts wi! use this ¿latent and attributes m tima m «rafter m the* lap e l XML
«fttfewAwidgr» ♦ Layout XML}9pfixa*$‘ an optimal XML file ts&tát res,'layout/ that describes the ¡«put of your widget Voa e&ád also do «asm code m p u t Java fie
saW c byt*co«j*
d&iv$¡ svstem m example at « « m g a «atom layout xml tag. labeiView See the teátowíísg hies Rws demonstrate implementing and using a

interfaces * UbdViewJav» The tmpictttcmtatwi Uk


MwUSirtew .stener * tes/vadues/attivwni Defmrtwnfde
absym'few.OnSa^M J&lisrm * tes/Layout/custom vkw I .wnl Layout hie
#teLiPtView.itey2^LiSfenw

2,2.3. LA CLASE TextView


Es el widget más simple es la etiqueta, representada la clase TextView. Se utiliza para
formar un texto que normalmente sirve para identificar visualmente otro componente
que se sitúa justo al lado. Por ejemplo, el campo Posición en la Activity de la Tarea 1,
tiene al lado una etiqueta con el texto “Posición”:

*~T7~ j
ÍE Q S i£ ÍO n |______ —:rrr—____________ _ _ _l-I’n-r _ _ . .

En Java, se crea una etiqueta con una instancia de la clase TextView, aunque en
Android lo más normal es crearlas a través de los ficheros XML para la disposición
(Layout) de los controles de la actividad, con la propiedad android:text para establecer
el texto de la propia etiqueta.
Unidad 2. Desarrollo de aplicaciones para móviles 47

Hay muchas propiedades de la clase TextView para formatear el texto de la etiqueta.


Por ejemplo:
android: ty p efa ce permite cambiar el tipo de fuente (normal, sans, serif,
monospace) para la etiqueta.
a n d roid rtextS ty le permite seleccionar el estilo de la letra (cursiva, negrita, o cursiva y
negrita).
an d roid :textC o lo r permite seleccionar el color de la etiqueta en formato RGB
hexadecimal.
A n d ro id :tex tS ize permite especificar el tamaño de la fuente, por ejemplo 20dp (20
density-independent pixels)

Veamos un ejemplo de uso de propiedades del campo TextView:

<TextView
android:id="@+id/txtHelloWorld"
android:text="@string/hello_world"
a n d r o i d : t y p e f a c e = "monospace"
a n d r o i d : t e x t s t y l e = " i ta l i c "
android:textColor= "#FF0000"
a n d r o i d : t e x t S i z e = " 2 Odp"
a n d r o i d :l a y o u t _ w i d t h = " w r a p _ c o n t e n t "
a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "

Puedes ver todas las propiedades de la clase TextView y sus características en la API
de Android: http://developer.android.com/reference/android/widget/TextView.html

P regun ta: ¿N o p u ed o cam biar en un T e x tV ie w al tip o d e fu en te q ue yo


quiera? Si, por supuesto, pero tienes que tener en cuenta que las fuentes incluidas por
defecto en Android son Droid Sans (sans), Droid Sans Mono (monospace) y Droid Serif
(serif). Cualquier otra opción debe ser cargada. Por ejemplo, para poder usar una fuente
Arial, debes cargar el fichero arial.ttf en el directorio de Assets (herramientas) y cargarlo
desde código:

T e x t V i e w t x t H e l l o W o r l d = ( T e x t V i e w ) f i n d V i e w B y l d ( R .i d . t x t H e l l o W o r l d ) ;
T y p e f a c e t y p e = T y p e f a c e .c r e a t e F r o m A s s e t ( g e t A s s e t s ( ) ," a r i a l .t t f ");
txtHelloWorld.setTypeface(type);

C uidado: Este tipo de ficheros son muy “pesados” y recuerda que estás programando
un dispositivo con recursos limitados. Además, estas fuentes, en muchos casos, requieren
licencia para su uso.
48 Programación en Android

2.2.4. LA CLASE Button


Ya has trabado en el primer capítulo con la clase Button, curiosamente resulta que la
clase Button es una subclase de la clase TextView, así que las propiedades que vimos en
el punto anterior también son válidas para la clase Button:

cButton
android: 1ayout_w±dth="wrap_content"
__android: layout fteight="yrai: content"
android: tex t= ”Botón Gigante'{
AVvWVvW /AVfy^VW vW I
android:id=® B+id/button?
android:typeface="s e r i f "
an d roid :textsty le = " ita lic "
android:textColor="#FF000Q" I
android: textSize= 80dp"_____I

También has visto que para responder a un evento de tipo Click, hay que
implementar un Listener. Mediante una clase que implemente el tipo de Listener
View.OnClickListener, será posible responder al evento de haber pulsado el botón. A
partir de la versión 1.6 de Android, también es posible declarar la respuesta a este
evento Click, en el fichero XML de la actividad, mediante la propiedad android:onClick.
Se puede indicar el nombre de cualquier método público que reciba un parámetro de
tipo View y que retorne void. Por ejemplo:
Supon que defines un método en tu Activity llamado “MeHicieronClick”:

p u b l i c v o i d M e H i c i e r o n C l i c k (View v ) {
S y s t e m . o u t . p r i n t l n f "DEBUG: M e h i c i e r o n C l i c k ! " ) ;
}

Verás que al escribir en el fichero XML la propiedad OnClick, el propio entorno de


desarrollo te ofrece como posibilidad el método que has creado:

<Button
android: la y out_width="vrrap_content *
android:la y out_height="wrap_cont e n t ”
android:text="Botón Gigante"
android: id ="g+ id /b u tton "
android:ty p e fa c e = " se r if"
android:t e x t s tyle= " i t a l i c "
android:textColor="#FFG000"
android:textSize="20dp"
android :onC lidt="MeHic}' I
android:la y e ir. HeHicíeconClick . c o m . e x a m p l e , il n . te s t!.H y A c tiv ity )
android : layo Press Ctrl+Punto to choose th e selected (or first: suggestion and insert a dot afterwards > >
android:layout_alignParentEnd="true" / >
Unidad 2. Desarrollo de aplicaciones para móviles 49

Una tercera posibilidad para responder al evento de Click, es en lugar de usar la


propiedad del código XML android:onClick, codificar clases Anónimas (anonymous -
inner clases), es decir, en el propio código de establecimiento del Listener OnClick del
botón, crear una clase sin nombre como argumento del método setOnClickListener de la
siguiente forma:

Button btnGigante = (Button) findViewByld(R.id.button);


btnGigante.setOnClickListener(
n e w V i e w . O n C l i c k L i s t e n e r () {
public void onClick(View v ) {
S y s t e m . o u t .p r i n t l n ("Me h i c i e r o n c l i c k ! " ) ;
}
}
);

Fíjate en la definición del listener del código anterior, es la parte donde a través del
operador new, se define un objeto de una clase anónima (sin nombre) derivada de la
clase View.OnClickListener en tiempo de ejecución. Esta clase anónima tan solo tiene un
método llamado onClick que es el que responde al evento.

ACTIVIDAD 2.2.
La documentación sobre cómo gestionar eventos es muy extensa. Te recomendamos que si tienes
dudas y quieres refrescar los contenidos del módulo de programación visites estos enlaces de la
documentación de Java
Cómo gestionar eventos:
h tt p: / / docs.oracle.com /i a vase / tutorial / niswing /events / generalrules.html
Cómo programar escuchadores de eventos:
h ttp ://d o cs.o racle.com /iavase/tutorial/uisw ing/events/

Un botón es totalmente personalizable, por ejemplo, se pueden hacer con él cosas


sencillas como deshabilitar el clic durante un periodo de tiempo (hacerlo no clicable) o
cosas más avanzadas como personalizar la forma del botón para que no rompa el estilo
de la interfaz de tu App. Puedes leer más documentación sobre todas estas acciones en:
http: / /developer .android.com / reference / android /widget /Button, html

ACTIVIDAD 2.3.
Prueba la herramienta online http://angrytools.com/android/button/ para personalizar de forma
sencilla los botones de tu App, de paso, podrás observar cómo cambian las propiedades de los
botones con los diferentes posibles parámetros.
50 Programación en Android

2.2.5. LAS CLASES RadioGroup y RadioButton


En ocasiones habrás visto que ciertas Apps permiten
al usuario la selección de un conjunto de opciones
excluyentes entre sí, es decir, tan solo puedes seleccionar
una de ellas a la vez. Este tipo de acciones se
implementan con el uso de las clases RadioGroup y
I¿Cual es tu equipo de
fútbol favorito?
RadioButton. Por decirlo de forma sencilla, los botones de
tipo radio (RadioButton) se agrupan en un conjunto
* TalaveraC.F.
llamado RadioGroup. De esta manera, solo una opción del Gimas tico de Alcazar

grupo puede estar activada a la vez. Para crearlo, tan solo Albacete Balompié

tienes que añadir desde tu pantalla de diseño de la Otros equipos menores (Atlético de Madrid.
R.Madiid, Barsa)
actividad, un RadioGroup y después arrastrar dentro del
RadioGroup los RadioButton que quieras que se excluyan
mutuamente.

Palette
rn o n e
I*" Los RadioGroup son contenedores, por tanto, los encontrarás en la
j Postal Address carpeta de contenedores, mientras que los RadioButton los tienes justo
Multiline Text
2 Time debajo de los widget de tipo Button.
i Date
Number Puedes probar a crear en modo diseño un RadioGroup para
Number (Signedj
Number (Decimal) seleccionar tus equipos de fútbol favoritos. El código que genera el
Containers
; ^ RadioGroup |
entorno de desarrollo es muy fácil de entender y de reproducir:
ü ListView
[ § GrtdView
=i ExpandableListView

<RadioGroup

<!--Propiedades del radio group-->

<RadioButton

android:text="Talavera C.F."
android:id="@+id/radioButton2"
android:checked="true" />
<RadioButton

android:text="Gimástico de Alcazar"
android:id="@+id/radioButton3"
android:checked="false" />
«RadioButton

android:text="Albacete Balompié"
android:id="@+id/radioButton"
android:checked="false" />
«RadioButton
Unidad 2. Desarrollo de aplicaciones para móviles 51

a n d r o i d : t e x t = " O t r o s e q u i p o s m e n o r e s ( A tl é t i co de
Madrid,R.Madrid, B a r s a ) "
android:id="@+id/radioButton4"
a n d r o i d : c h e c k e d = " f a l s e " />
</RadioGroup>

Habrás visto que el contenedor RadioGroup lleva dentro cada RadioButton. Como
propiedades importantes de los RadioButton destacar, el id, que todo widget debe llevar,
el texto y si está activado o no (checked=”tru e” o checked=”false”)
Desde código Java también podemos establecer estas propiedades, además, podemos
utilizar, entre otros, los siguientes métodos:

Clase Método Acción


RadioGroup void check(int id) Activa el RadioButton con
identificador id
RadioGroup void clearCheck() “Limpia”, es decir, quita la selección
dejando todos los RadioButton
desactivados
RadioGroup int getCheckedRadioButtonId() Retorno el Id del RadioButton
seleccionado
RadioButton void toggleQ Cambia el estado de activación del
RadioButton

E v en to s para R ad ioG rou p y R a d io B u tto n

El Click en un RadioGroup y en un RadioButton, se pueden capturar de forma


idéntica a un botón, además:
Se pueden capturar los eventos de cambio de selección en un RadioGroup mediante el
método:

v o id s e t O n C h e c k e d C h a n g e L is t e n e r ( R a d io G r o u p .O n C h e c k e d C h a n g e L is te n e r l i s t e n e r )

Se hace igual que con el botón desde código, pero no a través de propiedades en el
archivo XML de la actividad.
C A S O P R Á C T IC O : Crea una segunda App llamada “Test2” que dé al usuario 4
opciones para elegir su equipo de fútbol favorito. La aplicación responderá con mensajes
a cada elección del usuario.
Podemos capturar el cambio en la selección por parte del usuario de
la siguiente manera:
¿ C u a l e s tu e q u ip o d e
fú t b o l fa v o r i to ?
V /¿btcrte no «* ti rwmo Oevte out *e h# ¡«a

P a so 1. Im p lem en ta r la in terfaz
R adioG rou p . O n C h eck ed C h an geL isten er
52 Programación en Android

public class MyActi v i t y extends Activity implements


R a d i o G r o u p .O n C h e c k e d C h a n g e L i s t e n e r {

P aso 2. R eg istra r el listen er

protected void onCreate(Bundle savedlnstanceState) {


s u p e r .o n C r e a t e ( s a v e d l n s t a n c e S t a t e );
s e t C o n t e n t V i e w ( R . l a y o u t .a c t i v i t y _ m y ) ;
RadioGroup r=(RadioGroup)findViewByld(R.id.radioGroup);
r .s e t O n C h e c k e d C h a n g e L i s t e n e r ( t h i s ) ;
}

Vemos cómo se invoca el método setOnCheckedChangeListener recibiendo como


objeto this, es decir, la propia instancia en ejecución que implementa la interfaz
O nCheckedChangeListener

P aso 3. C odificarlo

public void onCheckedChanged(RadioGroup r,int a ) {


T e x t V i e w t = ( T e x t V i e w ) f i n d V i e w B y l d ( R . i d .e s t a d o ) ;
s w i t c h ( a) {
c a s e R .i d . r a d i o B u t t o n 2 : / / T a l a v e r a
t .s e t T e x t (" B u e n a e l e c c i ó n ! : El T a l a v e r a
p r o m e t e ! ! ") ;
break;
case R . i d . r a d i o B u t t o n 3 : //Alcazar
t .s e t T e x t (" G r a n e q u i p o la g i m n á s t i c a ! ! " ) ;
b re ak ;
case R.id.radioButton: //Albacete
t .s e t T e x t ("El A l b a c e t e n o es el m i s m o
d e s d e q u e se fue I n i e s t a " ) ;
b re ak ;
case R . i d . r a d i o B u t t o n 4 : //Otros
t .s e t T e x t ("El d i n e r o n o lo es t o d o . . . . " ) ;
break;
}
}

ACTIVIDAD 2.4.
Mira la extensa documentación en estos enlaces la docum entación de RadioGroup y
R adioButton.
R adioButton: http: / / developer.android.com/reference/android/widget /RadioButton.htrnl
RadioGroup: http: / / developer.android.com/reference/android/ widget/RadioGroup.html
Unidad 2. Desarrollo de aplicaciones para móviles 53

2.2.6. LA CLASE Checkbox


Seguro que alguna vez has tenido que marcar una casilla del tipo "He leído y
comprendido las condiciones de licencia de uso del software XXX” para poder disfrutar
de algún programa, videojuego o sistema operativo. El componente que se ha utilizado
para poder marcar que has leído las condiciones de licencia (aunque no las hayas leído),
es un Checkbox o casilla de verificación. Tiene dos estados, marcado (checked o
desmarcado unchecked), en programación setChecked(true) o setChecked(false).

Q Widgets
(§¿ radioButton2 "Talayera C.F.
IAbj Plain TextView
rad¡oButton3 Gimástico de Ai-cazar'
lAbj Large Text
radioButton - Albacete Balompié’
IAbj Medium Text Cual es tu equipo de
IAbj Small Text radioButton4 Otros equipos menor
! Button
fútbol favorito? checkBox
Aun no has hecho ninguna seieccfon
Th Small Button estado (TextView) - Aun no has hecho nn
(#) RadioButton
Talavera C.F.
fe j CheckBox^
Gimástico de Alcazar
Properties ? ¡D T
M Switch
Albacete Balompié
ToggleButton
layout.-height wrap_content
B ImageButton ros equipos menores (Atlético de Madrid
2 ImageView ► layout:gravity [right]
"*■ ProgressBar (Large) Me gusta el futbó^,tex f ► layoutrmargin □
ProgressBar (Normal) layout rweight
mm ProgressBar (Smalt) checked:
style
*■ ProgressBar (Horizontal)
accessibilityLiveRegion
*&• SeekBar
RatingBar alpha

Spinner background
WebView button
Text Fields ranilaB» __________________

En Android Studio, incluir un checkbox en tu actividad es de lo más sencillo:


Arrastra desde la paleta, ponle identificador y escribe el texto que quieras que aparezca.
Además del método setChecked para establecer el estado, tiene otro método para
consultarlo, isChecked que devuelve cierto (true), si el usuario marcó la casilla y falso
(false) en caso contrario.
Otro tercer método para alterar el estado es toggle(), que cambia el estado que tenga
en ese momento la checkbox.
CheckBox también es un descendiente de TextView, por lo que podrás poner todas
las características de texto de la misma manera que lo haces con TextView.
El evento de cambio de estado se puede capturar de manera idéntica a cómo
describimos en la sección anterior el cambio de estado de un RadioGroup, mediante el
onCheckedChanged que obliga a implementar la interfaz CheckBox. OnCheckedChangeListener

C aso P ráctico: Añadir una casilla a la aplicación Test2 del apartado anterior para que
el usuario pueda seleccionar si le gusta o no el fútbol:
54 Programación en Android

íf il T est2

¿Cual es tu equipo de
fútbol favorito?
Te gusta el fútbol»

é. Talavera C.F.

Gimástíco de Alcazar
Albacete Balompié

Otros equipos menores (Atlético de


Madrid. R Madrid, Barsa)

0 Me gusto el fútbol

Prim ero: Agregar a la lista de interfaces que implementa la actividad


CheckBox.OnCheckedChangeListener.

public class M y A c t i v i t y extends A c t i v i t y implements


R a d i o G r o u p .O n C h e c k e d C h a n g e L i s t e n e r ,C h e c k B o x . O n C h e c k e d C h a n g e L i s t e n e r {

Segundo: Registrar el listener

C h e c k B o x b = ( C h e c k B o x ) f i n d V i e w B y l d ( R ,i d ,c h e c k B o x ) ;
b .s e t O n C h e c k e d C h a n g e L i s t e n e r ( t h i s ) ;

Tercero: Crear el método que hace de listener

p u b l i c v o i d o n C h e c k e d C h a n g e d ( C o m p o u n d B u t t o n c, b o o l e a n b ) {
T e x t V i e w t = ( T e x t V i e w ) f i n d V i e w B y l d ( R ,i d .e s t a d o ) ;
if (b)
t .s e t T e x t ("Te g u s t a el f ú t b o l ! ! " ) ;
else
t .s e t T e x t ("No te g u s t a el f ú t b o l ? ! ? ? ! ! " ) ;

¿ y qué pasa si..., como es el caso de esta App, tengo que im plem entar dos interfaces, una para
RadioGroup y otra para Checkbox? ¿No habrá conflictos entre los dos métodos que hay que
implementar? ¿Cómo sabe Android a qué método invocar cuando se produzca un evento de
cambio de estado si los dos métodos se llaman OnCheckedChange?

La respuesta es que no. Gracias al polimorfismo estático, Jav a detecta qué método hay que
ejecutar dependiendo de los parám etros de entrada.___________________________________________

ACTIVIDAD 2.5.
PUEDES VER EL CÓDIGO DE LA A PP Test2, en el fichero test2.rar del material
complementario y examinarlo con detalle.
Unidad 2. Desarrollo de aplicaciones para móviles 55

2.2.7. LAS CLASES ToggleButton y Switch


Los ToggleButton y Switch son widgets muy parecidos a CheckBox. También son
widgets con dos estados, encendido y apagado (on y off). La única diferencia con un
CheckBox es el aspecto gráfico, que es diferente. El ToggleButton no tiene un texto
asociado y el Switch sí, pero en ambos se puede cambiar el título de los dos estados
mediante la propiedad android:textOn y android:textOff en el fichero XML.

Off On
ON
Toggle buttons Switches (in Android 4 0+)

(imagen de http: / /developer.android.com / guide/ topics/ ui /controls/togglebutton.html)

Para mantener activado o desactivado S Test4


cualquiera de los dos componentes se utiliza la
propiedad Checked, que funciona exactamente Quitado

igual que en una CheckBox. En resumen, para Bigote:

obtener un resultado gráfico como el de la


imagen Test4, tenemos el siguiente código en
XML:
<ToggleButton
android:text="Sombrero"
a n d r o i d :i d = " @ + i d / t o g g l e B u t t o n "
a n d r o i d :t e x t O n = "P u e s t o "
android:textOff= "Quitado"
a n d r o i d :c h e c k e d = " f a l s e "
/>
<Switch
android:text="Bigote:"
a n d r o i d :c h e c k e d = " t r u e "
android:id="@+id/switchl"
a n d r o i d :textOn="Con bigote"
android:textOff="Sin bigote"

De igual manera se puede capturar el evento OnCheckedChange como en


RadioGroup y CheckBox.

2.2.8, LAS CLASES ImageView e ImageButton


Estas clases son las análogas de TextView y Button, pero con imágenes. Cada widget
toma un atributo android:src en el fichero XML de la actividad para especificar qué
imagen usar mediante una referencia a un recu rso d ib u jable (@drawable). Son
análogas en el sentido de que tienen la misma funcionalidad, pero en lugar de mostrar
texto, muestran una imagen.
56 Programación en Android

T anto la clase ImageView como la clase ImageButton tienen una propiedad llamada
android:src, que referencia al identificador del recurso de la imagen dibujable. Por
tanto, antes de añadir un ImageView o un ImageButton, debemos añadir a las carpetas
“src->main->res->drawable*****” de nuestro proyecto los ficheros de las imágenes que
queremos utilizar. Ten en cuenta que Android recomienda usar ficheros con formato
png, aunque también admite bmp y jpgs. Cada imagen debe tener un identificador para
poder ser referenciadas. Este identificador será un número entero accesible a través de la
clase R, mediante la propiedad R .d ra w a b le. identificador.

Tienes que tener en cuenta que hay varias carpetas “drawable”, cada una con los
diferentes tipos de pantalla para los que tu App puede funcionar. Así por ejemplo, la
carpeta drawable-hdpi (high) es una densidad de pantalla de aproximadamente 240dpi
(puntos por pulgada).
Lee más en: h ttp :// developer.android.com/guide/practices/screens support.html

De esta manera, para añadir un botón con imagen a tu App, tan solo tienes que
arrastrar desde la paleta, poner un id, y escoger el recurso que acabas de copiar.

r-
__ Resources

Project System Color

D Color
▼ t i Drawable

■—“— 0 icjauncher

src: ©drawable/llamar " llamar


0 walterwhite
id: ¡mageButtcn2
Unidad 2. Desarrollo de aplicaciones para móviles 57

C A SO P R Á C T IC O : Crea una App llamada Test3 que muestre un botón con imagen
y una imagen simple. Al pulsar el botón se cambiará la imagen tanto del ImageView
como del ImageButton. Debe quedarte más o menos así:

Pulsa ¡m% ¡¡«ns«r$ wa&m W"ii» Samando a Walterwhi»

ACTIVIDAD 2.6.
Puedes utilizar la siguiente utilidad online (Android Asset Studio) para generar
imágenes para todas las densidades de pantalla.
h ttp ://ro m a n n u rik .g ith u b .io /A n d r o id A s s e tS tu d io /n in e -p a tc h e s .h tm l

ACTIVIDAD 2.7.
PUEDES VER EL CÓDIGO DE LA A PP Test3, en el fichero test3.rar del material
complementario y examinarlo con detalle.

Puedes escoger de la galería de ClipArt de Android Studio Image Asset multitud de


recursos dibuj ables pulsando en la carpeta “drawable” y con el botón derecho del ratón,
seleccionado la opción “New”- > ’’Image Asset”:

r1 Asset Studio . __

Asset Type: i Launcher Icons Preview

Foreground: Q Image (•) Clipart Q Text

Clips rt: Choose


* *
1__1 Trim surrounding blank j
0 f X A = -

Foreground scaling: (•) Crop Q Center H a S H U 0 & © X » t a 3 i s

Shape (*) None Q Square (


•-» x» t <$> <«> í 1 ,iil ▼*+ A
O

Background color:
^ B m Q ■ B I S illB l
Foreground color.
< ^ | O A n Q i i 0
Resource nam e j ic_launcher
r? ) 7 ;....; a a • m ¿ j ? m & j*, m
w • • M * g*¡ í? ü j ▼. ® ¡ X ) U c£ W S m g

0 t* x#t a+B+a++ g o f i x i
B B D -é. S, iS B 3

►< ¡ A 0 _ ? ® t i 3 C ñ á í ±**'
Description
y- @ ® ►m a 5 M g
J í I I 1 Í

A thawahlfc resource named ¡ciatm ther already exj


■X#
I
58 Programación en Android

2.2.9. LA CLASE EditText


La clase EditText representa el widget más popular junto con las etiquetas y los
botones. Es una subclase de la clase TextView y tiene otras muy interesantes
propiedades como por ejemplo:
• android: autotext, para controlar si el usuario recibirá asistencia automática al
escribir el texto.
• android capitalize, para controlar si el propio campo de texto debe escribir la
primera letra de cada palabra en mayúscula.
• android:digits, para configurar si el campo de texto debe admitir solo
determinados dígitos.
• android:singleline, para especificar si el campo admite solo una línea y al
pulsar enter se va al siguiente control, o por el contrario crea una línea nueva
al pulsar enter (multilínea)
• androiddnputType, que permite seleccionar
entre varios tipos de teclado que se mostrarán
1 2 3 ABC DEF *
cuando se esté introduciendo texto: “te x t”,
para textos normales, “textEmailAddress” 4 5 6 MNO
GHI JKL

para incluir el carácter “textUri” para 9 W XYZ « a


7 8 PQRS ru v
incluir el carácter / , “number” para mostrar
un teclado numérico normal y “phone” para 0 * # Next
+ —

obtener el teclado para marcación telefónica: android:inputType="phone"

androiddnputType también te permite controlar D Text Reids


Plain Text
ciertos comportamientos, como por ejemplo que Person Name
Password
al terminar de escribir una palabra se autocorrija Password (Numeric)
E-mail

con el diccionario o que los caracteres aparezcan Phone


Postal Address

convertidos en puntos (bueno para introducir Multiline Text ^


Time
□ Date
passwords). Number
Number (Signed)
Android Studio incorpora el la paleta de Number (Decimal)

widgets un montón de campos de texto distintos


para que no tengas que enredar mucho con el
XML:

A C T IV ID A D 2.8.
Puedes leer más sobre el comportamiento de la entrada de datos sobre campos de texto en el
manual de Android:
http://developer, android, com / guide/topics/ui/controls/text, litml
Unidad 2. Desarrollo de aplicaciones para móviles 59

A sociar u n a acción d e finalización al cam p o de tex to : Es posible añadir un botón


al teclado que aparece cuando se está editando un texto para que el usuario indique que
se ha finalizado la edición, y, de esta manera desencadenar una acción. Por ejemplo
“actionSend” (enviar) o “actionSearch” (buscar). Si no se especifica esta propiedad, por
defecto se establece “actionNone” (ninguna).
Para hacer esto se utiliza la propiedad android:imeOptions en el fichero XML. Por
ejemplo, para agregar un botón llamado “Enviar” al campo de texto:
a n d r o i d :i m e O p t i o n s = "a c t i o n S e n d "

Para responder a estos eventos, se puede programar un evento EditorAction


mediante el método setOnEditorActionListener() de la siguiente forma:

E d i t T e x t e d i t T e x t = (EditText) f i n d V i e w B y l d ( R .i d . e d i t T e x t ) ;
e d i t T e x t .s e t O n E d i t o r A c t i o n L i s t e n e r (new
T e x t V i e w .O n E d i t o r A c t i o n L i s t e n e r () {
p u b l i c b o o l e a n o n E d i t o r A c t i o n ( T e x t V i e w v, i n t a c ti o n l d ,
K e y E v e n t event) {
boolean manejada=false;
if ( a c t i o n l d == E d i t o r l n f o .I M E _ A C T I O N _ S E N D ) {
S y s t e m . o u t . p r i n t l n (" a c c i ó n e n v i a d a ! " ) ;
m a n e j a d a = true;
}
return manejada;

}>;
El método onEditorAction recibe como parámetros el campo de texto TextView t
(recuerda que EditText es una subclase de TextView), el identificador de la acción (se
puede consultar con la colección de enteros Editorlnfo (IME_ACTION_SEND
representa la acción de enviar) y el evento de tecla KeyEvent que en estos casos será
nuil. Hay que devolver si hemos manejado o no el tipo de acción que se nos ha
comunicado, en este caso, retornaremos cierto si se invocó el método para la acción de
terminar (IME_ACTION_SEND)
C ap tu ran d o los cam b ios en el tex to : Existe una forma de controlar
programáticamente cualquier interacción del usuario con un campo de texto. Se puede,
por ejemplo, programar un método que responda al cambio del texto en una
determinada posición o realizar algo justo antes de que el usuario se ponga a cambiar el
texto, o incluso después de cambiar el texto. Para hacer este tipo de programación, se
requiere un objeto que implemente la interfaz Text Watcher, y registrarla en el campo de
texto mediante el método:

addTextChangedListener(TextWatcher watcher);

La interfaz TextW atcher tiene tres métodos:


• public void beforeTextChanged(CharSequence s, int start,
int count, int after)
60 Programación en Android

Este método te avisa de que en la cadena s, los count caracteres empezando en start,
están a punto de ser reemplazos por nuevo texto con longitud after

• public void onTextChanged(CharSequence s, int start, int b e fo r e ,


int count)

Este método te avisa de que en la cadena s, se han reemplazado count caracteres


comenzando en start y han reemplazado before número caracteres

• public void afterTextChanged(Editable s)

Se ejecuta después de haberse cambiado el texto. Este método te avisa de que, en


algún punto de la cadena s, el texto ha cambiado.
Prueba este ejemplo para examinar cómo se ejecutan los distintos métodos
informándonos de cuándo se están cambiando los caracteres del campo de texto:

e d i t T e x t .a d d T e x t C h a n g e d L i s t e n e r (new T e x t W a t c h e r (){
/ / E s t e m é t o d o te a v i s a de q u e e n la c a d e n a s,
//se han reemplazado count caracteres
//comenzando en start y h an r e emplazado
//before número caracteres
p u b l i c v o i d o n T e x t C h a n g e d ( C h a r S e q u e n c e s, int start,
i nt b e f o r e , int c o u n t ) {
S y s t e m . o u t . p r i n t l n ("Se h a n r e e m p l a z a d o de '" + s + " ' " + c ou n t
+" c a r a c t e r e s a p a r t i r de " + s t a r t +
" que antes ocu p a b a n "+before+" p o s i c i o n e s " ) ;
}
/ / E s t e m é t o d o te a v i s a de q u e e n la c a d e n a s,
/ / l o s c o u n t c a r a c t e r e s e m p e z a n d o e n start,
/ / e s t á n a p u n t o de s er r e e m p l a z o s p o r n u e v o
//texto con longitud after
p u b l i c v o i d b e f o r e T e x t C h a n g e d ( C h a r S e q u e n c e s, int start,
int count, int a f t e r ) {
S y s t e m . o u t . p r i n t l n ("Se v a n a r e e m p l a z a r de '" + s + " 1" + c o u n t +
" c a r a c t e r e s a p a r t i r de " + s t a r t +
" por texto con longitud "+after);

}
/ / E s t e m é t o d o te a v i s a de que, e n a l g ú n
/ / p u n t o de la c a d e n a s, el t e x t o h a c a m b i a d o ,
public void afterTextChanged(Editable s ) {
S y s t e m . o u t . p r i n t l n ("Tu t e x t o t i e n e " + s .l e n g t h ()+" c a r a c t e r e s " ) ;
Unidad 2. Desarrollo de aplicaciones para móviles 61

ACTIVIDAD 2.9.
PUEDES VER EL CÓDIGO DE LA A PP Test4 en el fichero test4.rar del material
complementario.

2.3. INTERNACIONALIZARON DE APLICACIONES


MEDIANTE EL ARCHIVO STRING.XML
¿Q ué son los recursos (resou rces)?
Los recu rsos son los datos, im ágenes, sonidos, anim acion es, layouts y otros
datos e stá tico s que tu A pp puede u tiliza r. Una A p p puede in clu ir diversos
conjun tos de recu rsos que tu A pp puede u tiliza r p a ra fu n cio n a r con diversa s
configuraciones.

Es una muy buena práctica mantener todas las cadenas de caracteres con texto para
etiquetas, botones y en general cualquier elemento de la interfaz de usuario, fuera del
código fuente de la App. Hasta este momento, siempre hemos intentado en todos los
ejemplos que para ponerle un texto a un control utilices lo que hemos llamado recurso.
Este recurso, generalmente está almacenado en un archivo XML llamado
res/values/strings.xml que depende del proyecto.
Este fichero deberá contener todas las cadenas de caracteres en el idioma que
consideres que será el de uso mayoritario. Esto es lo que se llamará el RECURSO POR
DEFECTO (Default resource).

Esto es también aplicable al resto de recursos de tu App, por ejemplo, los ficheros
gráficos “dibujables (drawable”) ubicados en res/drawable, animaciones, layouts, etc.
cuando deseas que tu dispositivo funcione con varias configuraciones hardware, por
ejemplo, densidad y tamaño de pantalla. Recuerda la App Test3 que creaste utilizando
diversos gráficos para varias densidades de pantallas.

Ser muy disciplinado con esta buena práctica te permitirá, de forma muy fácil,
internacionalizar tu App, es decir, seleccionar el idioma del usuario dependiendo de la
zona geográfica en la que se encuentre o del idioma que tenga personalizado en su
dispositivo móvil.
Incluso si no vas a internacionalizar tu App, es mucho más fácil corregir los errores
(faltas ortográficas y errores tipográficos) si tienes todos tus textos almacenados fuera
del código.

Ejemplo de strings.xml
62 Programación en Android

<resources>
<string name="voldemort">Aquel cuyo nombre no debe ser
nombrado</string>
<string n a m e = "s p i d e r m a n " > E 1 hombre araña</string>
</resources>

El parámetro name identifica al recurso y entre las etiquetas va el texto que describe
el recurso.
Desde el código, puedes referenciar estos valores desde el diseño de una actividad
mediante @string/recurso, por ejemplo @string/voldemort o desde código invocando a la
función getString(IDRecurso) pasándole como parámetro del identificador del recurso.
Por ejemplo:
String s=getString (R.s t r i n g . v o l d e m o r t )

2.3.1. CREACIÓN DE APLICACIONES MULTIIDIOMA


Para poder utilizar distintos idiomas en tu App, además del recurso por defecto,
necesitas RECURSOS ALTERNATIVOS (alternative resources). Una Aplicación puede
especificar múltiples directorios /res/<cualificadores> donde un cualificador especifica
un lenguaje o una combinación de lenguaje-región.

Cuando un usuario
Cuando programes.... ejecuta tu App
C re a un c o n ju n to d e
A ndroid s e le c c io n a
r e c u r s o s p o r d e f e c to , y
q ué recu rso s cargar
d if e r e n te s a lte r n a tiv a s
d e p e n d ie n d o d e su
p a r a e j e c u ta r s e con
c o n fig u ra ció n
d if e r e n te s lo c aliz acio n e s
- J i _______________________/

Por ejemplo, supon que el lenguaje principal de tu App es el español. Pues en la


carpeta res/values tendrás todos los archivos con las cadenas de caracteres en idioma
español, y puedes generar además las siguientes carpetas:
res/values-fr -> Contendrá las cadenas de caracteres traducidas al francés,
res/values-en -> Contendrá las cadenas de caracteres traducidas al inglés.

De esta manera, si el dispositivo está configurado para usar inglés, Android cargará el
archivo correspondiente a los valores en inglés.

CONSEJO: Diseña tu App para funcionar con cualquier dispositivo. El dispositivo puede tener
una configuración software o hardware que puede que no conozcas, o simplemente que no
esperas. Prográmala para que “falle” elegantemente sin importar en qué dispositivo se está
ejecutando.____________________________________________________________________
Unidad 2. Desarrollo de aplicaciones para móviles 63

C A S O P R Á C T IC O : Crea un App con una actividad con capacidad de ejecución con


idioma español y con idiomas alternativos en inglés y alemán. Consistirá en un simple
formulario para rellenar datos personales para el registro de un usuario. El formulario,
en los tres idiomas, tendrá esta pinta:

í l * 14:13 tí * 13:03 *k » 2 :15 I

cfl Internacionalización ■ 1 # 1 Internationalization 1 í ! Internacionalizatíon ;

Bienvenido al formulario de registro' Willkommen zu anmeidung Formular' Welcome to sign up form

Nombre: J .............. ..... ._...

Apellidos: Nachname

1 Email: Email:

Clave Passwort.
* i

Primero: Crea dos recursos de Android para agregar dos ficheros strings.xml con
todas las cadenas de caracteres que vayas a utilizar tanto en Inglés como en Español.
Tendrás entonces tres subcarpetas en la carpeta de recursos values, values-en (inglés) y
values-de (alemán).

► C3 build
CU libs
f D src
► Cm android!est
Resource type | Values
▼ CU main
► CDjava Root slement:
T~ res
Directory nam e | values-en
► S3 drawable-hdpi
P E j drawable-mdpi Available qualifiers:

► E3 drawabie-xhdpi 1 0 Country Code


► É3 drawable-xxhdpi 0 Network Code
▼ S3 layout
activity.actividadjnt £ Layout Direction
► E j menu jaj Smallest Screen Width
▼ £ 3 values ¡¡¡k Screen Width
¡** dimensjeml 0 Screen Height
** stringsjcml : 5 Size
** stylesjcml SU Ratio
► C ] vaiues-w820dp H i Orientation
** AndroidManrfest.xml 1 UlMode
@ .gitignore © Night Mode
31 app.iml
build.gradle
1 proguard-rules.pro
C3 gradle

Imagen: Crear una carpeta de recursos para idioma inglés.

El código de los tres ficheros string.xml que tendrá tu aplicación será:


64 Programación en Android

i ü __________ ®_± <> stringsocml x ¡


▼ ED values <?xml version= 1.0* encoding='!utf-8'' ?>
<> dimensocml <5<resources>
5 stringsocml
<string name= "app_na2se">In t e m ^ ^ ^ ^ l 3^ciciK/string>
<Ü stylesocml
< s t r i n g naiae=' i n t r o " > B ie r n ^ n id c a l f c r ^ l a r i o de r e g i s t r e ! < / s t r i n g >
▼ SU values-de
6
< s t r i n g name="? ,c tie r ¿ _ s e tt i n g s ”> C ca ^ g u ra j:^ n < /s t r in g >
! 3 stringsocml <string naise=Mnombre;'>Ksg¡^re:</string>
▼ E3 values-en <string name="a^ll^dos^>^ellidos:</string>
§1stringsocml <string nane*" email''>Email :</string>
► El! values-w820dp 3 1
< s t r i n g n a»e= "pas v o r d ”>C a v e : < / s t r i n g >

<> AndroidManifest.xml
ó</resources>
jitignore

strings.xml de la carpeta res/values -> Cadenas de caracteres en español

<?xml version="1.0 " e n c o d i n g="utf-8"?>


<resources>
< s t r i n g n a m e = " a p p _ n a m e "> I n t e r n a c i o n a l i z a c i ó n < / s t r i n g >
< s t r i n g n a m e = " i n t r o " > B i e n v e n i d o al f o r m u l a r i o de
r e g i s t r o !</s t r i n g >
<string name=" a c t i o n _ s e t t i n g s " > C o n f i g u r a c i ó n < / s t r i n g >
< s t r i n g n a m e = " n o m b r e "> N o m b r e :</s t r i n g >
< s t r i n g n a m e = " a p e l l i d o s " > A p e l l i d o s :< / s t r i n g >
< s t r i n g n a m e = " e m a i l "> E m a i l :</s t r i n g >
< s t r i n g n a m e = " p a s s w o r d " > C l a v e :</s t r i n g >
</resources>

oject ■*; © 4= 111 en\stringsocml x


▼ E3 values ; < 7 x m L version="l. 0” encodings’’u tf -8” ?>
Ü dimensocml $J<resources>
Ü stringsocml
<strine naiae-"app naj&e">Intemacicnalizatio:i</string>
Ü stylesocml
<string nasae="ir>tro“>Kelcoine to sign up foms</string>
▼ & values-de
<string name=”action _settin gs">Settings</ string>
stringsocml <string name="nosbre">Na¡Be:</string>
▼ É3 values-en <s tr ing name="a p ellid os/ >SurnaiLe:< /s tring>
i l l stringsocml _ <string name=!'emaili!>Email:</string>
► S3 values-w820dp <string name=''password}’>Password:< /string>
jfi AndroidManifest.xml
r%, . . ÉK/ resources >

strings.xml de la carpeta res/values-en ->Cadenas de caracteres en inglés

< ? x m l v e r s i o n = " l .0" e n c o d i n g = " u t f - 8 " ? >


<resources>
< string name="app_name">Internacionalization</string>
< s t r i n g n a m e = "i n t r o " > W e l c o m e t o s i g n u p f o r m < / s t r i n g >
< s t r i n g n a m e = "a c t i o n _ s e t t i n g s "> S e t t i n g s < / s t r i n g >
< s t r i n g n a m e = " n o m b r e " > N a m e :</s t r i n g >
< s t r i n g n a m e = " a p e l l i d o s " > S u r n a m e :< / s t r i n g >
< s t r i n g n a m e = " e m a i l " > E m a i l :< / s t r i n g >
«string n a m e = " p a s s w o r d " > P a s s w o r d : < / string>
</resources>
Unidad 2. Desarrollo de aplicaciones para móviles 65

»¡ O Nr 5 1 de\stringsjcml x
Y E j values <?xml version= ”1.0” encoding='ut f -8"?>
<> d¡men5.xml (§<resources>
1» strings.xml <string naiae-l!a$>p_na»e”>Ince rna ci ona1i z at ion</ string>
Mi styles.xml <string name=”intro”>Wi11 kcmmen zu anmeldung Formular !</string>
<string naae=!'action settings' >Einstellungen</string>
w S3 values-de
<string name="noii>bre">VomaiBs:</striiig>
5 1 strings.xml <string naioe="apellidos ">Nachname:</string>
V É3 va lues-en <s tring name="ema11 ”>£mai 1: </ s tring>
¡Ü strings.xml s <string name=”passwordM >Passvrcrt:</string>
► É3 values-w620dp
£)</ resources >
<> AndroidManifest.xml
itignore

strings.xml de la carpeta res/values-de -> Cadenas de caracteres en alemán

< ? x m l v e r s i o n = " l 0" e n c o d i n g = " u t f - 8 " ? >


<resources>
< s t r i n g name^ ="a p p _ n a m e " > I n t e r n a c i o n a l i z a t i o n < / s t r i n g >
< s t r i n g name: ="i n t r o "> W i l l k o m m e n zu a n m e l d u n g
F o r m u l a r !< / s t r i n g >
< s t r i n g name: = " a c t i o n _ s e t t i n g s " > E i n s t e l l u n g e n < / s t r i n g s
< s t r i n g name: =" n o m b r e "> V o r n a m e :< / s t r i n g >
< s t r i n g name: ="a p e l l i d o s " > N a c h n a m e :< / s t r i n g >
< s t r i n g name: = " e m a i l " > E m a i l :< / s t r i n g >
< s t r i n g name: :" p a s s w o r d " > P a s s w o r t :< / s t r i n g >
</resources>

ACTIVIDAD 2.10.
Puedes aprender más sobre cómo soportar varios sistemas (pantalla, idioma y plataform a) en la
dirección:
http: / / developer, android.com / training/basics /supporting-devices / indcx.html

2.3.2. PROBANDO LA INTERNACIONALIZACIÓN EN EL


EMULADOR

Se puede cambiar la configuración del emulador mediante la pestaña de aplicaciones:


66 Programación en Android

O directamente desde la Shell del sistema operativo: Si te vas a la Shell del sistema
operativo y posicionado en la carpeta sdk->platform-tools de la carpeta donde has
instalado Android y ejecutas el comando adb -e shell tendrás acceso a la consola del
emulador.

A d m in istra d o r C :\W in d o w s\sy ste m 3 2 \c m d .e x e - a d b - e sh ell

D:\Android Studio Tools\sdkSplatform-tools>adb —e shell


root@generic_x86:/ # setprop persist.sys.language de;
setprop persist.sys.language de;
rootBgeneric_x86:/ tt setprop persist.sys.country DE;
setprop persist.sys.country DE;
root@generic_x86:/ ft stop;sleep 5;start
stop;sleep 5 ¡start

Para cambiar de lenguaje y de país, escribe en la consola:

s e t p r o p p e r s i s t .s y s .l a n g u a g e < l e n g u a j e > ;
s e t p r o p p e r s i s t .s y s .c o u n t r y <país>;
s t o p ; s l e e p 5 ; s t ar t

donde lenguaje es el código del lenguaje que quieras probar y país, el país donde el
usuario se supone que está. Por ejemplo, para Alemania sería:

s e t p r o p p e r s i s t .s y s .l a n g u a g e de;
s e t p r o p p e r s i s t .s y s .c o u n t r y DE;
stop;sleep 5 ; start

ACTIVIDAD 2.11.
PUEDES VER EL CÓDIGO DE LA A PP Test4 en el fichero
internacionalizacion.rar del material complementario y examinarlo con detalle.

CGR Greek

MdHtm.
Unidad 2. Desarrollo de aplicaciones para móviles 67

2.4. LAYO UTS Y CO N TEN ED O R ES


Los contenedores o containers, permiten organizar un conjunto o colección de widgets
en una estructura específica definida como tú quieras. Por ejemplo, si quieres organizar
un conjunto de campos para formar un formulario necesitarás un contenedor. Siempre
que necesites utilizar múltiples widgets necesitarás un contenedor para alojarlos y de
esta manera tener un elemento de referencia o raíz (root).
En el módulo de programación estudiaste cómo Java organiza los controles en un
contenedor y usaba gestores de distribución de esos controles (layouts managers) para
poder usarlos (Flowlayout, BoxLayout, etc.) Esta misma filosofía la usa Android para la
disposición y organización de los widgets en la pantalla.
Los layouts se pueden manejar de dos formas:
• Declarando los ficheros en XML: Como lo has hecho hasta ahora con las vistas
de diseño en Android studio.
• Instanciando objetos programáticamente en tiempo de ejecución.

La ventaja de declarar los elementos de tu layout a través de la definición en XML es


que se separa la interfaz de usuario (IU) de la lógica de tu programa que controla el
comportamiento de los elementos de la IU. Haciéndolo de este modo, las descripciones
de tu interfaz son externas al código de tu aplicación, pudiendo cambiar el diseño de tu
interfaz sin tener que tocar código y sin tener que recompilar. Esto te proporciona
F L E X IB IL ID A D . Nos centraremos por tanto, en explicarte cómo declarar los
elementos de los layout en XML.
Los LinearLayout, RelativeLayout y TableLayout son contenedores que gestionan la
distribución de los controles de una determinada manera en una pantalla cuando se
trabaja con Android.
En otras palabras, el contenedor agrupa controles (también llamados hijos) y el
layout los ordena/distribuye dentro del contenedor. De esta manera se crean
organizaciones jerárquicas de controles, donde el elemento p ad re es el contenedor y los
componentes que hay dentro son los hijos.
Hasta ahora en los ejemplos que has visto, siempre has utilizado el RelativeLayout,
tremendamente flexible y que crea Android Studio por defecto. Este RelativeLayout
utiliza un modelo basado en reglas (rule based model), mientras que LineaLayout ofrece
un modelo de cajas, box model y TableLayout que proporciona un modelo de celdas
(grid model).
Lo bueno de los contenedores es que puedes anidarlos unos dentro de otros para
hacer más flexible y potente tu interfaz de usuario.
68 Programación en Android

2.4.1. PROPIEDADES DE LOS LAYOUTS


Los Layout son subclases de una clase más abstracta llamada ViewGroup, que a su
vez, es una subclase de View. Todos los widgets son Views, y por tanto, implementan
métodos y propiedades comunes para todos los elementos gráficos de tu IU.

public class

RelativeLayout
extends ViewGroup

java, lartg.Object
Uandroid view. View
Landroid view. ViewGroup
Uandroid widget.RelativeLayout

Ejemplo de la documentación, jerarquía de clases para RelativeLayout

Cada clase ViewGroup implementa una clase anidada que hereda de


ViewGroup.LayoutParams. Esta clase implementa propiedades importantes que definen
la posición y el tamaño de los elementos hijos. Como puedes ver en la siguiente imagen,
cada descendiente de un Layout define estas propiedades.

Imágenes de android.developer.com

Por ejemplo, si consultas la documentación de la clase GroupView.LayoutParams, en


http://developer.android.com/reference/android/view/ViewGroup.LavoutParams.html.
todos los widgets dentro de un Layout van a tener las propiedades layout_width y
layout_height que permiten especificar cómo se va a rellenar el espacio del contenedor.
Imagina, por ejemplo, que tienes un layout con dos widgets de texto, que por su tamaño
inicial no rellenan todo el espacio del contenedor:
• Se puede indicar en layout_width y layout_height una dimensión
determinada, por ejemplo, 150dip para indicar que el widget debe tener
exactamente ese tamaño. (Cuidado, porque este tipo de parametrizaciones hace
que tu aplicación sea dependiente del tamaño de la pantalla).
Unidad 2. Desarrollo de aplicaciones para móviles 69

• Se puede indicar wrap_content, que significa que el widget debería ocupar


exactamente su espacio natural, a no ser que sea demasiado grande para entrar
en el contenedor, en cuyo caso se liaría ajuste automático de línea mediante
word-wrap.
• Se puede indicar fill_parent, en cuyo caso, el widget se haría todo lo grande
que pudiera hasta rellenar el espacio sobrante en el contenedor.

Summary

A ttrib u te N a m e R e la te d M e th o d D e s c rip tio n

android:layout_height Specifies the basic height of the view,


androidilayouLwidth Specifies the basic width of theview.

int F1LL_PARENT Special value for the height or width requested by a View,
int MATCH_PARENT Specialvalue for the height or width requested by a View,
int WRAP_CQNTENT Special value for the height or width requested by a View.

Imagen: Atributos de un GroupView.LayoutParams

Cada control se agrupa dentro de un contenedor, a cierta distancia del resto. Esto se
puede controlar a través de las propiedades de márgenes, android:layout_marginXXXX,
donde XXXX puede ser Top, Left, End, Right, etc. de manera muy similar a como lo
haces con las hojas de estilos css y el modelo de cajas.

En los próximos apartados podrás leer más sobre los diferentes tipos de layouts y sus
propiedades. Mira con atención los videos de las demos de los siguientes apartados.

2.4.2. RELATIVE LAYOUTS


El layout relativo permite a los hijos (elementos contenidos) especificar su posición
con relación al padre (contenedor) o uno de sus hermanos. De esta manera, se pueden
alinear elementos al borde, o poner uno debajo de otro, centrado en la pantalla, a la
izquierda, etc. Hasta ahora has utilizado el entorno de desarrollo para ordenar tus
controles, pero puedes hacerlo a través del fichero XML de Layout, de hecho, tarde o
temprano tendrás que hacerlo porque las limitaciones del entorno gráfico de Android
Studio no te permitirán hacer todas las cosas que deseas. Por ejemplo, si escribes:

android:layout_alignParentTop="true
70 Programación en Android

en un elemento hijo, harás que ese elemento se alinee en el borde de arriba para cuadrar
con el borde de arriba del padre:

Tienes otras muchas propiedades:

android:layout_centerVertical="true' Centra el hijo verticalmente con su padre


android:layout_belo w="@+ id/xxxxx" Coloca el elemento debajo de xxxxx
android:layout_toRightOf=’@+id/xxxxx" Coloca el elemento a la derecha de xxxxx
android: layout_alignP arent Left="true" Coloca el hijo alineado a la borde
izquierdo del padre

Tienes todas las opciones posibles documentadas en:


http://develoD er.android.com /reference-/android/w idget/R elativeLavout.LavoutParam s.htm l

A C T IV ID A D 2.12.
Observa con atención la demo del LinearLayout: htt.ps: / /www.vout.ube.com/watch?v=CU0DiP8Va6g

2.4.3. LINEAR LAYOUTS


El Layout lineal, es un modelo de cajas, es decir, alinea los controles horizontalmente
en una fila o verticalmente en una columna.
Unidad 2. Desarrollo de aplicaciones para móviles 71

Fila

Para indicar si los controles irán agrupados en fila o en columna se utiliza la


propiedad orientation:

a n d r o id :o r ie n ta tio n = " v e r tic a l" si se quiere agrupar los controles en columna


a n d ro id :o r ie n ta tio n = " h o riz o n ta l" si se quiere agrupar los controles en una fila

ACTIVIDAD 2.13.
Observa con atención la demo del LinearLayout: http://www.voutube.com/watcli7v—nTAXcQ7KMi8

Además de estas propiedades y de android:layout_width y android:layout_height


tenemos la propiedad de peso android :layout_weight. La propiedad peso permite
otorgar preferencia a la hora de rellenar el espacio del contenedor en caso de que
queramos que todos los controles ocupen el espacio entero del contenedor. Con la
propiedad peso, indicaremos la proporción de espacio que deben ocupar los widgets.

ACTIVIDAD 2.14.
Demo de la propiedad peso: http://w w w . youtube.com Avatcli?v=2WcJOVpQR.4g

L A G R A V E D A D : La otra propiedad importante de un Layout lineal es la propiedad


la.yout_gravity.Pov defecto todo se alinea de arriba abajo y de izquierda a derecha. Si
queremos romper esta regla, debemos usar la gravedad. Por ejemplo, en la imagen
anterior, los tres controles con peso, están alineados a la izquierda. Si quisiéramos
alinear alguno a la derecha, por ejemplo, el de peso 1, deberíamos establecer su
propiedad android:layout_gravity =" right"
72 Programación en Android

T * . >67

a C o n te n d o re s

N ew B u tto n

N ew B u tto n

N ew B u tto n

ACTIVIDAD 2.15.
Demo de la propiedad gravedad: l i tt o : / / www.voutube.com/wat.ch?v=V 3eTv wPZs

2.4.4. LAYOUTS TABULARES


El último grupo de Layouts son los Layouts tabulares. Estos layouts crean una
distribución basada en filas y columnas, tal y como lo haces con una tabla html o si lo
prefieres, como en una hoja de cálculo. De estos tenemos el GridLayout y el
TableLayout. Ambos son bastante similares, así que nos centraremos en el GridLayout y
después, puedes experimentar por tu cuenta el TableLayout.
Para comenzar, debes especificar el número de filas y columnas que va a tener tu
Grid:
Esto se hace con la propiedad android:rowCount y android:columnCount
Unidad 2. Desarrollo de aplicaciones para móviles 73

Una vez especificadas las filas y columnas, es posible incluir widgets dentro de cada
fila y cada columna de forma muy sencilla. Tan solo hay que especificar, en cada widget,
a qué columna (android:layout_row) y a qué fila va a pertenecer:
(android:layout_column). Aunque en la imagen siguiente hemos proporcionado valores a
estas dos propiedades para todos los componentes que hemos agregado a la grid, en
realidad no es necesario rellenarlas para todos los componentes, puesto que por defecto
se alinearán de izquierda a derecha automáticamente, si no indicas su posición exacta en
la grid.

tfr Nexus 4’- {_j- * AppTheme MyActivity’- iff"-


<GridLayout
android:layout_width=" filljparent"
android: layout_height='ifill_parent"
android: layout_centerVertical=,:true"
android:layout_centerHorizontal=" true*
android:rowCount="3n
android:columnCount*”3">

<Button f$f GridLayout


android:layout_width="wrap_content i ■
android:layout_height-"wrap_content New Button New Button New Button ■
android: text*"Nevr Button"
New Button New Button New Button |
android:layout_row= "0!! New Button New Button New Button ■
android:layout_column="0" />

<Button
android:layout_width= ■'wrap_content"
android:layout_height=•"wap_con tent"
android:text="fJew Button!!
android;id=”ip+-id/button2*
■imiwmV y ~y ' y .m y ;
android:layout_row=,!0" I
android; layout coluagi="1"I

<Button
android: 1ayout_wi.dth=*v?rap_content"
android:layout_height=!,wrap_content"
android: text»”New Button"
android:id="8.tid/butt<mjj|*
IarSJn^^
android: layout colum="2' |/'>

Como puedes ver en el ejemplo, la primera fila es la fila 0 y la primera columna es la


columna 0. Si quieres modificar el espacio entre los controles puedes utilizar la propiedad
layout_margin, al igual que lo haces en las hojas de estilo css.
74 Programación en Android

< B u tto n
1
a n d r o i d : la y o u t_ w id th = "w rap c o n t e n t ”
▼ | 4 57
I a n d r o id : la y o u t a a rg in = '2 0 d p " |
'0 ? GndLayout

an ^ i4 ll< ^ * 8 U 4 /b ttttO B 2 »
a n d r o i d :la y out_row = "0" 1 New Button New Bu
a n d r o id :la y o u t colum n="0" />
1". 1
New Button New Button New Bir

New Button New Button New Biz

Se puede dejar una posición vacía en el Grid simplemente no incluyendo ningún


control en esa posición:

V § A 57 I
\ ’W * GndLayout i 1
New Button New Button New Button

New Button New Button

New Button New Button New Button

Al igual que en HTML se utiliza el concepto de row spanning y col spanning (span)
para hacer que un elemento ocupe más de una celda, Android permite igualmente
hacerlo con los componentes en un LayoutGrid. Tan solo hay que jugar con las
propiedades android:layout_columnSpan y android:layout_rowSpan y activarlas
asignando el valor “fill” a la propiedad android:layout_gravity del componente. Por
ejemplo, queremos hacer que el primer botón de la segunda fila llene el espacio que
corresponde al hueco que hemos dejado, podemos “expandirlo” mediante las propiedades
mencionadas

< B u tto n ym M
a n d r o id : la y out_w idth= " v r a p _ c o n te n tn
a n d r o id : la y o u t_ h e ig h t= "w ra p _ c o n te n t* I
a n d r o id : t e x t = ”Hew B utton"
1 101 GndLayout
^djroid:^
a n d r o id : la y out_columnSpan= ’ 2"
New Button New Button New Button
a n d r o id : la y o u t_ g ra v i ty =nf i l l " -
a n d r o id : la y out_rovr=!’1 ■ New Button New Button
a n d r o id : la y out_coluion= "0" />
New Button New Button New Button
Unidad 2. Desarrollo de aplicaciones para móviles 75

2.4.5. TRABAJANDO PROGRAMÁTICAMENTE CON


CONTENEDORES
La otra posibilidad es trabajar con Layouts desde el código fuente. De hecho,
necesitarás hacerlo cuando quieras:
• Recorrer todos los hijos del Layout.
• Añadir o eliminar elementos hijos de un Layout en tiempo de ejecución.
• Responder a eventos de los widgets hijos de un Layout.

2.4.5.1. Recorrido del contenedor


El recorrido de los widgets de un Layout es la operación más básica que puedes
hacer. Recorrer significa obtener una referencia a cada uno de los widgets que están en
un Layout. Se puede hacer fácilmente con un bucle for.
¿Te acuerdas de que cada Layout es una subclase de la clase GroupView? GroupView
es una clase abstracta que representa una agrupación de objetos de la clase View. A su
vez cada widget es una subclase de la clase View, por tanto, examinando la
documentación de la clase GroupView podemos hacernos una idea de los métodos que
hay que utilizar para poder acceder a un widget dentro de un contenedor, y de esta
manera, programar un bucle que los recorra todos. Es así de sencillo:
El método getChildAt(int index) de la clase GroupView devuelve una referencia al
widget con identifícador index dentro del contenedor. Index representa el elemento
insertado en una determinada posición. Puedes imaginarte que el Layout es un array
dinámico con widgets dentro y que el gracias al índice accedes a uno de los elementos de
la colección.
Por otro lado, GroupView tiene otro método llamado getChildCount() que devuelve
un número entero indicando cuantos elementos hay en el contenedor.
Ya puedes programar el siguiente pedazo de código:

p u b l i c v o i d R e c o r r e r (){
V i e w v;
G r i d L a y o u t g = (GridLayout) f i n d V i e w B y l d ( R .i d .g r i d l ) ;
for (int i = 0; i < g . g e t C h i l d C o u n t (); i++) {
v = g .g e t c h i l d A t ( i ) ;
S y s t e m . o u t . p r i n t I n (" o b j e t o :"+ v . t o S t r i n g ());
}
}

Lo primero que hay que hacer es obtener una referencia al Layout, del tipo que sea,
en el ejemplo de recorrido se ha utilizado un GridLayout, pero podría haber sido
cualquier tipo de Layout.
Con un bucle for se visitan todos los hijos desde el hijo 0 hasta getChildCount()-l.
En cada iteración se obtiene una referencia al i-ésimo widget hijo (v).
76 Programación en Android

2.4.5.2. Diferenciando tipos


Como sabes, un contenedor puede tener distintos tipos de widgets. Para conocer qué
tipo de widget es cada uno de ellos (qué tipo de clase es), puedes utilizar el método
getClass de la clase View, e invocar a su método getSimpleName para conocer el nombre
de la clase a la que pertenece el objeto View. Luego puedes hacer un cast a un objeto de
una clase determinada para tratar sus propiedades específicas. Por ejemplo:

B u t t o n b;
i f ( v . g e t C l a s s ( ) . g e t S i m p l e N a m e ().e q u a l s (" B u t t o n " )){
b = ( B u t t o n ) v;
b .s e t O n C l i c k L i s t e n e r (...) ;
//o c u a l q u i e r o t r o m é t o d o / p r o p .d e la c l a s e B u t t o n
}

2.4.5.3. Añadiendo elementos al contenedor


Puedes añadir los elementos que quieras al contenedor, teniendo en cuenta las
siguientes consideraciones:
Cada widget que incluyas debe tener definido sus parámetros de Layout, de lo
contrario, encontrarás resultados desagradables. Los widgets tienen un método
llamado setLayoutParams para establecer las propiedades en tiempo de
ejecución.
Cada widget debe tener un identificador. Cuando lo haces con
I $ 1 Layouts
XML se indica la propiedad id mediante un recurso, mientras
btnO btnl btn2
que en código fuente se hace con un entero. La cuestión se
btn3 btn4 btn5
traduce en qué identificador puedes ponerle a un objeto creado
btn6 btn? btn8
en tiempo de ejecución. Para que no haya conflictos y
btn9 btniO b tn ll
dupliquemos estos id, la clase View, a partir de la Api 17 de
btn 12 btnl 3 b tn l 4
android proporciona un método llamado generateViewId que
btnl 5 btnl 6 btnl 7
devuelve un id único no utilizado hasta ese momento.
La clase GroupView incorpora el método addView(View v,int
index) para incorporar todos los elementos que desees. De esta manera, el siguiente
ejemplo de código, inserta 18 botones en un GridLayout sin necesidad de especificarlo en
código fuente:
p u b l i c v o i d a ñ a d e H i j o s (){
G r i d L a y o u t g = (GridLayout) f i n d V i e w B y l d ( R . i d . g r i d l ) ;
B u t t o n b;
f o r ( i n t i = 0 ;i < 1 8 ;i++) {
b = new Button(this);
b .s e t L a y o u t P a r a m s (new V i e w G r o u p .L a y o u t P a r a m s (
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ,
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ) );
b .s e t T e x t ("btn" + i ) ;
b .s e t l d ( V i e w . g e n e r a t e V i e w I d ());
Unidad 2. Desenrollo de aplicaciones para móviles 77

g.addView(b,i);
}
}

Por cada iteración se producen las siguientes operaciones:

En primer lugar se instancia el botón:

b = new B u t t o n ( t h i s ) ;

A continuación se establecen los parámetros de tamaño para el Layout, en concreto,


se ajusta al contenido del botón, tanto en altura con la anchura:

b .s e t L a y o u t P a r a m s ( n e w V i e w G r o u p .L a y o u t P a r a m s (
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ,
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ) );

Se establece el tex to del botón y el identificador:

b . s e t T e x t ( "btn" + i ) ;

Observa cómo se establece el identificador único para cada uno de los 18 botones
mediante la línea:

b .s e t I d ( V i e w . g e n e r a t e V i e w I d ());

Para responder a eventos generales de los widgets hijos, necesitas un objeto Listener
que escuche por todos (o por ciertos elementos individuales). Por ejemplo, imagina que
quieres que cada botón del GridLayout de los ejemplos anteriores, responda a un evento
para hacer una acción determinada. Podríamos programar el siguiente código:

public class MyActivity extends Acti v i t y implements


View.OnClickListener{

protected void onCreate(Bundle savedlnstanceState) {


s u p e r .o n C r e a t e ( s a v e d l n s t a n c e S t a t e ) ;
s e t C o n t e n t V i e w ( R . l a y o u t .a c t i v i t y _ m y ) ;
a ñ a d e H i j o s ();
}

p u b l i c v o i d a ñ a d e H i j o s (){
G r i d L a y o u t g = (GridLayout) findViewByld(R.id.gridl);
B u t t o n b;
f o r ( i n t i = 0 ;i<18 ;i++) {
b = new Button(this);
78 Programación en Android

b .s e t L a y o u t P a r a m s ( n e w V i e w G r o u p .L a y o u t P a r a m s (
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ,
V i e w G r o u p .L a y o u t P a r a m s .W R A P _ C O N T E N T ));
b .s e t T e x t ("btn" + i);
b . s e t I d ( V i e w , g e n e r a t e V i e w I d ( ) );
b .s e t O n C l i c k L i s t e n e r ( t h i s ) ;
g .a d d V i e w ( b , i ) ;
}
}

public void onClick(View v ) {


i f ( v . g e t C l a s s (),g e t S i m p l e N a m e ().e q u a l s (" B u t t o n " )) {
B u t t o n b = (Button) v;
acción(b);
}
}

p u b l i c v o i d a c c i ó n (Button b ) {
/ / p r o g r a m a a q u í t u a c c i ó n c o n el b o t ó n b
}

Observa cómo en cada iteración se establece el listener a la propia actividad, para


que responda mediante el método onClick. Finalmente, cuando se presiona uno de los
botones del contenedor GridLayout, se invoca al método acción pasando como
parámetro el botón pulsado para consultas posteriores.

C A SO P R Á C T IC O : Realiza una aplicación que programáticamente maneje controles


dentro de un GridLayout. El programa deberá crear 18 botones automáticamente con
diferentes colores, al pulsar cada uno de ellos, se volverá blanco. El último botón será un
botón de RESET, de tal manera que cuando se pulse, se vuelva a la configuración
original.

í j f I GridLayout

btn4 btn5
tm
btn? btn8

btn9 btnIO

btn 12 btn13 btn 14

btn15 btntfi RESET!


Unidad 2. Desarrollo de aplicaciones para móviles 79

ACTIVIDAD 2.16.
Observa el video de cómo program ar el caso práctico:
https://w v'w .voutube.com /w atch?v= CZ92rG5ekU
PUEDES VER EL CÓDIGO FUENTE en el fichero gridlayout.rar del material
complementario.

2.5. LOS DIÁLOGOS Y LOS FR A G M EN T O S


Los diálogos son ventanas que aparecen en la pantalla de tu dispositivo móvil de
manera espontánea para hacer al usuario alguna pregunta sobre alguna decisión que
deba tomar para poder proceder con el funcionamiento normal de tu App.

Hay muchos tipos de diálogos, tienes diálogos con listas de elementos, diálogos con
botones de tipo Radio o checkboxes, incluso puedes utilizar un fichero de recursos XML
para definir un Layout en el que diseñes tu diálogo personalizado.

ACTIVIDAD 2.17.
Puedes encontrar más documentación sobre cómo personalizar tus diálogos en:
littu: / / developer.android.eom /guide/topics/ui/diaIogs.Iitm l#C ustom Lavout

Android recomienda usar la clase DialogFragment para contener diálogos. Un


fragmento de una actividad es una parte de la App que controla una parte de la interfaz
de usuario de la Activity. Los fragmentos tienen su propio ciclo de vida, reciben sus
propios eventos y se pueden eliminar o añadir a la Activity mientras esta se ejecuta. Un
DialogFragment es un tipo especial de fragmento usado para crear diálogos.

El caso práctico que te vamos a plantear a continuación es un escenario que te


surgirá muchas veces. Imagina que tienes una App que en base a la actuación de un
usuario necesitas enseñarle un cuadro de diálogo para hacerle una pregunta. En base a
la respuesta a esta pregunta, la aplicación tomará un curso de acción u otro. Si lo haces
con las recomendaciones de Android y programas el diálogo como un DialogFragment,
80 Programación en Android

además, te servirá para futuras ocasiones. Por ejemplo, supon que tienes una Actividad
con un botón, al pulsar el botón aparecerá un cuadro de diálogo preguntándote por tu
sexo. Al terminar el diálogo, este retornará devolviendo a la actividad el resultado
mediante un callback.
Observa el siguiente esquema:

Nuestra App esta vez tendrá dos clases, una con nuestra actividad principal
“MyActivity” y otra con una clase llamada DialogoSexo que extenderá la clase
DialogFragment.

El código de la actividad principal es muy sencillo:

public class MyActivity extends Acti v i t y implements


D i a l o g o S e x o .R e s p u e s t a D i a l o g o S e x o {

@0verride
p u b l i c v o i d o n R e s p u e s t a ( S t r i n g s) {
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t (),s,
T o a s t .L E N G T H _ L O N G ) .s h o w ();
}

public void click(View v ) {


DialogoSexo ds=new DialogoSexo!);
d s .s h o w ( g e t F r a g m e n t M a n a g e r ( ) , "Mi d i á l o g o " ) ;
}

©Override
protected void onCreate(Bundle savedlnstanceState) {

}
}
Programamos una función que responda al clic del botón y creamos un objeto de la
clase DialogoSexo, invocando al método show de un Dialog, que recibe como parámetros
un objeto llamado FragmentManager que se obtiene con la llamada al método
Unidad 2. Desarrollo de aplicaciones para móviles 81

getFragmentManager() de la Activity, y una etiqueta con el texto identificador del


diálogo.
Además, hay que recibir la respuesta que nos envía el diálogo, esto lo haremos
registrando una función de callback llamada onRespuesta(), método que implementamos
a través de una interfaz llamada RespuestaDialogo y que programaremos nosotros en la
clase DialogoSexo. Este método onRespuesta crea un pequeño mensaje a modo de
notificación que le aparece al usuario en el dispositivo móvil a través de la clase Toast.
Crear una notificación es muy fácil con la clase Toast, cuya documentación puedes
consultar en http: / / developer.android.com/guide/ topics/u i/ notifiers/ toasts.html
Por otro lado, la clase DialogoSexo, tiene el siguiente código:

public class DialogoSexo extends DialogFragment {

RespuestaDialogoSexo respuesta;

/* Este método es llamado al hacer el show() de la clase D ialogFragment()


@0verride
public Dialog onCreateDialog(Bundle savedlnstanceState) {

// Usamos la clase Builder para construir el diálogo


AlertDialog.Builder builder = new AlertDialog.B u i l d e r ( g e t A c t i v i t y O ) ;
//Escribimos el título
b u i l d e r .s e t T i t l e ("Pregunta muy importante:");
//Escribimos la pregunta
builder.setMessage("¿Eres una chica?");
//añadimos el botón de Si y su acción asociada
b u i l d e r .setPositiveButton("¡SI!",new Dialoglnterface.OnClickListener(){
public void onClick(Dialoglnterface dialog, int id) {
respuesta.onRe s p u e s t a ("Es una chica!");
}
} );
//añadimos el botón de No y su acción asociada
b u i l d e r .setNegativeButton("¡NO!",new Dialoglnterface.OnClickListener(){
public void onClick(Dialoglnterface dialog, int id) {
respuesta.o n R espuesta("Es un chico!");
}
}>;
// Crear el AlertDialog y devolverlo
return b u i l d e r .c r e a t e ();
}
//interfaz para la comunicación entre la Actividad y el Fragmento
public interface RespuestaDialogoSexo{
public void o n R espuesta(String s ) ;
}

//Se invoca cuando el fragmento se añade a la actividad


@Override
public void o n A t t a c h (Activity activity) {
s u per.onAttach(activity);
respuesta=(RespuestaDialogoSexo)activity;
}

}
82 Programación en Android

Desgranamos paso a paso el código fuente:

Los diálogos se crean mediante la clase AlertDialog.Builder, estableciendo el título


con setTitle(), el mensaje principal setMessage(), y estableciendo los botones de sí o no
con su correspondiente respuesta a través de una función de callback de la interfaz
Dialoglnterface.OnClickListener. Todo esto se hace reprogramando el método
onCreateDialog de la clase DialogFragment, método que se invoca cuando la actividad
principal ejecuta el show de nuestra clase DialogoSexo.
Cuando el usuario pulsa uno de los botones del diálogo (sí o no), se desencadena la
llamada a la función de callback onRespuesta que se ha programado en la actividad
principal. Para poder hacer esto, se recurre a un ingenioso mecanismo:

• Cuando el diálogo se crea, el fragmento se une “A ttach” a la actividad


principal. En ese momento el método onAttach() se ejecuta y podemos
quedarnos con una referencia a la actividad.
respuesta=(RespuestaDialogoSexo)activity;
• Gracias a esa referencia a la actividad, podremos invocar al método
onRespuesta de la interfaz que obligamos a la actividad principal a
implementar.

b u i l d e r .setNegativeButton("¡NO!", new Dialoglnterface.OnClickListener(){


public void onClick(Dialoglnterface dialog, int id) {
respuesta.onRespuesta("Es un chico!");
}
}>;

La clase Builder de AlertDialog, tiene un montón de métodos que te serán de utilidad


para crear tu diálogo. Por ejemplo, además de los que has visto en el código de ejemplo
(setTitle, setMessage, setPositiveButton,setNegativeButton) tienes métodos como
s e tlte m s () para coger de un array una lista de elementos a seleccionar,
se tS in g le C h o ic e Ite m s() si quieres mostrar una lista con RadioButtons y
s e tM u ltiC h o ic e Ite m s () si quieres mostrar una lista de elementos con Checkboxes.
Esta clase, con estos métodos te da toda la libertad del mundo para construir casi
cualquier diálogo, no obstante, siempre puedes construirte tu propio recurso de layout
personalizado e inflar el diálogo a partir de este recurso personalizado.

ACTIVIDAD 2.18.
Puedes ver el código de este ejemplo en el fichero DialogosConRespuesta.rar del
material complementario.
Unidad 2. Desarrollo de aplicaciones para móviles 83

2.6. LOS W ID G ETS DE SELECCIÓN


Los widgets de selección permiten al usuario elegir un valor de una lista de posibles
valores. De esta manera nos evitaremos tener que hacer miles de comprobaciones a la
hora de validar la imprevisible entrada del usuario.
El widget de selección más típico de Android es el ListView. Para explicarte los
conceptos básicos usaremos esta clase, para que luego, con otros selectores, podamos
aplicar de forma más rápida los conceptos que aprenderás en este apartado. ListView
también es una subclase de la clase ViewGroup por lo que también es un contenedor,
pero con una peculiaridad: Los a d ap tad ores. Los adaptadores son objetos que actúan
de puentes entre un widget y sus datos. Determina, entre otras cosas, el tipo de datos
que va a contener el widget. Por ejemplo, para crear una actividad con una lista de
elementos seleccionables, crea un nuevo proyecto en Android y desde la paleta de
componentes, inserta un ListView y dos etiquetas para que quede una interfaz de
usuario como en la imagen:

Containers
[TI RadioGroup Ifi Selecciones
SE ListView
T I GridView Jigs---
j Item 1
§ f ExpandablelistView I sub tt«n i
[.tj ScroliView
Item 2
TT HorizontaIScrollView
O», SearchView
TabHost Item 3
PT StidingDrawer
¡é» Gallery Item 4
Q VideoView
=ss: TweLineListítem
PC] DialerFilter
Date 8 t Time
H1 TextClock
® AnalogClack
H1 BtgitglC'le ck
1¡! Chronometer
?f! DatePicker <1 O □
BIS Ti mcD¡<*t-cr

Para poder llenar tu lista de contenido necesitarás dos cosas:


• Un adaptador
• Un nuevo recurso a modo de layout file con la definición de un TextView que
se usará como elemento simple de tu ListView.
Para hacerlo, tan solo tienes que declarar en el código fuente un objeto de esta clase
y rellenarlo con los elementos de un array. Estos elementos, a través de un adaptador,
serán canalizados hasta los elementos individuales que contenga la lista. Aquí te
mostramos cómo hacerlo. Primero, inserta un nuevo fichero de recursos con el nombre
“fila.xml”:
84 Programación en Android

New Android resource file

X Cut Ctrl+X
uild
B File
0 Copy Ctrl+C
bs
2 Directory
Copy Path Ctrl+M ayús+C
'C
Im age Asset
¡3 an d rc
, Copy Reference Ctrl+Alt+M ayús+C

Í3 m ain Paste Ctrl+V •8 AIDL

• C 3jav,• m Activity
Find Usages Alt+F7
- res Folder
Find in Path... Ctrl+M ayús+F

A continuación abre el fichero fila.xml e inserta el código para crear un TextView con
el formato que desees, por ejemplo, inserta este código:

< T e x t V i e w x m l n s :a n d r o i d = "h t t p :/ / s c h e m a s .a n d r o i d . c o m / a p k / r e s / a n d r o i d "


a n d r o i d :o r i e n t a t i o n = " v e r t i c a l "
a n d r o i d :l a y o u t _ w i d t h = " m a t c h _ p a r e n t "
a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d r o i d :t e x t S t y l e = " i t a l i c "
android:textSize="20dp"
android:textColor="#FFOOOO"
>
</TextView>

Después, programa el método onCreate de la siguiente manera:

@Override
p r o t e c t e d v o i d o n C r e a t e (Bundle savedlnstanceState) {

/ / C r e a r u n a r r a y c o n los e l e m e n t o s s e l e c c i o n a b l e s
S t r i n g [] e l e m e n t o s = { " T o l e d o " , " C i u d a d Real",
" C u e n c a " , " G u a d a l a j a r a " , " A l b a c e t e " };

/ / D e c l a r a s u n a d a p t a d o r de T e x t o (String)
A r r a y A dapter<String> adaptador;

super.onCreate(savedlnstanceState);
s e t C o n t e n t V i e w ( R . l a y o u t .a c t i v i t y _ m y ) ;

/ / O b t i e n e s u n a r e f e r e n c i a a la l i s t a
ListView 1 = (ListView)findViewByld(R.id.listView);

/ / C r e a s el a d a p t a d o r
a d a p t a d o r = n e w A r r a y A d a p t e r < S t r i n g > (this,
R . l a y o u t .f i l a , e l e m e n t o s ) ;
// L e d a s el a d a p t a d o r a la l i s t a
1.setAdapter(adaptador);
Unidad 2. Desarrollo de aplicaciones para móviles 85

Fíjate, para crear el adaptador, el constructor de la clase ArrayAdapter recibe tres


parámetros:
• La referencia a la propia actividad (this)
• La referencia al nuevo fichero de recurso que has creado (R.layout.fila)
• Los elementos del array con el texto que se escribirá en cada TextView que
forme la lista de selección.
Si pruebas el código en este punto, obtendrás el siguiente resultado:

í $ l S ele 1 # Selecciones

Elige: Elige:
Toledo’' T oledo
C iudad Real
C iu d a d R eal
Cuenca
C u enca
G uadalajara
G u a d a la ja ra
A lb a ce te
A lb a c e te

En este punto, solo te queda saber cuál fue la selección del usuario. Para eso,
necesitas registrar el evento con setOnltemClickListener(this), y recibirlo con el método
OnltemListClick de la interfaz OnltemClickListener.

public class MyActi v i t y extends A c t i v i t y implements


ListView.OnltemClickListener{

p u b l i c v o i d o n l t e m C l i c k ( A d a p t e r V i e w < ? > p a r e n t , V i e w view,


int p o s i t i o n , l o n g i d ) {
T e x t V i e w t = ( T e x t V i e w ) f i n d V i e w B y l d ( R .i d . t e x t V i e w ) ;
t .s e t T e x t ("Has e l e g i d o : " +
p a r e n t .g e t l t e m A t P o s i t i o n (position) .t o S t r i n g O ) ;
}

}”

Fíjate en el método onltemClick, que nos pasa como parámetro el componente padre
donde se hizo la selección (AdapterView<?>), la View (el widget) que se pulsó, la
posición que ocupa en la lista y el identificador de la fila que se seleccionó.

public void onltemClick(AdapterView<?> parent, View v, int position, long id)

Este método es el método callback que se invoca cuando se hace clic en alguno de los
elementos de la lista. Puedes, gracias a la posición que se pasa como parámetro (int
position), elegir el elemento que se ha seleccionado con el método getltemAtPosition de
la siguiente forma:
86 Programación en Android

t .s e t T e x t ("Has e l e g i d o : " + a .g e t l t e m A t P o s i t i o n ( p o s i t i o n ) .t o S t r i n g ());

O tra alternativa sería, como también te pasan la vista que se ha pulsado, convertir la
vista a TextView y obtenerlo con el método getText:
t .s e t T e x t ("Has e l e g i d o : " + ( ( T e x t V i e w ) v i e w ) . g e t T e x t ());

Y no te olvides de registrar el listener de la lista, de lo contrario, no hará el callback.


1.setOnltemClickListener(this);

En la próxima unidad veremos la clase ListActivity, un tipo de actividad especial que


lleva incrustada una lista y que puede invocarse de manera externa.

ACTIVIDAD 2.19.
Puedes ver el código de esta unidad en el fichero Selecciones.rar del material
complementario.

2.6.1. SELECCIONANDO MÚLTIPLES ELEMENTOS


Un ListView se puede configurar para poder escoger selección múltiple. Esto se puede
seleccionar mediante el atributo XML android:choiceMode="multipleChoice" o
simplemente invocando el método setChoiceMode(CHOICE_MODE_MULTIPLE)
desde código fuente.
Además, deberás utilizar como Layout para la selección el predefinido por Android
para la selección múltiple, llamado android.R.layout.simple_list_item__multiple_choice,
de tal manera que al crear el adaptador, debes indicarlo como segundo parámetro:

a d a p t a d o r = n e w A r r a y A d a p t e r < S t r i n g > (this,


a n d r o i d . R . l a y o u t .s i m p l e _ l i s t _ i t e m _ m u l t i p l e _ c h o i c e ,
elementos);

Los métodos para capturar los eventos de selección funcionan exactamente igual que
un ListView de selección simple. Eso sí, si queremos explorar todos los elementos
seleccionados, por ejemplo mediante un bucle, podemos usar el siguiente código:

public void o n l temClick(AdapterView<?> a. View view, int position, long i d ) {


TextView t=(TextView)findViewByld(R.id.textview);
ListView 1 = (ListView)findViewByld(R.id.listView);
String seleccionado=new S t r i n g ();
SparseBooleanArray checked = l.getCheckedltemPositions();

for(int i = 0 ;i<checked.size();i++)
if(checked.valueAt(i)){
seleccionado=seleccionado+
a.getltemAtPosition(checked.keyAt(i)).t o S t r i n g ()
T> It /
. II .
/
}
t .set T e x t (seleccionado);
}
}
Unidad 2. Desarrollo de aplicaciones para móviles 87

Bicje:
Toledo

Ciudad Real m

Cuenca □

Guadalajara p

Albacete o

<

Esta fracción de código responde al evento de hacer una selección en uno de los
elementos de la ListView. Utiliza un array booleano disperso (SparseBooleanArray) para
obtener de la lista los elementos que están seleccionados (checked):

S p a r s e B o o l e a n A r r a y c h e c k e d = 1 . g e t C h e c k e d l t e m P o s i t i o n s ();

El método getCheckedltemPositions de la ListView devuelve este tipo de array para


que puedas consultar qué valores se han seleccionado.
Puedes consultar toda la documentación del SparseBooleanArray en
http: / / developer.android.com/ reference/android/ util/ SparseBooleanArray.html pero en
resumen, puedes ver que el método valueA t(i) devuelve un booleano indicando si el
elemento i del array fue seleccionado o no. Gracias al método keyAt(i) de este array,
devuelve un número indicando la posición de un elemento seleccionado dentro de la
ListView. De esta manera se puede hacer un bucle desde O hasta el tamaño del array, así
como extraer los valores múltiples seleccionados.

ACTIVIDAD 2.20.
Puedes ver el código completo en el fichero SeleccionMultiple.rar del material
complementario.

2.6.2. LOS SPINNERS


Los Spinners en Android son los equivalentes a los ComboBox de otros sistemas, es
decir, listas desplegables. Funcionan prácticamente igual que los ListView, esto es, hay
que utilizar un adaptador para enchufar los datos y capturar los eventos de selección, en
el caso de los Spinners a través de setOnltemSelectedListener () de la interfaz
OnltemSelectedListener de la clase Spinner.
88 Programación en Android

i f I Spinner

Elige: Elige:

Toledo Toledo

Toledo

Ciudad Real

Cuenca

Guadalajara

Albacete

El adaptador se programa prácticamente igual:

adaptador = new A r r a y A d a p t e r < S t r i n g > (this,


a n d r o i d . R . l a y o u t .s i m p l e _ s p i n n e r _ i t e m , e l e m e n t o s ) ;
a d a p t a d o r .s e t D r o p D o w n V i e w R e s o u r c e (
a n d r o i d . R . l a y o u t .s i m p l e _ s p i n n e r _ d r o p d o w n _ i t e m ) ;

En este caso hay dos layouts, uno para mostrar el elemento seleccionado
android.R.layout.simple_spinner_item que se pasa al constructor del adaptador como
segundo parámetro (al igual que en el ListView) y otro para mostrar los elementos
desplegables android. R.layout.simple_spinner__dropdown__item, que se especifica a
través del método setDropDownViewResource():

p r o t e c t e d v o i d o n C r e a t e (Bundle s a v e d l n s t a n c e S t a t e ) {
String!] e l e m e n t o s = { " T o ledo", " C i u d a d Real",
"Cuenca", " G u a d a l a j a r a " , " A l b a c e t e " } ;

ArrayAdapter<String> adaptador;

s u p e r .o n C r e a t e ( s a v e d l n s t a n c e S t a t e ) ;
s e t C o n t e n t V i e w ( R .l a y o u t .a c t i v i t y _ m y ) ;

S p i n n e r sp = (Spinner) f i n d V i e w B y l d ( R .i d .s p i n n e r ) ;
a d a p t a d o r = n e w A r r a y A d a p t e r < S t r i n g > (this,
a n d r o i d . R . l a y o u t .s i m p l e _ s p i n n e r _ i t e m , e l e m e n t o s ) ;
a d a p t a d o r .s e t D r o p D o w n V i e w R e s o u r c e (
a n d r o i d . R. l a y o u t .s i m p l e _ s p i n n e r _ d r o p d o w n _ i t e n i ) ;
s p .s e t A d a p t e r ( a d a p t a d o r ) ;
s p .s e t O n l t e m S e l e c t e d L i s t e n e r ( t h i s ) ;
}
Unidad 2. Desarrollo de aplicaciones para móviles 89

Para capturar el evento de selección de un elemento hay que implementar dos


métodos, puesto que la interfaz OnltemSelectedListener exige los dos:

public class MyActivity extends Acti v i t y implements


S p i n n e r .O n l t e m S e l e c t e d L i s t e n e r {

/ / C a l l b a c k c u a n d o se s e l e c c i o n a u n e l e m e n t o d e l Spinner
p u b l i c v o i d o n l t e m S e l e c t e d ( A d a p t e r V i e w < ? > a,
V i e w v i ew, int p o s i t i o n , l o n g i d ) {

T e x t V i e w t= (TextView) f i n d V i e w B y l d (R. id. t e x t V i e w ) ,-


S p i n n e r sp = (Spinner) f i n d V i e w B y l d ( R . i d . s p i n n e r ) ;

t .s e t T e x t ( s p .g e t S e l e c t e d l t e m ().t o S t r i n g ());
}

/ / C a l l b a c k c u a n d o se n o se s e l e c c i o n a u n e l e m e n t o d e l S p i n n e r
public void onNothingSelected(AdapterView<?> a ) {
TextView t=(TextView)findViewByld(R.id.textView);
t .s e t T e x t ("No se h a s e l e c c i o n a d o n a d a " ) ;
}

ACTIVIDAD 2.21.
Puedes ver este código en el fichero Spinner.rar del material complementario

2.6.3. LAS SELECCIONES PERSONALIZARLES


Cualquier tipo de control de selección es personalizadle. Es decir, podemos definir la
estructura de cada fila de la lista de selección como nosotros queramos. Para poder
integrarlo con un Spinner, o una ListView, hay que reescribir el funcionamiento del
adaptador (ArrayAdapter<T>) de manera que cuando el selector demande los datos de
las filas, devuelva una vista que sea la que nosotros queramos en cada momento. Es
esencial entender dos conceptos:
• El inflador: Se pueden crear vistas (View) en tiempo de ejecución a partir de
un fichero de recursos XML. De esta manera, se puede crear un widget
personalizado e “inflarlo” (crearlo) habiendo definido su estructura mediante
un Layout definido en XML.
• Se puede hacer “override”, es decir, redefinir métodos de una clase padre. En
concreto, del adaptador hay que reprogramar dos métodos,
getDropDownView(int position, View convertView, ViewGroup parent) y
getView(int position, View convertView, ViewGroup parent) que retornan la
vista personalizada de una determinada fila. Cuando el Spinner invoque a estos
métodos nosotros debemos pasarle la fila personalizada de la posición que nos
indique el parámetro posición.
90 Programación en Android

En el siguiente caso práctico, queremos mostrar un Spinner personalizado, de tal


manera que nos muestre las ciudades de Castilla-La Mancha, pero esta vez con un
formato personalizado:
La App consta esta vez de dos ficheros XML, textView
el propio de la actividad, con un Spinner y una nombre
caja de texto donde se escribirá el resultado de
la elección, y el fichero XML con el diseño de textView
una fila del Spinner. El fichero de la fila, descripción

llamado lineaspiner.xml tiene el siguiente


código: ImageView
imagenCiudad

<R e 1 a t i v e L a y o u t
a n d r o i d :l a y o u t _ w i d t h = "f i l l _ p a r e n t "
a n d r ó i d :1a y o u t _ h e igh t = " f i 1 l _ p a r e n t "
x m l n s :a n d r o i d = " h t t p :/ / s c h e m a s .a n d r o i d . c o m / a p k / r e s / a n d r o i d ">

cTextView
a n d r o i d :l a y o u t _ w i d t h = "w r a p _ c o n t e n t "
a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d r o i d : text="Albacete, qué gra n ciudad!"
android:id="@+id/descripcion"
a n d r o i d :l a y o u t _ a l i g n P a r e n t T o p = " t r u e "
a n d r o i d :l a y o u t _ a l i g n P a r e n t L e f t = " t r u e "
a n d r o i d :l a y o u t _ a l i g n P a r e n t S t a r t = "t r u e "
android:ellipsize="none"
a n d r o i d :s c r o l l H o r i z o n t a l l y = " f a l s e "
a n d r o i d :l a y o u t _ m a r g i n T o p = "2 6 d p "
android:layout_alignBottom="@+id/imagenCiudad"
android:layout_alignRight="@+id/nombre"
a n d r o i d : l a y o u t _ a l i g n E n d = " @ + i d / n o m b r e " />

<TextView
a n d r o i d :l a y o u t _ w i d t h = "w r a p _ c o n t e n t "
a n d r o i d :l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d r o i d :t e x t A p p e a r a n c e = "?a n d r o i d :a t t r / t e x t A p p e a r a n c e L a r g e "
android:text="Albacete"
android:id="@+id/nombre"
a n d r o i d :l a y o u t _ a l i g n P a r e n t T o p = " t r u e "
android:layout_alignParentLeft="true"
a n d r o i d : l a y o u t _ a l i g n P a r e n t S t a r t = " t r u e " />

<ImageView
a n d r o i d :l a y o u t _ w i d t h = "w r a p _ c o n t e n t "
a n d r ó i d :1a y o u t _ h e i g h t = " w r a p _ c o n t e n t "
a n d r o i d :s r c = " @ d r a w a b l e / a l b a c e t e "
android:id="@+id/imagenCiudad"
android:layout_toEndOf="@+id/descripción"
a n d r o i d :l a y o u t _ a l i g n P a r e n t T o p = " t r u e "
a n d r o i d :l a y o u t _ a l i g n P a r e n t R i g h t = "t r u e "
a n d r o i d : l a y o u t _ a l i g n P a r e n t E n d = " t r u e " />
< / R e 1 at i v e L a y o u t >
Unidad 2. Desarrollo de aplicaciones para móviles 91

En el fichero Java de la actividad, vamos a crear una subclase de la clase


Array Adapter, con los dos métodos reescritos, esta clase se apoyará en tres arrays de
datos para crear el objeto View que se mandará al Spinner, cada array tiene una
información para los diferentes campos que componen la fila del Spinner:

String!] ciudades = { "Toledo", "Ciudad Real",


"Albacete","Cuenca", "Guadalajara" };
String!] descripciones = { "La ciudad Imperial", "Qué gran ciudad",
"Ciudad gastronómica", "Ciudad encantada", "Ciudad colgante" };

int imágenes!] = { R.drawable.toledo, R . d r awable.ciudadreal,


R .drawable.albacete, R .d r a w a b l e .c u e n c a ,
R.drawable.guadalajara};

public class AdaptadorPersonalizado extends ArrayAdapter<String> {


public AdaptadorPersonalizado(Context ctx,
int txtViewResourceld, String!] objects)!
super(ctx, txtViewResourceld, objects);
}

@Override
public View getDropDownView(int position, View cnvtView, ViewGroup p r n t ) {
return crearFilaPersonalizada(position, cnvtView, prnt);
}

@Override
public View getView(int pos, View cnvtView, ViewGroup p r n t ) {
return crearFilaPersonalizada(pos, cnvtView, prnt);
}

public View crearFilaPersonalizada(int position,


View convertView, ViewGroup par e n t ) {

Layoutlnflater inflater = getLayoutlnflater();


View miFila = inflater.inflate(R.layout.lineaspiner, parent, false);

TextView nombre = (TextView)miFila.findViewByld(R.id.n o m b r e ) ;


n o m b r e .s e t T e x t (ciudades[position]);

TextView descripción = (TextView)miFila.findViewByld(R.id.descripción);


descripción.s e t T e x t (descripciones[position]);

ImageView imagen = (ImageView)miFila.findViewByld(R.id.imagenCiudad);


imagen.setlmageResource(imágenes[position]);
return miFila;

}
}

Observa cómo los dos métodos reescritos invocan a una tercera función
crearFilaPersonalizada() que “i n f l a , la vista del fichero xml lineaspiner.xml para crear
un objeto View con los datos de los arrays y lo retorna.
92 Programación en Android

Cuando crees el Spinner, acuérdate entonces de enviarle el adaptador de esta


subclase:
protected void o n C r e a t e (Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.l a y o u t .activity_my);

Spinner selectorCiudades = (Spinner) findViewByld(R.id.spinner);


AdaptadorPersonalizado a=new AdaptadorPersonalizado(this,
R . l a y o u t .lineaspiner, ciudades);
selectorCiudades.setAdapter(a);
selectorCiudades.setOnltemSelectedListener(this) ;
}

De esta forma, quedan resultados tan espectaculares como éste:

$ S i» & 7 o 7 ^ T o T , O 20:30

S SpinnerPersonai 'l l 1 SpinnerPersonal

ACTIVIDAD 2.22.
Puedes ver este código en SpinnerPersonalizado.rar del material complementario.

2.6.4. LOS SELECTORES DE FECHA/HORA


En inglés se llaman Pickers, DatePicker o TimePicker. El uso de estos selectores te
evitará muchas comprobaciones en código sobre si el usuario escribió correctamente el
formato de hora/fecha o si se ajustan a las configuraciones locales del dispositivo móvil o
si es una fecha válida.
Unidad 2. Desarrollo de aplicaciones para móviles 93

dom, 12 de octubre de 2014 0 9 :5 5

11 sep 2013 08 54

12 oct 2014 09 55

13 nov 2015 10 56

Cancelar Definir Cancelar Definir

Al igual que los Diálogos, y aunque se pueden usar directamente en un Layout,


Android también recomienda utilizar la clase DialogFragment para implementarlos.
Puedes releer el Apartado 2.5 si quieres repasar lo que era un DialogFragment porque en
el siguiente caso práctico los vamos a volver a utilizar.
El caso práctico consiste en un sencillo formulario que muestra al usuario la
posibilidad de introducir su fecha de nacimiento y una hora a través de un campo de
texto (EditText). Estos campos de fecha y hora estarán inhabilitados para forzar al
usuario a pulsar un botón para poder seleccionar de un Picker tanto la fecha como la
hora. Al pulsar en cada respectivo botón se le mostrará un diálogo con su Picker
correspondiente:

Para cada diálogo creamos una clase que herede de DialogFragment y que poseerá
una interfaz para comunicarse con la actividad principal, devolviendo esta un objeto
Fecha cuando se haya seleccionado tanto la fecha como la hora. Para la devolución
utilizaremos la clase GregorianCalendar, que sustituye a la clase Date de Java al quedar
esta depreciada (deprecated) en la API 1. Para la creación del diálogo se puede usar un
Layout XML que contenga un Picker o directamente utilizar las clases DatePickerDialog
y TimePickerDialog que directamente construyen un Dialog con un Picker. En nuestro
ejemplo usaremos esta segunda opción. Además, al igual que en el caso práctico del
punto 2.5, al crear el fragmento se ejecutará el método onAttach, momento que
aprovecharemos para quedarnos con una referencia a la actividad y así poder invocar al
método onResultadoFecha/onResultadoHora de la interfaz y pasar el resultado de la
elección del usuario a la actividad principal.
94 Programación en Android

La clase DialogoFecha consistirá en:


public class DialogoFecha extends DialogFragment implements
DatePickerDialog.OnDateSetListener{

OnFechaSeleccionada f;
©Override
public void onAttach(Activity activity) {
f= (OnFechaSeleccionada)activity;
super.onAttach(activity);
}

©Override
public Dialog onCreateDialog(Bundle savedlnstanceState) {

Calendar c=Calendar.getlnstance0;
int año=c.get (Calendar.YEAR) ,-
int mes=c.get(Calendar.MONTH);
int dia=c.get(Calendar.DAY_OF_MONTH);

return new DatePickerDialog(getActivity(),this,año,mes,dia);


}

©Override
public void onDateSet(DatePicker datePicker, int i, int i2, int i3) {
GregorianCalendar g=new GregorianCalendar(i,i2,i3);
f .onResultadoFecha (g) ,-
}

public interface OnFechaSeleccionada{


public void onResultadoFecha(GregorianCalendar fecha);
}

Y la clase DialogoHora es muy similar:


public class DialogoHora extends DialogFragment implements
TimePickerDialog.OnTimeSetListener}

OnHoraSeleccionada f;
©Override
public void onAttach(Activity activity) {
f= (OnHoraSeleccionada)activity;
super.onAttach(activity);
}

©Override
public Dialog onCreateDialog(Bundle savedlnstanceState) {
Calendar c=Calendar.getlnstance();
int hora=c.get(Calendar.HOUR);
int minutos=c.get(Calendar.MINUTE);

return new TimePickerDialog(getActivity(),this,hora,minutos,true);


}

©Override
public void onTimeSet(TimePicker timePicker, int i, int i2) {
GregorianCalendar g=new GregorianCalendar();
g .set(Calendar.HOUR,i);
g .set(Calendar.MINUTE,i2);
f .onResultadoHora(g);
}

public interface OnHoraSeleccionada}


public void onResultadoHora(GregorianCalendar hora);
}
Unidad 2. Desarrollo de aplicaciones para móviles 95

Al crear un objeto de estos tipos DialogoFecha/DialogoHora e invocar a su método


show(), se ejecutará el método onCreateDialog() que debe retornar el diálogo. Para crear
el diálogo usamos los constructores:

n e w D a t e P i c k e r D i a l o g ( g e t A c t i v i t y (),t h i s ,a ñ o , m e s ,d í a ) ;
n e w T i m e P i c k e r D i a l o g ( g e t A c t i v i t y () , t h i s ,hora, m i n u t o s ,true)

A estos constructores, les pasamos los valores de fecha y hora actuales (año, mes, día)
y (hora, minutos) obtenidos la clase Calendar. La clase Calendar nos proporciona acceso
a la fecha y hora actual del dispositivo móvil. Para formar una fecha y una hora a partir
de la elección del usuario usamos los métodos de callback on<X>Set de las interfaces
OnXSetListener, donde <X> puede ser Time o Date. Estos métodos nos avisan de que
el usuario ha definido una nueva fecha y hora en el diálogo y ha retornado con éxito de
la selección. En ese momento, se construye un objeto GregorianCalendar con los valores
seleccionados por el usuario (estos se reciben en los parámetros de la función
On<X>Set) y se invoca a la función onResultadoHora/onResultadoFecha de la interfaz
correspondiente y que debe implementar la actividad. Estos dos últimos métodos reciben
como parámetro un objeto GregorianCalendar, subclase de Calendar.

ACTIVIDAD 2.23.
Puedes obtener más información sobre estas dos clases en:
littp: / /developer.android.coin/reference/java/util / C alendar.htm l
http://developer.android.com /reference/iava/util/G regorianC aIeiidar.htm l
Por último, la actividad principal, tendrá 4 funciones programadas:
public void onClickFecha(View view) (
DialogoFecha d=new Dialog o F e c h a ();
d.show(getFragmentManager(),"Mi diálogo Fecha");
}
public void onClickHora(View v i e w ) {
DialogoHora d=new D i a l o g o H o r a ();
d.show(getFragmentManager(),"Mi diálogo Hora");

@Override
public void onResultadoFecha(GregorianCalendar fecha) {
EditText et=(EditText)findViewByld(R.id.etFechaNacimiento);
e t .setText(fecha.g e t (Calendar,DAY_0F_M0NTH)+"/"+
(fecha.g e t (Calendar.MONTH)+1)
+"/"+fecha.get(Calendar.Y E AR));
}

@Override
public void onResultadoHora(GregorianCalendar hora) {
EditText et=(EditText)findViewByld(R.id.etHora);
e t .setText(hora.get(Calendar.HOUR)+ " : "+hora.g e t (Calendar.MINUTE));
}______ ______ ____________________________________
96 Programación en Android

Las funciones onClickFecha y onClickHora son los callback de los botones que pulsa
el usuario cuando quiere cambiar la fecha, y onResultadoFecha y onResultadoHora son
las funciones de callback que hay que implementar por la necesidad de comunicarse con
el DialogFragment correspondiente.

public class MyActivity extends Activity implements


DialogoFecha.OnFechaSeleccionada,DialogoHora.OnHoraSeleccionadaj

ACTIVIDAD 2.24.
Sigue la demo de cómo realizar este caso práctico en
https: / / www.voutube.com / wat ch?v=aI7ghiVDRF4

ACTIVIDAD 2.25.
Puedes ver el código del ejemplo en el fichero D atePicker.rar del m aterial complementario.

2.7. C O N ST R U C C IÓ N DE M EN Ú S
Crear elementos de menus en Android es muy sencillo. Tan solo tienes que definir un
archivo de recursos XML dentro de la carpeta 11Menu” del proyecto. Con solo dos
etiquetas (menu e item) puedes hacer cualquier organización jerárquica de menús. La
etiqueta <m enu> describe un grupo de elementos (ítem) que será cada entrada del
menú. Pero ojo, tenemos que advertirte: Los m en ú s y su b m en ú s han caído en
d esu so d esd e A nd roid 3 .0 + , p rin cip a lm en te por la e x isten cia de la A ctio n B a r
que exp licam o s en el sig u ien te p u n to .

¡7 5554:MiAndi

1 * 6:10


«1 Menus ■

Hello world1 Clientes

Facturas

Ajustes

Hay varios tipos de menús:


Unidad 2. Desarrollo de aplicaciones para móviles 97

Menús de Opciones o Ajustes (settings): Son los menús que salen en la barra de acción
de tu App y se controlan a través de un método callback que genera automática
Android Studio y que se llama onOptionsItemSelected. Este método es invocado cuando
el usuario selecciona uno de los elementos de este menú. Android studio también genera
el código para crear el menú onCreateOptionsMenu, que “infla” un determinado menú
diseñado en un fichero de recursos xml.

Menús contextúales: Son los menús flotantes que aparecen cuando un usuario hace un
clic de manera prolongada en un elemento de la interfaz.

Menús de Pop-up: Visualizan elementos de menú en una lista vertical. Estos menús
aparecen anclados al elemento de la IU que provocó su aparición.

Submenus: Aparecen al seleccionar una opción de menú, reemplazando el menú principal


por las opciones del submenú.

Si utilizas el recurso m y .x m l estás definiendo el menú por defecto de la aplicación.


La función onCreateOptionsMenu “infla” este recurso por defecto. Por ejemplo, imagina
que quieres hacer una App típica de gestión comercial. Puedes tener como menú de
opciones de tu App el siguiente menú:

Los menús en la barra de acción, a partir de Android 3.0, aparecen también en la


parte superior derecha al presionar el botón de Overflow (icono 1 en la barra de acción),
en versiones anteriores aparecen en la esquina inferior izquierda al pulsar la tecla de
menú del dispositivo.
98 Programación en Android

El código en XML para este menú es:


cmenu x m l n s :android="h t t p ://schemas.a n d r o i d .com/apk/res/android"
x m l n s :tools="h t t p ://schemas.and r o i d .com/tools"
t o o l s :context=".MyActivity" >
<item android:id="@+id/ajustes"
android:title="Ajustes"
andr o i d :orderInCategory="100"
android:showAsAction="n e v e r ">
<menu> <!-- submenú -->
<item android:id="@+id/confFacturas"
android:title="Configuración de Facturas"
a n d r o i d :orderInCategory="1"/>
<item android:id="@+id/confPedidos"
an d r o i d :title="Configuración de Pedidos"
an d r o i d :orderInCategory="2"/>
<item android:id="@+id/confClientes"
android:title="Configuración de Clientes"
a n d r o i d :orderInCategory="3"/>
</menu>
</item>
<item android:id="@+id/Clientes"
android:t itle="Clientes"
andr o i d :orderInCategory="1">
<menu> <!-- submenú -->
<item android:id="@+id/NuevoCliente"
android:title="Nuevo Cliente"
a n d r o i d :orderInCategory="1" />
<item android:id="@+id/BuscarCliente"
a n d r o i d :title="Buscar Cliente"
android:orderInCategory="2" />
</menu>
</item>
<item android:id="@+id/Facturas"
a n d r o i d :t i t le="Facturas"
andr o i d :orderInCategory="2">
<menu>
<item android:id="@+id/NuevaFactura"
android:title="Nueva Factura"
android:orderInCategory="l" />
<item android:id="@+id/BuscarFactura"
android:title="Buscar Factura"
android:orderInCategory="2" />
</menu>
</item>

</menu>__________________________ ___________ ____________ ______

Observa cómo cuando dentro de una pareja de etiquetas <m enu> </m enu> incluyes
un <item > se crea una opción y si anidas de nuevo un pareja <m enu> < /m enu> se
crea un submenú.
Las opciones de cada <item > son las siguientes:
• an d ro id á d Identifícador para el elemento de menú. Gracias al id puedes luego
diferenciar en el código en qué elemento se hizo “Click”
• an d ro id :title Texto que aparece en el elemento de menú.
Unidad 2. Desarrollo de aplicaciones para móviles 99

• an d roid rord erln C ategory Orden en el que aparece el elemento de menú


dentro de su agrupación.
• androidricon Icono para el elemento de menú. Los iconos no aparecerán si tu
aplicación se ejecutan en Android 3.0 o mayor. Tan solo se mostrarán si
forman parte de tu ActionBar.
• an d ro id tsh o w A sA ctio n Indica si el elemento aparecerá como un elemento en
la barra de Acción. El valor never, hará el elemento de menú no se muestre en
la barra de acción. Si se especifica always, siempre aparecerá y si se especifica
ifRoom solo aparecerá si hay espacio suficiente en la barra de acción. Por
ejemplo, si quisiéramos que cierta parte de nuestro menú (por ejemplo, el
submenú de configuración) apareciera vinculado a la barra de acción, por
ejemplo, mediante un icono, tendríamos que usar las propiedades:

t « fi 53 G 18:29
V M en u sD em o
<item android:id="@+id/action_settings"
Hello world! C onfiguración d e Facturas
android:title="Ajustes"
android:icon="@drawable/sys_settings" C onfiguración d e P edidos
android:showAsAction="ifRoom"
C onfiguración d e C lientes
a n d r o i d :o r d e r I n C a t e g o r y = "100"
>
< m e n u > <!-- s u b m e n ú -->
<item android:id="@+id/confFacturas"
a n d r o i d : t i t l e = " C o n f i g u r a c i ó n de F a c t u r a s "
a n d r o i d :o r d e r I n C a t e g o r y = "1"/>
<item android:id="@+id/confPedidos"
a n d r o i d : t i t l e = " C o n f i g u r a c i ó n de P e d i d o s "
a n d r o i d :o r d e r I n C a t e g o r y = "2"/ >
<item android:id="@+id/confClientes"
a n d r o i d : t i t l e = " C o n f i g u r a c i ó n de C l i e n t e s "
a n d r o i d :o r d e r ! n C a t e g o r y = "3"/>
</menu>
< / item>

También puedes diseñar menús con opciones seleccionables utilizando la opción


<group android:checkableBehavior='' modo'' > siendo modo single (solo un elemento es
seleccionable, tipo RadioButton), all (todos los elementos son seleccionables, tipo
CheckBox) y none (ningún elemento seleccionable).
Por ejemplo:

<menu>
<group android:checkableBehavior="single">
<item android:id="@+id/PedPend"
android:title="Ver Pedidos Pendientes" Ver Pedidos Pendientes
a n d r o i d : o r d e r I n C a t e g o r y = " l " />
Ver Pedidos Enviados
<item android:id="@+id/PedEnviados"
android:title="Ver Pedidos Enviados" Ver Pedidos Recibidos
a n d r o i d : o r d e r ! n C a t e g o r y = "2" />
Ver Pedidos Anulados
100 Programación en Android

< ite m a n d r o id : id = " @ + id /P e d R e c ib id o s"


a n d r o id :t it le = " V e r P e d id o s R e c ib id o s"
a n d r o i d : o r d e r I n C a t e g o r y = " 2" / >
< it e m a n d r o i d : id = " @ + id /P e d A n u la d o s"
a n d r o id : t i t l e = " V e r P e d id o s A n u la d o s"
a n d r o i d : o r d e r I n C a t e g o r y = " 2" / >
</g r o u p >
< /m en u >

2.7.1. DOTANDO DE ACCIÓN A LOS ELEMENTOS DE


MENÚ
Para responder al clic en un elemento de menú, hay que modificar el método
onOptionsItemSelected:

@ 0 v errid e
p u b l i c b o o l e a n o n O p t i o n s I t e m S e l e c t e d (M e n u l t e m i t e m ) {
/ / H a n d l e a c t i o n b a r i t e m c l i c k s h e r e . T he a c t i o n b a r w i l l
/ / a u t o m a t i c a l l y h a n d l e c l i c k s o n t h e Home/U p b u t t o n , s o l o n g
/ / a s you s p e c i f y a p a r e n t a c t i v i t y in A n d r o id M a n ife st.x m l.
in t id = ite m .g e tlte m ld O ;
sw itc h (id ){
ca se R .id .B u sc a r C lie n te :
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Se h a p u l s a d o
B u s c a r C l i e n t e " , T o a s t . LENGTH_LONG) . s h o w ( ) ;
retu rn tru e;
case R .id .C lie n te s :
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Se h a p u l s a d o
C l i e n t e " , T oast.LENG TH _LON G). s h o w ( ) ;
retu rn tru e;
case R .id .F actu ras:
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Se h a p u l s a d o
F a c t u r a s " , T o a s t . LENGTH_LONG). s h o w ( ) ;
retu rn tru e;
case R .id .a ju s te s :
case R .id .c o n fC lie n te s:
case R .id .con fF a ctu ra s:
ca se R .id .c o n fP ed id o s:
c a se R . i d .N uevaF actura:
ca se R. i d .N u ev o C lien te:
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Se h a p u l s a d o o t r o
e l e m e n t o d e menu
(" + i t e m . g e t T i t l e 0 + " ) " , T o a s t . LENGTH_LONG) , s h o w ( ) ;
retu rn tru e;
}
retu rn su p e r .o n O p tio n s Ite m S e le c te d (ite m );

Este método recibe como parámetro el elemento seleccionado Menultem Item, y a


partir de ahí podemos discriminar qué elemento se seleccionó. Por ejemplo, con el
método getltemld() del objeto ítem nos quedamos con el identificador y después
podemos comparar con un evaluador múltiple sw itch qué elemento de menú se pulso.
El método exige que se retorne el valor true si se procesó correctamente el clic del menú.
Unidad 2. Desarrollo de aplicaciones para móviles 101

2.7.2. MENÚS CONTEXTUALES


Un menú contextual ofrece opciones que afectan a un elemento de la interfaz, por
ejemplo un elemento de una lista o una imagen. Hay dos formas de hacer menús
contextúales:
io B C S o ^ . | 08:2(
*
m v ' © it
?üp) MenusDemo

Robb Stark
Robb Stark
Ned Stark
Ned Stark filfE
Brandon Stark
Brandon Stark

•- ’ .'S :| _ p Sansa Stark

Matar Aria Stark

Sanar
©
Enviar Mensaje Jaime Lannister

C u’ ■
-■■■* '
Cersev Lannister 5?
Cersey Lannister

Menú en Contextual Action Bar (CAB)


Menú contextual

El primero se utiliza cuando el usuario realiza una pulsación larga (long click) en un
elemento de la interfaz y el segundo visualiza una barra de acción contextual, en inglés
Contextual ActionBar (CAB). Esta CAB se puede programar para que aparezca
también cuando se produce un clic largo en un elemento de la IU o, por ejemplo, cuando
se selecciona uno o varios elementos de una lista.
A continuación mostramos en un caso práctico los dos tipos de menús. Primero el
menú flotante:
¿Te acuerdas de lo que era Inflar un View? Consistía en, a partir de la definición de
un fichero de recursos xml, crear una vista. Pues eso mismo es lo que hay que hacer,
definir un recurso de menú, para luego “inflarlo”.
102 Programación en Android

N ew M enu R esou rce File

Enter a new file name

starks.xml

C ancel

Después hay que registrar el componente al que se le va a asociar el menú contextual,


mediante el método registerForContextMenu(). Después hay que crear (override) el
método onCreateContextMenu() de Actividad e inflar el menú que hayas creado en xml.
Después, para responder a la selección del elemento del menú, se programa el método
onContextItemSelected().
Te mostramos a continuación el siguiente caso práctico. Vamos a crear una App para
jugar con los personajes de una famosa serie de televisión. En esta aplicación tenemos
dos listas de personajes. A la primera lista (con los personajes de la familia Stark) le
vamos a vincular un menú contextual flotante y a la segunda lista (con los personajes de
la familia Lannister) le vamos a asociar un menú contextual de Acción, tal y como
puedes ver en la primera figura de esta sección.

2.7.2.1. Creación del menú contextual


Este es el fichero de menú xml que vamos a crear:
<menu x m l n s :a n d r o i d = " h t t p :/ / s c h e m a s .a n d r o i d .c o m / a p k / r e s / a n d r o i d ">
c i t e m a n d r o i d :i c o n = " @ a n d r o i d :d r a w a b l e / i c _ d e l e t e "
a n d r o i d :title="Matar"
a n d r o i d : id="@+id/matar"
> < /item >
< ite m a n d r o i d : ico n = " @ a n d ro id :d r a w a b le /i c _ m e n u _ e d i t "
a n d r o id :title = " S a n a r "
a n d r o id : id = " @ + id /sa n a r " >
> < /item >
< ite m a n d r o id : icon = "@ an d roid : d r a w a b le /s y m _ c a ll_ in c o m in g "
a n d r o i d : t i t l e = " E n v i a r M ensaje"
a n d r o i d : i d = " @ + i d / e n v i a r m e n s a j e ">
> < /item >
< /m en u >

Primero hay que registrar la lista como la asociada al menú contextual, esto lo
puedes hacer con la función registerForContextMenu(lista) en el método onCreate de la
actividad:
Unidad 2. Desarrollo de aplicaciones para móviles 103

/ / C r e a m o s l i s t a de s t a r k s p a r a el m e n ú c o n t e x t u a l
starks=(ListView)findViewByld(R.id.listaStarks);
registerForContextMenu(starks);

Después, tienes que crear el menú onCreateContextMenu() para inflar el menú desde
el archivo de recursos xml:
@0verride
p u b l i c v o i d o n C r e a t e C o n t e x t M e n u ( C o n t e x t M e n u menu, V i e w v,
ContextMenu.ContextMenuInfo menulnfo) {
M e n u l n f l a t e r m = g e t M e n u I n f l a t e r ();
m . i n f l a t e (R.m e n u . s t a r k s , m e n u ) ;
s u p e r .o n C r e a t e C o n t e x t M e n u (menu, v, m e n u l n f o ) ;
}
Finalmente, tienes que crear el método onContextItemSelected() para responder a los
eventos del menú contextual. Este método recibirá como parámetro el elemento de menú
pulsado item. A través de este parámetro puedes obtener, con el método getMenuInfo()
de item puedes obtener un objeto AdapterContextMenuInfo con el que poder conocer
sobre qué elemento de la listView fue pulsado el menú contextual.

@ 0 v errid e
p u b l i c b o o le a n o n C o n te x tlte m S e le c te d (M e n u lte m item ) {
A d a p te r V ie w .A d a p te r C o n te x tM e n u I n fo i n f o =
(A d a p terV iew .A d a p ter C o n tex tM en u In fo ) i t e m . g e t M e n u I n fo ( ) ;

sw itch (ite m .g e tlte m ld O ) {


c a s e R .id .m a ta r :
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Hemos m a t a d o a " +
s t a r k s .g e tI te m A tP o s iti o n ( i n f o . p o s i t i o n ) ,
T o a s t , L E N G T H _ L O N G ) ,s h o w ( ) ;
retu rn tru e;
ca se R .id .sa n a r :
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t ( ) , "Hemos s a n a d o a " +
s ta r k s .g e tlte m A tP o s itio n (in fo .p o s itio n ),
T o a s t . LENGTH_LONG) . s h o w ( ) ;
retu rn tru e;
c a se R .i d . e n v ia r m e n sa je :
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t(),
"Le h e m o s e n v i a d o u n m e n s a j e a " +
s ta r k s .g e tlte m A tP o sitio n ( in f o .p o s it io n ) ,
T o a s t . LENGTH_LONG). s h o w ( ) ;
retu rn tru e;
d e fa u lt:
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t O ,
"Le h em o s h e c h o o t r a c o s a a " +
sta r k s.g e tlte m A tP o sitio n ( in f o .p o s it io n ) ,
T o a s t . L E N G T H _ L O N G ) ,s h o w ( ) ;
retu rn tru e;
}
}
2.7.2.2. Creación del menú action bar contextual
Crear el menú contextual en la barra de acción, exige un poco más de código, pero el
fundamento es el mismo. Primero, crear el menú. Ten en cuenta que en la barra de
104 Programación en Android

acción si se muestran los iconos, al contrario que en un menú normal, por tanto, la
adición de la propiedad androidúcon toma esencial importancia:

Primero creamos el menu en el fichero lannisters.xml


<menu xm lns: android= "h t t p : / / schem as. a n d ro id . c o m /a p k /re s /a n d ro id ">

c i t e m a n d r o i d :i c o n = " @ a n d r o i d : d r a w a b l e / i c _ d e l e t e "
android:title="Aniquilar"
android:id="@+id/aniquilar"
></item>
< i t e m a n d r o i d :i c o n = " @ a n d r o i d : d r a w a b l e / p r e s e n c e _ a w a y "
android:title="Encerrar"
android:id="@+id/encerrar"
></item>
<item android:icon="@android:drawable/btn_star"
android:title="Salvar"
android:id="@+id/salvar"
></item>
</menu>

Después, dentro del método onCreate de la actividad, creamos la segunda lista de


elementos a través de un ArrayAdapter<String> de tal manera que sean todos
seleccionables. Observa la última instrucción que registra el listener de Click en los
elementos de la lista (hay que implementar en la actividad la interfaz
ListView.OnltemClickListener, para que cuando se seleccione en una de las opciones de
la lista, aparezca la Action Bar Contextual.
//c r e a m o s l i s t a de l a n n i s t e r s ( s e le c c i o n a b l e m ú lt ip le ) p ara e l
/ / A c t i o n Mode C o n t e x t Menu
L istV ie w l i s t a L a n n i s t e r s = ( L i s t V i e w ) f i n d V i e w B y l d ( R .i d .l i s t a L a n n i s t e r s ) ;

A r r a y A d a p te r < S tr in g > ad ap tad or= n ew A r r a y A d a p te r < S tr in g > ( th is ,


a n d r o id .R .la y o u t. sim p le _ lis t_ ite m _ m u ltip le _ c h o ic e ,
g e t R e s o u r c e s ( ) . g e t S t r i n g A r r a y ( R . a r r a y . l a n n i s t e r s ) ),-

lis t a L a n n is t e r s . se tA d a p te r (a d ap tad or);


lis t a L a n n is t e r s . se tO n lte m C lic k L iste n e r (th is );

El gran truco para mostrar la barra de acción contextual es la creación de dos


objetos, uno de tipo ActionMode, que representa el modo contextual en la barra de
acción y otro de tipo ActionMode. Callback, que es la interfaz que se debe programar
para responder a sus eventos y que tiene su propio ciclo de vida también:
// M e n ú A c t i o n M o d e
A c t i o n M o d e m A c tio n M o d e ;
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
/ / C a l l e d w hen t h e a c t i o n mode i s c r e a t e d ; s t a r t A c t i o n M o d e () w a s c a l l e d
© O v errid e
p u b l i c b o o l e a n o n C r e a t e A c t i o n M o d e ( A c t i o n M o d e m o d e, Menu menu) {
/ / I n f l a t e a menu r e s o u r c e p r o v i d i n g c o n t e x t menu i t e m s
M e n u ln fla te r i n f l a t e r = m o d e .g e tM e n u In fla te r ();
i n f l a t e r . i n f l a t e ( R . m e n u . l a n n i s t e r s , m en u );
retu rn tru e;
Unidad 2. Desarrollo de aplicaciones para móviles 105

}
// Called each time the action mode is shown.
// Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
@0verride
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Se llama a este método cuando se ha pulsado en
// la lista de los lannisters
@0verride
public boolean onActionltemClicked(ActionMode mode, Menultem item) {

switch (item.getltemldO ) {
case R.id.aniquilar:
//hay que crear un A n i q u i l a r () para
//recorrer todos los elementos seleccionado (checked)
//en la listView
Toast.makeText(getApplicationContext(),
"Hemos aniquilado a algún Lannister",
T o a s t .LENGTH_LONG).s h o w ();
return t r u e ;
case R.id.encerrar:
Toast.makeText(getApplicationContext(),
"Hemos encerrado a algún Lannister",
T o a s t .LENGTH_LONG).s h o w ();
return true;
case R.id.salvar:
Toast.makeText(getApplicationContext(),
"Hemos salvado a algún Lannister",
Toast.LENGTH_LONG).show();
return true;
def a u l t :
return false;
}
}
// Called when the user exits the action mode
@0verride
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}

En este código tienes que fijarte en la creación de la interfaz. El método


onCreateActionMode() infla el menú lannisters.xml adjuntándose a la barra de acción.
El método onActionItemClicked() discrimina el elemento del menú que se seleccionó y se
actúa en consecuencia.

A C T IV ID A D 2.26.
M ira el código de esta aplicación en el fichero M enusDemo.rar del m aterial complementario
106 Programación en Android

2.8. EL A C T IO N B A R
Ya has visto en la última sección la utilidad de la ActionBar, aunque aplicado a
menús contextúales. La barra de acción es una de las principales características de tu
App. Está visible en todo momento mientras tu aplicación está visible y te da la
posibilidad de dotar a tu aplicación de una identidad (hasta ahora lo hemos hecho con
un icono), pero también te permite situar iconos para acciones importantes para tu
aplicación (por ejemplo buscar o crear algo importante) y permite que el usuario realice
una navegación consistente por toda la aplicación, por ejemplo a través de pestañas o
tabs.

** O S3 E ar
Imagen: ActionBar de gmail

A partir del API de nivel 11, se incluye en todas las actividades que utilicen el tema
Theme.Holo en el diseño de la actividad (Este es el tema por defecto). Si no quieres
incluir un ActionBar en tu App, tan solo tienes que cambiar el tem a de la actividad a
Theme.Holo.NoActionBar en el fichero styles.xml:

<!-- Base application theme. -->


<style n a m e = "A p p T h e m e " parent="a ndroid:T h e m e .H o l o .NoActionBar">
<!-- Customize your theme here. -->
</style>

No obstante, no recomendamos que prives a tus usuarios de las bondades de la


ActionBar, por tanto, a partir de ahora, te pondremos ejemplos de cómo utilizarla.

A C T IV ID A D 2.27.
Puedes utilizar y descargar iconos típicos de Android en
http://developer.android.coni/design/stvIe/iconograpliv.iitm I

Para añadir la barra de acción tan solo hay que modificar el método
onCreateOptionsMenu() e “inflar” el archivo de recursos de menú xml que hayas creado.
Cuando vayas a crear el menú recuerda que, al igual que en un menú contextual, puedes
solicitar que aparezca directamente en la barra de acción alguno de los elementos del
menú con la opción showAsAction=”ifRoom” o showAsAction=”always”.
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
Menulnflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_action_bar, m e n u ) ;
return s u p e r .onCreateOptionsMenu(menu);
}
Unidad 2. Desarrollo de aplicaciones para móviles 107

A continuación, para responder a los eventos de clic de los elementos que has
colocado en la Action Bar debes escribir tu código en el método
onOptionsItemSelected().
@Override
p u b l i c b o o l e a n o n O p t i o n s I t e m S e l e c t e d ( M e n u l t e m item) {
// H a n d l e p r e s s e s o n t h e a c t i o n b a r i t e m s
switch (item.getltemldO ) {
case R.id.borrar:
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t () ,
"Se h a p u l s a d o b o r r a r " ,T o a s t .L E N G T H _ L O N G ) .s h o w ();
r e t u r n true;
case R.id.edit:
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t (),
"Se h a p u l s a d o b o r r a r ", T o a s t .L E N G T H _ L O N G ) .s h o w ();
r e t u r n true;
c a s e R . i d . lla m a r :
T o a s t . m a k e T e x t ( g e t A p p l i c a t i o n C o n t e x t (),
"Se h a p u l s a d o b o r r a r ", T o a s t .L E N G T H _ L O N G ) .s h o w () ;
r e t u r n true;
default:
r e t u r n s u p e r .o n O p t i o n s I t e m S e l e c t e d (i t e m ) ;
}
}

2.9. OTROS W ID G ETS P A R A T U IU


Existen para tu uso y disfrute un montón de widgets y componentes de interfaz de
usuario que pueden ayudarte a crear una App muy profesional. Échale un ojo por
ejemplo a las barras de desplazamiento, webviews, Menús Pop-up y el uso de acciones
drag-and-drop. Herramientas como el progress-bar pueden dar a tu App un toque
profesional que le encantará al usuario cuando programes en tu aplicación procesos que
mueven la barra de progreso a toda velocidad. Calendarios, botones de zoom,
controladores de medios son widgets que facilitarán tu vida y que facilitarán el éxito de
tu App. Diferentes tipos de campos de textos, de barras de ítems, etc. Toda la
experiencia de los programadores de Google y de Linux puesta a tu disposición
totalmente gratis y agrupados en carpetas en la paleta de widgets para que puedas
utilizarlos de forma sencilla y millones de páginas de documentación y código para que
no tengas que hacer mucho esfuerzo en tu aprendizaje. Prueba también los controles de
tipo Tab a través de la “Split Action B ar” para que tu App pueda manejar varias
actividades cada una con su correspondiente Tab.
108 Programación en Android

Expert Containers Text Fields Widgets


: r Space [H RadioGroup Plain Text |Abj Plain TextView
■ft*' CheckedTextV'iew l§ ListView j l j Person Name ¡Ab| Large Text
A» QuickContactBadge F~1 GridView " : Password |AP Medium Text
jAbj ExtractEditText ExpandableüstView II 1Password (Numeric) |Abi Small Text
■Ur AutoCompleteTextView [Ti ScrollView f Button
i l i E-mail
" MuttiÁutoCompleteTe<t\i [Hi HorizontalScrollView I : Phone ! Small Button
X NumberPicker SearchView 11 Postal Address ® RadioButtcn
GJi ZoomButton " TabHost HI Multiline Text t¡£i CheckBox
Q. ZoomCcntrcIs gSlidingDrawcr 111 Time *■ Switch
1►1MediaControlier m Gallery H j Date — ToggleButton
$ GestureGveriayView 12 VideoView 111 Number Ü ImageButton
PH SurfaceView TwoLineLrstltcm I Number (Signed) 2 ImageView /-v
Ü3 TextureView PC DialerFilter I Number (Decimal) mm ProgressBar (Large)
| H stackView O Date & Time "* ProgressBar (Normal)
Layouts
VíewStub fflTextClock *■ ProgressBar (Small)
•FrameLayout
¡ Ai- ViewAnimator (5) ÁnalogClock ** ProgressBar (Horizontal)
ILinearLayout (Horizontal
^PviewFlípper HE DigitalOode *•* SeekBar -"V
|:: :::! LinearLayout (Vertical)
*§* ViewSwitcher Chronometer RatingBar
1 TableLayout
% ImageSwitcher 20" DatePicker r::‘ Spinner
fiÉTableRow
A¡ TextSwitcher 0o TimePicker WebView
F 3 AdapterViewFlipper
[i'Q RelativeLayout

2.10. M ÁS FU N C IO N E S DE CALLBACK
Ya conoces las funciones de callback más básicas OnClick, OnLongClick,
OnltemSelected, etc. Todas ellas implementadas mediante interfaces:

a — k.ia

Tienes una larga lista de interfaces con sus funciones de callback respectivas para
programar en tu dispositivo Android. Es tu deber como programador, familiarizarte con
todas ellas y probablemente no encuentres un texto de menos de 500 páginas que te
cuente todas y cada una de ellas. Como este texto no pretende ser una guía de referencia
sino una guía para ayudarte a comenzar a programar en Android, te redirigimos a la
documentación oficial de Android para aprender todas estas interfaces y funciones de
callback.
Unidad 2. Desarrollo de aplicaciones para móviles 109

PRÁCTICA 2. ENCUENTRA HIPOTENOCHAS


El objetivo de esta tarea es programar un videojuego muy
sencillo llamado encuentra hipotenochas. Este juego lo
puedes descargar de google playstore para probarlo y
examinar y analizar su funcionamiento.
Hipotenocha es la mascota de un proyecto de
matemáticas de varios institutos de Castilla La Mancha.
Nuestro objetivo es crear un App para que los chicos que
participan en este proyecto puedan jugar con la mascota.
En realidad el juego no es ninguna invención particular, es
una copia del popular juego “Buscaminas”, solo que el lugar
Sesea?gas 6i EducasesSanto*
de buscar minas, se busca “Hipotenochas”.
Juego tipo buscam inas basado en la

Tu objetivo es replicar y pro g ra m a r el m ism o famosa m ascota Hipotenocha


<
juego, dotándole si quieres, de tu toque personal. Es MÁS INFORMACIÓN

decir, en lugar de buscar hipotenochas, puedes


buscar algún otro tipo de person aje.
C O N S E JO : Para que te sea más fácil la programación estudia en profundidad cómo
funciona el buscaminas. Tienes muchísimos ejemplos en internet.
Lo único que te pedimos para tu App son los siguientes requisitos:
* Debe contener un menú en la ActionBar con las siguientes opciones:

* Encuentra hipotenochas!
- Instrucciones (Opción de Menú): Sacará un Diálogo con
las instrucciones del juego.
- Nuevo Juego (Opción de Menú): Comenzará un nuevo
ICONO DE MENÚ
EN ACTION BAR juego, rellenando el tablero de hipotenochas y presentando
la pantalla en blanco, dispuesta para que el jugador
comience a buscarlas.
- Configura el juego (Opción de Menú): Mostrará un
diálogo con RadioButton para poder seleccionar entre
MENO DE OPCIONES
varios niveles de dificultad (Principiante, Amateur,
Avanzado).
- Selecciona personaje (Opción de Menú, con icono visible
Instrucciones en la ActionBar): Permitirá seleccionar el tipo de
C om enzar juego!
personaje a buscar a través de un diálogo con un Spinner
que te permita seleccionar el gráfico a utilizar.
Configura el ju eg o
Los diálogos que lanzan los menús son los siguientes:
110 Programación en Android

Selección de personaje Cuadro de instrucciones:

Se ha desenganchado S Pen

% Encuentra hippiene»

I Instrucciones

I El juego es tipo buscaminas:


I Cuando pulsas en una casilla, sale
I un número que identifica cuántas
I hipotenochas hay alrededor: Ten
I cuidado porqués! p ulsasen una
I casilla que tenga una hipotenocha
I escondida, perderás. Si crees o
I tienes la certeza de que hay una
I hipotenocha, haz un click largo
I sobre la casilla para señalarla. No
I hagas un click largo en una casilla
I donde no hay una hipotenocha
I porque perderás. Ganas una vez
I hayas encontrado todas las
I hipotenochas.

Ok
[

F u n cio n a m ien to d el juego:


El juego presentará al iniciarse un tablero para nivel principiante (8 filas x 8
columnas) con 10 hipotenochas escondidas. Para construir el tablero puedes utilizar un
Layout de tipo GridView. Este tablero se puede configurar también en modo amateur,
con 12x12 filas (30 hipotenochas) y modo avanzado con 16x16 filas (60 hipotenochas).
En cada celda del tablero habrá o bien un ImageButton o un Button, dependiendo de
si hay hipotenocha o no.
El usuario podrá utilizar dos acciones: Clic o pulsación larga sobre una casilla
(marcar posición de hipotenocha) y clic o pulsación corta sobre una casilla (descubrir
contenido de una casilla).
Por tanto, el primer algoritmo que debes escribir es para rellenar una matriz, por
ejemplo de números enteros, donde tienes que ubicar 10 hipotenochas (por ejemplo con
valor -1). -1 hay hipotenocha, > -1 no hay hipotenocha.
El segundo algoritmo que debes programas es para calcular el número de
hipotenochas que hay alrededor de cada casilla. De esta manera, tendrás todo preparado
para que el usuario pueda ir descubriendo casillas. Si hay una hipotenocha en la casilla
que el usuario ha pulsado con el dedo, se le mostrará la hipotenocha, si no, se le
mostrará un número con el número de hipotenochas que tiene en las casillas adyacentes.
De forma opcional y para sacar más nota, puedes programar un algoritmo (te será
muy fácil si lo haces recursivo) de manera que, si el usuario pulsa sobre una casilla
donde hay 0 hipotenochas alrededor, descubra automáticamente todas las casillas que
hay alrededor (que seguro que no tienen hipotenochas):
Unidad 2. Desarrollo de aplicaciones para móviles 111

Se ha desenganchado S Pen

«R E ncuentra h ip o ten o ch as!

1 1 1

2 1 2

DESARROLLO DEL JUEGO:


El usuario puede efectuar dos acciones: Marcar hipotenocha (clic largo) o descubrir
casilla (clic corto).
Ocurrirán cuatro distintas situaciones:
1. El usuario realiza un clic largo (onLongClick) sobre una casilla donde no hay una
hipotenocha. El juego muestra la casilla descubierta y termina.
2. El usuario realiza un clic largo donde sí hay una hipotenocha, en este caso, se
marca y se indica que se ha encontrado una hipotenocha.

Encuentra hipotenochas!

2
Hipotenocha
se —
marca con el icono

\
1 i i 2

i 1 2 2

1
112 Programación en Android

3. Se realiza un clic corto (onClick) en una casilla donde sí hay una hipotenocha. El
juego termina con derrota mostrando una hipotenocha muerta (boca abajo y
tachada).
4. El usuario realiza un clic corto (onClick) en una casilla donde no hay una
hipotenocha. Se descubre el número que oculta.

C O N D IC IÓ N DE V ICTO RIA: El usuario gana cuando ha ocurrido la situación 2 y


ha marcado correctamente todas las hipotenochas.

C O N D IC IÓ N DE D ERR O TA : El usuario pierde cuando ha ocurrido la situación 1


y ha marcado correctamente todas las hipotenochas.
Criterios de corrección:
• La aplicación contiene el menú de Opciones pedido en el enunciado. (1
punto)
• La aplicación rellena el tablero correctamente con hipotenochas de forma
aleatoria. (1 punto)
• La aplicación calcula por cada casilla, cuantas hipotenochas hay alrededor. (2
puntos)
• La aplicación tiene programado el evento onLongClick en cada casilla del
tablero y responde de forma correcta ante las posibles posibilidades (fin de
juego, hipotenocha encontrada). (2 puntos)
• La aplicación tiene programado el evento onClick en todas las casillas del
tablero y responde de forma correcta ante las posibilidades (fin de juego,
casilla descubierta). (2 puntos)
• La aplicación descubre automáticamente las casillas adyacentes cuando
encuentra una casilla con valor 0 (0 hipotenochas en las casillas adyacentes).
(2 puntos)
UNIDAD 3

COMUNICACIONES

CONTENIDOS
3.1 Ejecución de proyectos en dispositivos reales
3.2 Comunicación con otros componentes
3.3 Solicitud de permisos
3.4 Los servicios
3.5 Conexiones a Internet
3.6 Las notificaciones
3.7 Recibiendo Broadcasts
3.8. Los mensajes de texto. Enviar y recibir SMS a través de código
3.9. Los proveedores de contenido (Content Provider)
3.10 Acceso a bases de datos SQLite
3.11 Las conexiones Bluetooth
3.12 Las alarmas
3.13 Publicación de aplicaciones en Google Play Store

V.
114 Programación en Android

3.1. EJECUCIÓ N DE PR O Y EC TO S EN
D ISPO SITIV O S REALES

Seguramente en este punto del texto te has aburrido de trabajar con el emulador.
Estamos de acuerdo contigo, es pesado y lento, y ya lo es bastante el entorno de
desarrollo como para añadir más lentitud a nuestro ya perjudicado equipo. Además,
probablemente te habrás dado cuenta de que el emulador carece del google play store,
google maps y aplicaciones muy útiles que te serán imprescindibles para desarrollar
aplicaciones con más potencia.

NOTA: Puedes intentar instalar el google playstore en el emulador siguiendo este tutorial:
h ttp : / /hmkcode.com / run-google-map-v2-on- android-em ulator /

En cualquier caso, es el momento de dejar el emulador de lado y, si te lo puedes


permitir, ejecutar tus Apps en dispositivos reales con hardware físico.
Hay dos formas de ejecutar un proyecto en un dispositivo móvil real:
1. Generando el proyecto y copiando el apk en el dispositivo. A partir de ahí, hay
que decirle a nuestro dispositivo que confíe en fuentes externas para poder
ejecutarlo.
2. Configurando el proyecto, nuestro ordenador de desarrollo y el dispositivo
móvil en modo depuración.
La primera forma la usaremos cuando ya tengamos una versión estable de nuestra
App.
La segunda forma es mucho más cómoda cuando estamos desarrollando, precisamente
porque es el entorno de desarrollo el encargado de transferir el proyecto al dispositivo
real de forma automática a través de un cable USB.
Para poder ejecutar un proyecto en un dispositivo real debes seguir tres pasos:
1. Convertir tu proyecto en depurable
2. Instala el driver USB para tu móvil en tu sistema operativo
3. Activa en tu dispositivo móvil la opción de depuración por USB
Sin estos tres pasos, será imposible que el entorno de desarrollo de Android Studio
pueda controlar la ejecución de tu App en un dispositivo real, dejándote la opción a)
como única posibilidad para ejecutar tu App en hardware físico.
Capítulo 3. Comunicaciones 115

P A S O 1:
Lo primero que tienes que hacer es convertirlo en depurable. Es decir, incluir en el
archivo de manifiesto “AndroidManifest.xml” dentro de la sección Application la
propiedad:
a n d r o i d :d e b u g g a b l e = t r u e ;

P A S O 2:
A continuación hay que configurar nuestro equipo para poder establecer una
comunicación con el dispositivo móvil. Para poder configurar el equipo de desarrollo hay
que instalar el driver USB para poder establecer comunicación vía USB con el
dispositivo móvil. Esta configuración depende de tres factores:
1. El sistema operativo que utilizas
2. El modelo de dispositivo móvil
3. El propio driver USB para el dispositivo móvil
Android Studio trae un driver universal para USB para los dispositivos de tipo Nexus
y similares dentro del directorio sdk->extras->google->usb driver. Este driver no es
válido para todos los dispositivos, por ejemplo los dispositivos móviles de marca
Samsung tienen sus propios drivers. Si tienes un Samsung, puedes descargar el
instalador desde: h ttp: / / developer.samsung.com / android / tools-sdks / Samsung- Android-USB-
Driver-for-Windows.

« i »c i tnggsn'

SAMSUNG DEVELOPERS * d e v e lo p distribute devices forum events 5”

Q Device
Android
Samsung Gear
Samsung Mobile Overview Technical Docs Samples
HomeSynt
S Console Samsung Android USB Driver for Windows
Hf Services May 5. 2011 1477

Samsung Account API Sí SAMSUNG.US8_Driver_to-_Mo&iie_f^ones.bp{25.32MB>


Samsung AdHub SDK
Samsung ChatON SDK The USB Driver for Windows is available for download in this page. You need the dr iver only if you are developing on
Samsung Group Play SDK Windows and want to connect a Samsung android device to your development environment over USB.

Más información sobre cómo instalar el driver USB en


http://devdopcr.aiiciroid.com /tools/extras/oem -usb.litnil
116 Programación en Android

PASO 3
Finalmente hay que configurar el dispositivo para que acepte operaciones de
depuración. En el menú de ajustes generales de tu dispositivo podrás encontrar las
opciones del desarrollador. (A partir de la versión 4.2. de Android están ocultas por
defecto y tienes que activarlas pulsado 7 veces en el campo “Número de compilación”,
dentro del menú acerca del dispositivo->Estado).

Dentro de las opciones del desarrollador, tienes la posibilidad de activar la depuración


a través del USB. Actívalas y cuando vayas a ejecutar un proyecto dentro de Android
Studio te dará la opción de ejecutarlo en un dispositivo real.
Puedes obtener toda la información sobre las configuraciones de dispositivos
hardware en http://developer.android.com/tools/device.html

3.1.1. DEPURACIÓN DE APLICACIONES EN


DISPOSITIVOS REALES
Depurar una aplicación que se ejecuta en un dispositivo real es idéntico a depurarlo
en un emulador. Tan solo tienes que hacer uso de las opciones de Android Studio del
menú de ejecución “Run” para poner a depurar (debug) tu App, pausar, reiniciar, poner
un punto de ruptura y detener la App en ese punto para poder ejecutar paso a paso,
consultar el estado de las variables y todas las opciones de depuración típicas de un
Entorno Integrado de Desarrollo. De esta manera podrás eliminar bugs de tu App de
manera sencilla.
Capítulo 3. Comunicaciones 117

loots VCS Window Help

pf Ryn app' Mayús-F10


W- Debug app' Mayús-*-F9
► Run... Alt-*- Mayus-F10
Debug... Aft*-Mayús«-F9
c MyActivity.java * c DiaiogoFecha.java *■ C DialogoHora.java * *> dialogo.fechajonl *
• Edit Configurations...
r- ( Onfior a S e le c c i cnada 5a c t i v i t y ;
E Stop Ctrl-F2 su p er. o n & tta c n { a c tiv ity } ;

Reload Changed Classes é J


t * Step Over FB 9C verride
#T p u b lic D ia lo g cnC reateD ialog(B undle sa v e d ln sta n c e S ta te ) {
Force Step Over A ft.M íysk .F Í C a le n d a r c= C a le n d a r. g s S I n s ta n c e {);
r# Step Into F7 i n t h o ra d e . g e t( C a le n d e r . HOUR):
i n t E i n u t o s f c .g e t (C a le n d a r.MD&TE):
r# Force Step Into Aft-Mayús-»-F7
r etu rn new T ia e g ic b e r D ia lo g (g e c A c tiv ity Q , t h i s , h ora, « la n to a , t r u e ) ;
r* Smart Ste£ Into Mayús- F7
* * Step Out Mayús^FS
$ )
fO v errid e
. Runto£ursor Ait-F§
«T ¿1 p u b lic v o id c n lis s S e t ilis s e F ic k e : tisseEicJrer, in t i , in t i 2) {
Wt Force Run tc Cursor # G regorianC aleadar g=new -SregcrianCaleadar {);
g . s e t ( C a l e n d a r . HOUR, 15;
Drop Frame g. s e t (C a le n d a r, MZSLTST, i2 ) ;
I I Pause Program f . cn R esu lted a ñ sra (g2;

!§► Resume Program F9


6 )
üü Evaluate Expression... AH>F8
guíele Evaluate Expression Ctrl-*-Alt-FB
Show Execution Point Alt-*-FIO

Toggle Line Breakpoint Ctri+F8


Toggle Method Breakpoint

Una característica interesante del desarrollo en Android es el Log (android.util.Log).


Es una clase que te permitirá incluir mensajes en el Log de tu App, no solo de
depuración, sino también de errores inesperados o cualquier tipo de suceso que parezca
relevante para tu App. Este Log, también llamado LogCat es accesible desde tu Android
Studio y se pueden filtrar los errores por tipos, de manera que puedes decorar tu código
fuente con mensajes que en un futuro te pueden ser de ayuda y que, sin embargo, no
quieres que el usuario conozca.
Los métodos y atributos del Log son todos estáticos, por tanto no necesitas
instanciarlo. Para generar una entrada en el log de tu App, tan solo tienes que invocar a
uno de esos métodos estáticos para registrar una entrada en el log. Por ejemplo, Log.i
escribe una nota informativa, Log.w escribe un aviso (warning), Log.e escribe un error y
Log.wtf escribe un error (What a terrible failure), que humor más ácido tienen estos
señores de google.
Log.w ("Aplicación Creada","Atención, Chuck Norris está usando la App");
L o g .i ("Atención", "Chuck Norris ha pulsado el botón");
i f (algo_horrible)
Log.wtf("ERROR CRITICO EN ACTIVIDAD PRINCIPAL", "Ocurrió algo terriblemente malo");

; « logeat
I t F I Z lf c : 1 3 :U3 .1 2 3 2 6 S 96- 2 6 B9 6 /c o B .e z fts ^ :ie .i'J jc .'lD g s |^ " T /s y s te m . o u t¡ J JJ ^
. 1 0 - 1 2 1 6 : 1 3 : 0 3 .1 2 3 2 6 £ 9 6 - 2 6 8 9 6 /c o K .e x B 2$ : l e . i l E . l o g s p p I / A p l i c a c i ó n C read a! S e h a p u ls a d o e l b o có n
| 1 0 - 1 2 1 6 : 1 3 : 1 1 .4 2 3 2 € 8 9 € - 2 6 S9 6 /c o m .e x a s 5: ie .i] J E .lo g a p p « í/A p p lic a tio n F a c k a g e lia n a g e r : g e c C S C F a c k a g e lte a s I e x t() P§
| 1 0 - 1 2 1 6 : 1 3 : 1 1 .4 4 3 2 6 8 9 6 - 2 6 8 9 6 / c o m .e x a a ip le .ilJ t .io g a p p E /M oreInfcH PK _V iew G roup: F a r e n t v ie w i s n o t a I e x t V ie w
i 1 0 - 1 2 1 6 : 1 3 : 1 1 .4 5 3 2 6 8 9 6 - 2 6 8 9 6 /c o m .e x a m p le . ilj s .lo g a p p W /A p lic a c ió n C read a: A t e n c ió n , Chuck n e r r i s e s t á u sa n d o l a App
1 0 - 1 2 1 6 : 1 3 : 1 1 .5 0 3 2 6 8 9 6 - 2 6 8 9 6 /c o m .e x a 3i p l e . i l E . l c g a p p A/ERROR CRITICO EN ACTIVIDAD FRINCTPAL: O c u r r ió a l g o t e r r i b l e c e n t e m alo
1 0 - 1 2 1 6 : 1 4 : 0 9 .9 2 3 2 6 8 9 6 - 2 6 8 9 6 /c o n s . e x e a p l e . i l x í .l o g a p p I /S y s t e m .o u t : JJJ —#
1 0 - 1 2 1 6 : 1 4 : 0 9 .9 2 3 2 € 8 9 6 - 2 6 e 9 6 /c o ic .e x a 2¡ E i e .ilS i.lo a a p p I / A p l i c a c i ó n C read a: s e ha p u ls a d o e l b o tó n
118 Programación en Android

3.2, COMUNICACIÓN CON OTROS COMPONENTES


Hasta ahora te hemos insistido en que un ordenador no es un dispositivo móvil, y que por
tanto, debes ser cuidadoso a la hora de hacer tu interfaz de usuario. También te hemos contado
que una Activity representa una “pantalla” con su interfaz de usuario. Pues bien, es el momento
de empezar a dividir tu aplicación en varios componentes, es decir, varias actividades, varios
servicios y receptores de broadcast, no solo para hacerla más flexible, sino para poder ampliar la
funcionalidad de la misma.

3.2,1. LLAMANDO A OTRAS ACTIVIDADES


En esta sección vas a aprender a llamar a otras actividades:
Para poder invocar a otra actividad hay que construir un intento. Es decir, un objeto
de la clase Intent que vincula dos componentes separados (Por ejemplo, dos
actividades). El intent sirve tanto para invocar a otro componente como para recibir un
resultado que genere ese componente.

L L A M A D A S A O T R A S A C T IV ID A D E S C O N R E S U L T A D O :
Es posible delegar una tarea en una actividad y que esta nos devuelva el resultado
que ha obtenido. ¿Te acuerdas de las listas de selección y el ejemplo de la selección de
las 5 provincias de Castilla-La Mancha? Pues en el siguiente caso práctico, vamos a
explicar cómo se puede delegar esta selección en una actividad completamente
autónoma, de tal manera que cada vez que alguna de nuestras Apps quiera seleccionar
una provincia no tendremos que copiar y pegar código en nuestras actividades. La
siguiente App que vamos a mostrar contiene, por tanto, dos actividades, la actividad
principal (ActividadExterna) y la actividad para seleccionar (Selecciona):
í SS» i S a < ^ a I 20 34

$ 1 ActividadExterna

s ta rtA c tiv ity F o r R e s u lt()


o n A c t i v i t y R e s u l t ()
--------- »
T o le d o

^ r .Ciudad Real
Selecciona tu provincia! 'Cuenca
Guadalajara
f i n i s h () Albacete

La primera actividad simplemente tendrá un botón y una etiqueta de texto. Para


agregar la segunda actividad desde Android Studio, deberás pulsar en el explorador de
Capítulo 3. Comunicaciones 119

proyectos el paquete del proyecto y seleccionar la opción New->Activity->Blank


Activity (o el tipo de actividad que quieras crear):
- . d t «• r C MyAtfMfijjgw» * i» x

* Qjd«« ~ «»*»*f«« h i m ■-anrf’rftti*» ;11»r


v D«pp ptriny-.jg mac|i1« ll*.*f-‘iv

.^application
ftoiroid:•llovBk-ck'o©»''trat-
* C3 *i ar.-irairt•»(-«ft-r r
» Hi sndrc*dTest Android ilabal-
♦ Orea* a n d r o id : U w p *ti ia/
“ r; |«v* <activity
1 MirtTAt<*a w * .t t r Z c t i v
MKtro5d:l*h«l*»*»*tTíny
ÜC«í CSifiX <«£llQC auxlteur.»*.
i res Cf £ « « ctrf.tr
Orf.M^K-r 5
d Copy Reftwvc* CW*A*.fct».'V!-C '«• ACX_______
cw-v " E B WÜBS
Alt» FT
Bank Activity «Mih Ff»9ra«r«
E i *1 find in £atfc... Ctrf.M*yiK-f
Blank W t* Activity
toPath-.
FuHvirven Actnntv

Después de crear la actividad, verás que automáticamente se ha añadido al fichero de


manifiesto donde se detallan los componentes de la aplicación:

<?x2ikl version*”1.0” encoding* "utf-8!i?>


<manifest xmlns:android*’’http://schemas.android.com/api/res/android"
package*’’etas.example.ilm ..actividadexterna 3 >

application
android:allowBackup*''true"
android:icon*5’0drawable/ic_launcherR
android:label*"§string/app_najse
android:theme*'§style/AppTheme” >
<activity
android:name* ’:
1.MyAc tivity!!
android:1abel*"§string/app_name" >
<intent-filter>
<action android mame*”android, intent, action .MAIN" />

<category android mame*'1android, intent,category .LAÜHCHER” />


</intent-filter>
</activity>
<activity
android:name*”.Sel^oclona"
android: label*Ӥstring/title_activity_sele^i^ia)! >
</activity>
</application>

</manifest»

Esta segunda Activity tendrá una peculiaridad, que será de una subclase llamada
ListActivity. En realidad no es nada más que una Activity común, pero orientada a
selecciones de tipo ListView. Para que pueda funcionar, esta ListActivity debe tener
incrustado un ListView con el identificador “@android:id/list” (es obligatorio que se
llame así):

< R elativeL ayout


xm lns:an d ro id = "h t t p : / / schem as. a n d ro id . c o m /a p k /re s /a n d ro id "
xm lns: to o ls = " h t t p : //s c h e m a s .a n d ro id .c o m /to o ls "
a n d ro id : la y o u t_ w id th = "m atch _ p aren t"
a n d ro id : la y o u t_ h e ig h t= " m a tc h _ p a re n t"
a n d ro id :p a d d in g L e ft= "@ d im e n /ac tiv ity _ h o riz o n tal_ m arg in "
a n d ro id :p ad d in g R ig h t= "@ d im en /activ ity _ h o rizo n tal_ m arg in "
a n d ro id :paddingT o p = "@ d im en /activ ity _ v ertical_ m arg in "
a n d ro id :p ad d in g B o tto m = "@ d im en /activ ity _ v ertical_ m arg in "
t o o l s : c o n te x t="com .exam ple. ilm .a c tiv id a d e x te r n a . S e le c c io n a ">
120 Programación en Android

<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@android:id/list"
android:layout_centerVertical="true"
android:layout_centerHorizontal=”true" />
</RelativeLayout>

En realidad, hemos optado por hacer un ListActivity para ampliar tus conocimientos
sobre los tipos de actividades, pero los conceptos y el código que te ponemos a
continuación son perfectamente aplicables a cualquier Activity.
El funcionamiento en código fuente es muy sencillo. Al pulsar el botón se
desencadenará una llamada a la siguiente actividad mediante el método
startActivityForResult(), que creará la actividad “Selecciona” y la cargará en la pantalla
del dispositivo. Al seleccionar el usuario una de las provincias, se capturará el evento
con la función callback onItemClick() y se finalizará la actividad invocando al método
finish() de la actividad. Los resultados se devuelven, como verás en el código siguiente, a
través de un intent y un método llamado setResult() de la propia actividad. A
continuación la actividad principal obtiene los resultados a través de otra función de
callback llamada onActivityResult, que a través del objeto Intent, obtiene el resultado
enviado por la Actividad de selección.

public class MyActivity extends Activity {

public static final int SELECCIONA_PROVINCIA = 1;

// se activa al hacer Clic en el botón "Selecciona tu Provincia!"


public void p u l s a d o (View v ) {
Intent intent = new Intent(this, S e lecciona.c lass);
startActivityForResult(intent,SELECCI0NA_PR0VINCIA);
}
//recibe los resultados
protected void onActivityResult(int requestCode,
int resultCode, Intent d a ta ) {
TextView t=(TextView)findViewByld(R.id.textView);

if (requestCode == SELECCIONA_PROVINCIA) {
if (resultCode == RESULT_0K) {
// se seleccionó correctamente la provincia
t .s e t T e x t ("Se ha
seleccionado:\n"+data.getStringExtra("PROVINCIA"));
}
}
}
}
Observa, en el método “Pulsado” que recibe el click del botón, la llamada a la
Activity “Selecciona”. Hay que crear un objeto Intent (Intención de arrancar una
actividad del tipo Selecciona).
Capítulo 3. Comunicaciones 121

Intent intent = new Intent(this, S e l e c c i o n a .c l a s s ) ;

Para invocar a la Actividad a continuación se utiliza la llamada a


start ActivityF orResult (intent,SELECCION A _PRO VIN CIA);

Se pasa como parámetro, además del objeto Intent, una constante:


SELECCIONA_PROVINCIA (=1) que funciona como el código de Intent que se
está realizando. Esto sirve fundamentalmente para diferenciar tipos de llamadas a
Actividades.
Cuando retorna la actividad, se invoca a la función onActivityResult, y, después de
un par de comprobaciones pertinentes; si el código era el de nuestra llamada y si resultó
la operación correcta (OK), se pone en el textview el texto de la provincia seleccionada.
Fíjate en que este método recibe como parámetro un objeto intent con datos. Estos
datos se pueden consultar con el método getStringExtra. Para entenderlo mejor fíjate
ahora en el código de la Actividad llamada, la Activity Selecciona:

public class Selecciona extends ListActivity implements ListView.OnItemClickListener{


public void onltemClick(AdapterView<?> a, View view, int position, long id){
Intent i=new Intent();
i .putExtra("PROVINCIA",a .getltemAtPosition(position).toString());
setResult(RESULT_OK,i);
finish();
1
1

Verás que la clase Selecciona “extiende” la clase ListActivity en lugar de Activity y


que implementa un OnltemClickListener al igual que en el apartado 2.4.4 de la Unidad
2. Este Listener implementa el método onltemClick, que finaliza la Actividad y envía los
datos a través del Intent. El intent recibe la provincia seleccionada a través del método
putExtra, que sirve para añadir información extra al intent que vinculará la vuelta a la
Actividad llamante.
Si consultas http: / / developer.android.com/ reference/android/content/Intent.html, en
la documentación de la clase Intent, te darás cuenta de que hay miles de tipos de
información que se pueden enviar con el método putExtra puesto que está
tremendamente sobrecargado.

ACTIVIDAD 3.1.
Puedes ver el código de este caso práctico en el fichero A ctividadExterna.rar del m aterial
complementario.
122 Programación en Android

3.2.2. MÁS SOBRE INTENTS: COMPARTIR DATOS


Ya has visto hasta ahora, que los intents son algo así como los carteros que
transportan mensajes entre los diferentes componentes de una aplicación; pues estos
carteros tienen la facultad de solicitar acciones de otros componentes de la aplicación,
entre las más probables arrancar una actividad o un servicio o entregar un mensaje de
broadcast (multidifusión). Hay dos tipos de intents:
I n te n ts exp lícito s: Son aquellos que nombran de forma explícita el componente que se
quiere arrancar. Se usan típicamente para arrancar componentes de tu propia aplicación.
Estos ya los has utilizado en la sección anterior para construir una llamada a otra
actividad. Si recuerdas el código era algo así como:
C o m p a rtir
Intent intent = n e w Intent(this. S e l e c c i o n a . c l a s s ) ;
startActivityForResult(intent,SELECCIONA_PROVINCIA);
*
CharON Group Play

Sería algo así como construir un intento “intent” para que


“esta clase” this, invoque a la Actividad Selecciona w □
Añadir a Droptxu M a p a a f rem óle
“Selecciona.Class” en el que se está haciendo uso del
constructor: * El
BJurtoo». Correo elec?ror»co
Intent(Context packageContext, Class<?> els)

4
Al indicar la clase Selecciona, se construye de forma explícita un Un*e trm a r por LAN

intent para arrancar esa actividad.


In te n ts im p lícitos: ¿Has oído alguna vez hablar de ES Gtwrd* En Facetxxs*

reusabilidad? Es esa característica semi ciencia ficción en la que


una aplicación reusa características de otras aplicaciones. Los intents implícitos son las
herramientas gracias a las que la reusabilidad se ha hecho realidad en esta última
década de avances tecnológicos. Con los intents implícitos, se declara la intención de
comenzar una acción genérica sin nombrar el componente que debe realizarla.
Imagina el siguiente caso: tu App maneja fotos y en algún momento necesitas realizar
una captura. En alguna parte de tu IU das la opción al usuario de realizar una captura
pero no sabes cómo capturar una foto o simplemente no tienes programada esa función.
Seguramente el usuario de tu App ya tiene alguna App para capturar fotos, por tanto,
no tienes la necesidad de programar el software para realizar una captura con la cámara.
Tu Android ya tiene esa funcionalidad y puedes in te n ta r utilizarla para tu App.
En este contexto tu App debe construir un intent implícito para seleccionar una
captura de foto:
Intent foto = new I nt e n t (MediaStore.ACTION_IMAGE_CAPTURE);
De esta manera, Android tratará de encontrar el componente necesario para poder
realizar el envío del mensaje.
Otro ejemplo, si tu aplicación quiere enviar un mensaje y declaras la intención
mediante un intent explícito, Android buscará en el sistema todas las Apps capaces de
mostrar un mensaje, y si hay varias, te mostrará un diálogo para que lo selecciones.
Capítulo 3. Comunicaciones 123

Seguro que has visto algo parecido cuando quieres c o m p a rtir algo en la red con tu
dispositivo móvil y le das al botón ^ y te aparece un diálogo con muchas alternativas.
Esto es gracias a que se declara un intent explícito con la acción
Intent .ACTION_SEND.
En el siguiente caso práctico, vamos a hacer una pequeña aplicación que utilice
intents implícitos para visualizar una página web, mostrar el mapa de una coordenada
GPS y enviar programáticamente un email.
• La aplicación constará de 4 campos de texto y tres
botones.
• Un texto para introducir una URL y su botón para ver la
página web.
• Un par de cajas de texto para introducir la longitud y la
latitud de una coordenada.
• Un texto para indicar la dirección de email al que mandar el correo electrónico.
La gracia de esta App es que sin programar un complejo navegador, ni un potente
visualizador de mapas ni un gestor de correo electrónico, podemos completar nuestro
programa fácilmente. Tan solo debemos aprovecharnos de los intents implícitos y dejar
que Android y el usuario elijan la aplicación que realizará la acción.

Se programa en 5 pasos:
1. A c c ió n : Define lo que quieres hacer. Por ejemplo, enviar un mensaje:
//Forma 1
I n t e n t i = n e w I n t e n t ( I n t e n t . ACTIO N_SEND);
//Forma 2
I n t e n t j=new I n t e n t ();
j . s e t A c t i o n ( I n t e n t . ACTIO N_SEND);

Algunos ejemplos de acciones son A C TIO N _V IEW (ver), A C TIO N _D IA L (Marcar),


A C T IO N _E D IT (Editar)...

2. D ata: Hay que indicar con qué tipo de datos quieres trabajar. Por ejemplo,
adjuntos como imágenes o un URL Un URI o Uniform Resource Identifier es un
conjunto de caracteres utilizado para identificar un recurso en la red. Ejemplos de
URI son:
* mailto:pacoperez@gmail.com
* http: / /www.google.es (Sí, un URI es un URL, pero no al revés,
https://w w w .youtube.com/watch?v=ifOpzXWZOfY)
* geodatitud,longitud
124 Programación en Android

i .s e t D a t a ( U r i . p a r s e ("h t t p :// w w w . g o o g l e . e s " ) );

3. E xtra s: Qué información adicional necesitas aportar. Son parejas de datos (Tipo
de Extra,Valor). Por ejemplo, dirección de email o número de teléfono al que quieres
enviar un mensaje.
i . p u t E x t r a (I n t e n t . E X T R A _ T E X T , " H o l a ! ! ¿Q u é tal?") ;____________________________

4. Chooser: El chooser es el mecanismo por el cual Android permite al usuario


elegir una aplicación de entre las posibles candidatas a tratar la petición que envía el
intent.
c h o o s e r = i .c r e a t e C h o o s e r ( i , " E s c o g e la A p p p a r a completar la a c c i ó n " ) ;

5. Arrancar la actividad deseada, con el intent construido pasado como parámetro.


startActivity(i);

A veces, también es necesario indicar el tipo MIME con el método setType antes de
arrancar la actividad. Normalmente el tipo se deduce, pero a veces es conveniente
indicarlo, como por ejemplo, para enviar un email hay que indicar que el tipo
corresponde al MIME especificado en la RFC 822.

i .s e t T y p e (" m e s s a g e / r f c 8 2 2 ")

O tra característica de los Intents son las categorías, necesarias para agrupar las
acciones de intents en grupos para manejar acciones. Sirve para que Android escoja las
posibles Apps candidatas a manejar el Intent, hasta el siguiente apartado no lo vas a
necesitar. Una vez descrito el procedimiento, podemos comenzar a programar.
El método que responde a los clics de todos los botones se llamará abrir, y tendrá el
siguiente código:
p u b l i c v o i d a b r i r (View v ) {
I n t e n t i = n e w I n t e n t ();
Intent c h o o s e r = n u l l ;
switch(v.getldO ){
case R.id.btnWeb:
E d i t T e x t e d U R L = ( E d i t T e x t ) f i n d V i e w B y l d ( R .i d . e d U R L ) ;
i .s e t A c t i o n ( I n t e n t .A C T I O N _ V I E W ) ;
i .s e t D a t a ( U r i .p a r s e ( e d U R L .g e t T e x t ().t o S t r i n g ()));
c h o o s e r = i .c r e a t e C h o o s e r ( i , " E l i g e N a v e g a d o r " ) ;
startActivity(i);
T o a s t . m a k e T e x t ( t h i s .g e t A p p l i c a t i o n C o n t e x t (),
" A c c e s o a w e b !",T o a s t .L E N G T H _ L O N G ) .s h o w ();
b reak;

c a s e R .i d .b t n M a p a :
Capítulo 3. Comunicaciones 125

EditText edLatitud=(EditText)findViewByld(R.id.edLatitud);
EditText edLongitud=(EditText)
findViewByld(R,id.edLongitud);
i .s e t A c t i o n (Int e nt . A C T I O N _ V I E W ) ;
i .s e t D a t a ( U r i . p a r s e (" g e o :"+
e d L a t i t u d . g e t T e x t ().t o S t r i n g ()+" , " +
e d L o n g i t u d .g e t T e x t ().t o S t r i n g ()));
c h o o s e r = i .c r e a t e C h o o s e r ( i , " L a n z a r M a p a s " ) ;
startActivity(i);
T o a s t . m a k e T e x t ( t h i s .g e t A p p l i c a t i o n C o n t e x t (),
" A c c e s o a m a p a s !",T o a s t .L E N G T H _ L O N G ) .s h o w ();
b re a k ;

c a s e R .i d .b t n E n v i a r :
EditText edEmail=(EditText)findViewByld(R.id.edEmail);
i .s e t A c t i o n ( I n t e n t .A C T I O N _ S E N D ) ;
i .s e t D a t a ( U r i . p a r s e (" m a i l t o :"));
S t r i n g p a r a [] =
{ e d E m a i l . g e t T e x t ().t o S t r i n g ( ) , " o t r o c o n t a c t o @ g m a i l . c o m " } ;
i . p u t E x t r a (I n t e n t .E X T R A _ E M A I L , p a r a ) ;
i . p u t E x t r a ( I n t e n t .E X T R A _ S U B J E C T , " S a l u d o s d e s d e A n d r o i d " ) ;
i .p u t E x t r a (I n t e n t .E X T R A _ T E X T ,
"Hola!!. ¿Qué tal?. E s t e es n u e s t r o p r i m e r e m a i l" ) ;
i .s e t T y p e (" m e s s a g e / r f c 8 2 2 ");
c h o o s e r = i .c r e a t e C h o o s e r ( i , " E n v i a r E m a i l " ) ;
startActivity(i);
T o a s t . m a k e T e x t ( t h i s . g e t A p p l i c a t i o n C o n t e x t (),
" E n v í a el e m a i l !!",T o a s t .L E N G T H _ L O N G ) .s h o w ();
break;
}
}
Se distingue el botón que generó el evento de “click” a través de su identificar.
Cuando se pulsa en el botón btnWeb, se genera el intent de la siguiente manera:
E d i t T e x t e d U R L = ( E d i t T e x t ) f i n d V i e w B y l d ( R .i d . e d U R L ) ;
i .s e t A c t i o n ( I n t e n t .A C T I O N _ V I E W ) ;
i .s e t D a t a ( U r i . p a r s e ( e d U R L . g e t T e x t ().t o S t r i n g ()));
c h o o s e r = i .c r e a t e C h o o s e r ( i , " E l i g e N a v e g a d o r " ) ;
startActivity(i);

Se declara la acción a realizar, se extrae del campo de texto la dirección de la página


web que se quiere explorar y se especifica en los datos del intent.
Por ejemplo, si en la caja de texto introdujéramos la URL: http: / / www.google.es, el
Data del intent sería Uri.parse("http://www.google.es"). Fíjate que debes escribir la
URL con protocolo y todo, de lo contrario saltaría una excepción.
126 Programación en Android

a I Intent implícitos Googte

Sí http://www google es/ Q» H


http://www qooqle IRAWEBÜ
mam o

IMltud:

longitud IRA MAPA

ENVIAR

Cuando se pulsa el botón btnMapa, el procedimiento es idéntico, solo que se forma


un URI de tipo geo:
i .s e t D a t a ( U r i . p a r s e (" g e o :" + e d L a t i t u d . g e t T e x t ().t o S t r i n g ()+
" , " + e d L o n g i t u d .g e t T e x t ().t o S t r i n g ()));

En el ejemplo cogemos las coordenadas de los dos campos de texto edLatitud y


edLongitud, pero podríamos haberlo construido indicando la cadena de caracteres
directamente. Por ejemplo, para ver Talavera de la Reina (39.95N, 4.98 O),
construiríamos el URI de la siguiente forma:
i .s e t D a t a ( U r i . p a r s e (" g e o : 3 9 . 9 5 , - 4 . 9 8 " ) ) ;

Finalmente, si se ha pulsado el botón btnEmail, se construye el intent igual, solo que


agregan parámetros extras:

S t r i n g p a r a [ ]= { e d E m a i l . g e t T e x t ().t o S t r i n g ( ) , " o t r o c o n t a c t o @ g m a i l .c o m " };


i . p u t E x t r a ( I n t e n t .E X T R A _ E M A I L , p a r a ) ;
i . p u t E x t r a ( I n t e n t .E X T R A _ S U B J E C T , " S a l u d o s d e s d e A n d r o i d " ) ;
i . p u t E x t r a ( I n t e n t .E X T R A _ T E X T , " H o l a !!. ¿Q u é tal?. E s t e es n u e s t r o p r i m e r
email");
Capítulo 3. Comunicaciones 127

Intentimplicitos Compose send save da a f t g

URL ilopezmon@gmail.com
httD.//wwwaooa!e.es irawfr

Ivan Lopez Montatban | | j |


...
latstud 3995 otrocontactofearnailecim
............................................ .... * CC/SCC

longitud ’* * IRA MAPA


r- mmmw

Hola!!. ¿Qué tal?. Este es mi primer email

A b

ivanfopez@nberadeltaio.es

Con la pareja de valores (EXTRA_EMAIL,dirección) se indica el destinatario, en


forma de array de Strings se pueden especificar todos los destinatarios que se desee, con
(EXTRA__SUBJECT, asunto) se indica el asunto del email y finalmente
(EXTRA__TEXT,texto_del_email) es el texto que lleva el cuerpo del mensaje.
Para terminar, se indica el tipo MIME del email, definido en la RFC 822:
i .s e t T y p e (" m e s s a g e / r f c 8 2 2 ") ;

¿Q ué puede ir m al?
Por ejemplo, que Android no disponga de una App que cumpla los requisitos del
Intent, en tal caso, al arrancar la actividad mediante Start Activity, saltará una
excepción. Por ejemplo, supon que el usuario no tiene una Actividad para manejar
mapas, al ejecutar la App y al pulsar el botón de Ver Mapa, saltaría el error:

P r o c e s s : c o m . e x a m p l e .i l m . i n t e n t i m p l i c i t o s , PID: 2009
j a v a . l a n g . I l l e g a l S t a t e E x c e p t i o n : C o u l d n o t e x e c u t e m e t h o d of t h e
activity
at a n d r o i d . v i e w . V i e w $ l .o n C l i c k ( V i e w . j a v a :4007)
at a n d r o i d . v i e w . V i e w . p e r f o r m C l i c k ( V i e w . j a v a :4 756)

C a u s e d by: a n d r o i d . c o n t e n t .A c t i v i t y N o t F o u n d E x c e p t i o n : N o A c t i v i t y f o u n d
t o h a n d l e I n t e n t { a c t = a n d r o i d . i n t e n t .a c t i o n . V I E W d a t = g e o : 3 9 . 9 ,-4.8 }
128 Programación en Android

3.2.3. LOS FILTROS DE INTENTS

En el apartado anterior hemos solicitado ejecutar una Activity que cumpliera unos
requisitos. Para esto hemos utilizado un objeto Intent. Ahora nos ponemos en el otro
lado: queremos crear una Activity que pueda recibir un Intent implícito. De esta
manera, pondremos nuestra App a disposición de otras Apps para determinar
determinadas funciones.
El componente de Android que escoge las aplicaciones candidatas a ser ejecutadas, se
llama Gestor de Paquetes o Package Manager. Este componente es el encargado de
buscar las actividades y servicios que proporcionan la funcionalidad solicitada.
Para que Package Manager pueda realizar esta búsqueda en las aplicaciones que tiene
instaladas, se utilizan los filtr o s de In te n ts. Estos elementos se declaran en el fichero
de manifiesto de cada App. Los filtros de intent representan las acciones que tu App
puede realizar. Si hay varias aplicaciones con varios filtros de intent apropiados para
cierta acción que se desea realizar, Android muestra un diálogo para que el usuario
escoja la aplicación apropiada.
Los filtros de intents se insertan en el archivo de manifiesto, anidado en la actividad
correspondiente y se construyen como se indica a continuación, indicando la acción,
categoría y datos para los que nuestra actividad está preparada:
< a c t i v i t y a n d r o i d :n a m e = " M i A c t i v i d a d " >
<intent-filter>
< a c t i o n a n d r o i d : n a m e = " a n d r o i d .i n t e n t .a c t i o n .S E N D " />
< c a t e g o r y a n d r o i d : n a m e = " a n d r o i d .i n t e n t .c a t e g o r y .D E F A U L T " />
< d a t a a n d r o i d :m i m e T y p e = "t e x t / p l a i n " />
</intent-filter>
</activity>

Este filtro de intent, indicaría que nuestra App está preparada para recibir datos, es
decir, tratar un ACTIONJ3END. formateado en texto plano (mimeType= ’’text/plain”).
Capítulo 3. Comunicaciones 129

La categoría DEFAULT es la categoría que se debe usar para que tu App pueda ser
candidata a tratar un intent implícito de este tipo de acción.
Para ilustrar cómo funcionan estos filtros, vamos a programar una App que sea capaz
de recibir un texto plano desde otra App y hacer algo con él. En nuestro caso, el
funcionamiento será simplemente mostrar lo que hemos recibido al ejecutar una
operación de “Compartir” en un texto plano. Para ello vamos a aprovechar la App del
apartado anterior. Esta App, por tanto, será capaz de llamar a otras actividades a
través de Intents, pero podrá, a su vez recibir Intents para tratar texto plano
compartido.
Sigue estos pasos sencillos para dotar de esta nueva capacidad a tu App:
1. Incorpora en el fichero de manifiesto la declaración del intent:

▼ tjapp
a n d r o id : i c o n = "& d ra w a b le /ic _ la u n c h e r"
▼ CU manifests a n d r o id : l a b e l - ”I n t e n t l a p l i c i t o s *
<> AndroidManifestjemi a n d r o id : them e» ? sty le/A p p T h em e ” >
► C üjava FJ « a c tiv ity
► C l res a n d r o i d : name» com. e x a m p le . ilm .jn tg n tiH g ^ l j e i t o s . M a in A c tiv i t y E
P ‘í* Gradie Scripts a n d r o i d : l a b e l * 'I n t e n t l m p l i c i t o s " >
< in te n t-filte r>
< a c t i o n a n d r o id : nam e*”a n d r o i d . i n t e n t . action.M A U D />

< c a t e g o r y a n d r o id : nam e»: a n d r o id . i n t e n t . c a t e g o r y . LAUHCHER" />


¿1 < /i n t e n t- f ilte r >

O n tent- filter»
•«.action a n d r o x d : name» " a n d r o id . i n t e n t . a c t i o n . SEED”/ >
<category android:na»e=Handroid-intent.category.DEFAULT*/»
<data android:aimeType="text/plain"/>
</intent-filter

s~i « /a c tiv ity »


B « /a p p lic a tio n »

2. Modifica la función onCreate de tu App. Recuerda que el intent que se envía,


solicita la creación de una actividad, por tanto, cuando recibes el intent, se ejecutará
la función onCreate. Con el método getlntent() puedes obtener el objeto intent que
creó la App y tratar los parámetros Acción, Categoría, Datos y tipo que aporta. El
siguiente código es muy sencillo, se obtiene una referencia al Intent y se compara con
la acción. Si es la esperada (ACTION_SEND) se actualiza el contenido de la caja de
texto con el string extra EXTRA_TEXT:

protected void onCreate(Bundle savedlnstanceState) {


Intent intent = getlntentO;
String action = intent.getAction();
String textoRecibido;
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);

EditText ed=(EditText)findViewByld(R.id.edRecibido);
//Si es tipo de comando es ACTION_SEND
if (action.equals(Intent.ACTION_SEND)) {
//obtenemos la información que nos han compartido
textoRecibido= intent.getStringExtra(Intent.EXTRA_TEXT);
if (textoRecibido != nuil) {
130 Programación en Android

ed.setText(textoRecibido) //actualizamos c a j a de t e x t o
}
}
}

Podemos utilizar cualquier aplicación capaz de compartir texto e intentar


“compartirlo”. Por ejemplo, con la App Color Note compartimos una nota de texto:

&s*n*í t«* "«Í

&uí«*s «Ffln. a to M d u a »
m é 0
;l \
vi m
\ p

Observamos como al seleccionar “Enviar” nuestra App sale como candidata a recibir
esa información, puesto que el Package Manager de Android ha visto que en el fichero
de manifiesto tenemos declarado el intent-filter correspondiente. Tras seleccionar nuestra
App, se crea la actividad con el correspondiente Intent lleno de datos compartidos.

ACTIVIDAD 3.2.
Puedes ver el código de este caso práctico en el fichero Intentslmplicitos.rar del material
complementario.
Mira el vídeo con la demo de este apartado: https: I I www.voutube.com/watch?v=lBzxvLA2B-4

3.3. SOLICITUD DE PERM ISO S


Debido a la arquitectura de seguridad de Android, cuando una App necesita hacer
alguna operación especial (como por ejemplo conectarse a la red), Android solicita al
usuario permisos especiales para desarrollar esa operación. De esta manera el usuario
puede estar seguro de que su aplicación no realizará operaciones que comprometan la
seguridad de tu dispositivo móvil.
Por tanto, para que tu App pueda utilizar ciertas características, el archivo de
manifiesto debe contener una declaración con los permisos especiales para utilizar esas
características. La sintaxis es muy sencilla:
Dentro del fichero AndroidManifest.xml se escribe la etiqueta:
cuses-permission android:name="nombre_permiso"/>
Por ejemplo, para solicitar que tu App pueda recibir SMSs debes incluir el
siguiente código:
cmanifest x m l n s :android="h t t p ://schemas.android.com/apk/res/android"
package="c o m .a nd r o i d .a p p .m y a pp ">
cuses-permission a ndroid:name="android.permission.RECEIVE_SMS"/>
Capítulo 3. Comunicaciones 131

Aquí te ponemos una lista con todos los permisos que puedes darle a tu App:
P e rm ite acceso d e le c tu ra / e s c ritu ra a la ta b la d e 'p ro p ied ad e s* e n la b a se de d a to s
A CCESS C H E C K IN P R O P E R T IE S de re g istro
P e rm ite q u e u n a ap licac ió n p u e d a a c ced er a la u b ic a c ió n a p r o x im a d a d e riv a d o d e la s
A CCESS CO ARSE L O C A T IO N fu e n te s d e u b ic ació n d e red , com o la s to r r e s d e c e lu lare s y W i-F i
P e rm ite q u e u n a ap licac ió n p u e d a a c ced er a la u b ic a c ió n p re c isa d e la s fu en tes de
A CCESS F IN E L O C A T IO N u b ic ació n , com o G P S , las a n te n a s te lefó n icas y W i-F i
P e rm ite q u e u n a ap licac ió n p u e d a acced er a co m an d o s d e p ro v e e d o r d e u b ic ació n
A CCESS L O C A T IO N EXTRA COM M ANDS ad icio n al
P e rm ite q u e u n a ap licac ió n p u e d a c re a r p ro v eed o re s d e u b ic a c ió n s im u la d a s p a r a
A CCESS M OCK L O C A T IO N p ru e b a s

ACCESS NETW ORK STATE P e rm ite q u e las ap licacio n es ac c e d a n a in fo rm ació n s o b re red es

A CCESS SURFACE F L IN G E R P e rm ite q u e u n a ap licació n p u e d a u tiliz a r las fu n cio n es d e b a jo n iv e l d e S u rfaceF lin g er

A C C E S S W IF I STATE P e rm ite q u e las ap licacio n es a c c e d a n a in fo rm ació n s o b re red es W i-F i

ACCOUNT M ANAGER P e rm ite q u e las ap licacio n es llam e n a A c c o u n tA u th e n tic a to rs

ADD V O IC E M A IL P e rm ite q u e u n a ap licació n p u e d a a g re g a r m e n sa je s d e v o z e n el s iste m a .


P e rm ite q u e u n a ap licació n p u e d a a c tu a r com o u n A c c o u n tA u th e n tic a to r p a r a el
A U T H E N T IC A T E ACCOUNTS a d m in is tra d o r de c u e n ta s

BATTERY STA TS P e rm ite q u e u n a ap licac ió n p u e d a reco g e r e s ta d ís tic a s d e la b a te r ía


R e q u e rid o p o r u n A ccessib ility S erv ice, p a r a a s e g u r a r q u e solo el s is te m a p u ed e u n irs e
B IN D A C C E S S IB IL IT Y S E R V IC E a ella
P e rm ite q u e u n a ap licació n p u e d a d ec irle al serv ic io A p p W id g e t q u é ap licac ió n p u e d e
B IN D A P P W ID G E T a c ced er a los d a to s de A p p W id g e t
R e q u e rid a p o r el re c e p to r d e a d m in is tra c ió n d el d isp o sitiv o , p a r a a s e g u ra r q u e solo el
B IN D D E V IC E A D M IN s is te m a p u e d e in te r a c tu a r co n él

B IN D DREAM S E R V IC E R e q u e rid o p o r u n D ream S erv ice, p a r a a s e g u ra r q u e solo el s is te m a p u e d e u n irse a ella


R e q u e rid o p o r u n I n p u tM eth o d S erv ice, p a r a a s e g u ra r q u e solo el s is te m a p u ed e u n irse
B IN D IN P U T M ETHOD a ella
R e q u e rid o p o r u n H o stA p d u S erv ice o O ffH o stA p d u S erv ic e p a r a a s e g u ra r q ue so lo el
B IN D N FC S E R V IC E s is te m a p u ed e u n irse a ella
R e q u e rid o p o r u n N o tificatio n L isten erS erv ic e, p a r a a s e g u ra r q u e solo el s is te m a p u e d e
B IN D N O T IF IC A T IO N L IS T E N E R S E R V IC E u n irs e a ella

B IN D P R IN T S E R V IC E R e q u e rid o p o r u n P rin ts e rv ic e , p a r a a s e g u ra r q u e solo el s is te m a p u e d e u n irs e a ella


R e q u e rid o p o r u n R em o teV iew sS erv ice, p a r a a s e g u ra r q u e solo el s is te m a p u e d e u n irse
B IN D R E M O T E V IE W S a ella

B IN D TEXT S E R V IC E R e q u e rid o p o r u n T e x tS e rv ic e

B IN D TV IN P U T R e q u e rid o p o r u n T v In p u tS e rv ic e p a r a a s e g u ra r q u e solo el s is te m a p u e d e u n irs e a ella


R e q u e rid o p o r u n V o ic e ln te ra c tio n S e rv ic e , p a r a a s e g u ra r q u e solo el s is te m a p u ed e
B IN D V O IC E IN T E R A C T IO N u n irs e a ella

B IN D V PN S E R V IC E R e q u e rid o p o r u n V p n S e rv ice, p a r a a s e g u ra r q u e solo el s is te m a p u e d e u n irse a ella


R e q u e rid o p o r u n W a llp ap e rS e rv ice , p a r a a s e g u ra r q u e so lo el s is te m a p u e d e u n irs e a
B IN D W A LLPA PER ella

BLUETOOTH P e rm ite q u e las ap licac io n es se c o n e c te n a d isp o sitiv o s B lu e to o th em p a re ja d o s

BLUETOOTH A D M IN P e rm ite ap licac io n es p a r a d e s c u b rir d isp o sitiv o s b lu e to o th


P e rm ite q u e la s ap licac io n es se e m p a re je n con d isp o sitiv o s b lu e to o th s in la in te ra c c ió n
BLUETOOTH P R IV IL E G E D d el u su a rio , y p a r a p e rm itir o d e n e g a r el acceso te lefó n ico o d e acceso a m en sajes
P e rm ite q u e u n a ap licac ió n p u e d a acced er a d a to s d e los sen so res c o rp o ra le s d el
BODY SEN SO R S u s u a rio

B R IC K Se n e c e sita p a r a s er cap az d e d e s a c tiv a r el d isp o sitiv o (¡m u y p eligroso!)


P e rm ite q u e u n a ap licac ió n e m ita u n a n o tifica ció n de q u e u n p a q u e te d e ap licac ió n se
B RO AD CA ST PACKAGE REM OVED h a elim in ad o
132 Programación en Android

B RO AD CA ST SM S P e rm ite q u e u n a ap lic a c ió n e m ita u n a n o tifica ció n d e rece p ció n d e SM S

B RO A D CA ST S T IC K Y P e rm ite q u e u n a ap licac ió n p u e d a tr a n s m itir S T IC K Y B R O A D C A S T

B RO A D CA ST WAP PUSH P e rm ite q u e u n a ap licac ió n e m ita u n a n o tifica ció n d e recep ció n W A P P U S H

CALL PHO N E P e rm ite q u e u n a ap licac ió n p u e d a in icia r u n a lla m a d a d e teléfo n o


P e rm ite q u e u n a ap lic a c ió n llam e a cu a lq u ie r n ú m e ro d e teléfo n o , in c lu id o s los
CALL P R IV IL E G E D n ú m e ro s d e e m erg en cia

CAM ERA Se n e c e sita p a r a s er c a p az d e a c ced er a la c á m a r a del d isp o sitiv o

CA PTU RE A U D IO O U TPU T P e rm ite q u e u n a ap lic a c ió n p u e d a c a p tu r a r la s a lid a d e au d io

CA PTURE SECURE V ID E O O U TPU T P e rm ite q u e u n a ap lic a c ió n p u e d a c a p tu r a r la s a lid a d e v ídeo seg u ra

CA PTU RE V ID E O O U TPU T P e rm ite q u e u n a ap lic a c ió n p u e d a c a p tu r a r la s a lid a d e vídeo


P e rm ite q u e u n a ap licac ió n c a m b ie si u n co m p o n e n te de ap licac ió n e s tá h a b ilita d o o
CHANGE CO M PO NENT ENABLED STA TE no
P e rm ite a u n a ap lic a c ió n m o d ifica r la co n fig u ració n a c tu a l, ta le s com o la co n fig u ració n
CHANGE C O N F IG U R A T IO N regional

CHANGE NETW ORK STA TE P e rm ite a la s a p licac io n es c a m b ia r el e s ta d o d e co n e c tiv id a d de red

C H A N G E W IF I M U L T IC A S T STATE P e rm ite a la s a p licac io n es a c ced er al m o d o W i-F i d e m u ltid ifu sió n

C H A N G E W IF I STA TE P e rm ite a las a p licac io n es c a m b ia r el e s ta d o d e co n e c tiv id a d W i-F i


P e rm ite q u e u n a a p licac ió n p u e d a b o r r a r la s ca ch és d e to d a s las ap licaciones
CLEAR A PP CACHE in s ta la d a s en el d isp o sitiv o

CLEAR A PP U SER DATA P e rm ite q u e u n a a p licac ió n lim p ie los d a to s d e u s u a rio d e u n a ap licac ió n

CONTROL L O C A T IO N U PD A TES P e rm ite a c tiv a r / d e s a c tiv a r la s n o tifica cio n es d e ac tu a liz a c ió n de la s e ñ a l m óvil

DELETE CACHE F IL E S P e rm ite q u e u n a a p licac ió n p u e d a e lim in a r los arch iv o s d e cach é

D ELETE PACKAGES P e rm ite q u e u n a ap licac ió n elim in e p a q u e te s

D E V IC E POW ER P e rm ite el acceso d e b a jo niv el a la a d m in is tra c ió n d e en e rg ía

D IA G N O S T IC P e rm ite q u e las ap licac io n es a c c e d a n a los recu rso s d e d ia g n ó stico

D IS A B L E KEYGUARD P e rm ite a la s ap licac io n es d e s a c tiv a r el b lo q u eo d el te clad o


P e rm ite q u e u n a ap licac ió n p u e d a r e c u p e ra r la in fo rm ació n d e v o lc ad o de e s ta d o de
DUM P los servicio s d el s is te m a

EXPAND STA TU S BAR P e rm ite q u e u n a ap licac ió n p u e d a e x p a n d ir o c o n tra e r la b a r r a d e e s ta d o

FA CTO RY TEST E je c u ta r co m o u n a a p licac ió n d e p r u e b a d el fa b ric a n te , se e je c u ta co m o el u su ario root,

F L A S H L IG H T P e rm ite el acceso a la lin te rn a

FORCE BACK P e rm ite q u e u n a a p licac ió n p u e d a fo rz a r u n a o p erac ió n B A C K

G F.T A C C O U N T S P e rm ite el acceso a la lis ta d e c u e n ta s en el serv icio d e C u e n ta s

GET PACKAGE S IZ E P e rm ite q u e u n a a p licac ió n p u e d a a v e rig u a r el esp acio u tiliz ad o p o r cu a lq u ier p a q u e te

GET TOP A C T IV IT Y IN F O P e rm ite q u e u n a a p licac ió n re c u p e re in fo rm ació n p riv a d a a c e rc a de la a c tiv id a d a c tu a l


E s te p erm iso se p u e d e u tiliz a r e n los p ro v eed o re s d e c o n te n id o p a r a p e rm itir q u e el
GLOBAL SEA RCH siste m a d e b ú s q u e d a g lo b a l p u e d a acced er a su s d a to s

HARDW ARE TEST P e rm ite el acceso a p eriférico s d e h a rd w a re


P e rm ite q u e u n a ap licac ió n p u e d a in y e c ta r ev e n to s d e u su a rio (clav es, ta c to ,
IN J E C T EVENTS tra c k b a ll) e n la c o rrie n te d e e v e n to s y e n tre g a rlo s a c u a lq u ie r v e n ta n a
P e rm ite q u e u n a ap licac ió n p u e d a in s ta la r u n p ro v e e d o r d e u b ic ació n en el
IN S T A L L L O C A T IO N P R O V ID E R A d m in istra d o r de u b ic acio n e s

IN S T A L L PACKAGES P e rm ite q u e u n a ap licac ió n in s ta le p a q u e te s

IN S T A L L SH O RTCU T P e rm ite q u e u n a ap lic a c ió n p u e d a in s ta la r u n acceso d ire c to en el L a u n ch er


Capítulo 3. Comunicaciones 133

P e rm ite q u e u n a ap licac ió n p u e d a a b r ir la s v e n ta n a s q u e son p a r a el u so d e p a r te s d e


X IN T E R N A L SYSTEM W IN D O W la in terfaz d e u s u a rio del s is te m a

IN T E R N E T P e rm ite q u e la s ap licac io n es a b r a n co n e c to re s d e red


A
K IL L BACKGROUND PRO CESSES P e rm ite la lla m a d a a k illB a c k g ro u n d P ro c e sse s (S trin g )
P e rm ite q u e u n a ap licac ió n p u e d a u tiliz a r las fu n cio n es de u b ic ació n e n h a rd w a re ,
L O C A T IO N HARDW ARE com o el ap i geofencing

X M ANAGE ACCOUNTS P e rm ite q u e u n a ap licació n p u e d a a d m in is tr a r la lis ta d e c u e n ta s d el a d m in is tra d o r


P e rm ite q u e u n a ap licac ió n p u e d a g e s tio n a r (cre a r, d e s tru ir, z-o rd er) fich as de
M ANAGE A PP TOKENS aplicación en el g e s to r de v e n ta n a s

M ANAGE DOCUM ENTS P e rm ite q u e u n a ap licac ió n p u e d a g e s tio n a r el acceso a los d o cu m en to s


P e rm ite q u e u n a ap licació n p u e d a s a b e r lo q u e se e s tá re p ro d u c ie n d o y c o n tro la r su
/-y
M E D IA CONTENT CONTROL rep ro d u cc ió n

M O D IF Y A U D IO S E T T IN G S P e rm ite q u e u n a ap licació n m o d ifiq u e la co n fig u ració n d e a u d io global

M O D IF Y PH O N E STA TE P e rm ite la m o d ificació n del e s ta d o d e la te le fo n ía - en c en d id o , M M I. etc.

M OUNT FORM AT F IL E S Y S T E M S P e rm ite siste m a s d e a rc h iv o s d e fo rm a to de a lm a c e n a m ie n to e x tra íb le


P e rm ite el m o n ta je y d e s m o n ta je d e s is te m a s d e arch iv o s p a r a u n a lm a c e n a m ie n to
MOUNT UNM OUNT F IL E S Y S T E M S e x tra íb le

/*>
N FC P e rm ite a las ap licac io n es re a liz a r o p erac io n es d e E / S so b re N F C
P e rm ite q u e u n a ap licac ió n p u e d a v er el n ú m e ro q u e se e s tá m a rc a n d o d u r a n te u n a
lla m a d a s a lie n te co n la o p ció n p a r a red irig ir la lla m a d a a un n ú m e ro d ife re n te o
r~ \
PRO CESS O U T G O IN G CALLS a b o rta r la co m u n icació n

READ CALENDAR A d m ite q u e u n a ap licac ió n le a los d a to s del c a le n d a rio del u su ario

READ CALL LOG P e rm ite q u e u n a ap licac ió n p u e d a leer el re g istro d e lla m a d a s d el u su a rio

READ CONTACTS A d m ite qu e u n a ap licac ió n p u e d a leer los d a to s d e c o n ta c to s d el u s u a rio

/*■> READ EXTERNAL STORAGE A d m ite u n a ap licac ió n lea d e a lm a c e n a m ie n to e x te rn o


P e rm ite q u e u n a ap licac ió n p u e d a to m a r c a p tu r a s de p a n ta lla y te n e r acceso a los
/■■»•
READ FRAM E B U FFER d a to s del fram e b u ffer
P e rm ite q u e u n a ap licac ió n p u e d a leer (p ero n o e s crib ir) h isto ria l y m a rc a d o re s d e
READ H IS T O R Y BOOKM ARKS n avegación del u su a rio
P e rm ite q u e u n a ap licació n p u e d a leer los arch iv o s d e re g istro d el s is te m a d e b a jo
READ LO G S nivel

READ PH O N E STATE P e rm ite acceso d e solo le c tu ra al e s ta d o d e l teléfo n o

READ P R O F IL E A d m ite q u e u n a ap licac ió n lea los d a to s d el p erfil j>ersonal del u su a rio

READ SMS P e rm ite q u e u n a ap licac ió n lea m e n sa jes SM S

READ SYN C S E T T IN G S P e rm ite a la s ap licac io n es leer la co n fig u ració n d e sin cro n izació n

READ SYN C STA TS P e rm ite a la s ap licac io n es leer la s e s ta d ís tic a s d e sin cro n izació n

— READ U SE R D IC T IO N A R Y P e rm ite q u e u n a ap licació n p u e d a leer el d ic cio n ario d e u su ario

f-v
READ V O IC E M A IL P e rm ite q u e u n a ap licació n le a los m e n sa jes d e voz en el sistem a

REBOOT Se n e c e sita p a ra s er c a p az d e re in ic ia r el d isp o sitiv o


// ~ \
P e rm ite q u e u n a ap licac ió n p u e d a re c ib ir el A C T I O N _ B O O T _ C O M P L E T E D q u e se
R E C E IV E BOOT COM PLETED e m ite d esp u és de q u e el s is te m a te rm in e d e a rr a n c a r
P e rm ite q u e u n a ap licac ió n p u e d a m o n ito riz a r los m e n sa jes M M S e n tr a n te s , p a r a
p R E C E IV E MMS g ra b a r o rea liz a r el p ro c e s a m ie n to e n ellos
P e rm ite q u e u n a ap licació n p u e d a m o n ito riz a r los m en sajes SM S e n tr a n te s , p a ra
RECEDE SM S g ra b a r o rea liz a r el p ro c e s a m ie n to en ellos

R E C E IV E W AP PUSH P e rm ite q u e u n a ap licació n p u e d a m o n ito riz a r los m e n sa jes W A P P U S H e n tr a n te s

X RECORD A U D IO P e rm ite q u e u n a ap licació n realice d e g ra b a c ió n d e au d io


134 Programación en Android

REORDER TASKS P e rm ite q u e u n a ap licac ió n p u e d a c a m b ia r el o rd e n Z d e ta r e a s


P e rm ite q u e u n a ap licac ió n (T eléfono) p u e d a e n v ia r u n a p e tic ió n a o tr a s ap licacio n es
SEN D RESPOND V IA M ESSA G E p a r a m a n e ja r la acció n resp o n d -v ia-m essa g e d u r a n te la s lla m a d a s e n tr a n te s

SEND SM S P e rm ite q u e u n a ap licac ió n p u e d a e n v ia r m e n sa jes SM S


P e rm ite q u e u n a ap licac ió n p u e d a v e r y c o n tro la r có m o se p o n e n en m a rc h a
SET A C T IV IT Y W ATCHER g lo b a lm e n te la s a c tiv id a d e s e n el siste m a
P e rm ite q u e u n a ap licac ió n e m ita u n a in ten ció n d e e s ta b le c e r u n a a la rm a p a r a el
SET ALARM u s u a rio
P e rm ite a u n a ap licac ió n c o n tro la r si la s a c tiv id a d e s se fin a liz an c u a n d o se p o n e n en
SET ALW AYS F IN IS H b a c k g ro u n d

SET A N IM A T IO N SCALE M odificar el fa c to r d e escala d e an im ació n glo b al

SET DEBUG A PP C o n fig u ra r u n a a p licac ió n p a r a d ep u rac ió n

SET O R IE N T A T IO N P e rm ite el acceso de b a jo niv el p a r a esta b le c e r la o rie n ta c ió n d e la p a n ta lla

SET P O IN T E R SPEED P e rm ite el acceso d e b ajo nivel p a r a e s ta b le c e r la v elo cid ad del p u n te r o


P e rm ite q u e u n a ap licac ió n p u e d a e s ta b le c e r el n ú m e ro m á x im o d e p ro ceso s d e
SET PROCESS L IM IT ap licac ió n q u e se e s té n e je c u ta n d o

SET T IM E P e rm ite q u e la s ap licac io n es co n fig u ren la h o ra d el siste m a

SET T IM E ZONE P e rm ite a las a p licac io n es esta b le c e r la zo n a h o ra ria d el s is te m a

SET W A LLPA PER P e rm ite a la s ap licac io n es co n fig u rar el fo n d o de p a n ta lla

SF.T W A L L P A P E R H IN T S P e rm ite a las ap licac io n es p a r a e s ta b le c e r los consejos w a llp a p e r

S IG N A L P E R S IS T E N T PR O C ESSES P e rm ite q u e u n a ap licac ió n en v ie señ ales a to d o s los p ro ceso s p e rs is te n te s


P e rm ite q u e u n a ap licac ió n p u e d a a b rir, c e rra r o d e s a c tiv a r la b a r r a d e e s ta d o y sus
STA TU S BA R iconos
P e rm ite q u e u n a ap licac ió n p u e d a p e rm itir el acceso a los s u sc rip to re s d e F eed s
S U B S C R IB E D FEEDS READ C o n te n tP ro v id e r
P e rm ite q u e u n a ap licac ió n p u e d a p e rm itir el acceso de e s c r itu r a a lo s s u sc rip to re s de
S U B S C R IB E D FEEDS W R IT E F eed s C o n te n tP ro v id e r
P e rm ite q u e u n a ap licac ió n p u e d a a b rir v e n ta n a s u tiliz a n d o el tip o
T Y P E _ S Y S T E M _ A L E R T , q u e se m u e s tra en la p a r te su p e rio r d el re s to de
SYSTEM A LERT W IN D O W aplicacio n es

T R A N S M IT IR P e rm ite el u so d e l tra n s m is o r d e in fra rro jo s d el d isp o sitiv o

U N IN S T A L L SHORTCUT P e rm ite q u e u n a ap licac ió n p u e d a d e s in s ta la r u n acceso d ire c to en el L a u n c h e r

U PD A TE D E V IC E STA TS P e rm ite q u e u n a ap licac ió n p u e d a a c tu a liz a r las e s ta d ís tic a s d e d isp o sitiv o

U SE C R E D E N T IA L S P e rm ite q u e u n a ap licac ió n so licite a u th to k e n s d el a d m in is tra d o r d e c u e n ta s

U SE S IP P e rm ite q u e u n a ap licac ió n u tilic e el servicio S IP

V IB R A T E P e rm ite el acceso a l v ib ra d o r
P e rm ite el u so d e P o w e rM a n a g e r W a k eL o ck s p a r a e v ita r q u e el p ro c e s a d o r d u e r m a o
WAKE LOCK q u e la p a n ta lla se o scu rezc a

W R IT E A PN S E T T IN G S P e rm ite q u e las ap licac io n es e s c rib a n la co n fig u ració n d e A P N


P e rm ite q u e u n a ap licac ió n p u e d a escrib ir (p ero n o leer) los d a to s d el c a le n d a rio del
W R IT E CALENDAR u su a rio
P e rm ite q u e u n a ap licac ió n p u e d a escrib ir (p ero n o leer) C o n ta c to s d e los d a to s del
W R IT E CONTACTS u su a rio

W R IT E EXTERNAL STO RA G E P e rm ite q u e u n a ap licac ió n e sc rib a en el a lm a c e n a m ie n to e x te rn o

W R IT E G S E R V IC E S P e rm ite q u e u n a ap licac ió n m o d ifiq u e el m a p a d e serv icio s d e G oogle


P e rm ite q u e u n a ap licac ió n p u e d a escrib ir (p ero n o leer) h is to ria l y m a rc a d o re s de
W R IT E H IS T O R Y BOOKM ARKS n av e g ació n d el u s u a rio
P e rm ite q u e u n a ap licac ió n p u e d a escrib ir (p ero n o leer) d a to s d e l perfil p e rs o n a l del
W R IT E P R O F IL E u su a rio
Capítulo 3. Comunicaciones 135

W R IT E SECU RE S E T T IN G S P e rm ite q u e u n a ap licac ió n p u e d a leer o esc rib ir la co n fig u ració n se g u ra d el s is te m a

W R IT E S E T T IN G S P e rm ite q u e u n a ap licac ió n p u e d a leer o esc rib ir la co n fig u ració n del sistem a

W R IT E SM S P e rm ite q u e u n a ap licac ió n p u e d a e s c rib ir m e n sa jes SM S

W R IT E SY N C S E T T IN G S P e rm ite a las a p licac io n es esc rib ir la co n fig u ració n d e sin cro n izació n

W R IT E U SE R D IC T IO N A R Y P e rm ite q u e u n a ap licac ió n e sc rib a en el d ic cio n ario d e u su ario


P e rm ite q u e u n a ap licac ió n p u e d a m o d ifica r y elim in a r m en sajes d e voz e x iste n te s en
W R IT E V O IC E M A IL el sistem a

3.4. LOS SERVICIOS


Un servicio es un programa que se ejecuta sin interfaz de usuario y que realizan
muchos cálculos en segundoplano o background. Como son programas invisibles, sin
interfaz de usuario, y la única forma de comunicarse con ellos es a través de otros
programas, estableciendo un interfaz directo de comunicación llamado IPC, en inglés
Inter Process Comunication. Típicamente un servicio se puede utilizar para:
• Realizar entrada salida pesada sin que el usuario note cómo su interfaz de
usuario se queda esperando hasta que acabe.
• Cálculos que van a requerir mucho tiempo de CPU y que pueden resultar en
un bloqueo de la interfaz de usuario.
• Comunicaciones con la red (transferencia de archivos desde la red, acceso a
servicios web, etc.)
• Otras operaciones que se puedan ejecutar en background: Reproducir música,
esperar notificaciones, etc.
Un servicio puede ser vinculado (bounded ---------------X / ---------------\
Call to Call to
service) o no vinculado (unbounded service). startSarvice() bindServiced

Los no vinculados se llaman así porque se T


onCreateO onCreateO
ejecutan de manera totalmente
independiente, ni siquiera devuelven un I
or.StartCommandO onBindO
resultado al componente que les creó. Se ¡ 1
Clients are
ejecutan en background indefinidamente running bound to
service
hasta que completan su servicio. Siguen
The sevtce is stopped Ail c lie n ts u n b in d b y c a llin g
ejecutándose incluso cuando el componente by itself or a client u n b in d S e r v ic e Q

»
que los ha creado ha sido destruido, onUnbind()
solamente terminan su ejecución cuando han 1
cumplido su misión. Sin embargo, los onDestroy!) onDestroyO

vinculados sí que tienen una relación muy / 3 S 5 IT


Service Service
estrecha con el componente que los ha •hut down shut down

creado. Disponen de una interfaz de Unbounded Bounded


service service
comunicación IPC a través del cual se
intercambian notificaciones y resultados. Un el ciclo de vida de un servicio
servicio vinculado se ejecuta mientras la imagen de developer.android.com
136 Programación en Android

aplicación que lo creó continúe también ejecutándose.


Para crear estos servicios hay que crear una subclase de Service (o de la subclase
IntentService si no necesitas que tu servicio atienda a varias peticiones
simultáneamente). El que sea vinculado o no, tan solo depende de que implementes el
método onStartCommand(No vinculado) o onBind(vinculado).

SERVICIOS vs HILOS: Ten en cuenta que un servicio se ejecuta en background y sin


depender de la IU. Si tu proceso va a depender de la interfaz de usuario, por ejemplo, enviar un
fichero solo m ientras tu actividad se está ejecutando, entonces, utiliza un hilo. Además, un
servicio es más inteligente, por ejemplo, si tienes un servicio creado y lo vuelves a arrancar, no se
creará o tra vez, y un hilo sí.
Tam bién tienes que tener en cuenta que un servicio se ejecuta en el hilo (thread) principal de
tu aplicación, por lo que si tu servicio va a hacer cálculo intensivo que consuma m ucha CPU
deberás crear un thread independiente para su ejecución.

Si te fijas en la imagen del ciclo de vida, verás que la clase Service define varios
callbacks, que se resumen en la tabla que viene a continuación
Callback Descripción
onStartC om m and () El sistema invoca a este m étodo cuando una actividad u otro
componente, llam a al método startService() para arrancar un servicio
no vinculado (Unbounded). Si program as este método, hay que
pararlo llam ando al m étodo stopSelf() de la clase service, o stopService
desde fuera. Este m étodo debe devolver cómo com portarse si el
servicio no puede ser creado por memoria insuficiente. Generalmente
se devolverá la constante entera ST A R T_STIC K Y (intentar crear el
servicio cuando se vuelva a tener memoria suficiente)
onBind() El sistema llam a a este m étodo cuando se produce la vinculación entre
el servicio y un componente cliente al llam ar al m étodo bindServiceQ.
Si implementas esta función debes crear una interfaz entre el servicio
y el componente vinculado a través del objeto IBinder. Es obligatorio
program ar este método, pero si no quieres perm itir que se vincule,
simplemente devuelve null (return null)
onUnbind() El sistema llam a a este método cuando todos los clientes se han
desvinculado del servicio
onCreateQ El sistem a llam a a este m étodo cuando se arranca por prim era vez el
servicio
onDestroyQ El sistema llam a a este m étodo cuando el servicio ya no se utiliza más
y es destruido

CASO PRÁCTICO: Crea un servicio no vinculado (Unbounded) que cada tres segundos
compruebe si hay conexión Wifi, reportándolo al LogCat.
Capítulo 3. Comunicaciones 137

Vamos a desgranar la aplicación en 2 partes. La parte en la creamos el servicio y la


parte en la que creamos la tarea de consultar si la wifi está o no activada.

CREACIÓN DEL SERVICIO Y PUESTA EN MARCHA:

Para crear el servicio debes seguir los siguientes pasos:


1. Crea una subclase del servicio (extends Service) y programa sus métodos
onCreate(), onBind(), onStartCommand() y onDestroy(). No hemos incluido todo el
código de la detección de conexión WIFI que necesita para que la subclase que te
mostramos a continuación te sirva de plantilla/esquema para tus próximos servicios.

p u b lic c la s s W ir e le ssT e ste r exten d s S e r v ic e {


f i n a l S t r i n g tag = " D em o S e r v i c i o " ;
p u b lic b o o lea n e n E je c u c io n = fa ls e ;

/ * * L la m a d o c u a n d o s e c r e a e l s e r v i c i o . * /
@ O verride
p u b lic v o id on C reateO {
L o g .i( t a g , " S e r v ic io W ir e le ssT e ste r c rea d o !" );
}

/ * * E l s e r v i c i o s e a r r a n c a m e d i a n t e u n a l l a m a d a s t a r t S e r v i c e () * /
@ O verride
p u b l i c i n t on S tartC om m an d (In ten t i n t e n t , i n t f l a g s , i n t s t a r t l d ) {
i f ( len E je c u c io n ) {
en E jecu cio n = tr u e ;
L o g .i(t a g , " S erv icio W ir e le ssT e ste r arra n ca d o !" );
}
e lse {
L o g .i(ta g , "E l s e r v i c i o W ir e le ssT e ste r ya esta b a arra n ca d o !" );
}

r e t u r n START_STICKY;
}

/ * * u n c l i e n t e s e v i n c u l a c u a n d o l l a m a a b i n d S e r v i c e ()
* Como e s u n s e r v i c i o n o v i n c u l a d o , d e v o l v e m o s n u l l * /
© O v errid e
p u b lic IB in d er o n B in d (I n te n t in t e n t ) {
retu rn n u ll;
}
/ * * L la m a d o c u a n d o s e d e s t r u y e e l s e r v i c i o * /
(© O v errid e
p u b lic v o id o n D estroyO {
L o g .i (tag, " S e r v ic io W ir e le ssT e ste r d e s tr u id o !" );
}

Fíjate especialmente en el método onStartCommand(), que solo arranca el servicio si


previamente no se estaba ejecutando.
2. Crea la Actividad que invocará el arranque y parada del servicio:
138 Programación en Android

“ <S*l*tiw U ísw .; n í a » . ¡taassaa-co*/»?*


S tap ! / >*C&a6X».SfiS«i« -«fe'*.®*!*""
*av8r*xa:laye«t jo a re s!”
; X«y«-al fcoáqfct-'jKt-d»
«MSnsti4^j»*í4s>3U:rij- ■»sssja=-.; o>-!.# sj !j_!xnT i *i*=ÍJil_iaaS3 111*
«M»*5 tfiansu-ae t ífestjj s a * 1*okí*í_&«-<j11, •
*nítf»us;jwittiSajiíTo(i* ;viíyji»rti-.*Sjranji:K’

t&sisSC
SSlV
M tí?"r.Prissxpal*>

*a&S*®4: la y a a t « la tís» 'wrap c*at«aE'


aiMírsigíS-Tí -hí»_5eiaM»'w«p esa*-**’ .1:'
saátPtsies fts is f e r
wadrsid víaJ»'í- • td/fctnte-raasT *
«wSraííáaenCJ * ^ ”*fe.tSí2S4"
«naralrt: í^ a u tja ss lrr< 5 o r ‘« « t* ! » - í w *
«BíSNJlá; Jjy«at_««rBiBíapu-&n^r í»

•SJatVxr.
« ^ r« ia ; Uys*t_vx«8&r‘ ¿i s í _w ¡¡i « sí ’
a n d ro id : layout íie ig tt- «nv.pc-Eiten-t'
a n d ro id : m k * *»£ggag£ S e rv icie ”
a&dr«id:ld» ? >sfi/itc S e se K r'
« iw m e te e C U e ^ 'p ^ tg s ? "
« sd ro id : 1*101:*. ic io *- *t •i i - - l ^ C S S a ' ’
«ttdresilí:. Í* f ouÍ^t*siiÍe¿S«ri«»k«l»'í r^sr*

■:' k*i» 1í ve i* f om>

Y añade a la actividad el código para los botones de arrancar y detener:

/* Arranca el servicio Wireless Tester */


public void Arrancar(View v ) {
startService(new Intent(getBaseContext(), WirelessTester.class));
}
/* Detiene el servicio Wireless Tester */
public void Detener(View v ) {
stopService(new Intent(getBaseContext(), WirelessTester.class));
}

3. Añade tu servicio al fichero de manifiesto.

i<?xml version="l.O" encoding="utf-8 " ?>


íijtmanifest xrnlns:android» http://schemas.android.com/apk/res/android"
package»”com.example,ilm .^efflose^ylcio,, >

0 <application
android:allowBackup»"true”
android:icon="§drawable/ic_launcher"
android:label="DessoServicio“
android:theme="gstjle/AppTheme” >
<activity
android:name»".Principal”
android: label="DemoServicio'' >
9 <intent-filter>
taction android:ñame»”android.intent.action.MAIN" />

«¡category android:name»"android,intent.category .LAÜHCHER" />


él </intent-filter>
Capítulo 3. Comunicaciones 139

CÓDIGO PA R A LA EJECUCIÓN DE LA TAREA

Tienes que tener en cuenta que necesitas algún mecanismo para que el servicio
ejecute la tarea indefinidamente (es un servicio) y qué mejor que un hilo (thread) para
hacerlo. El thread se programa sobreescribiendo el método run, que se ejecutará
mientras el servicio esté arrancado. El método CompruebaConnexiónWifi utiliza un
objeto ConnectivityManager para conseguir información sobre la red y saber si el
dispositivo está o no conectado a la Wifi. El bucle del thread itera cada tres segundos
gracias a la instrucción, sleep(3000), y cuando se detecta algún cambio de estado en la
conexión por Wifi, se reporta a través del Log.

1. Crea una subclase de la clase thread:

private class Tester extends Thread{


©Override
public void n m ( ) {
while(enEjecucion) {
try {
Log.i(tag, "servicio ejecutándose....");
if(wifi_activo!=CompruebaConexionWifi()){
wifi_activo=!wifi_activo; //Cambio de estado
if(wifi_activo)
Log.i(tag,"Conexión wifi activada");
else
Log. i (tag, "Conexión wifi desactivada") ,-
}
this.sleep(3000);
}
catch (InterruptedException e) {
enEjecucion=false ;
Log.i(tag, "hilo del servicio interrumpido....");
}
}
}
public boolean CompruebaConexionWifi(){
//crea un objeto ConnectivityManager que nos da información de la red
ConnectivityManager connectivity = (ConnectivityManager)
getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
//Objtener información de la red; acceso vía WIFI
Networklnfo info =
connectivity.getNetworklnfo(ConnectivityManager.TYPE_WIFI);
if (info != null) {
//Mirar si el dispositivo está conectado por WIFI
if (info.isConnected()) {
return true;//hay conexión
}
}
}
return false; //no hay conexión
}
}

Ahora, hay que incluir en el código el código que lanza el thread y que detecta la
conexión de Wifi:
140 Programación en Android

Añade el objeto tester (de la subclase de Thread “Tester”) y modifica la función


onCreate del servicio para crear el thread:
p r iv a te T ester te s te r ;
/ * L la m a d o c u a n d o s e c r e a e l s e r v i c i o * /
@ O verride
p u b l i c v o i d o n C r e a t e () {
L o g .ift a g , " S erv icio W irelessT ester crea d o !" );
tester= n ew T e s te r O ;
}

Después en la función onStartComand() invoca al método start() del thread, para


que el thread se lance la primera vez que se arranca el servicio:
/ * E l s e r v i c i o s e a r r a n c a m e d i a n t e u n a l l a m a d a s t a r t S e r v i c e () * /
@ 0 v errid e
p u b li c i n t on S tartC om m an d (In ten t i n t e n t , i n t f l a g s , i n t s t a r t l d ) {
i f ( ! e n E jecu cio n ) {
e n E je c u c io n = tr u e ;
t e s t e r . s t a r t ();
L o g .i( t a g , " S e r v ic io W ir e le ssT e ste r a rra n ca d o !" );
}
e lse {
L og. i (tag,
"E l s e r v i c i o W ir e le s s T e s te r ya e sta b a a r r a n c a d o !" );
}
r e t u r n START_STICKY;
}
Finalmente, termina el thread cuando se destruye el servicio:
/ * L la m a d o c u a n d o s e d e s t r u y e e l s e r v i c i o * /
@ O verride
p u b lic v o id o n D estroyO {
L o g .i( t a g , " S e r v ic io W ir e le ssT e ste r d e s tr u id o !" );
i f (en E jecu cio n )
t e s t e r . in t e r r u p t ();
}

Para poder acceder a la información de la red a través del ConnectivityManager,


necesitas que tu App disponga de permisos para acceder a la red y comprobar su estado.
Para poder hacer esto, tienes que agregar los permisos pertinentes al fichero de
manifiesto (justo encima de la etiqueta Application):
< u s e s - p e r m i s s i o n a n d r o i d :n a m e=" a n d r o i d . p e r m i s s i o n . INTERNET" / >
< u s e s - p e r m i s s i o n a n d r o i d : nam e=" a n d r o i d . p e r m i s s i o n . ACCESS_NETWORK_STATE" / >

Fíjate cómo al desactivar y activar la Wifi, aparece en el logcat los eventos que
hemos programado:
Capítulo 3. Comunicaciones 141

•i» loocat • K

' 20 25 4£ 367 5580- ■5580/caB, ex am ple. ilm . d em oservicio I/D eao S e r v ic io : S e r v ic io K ir e le a a le a te r creado!
' 20 35 48 367 5580- ■5560/com. e x a s p ie . ilm . dem oaervicio I/Demo S e r v ic io : S e r v ic ie K ir e le a a le a te r arrancadoí
> 20 25 48 367 5580- ■5595/ccm.ex am ple. lis .d e m o a e r v ic io I/Demo S e r v ic io : a e r v ic io ejecu tá n d o a e -----
i 20 35 48 371 5580- •5595/com .exssple. ilm . dem oaervicio I/Demo S e r v ic io : Conexión w i í i activ a d a
i 20 35 51 375 5580- 5595/ccm .ex am ple. ilm . dem oaervicio I/Deme S e r v ic io : a e r v ic io e je c u tá n d o a e .. . .
> 20 35 54 379 5580- •5595/ccm.exam ple. ilm . dem oaervicio I/Demo S e r v ic io : a e r v ic io e }e c u tá n d c a e .. . .
:: 35 57 379 5580- ■5 595/ c o r . e x e s p l e . ilm . dem oaervicio I/Demo S e r v ic io : a e r v ic io ejecu tá n d o a e -----
' 20 36 00 383 5580- ■5595/com. ex am ple. ilm . dém eservícío I/Demo S e r v ic io : a e r v ic io e 3 e cu tá n d o a e.__
i 20 36 00 383 5580- 5595/CCS. 5X62?l e . ilm .d em oaervicio I/Demo S e r v ic io : w i í i d eaactivad a
' 20 36 03 383 5580- ■5595/e o s , exam ple. ilm . dem oaervicio I/Demc S e r v ic io : a e r v ic io e3ecu tán d cae-----
i 20 36 03 999 s s e o - 5580/cam . exam ple. ilm .d em oaervicio I/Demo S e r v ic io : S e r v ic io K ir e le a a le a te r d eatraíd o!
i 20 36 03 999 5580- ■5595/ccm.exam ple. ilm . dem oaervicio I/Demc S e r v ic io : h i l o d e l a e r v ic io interrum pido-----

A rra n c a r S erv icio

D e te n w ,S « v tc io

Un servicio se puede ver en la lista de aplicaciones de Android en ejecución


running”:

a V I 8 40

5 * , Running app

i f i i DemoServicio A 4MB
** 1 1 pi ocess and 1 s * r v = e * 0 10

S E R V IC E S

iB i WirelessTester ule
S Janedbyapp

This service w as started by its app. Stopping it may cause the app
to fail

Stop

PROCESSES

; S | DemoServicio 4 4MB
tom oxarnplc ilm dieirioserviciO

Mam process in use

Mira la demo de cómo crear un servicio en el video: littn:/ /voutu.be/tFreingTpEuo


142 Programación en Android

NOTA: Este ejemplo es una demostración sencilla de cómo programar un servicio.


Pero este mismo caso práctico se puede programar de manera mucho más fácil con
un broadcast receiver (véase apartado 3.7)

ACTIVIDAD 3.3.
Puedes ver y analizar este caso práctico en el siguiente fichero DemoServicio.rar del material
complementario.

3.5. CO NEXIO NES A IN T E R N E T


Las clases URL, URLConnection, HttpURLConnection y HttpsURLConnection
permiten establecer conexiones con servidores de internet para la transferencias de datos
basados en protocolos de la arquitectura TCP-IP. Se pueden abrir conexiones para
lectura/escritura con fuentes de tipo:
• Servidores ftp: Se establece una conexión con un servidor de ficheros a través del
protocolo FTP. Se construye la conexión con URLs del tipo
ftp : / / u s u a r io :p a s s w d @ s e r v id o r /r u ta

• Ficheros locales: Se accede al sistema de archivos local con URLs del tipo
f i le : r u t a _ a l _fic h e r o

• HTTP y HTTPS. Con URLs del tipo h ttp : / /s e r v i d o r /r u t a : p u e r t o o


h t t p s : / / s e r v id o r /r u ta ¡p u erto

Para crear una conexión sigue estos pasos:


1. Hay que crear un objeto del tipo URL. Con el constructor indicamos al
objeto la dirección URL de la conexión a utilizar:

Por ejemplo: URL url = new U R L ("ftp://www.rediris.es/");

2. Después se utiliza una clase del tipo URLConnection (FTP) o


HttpURLConnection (H ttp/H ttps) para establecer la conexión a la URL que
mediante el método openConnection(). Esto provocará la apertura de un flujo de
datos (stream) para la comunicación con el servidor.
URLConnection urlConnection = (HttpURLConnection) u r l .openConnection() ;

3. Finalmente se lee el stream creado con un objeto de la clase


BufferedStreamReader o de la clase InputStreamReader (dependiendo de si la
lectura es con buffer o no):

InputStream in = new
BufferedlnputStream(urlConnection.getlnputStream());
try {
Leer(in);
Capítulo 3. Comunicaciones 143

}
fin a lly {
i n . c l o s e ();
}
3.5.1. EJEMPLO: ESTABLECER UNA CONEXIÓN HTTP A
TRAVÉS DE AsyncTask
CASO PRÁCTICO: Crea un App que establezca una conexión con un servidor H T T P con una
URL a elección del usuario. La IU tendrá dos campos de texto, uno para introducir la URL y
otro para m ostrar el contenido del fichero descargado del servidor web. La conexión se realizará a
través de una subclase de AsyncTask (un tipo de thread simplificado).

fflh ConexionesHTTP

IIR n d e s.^ rrjn r ¡fre r a ( j e |t a j0 -Corn/ text.tXlj

descargar

texto descargado de ejemplo


desde iesriberadeltajo.com

Cuando se realiza una tarea que puede durar un tiempo (incluso hasta segundos), hay
que independizar a la IU de esa tarea, de lo contrario podemos dar la sensación al
usuario de que se ha quedado bloqueada. Esta tarea se puede completar
independizándola de la IU utilizando Threads, Servicios o tareas asincronas (AsyncTask,
un tipo de thread simplificado para devolver resultados). En este caso vamos a utilizar
un A sy n cT a sk para realizar la conexión. De hecho Android nos exige que para realizar
una conexión a la red, se realice en un thread diferente.
Para hacer la AsyncTask creamos una nueva subclase llamada DescargaPaginaWeb.
Esta subclase de tarea asincrona se lanzará mediante el método executeQ.

onPreExecute

Params... Progress

dolnBackground

Result
onPostExecute ciclo de vida de una
AsyncTask
144 Programación en Android

Una AsyncTask se define mediante 3 tipos genéricos <Param ,Progress,Result>.


• El primer tipo define el tipo de parámetros que recibirá el método execute() y
que se pasará al método doInBackground() de la tarea.
• El segundo, Progress, es el tipo de unidades que generalmente se usará para
mostrar el progreso del proceso en background, que se actualizará mediante el
método onProgressUpdate
• Finalmente, Result, que es el resultado que genera la tarea y que se enviará al
método onPostExecute.
Cuando se lance la tarea se ejecutará el método dolnBackgroundQ que recibe un
array de cadenas y que, a su vez, llama al método descargaUrl, que establece la conexión
a través del método HttpURLConnection . Cuando termina la ejecución de
descargaURL el método dolnBackgroundQ termina invocándose al método
onPostExecute, que es el último método que se ejecuta en la tarea asincrona.
Finalmente, el método LeerQ, lee del InputStream que genera el objeto
HttpURLConnection y convierte, a través de un objeto ByteArrayOutputStream.

p r iv a te c l a s s D esca rg a P a g in a W eb e x t e n d s A s y n c T a s k < S t r in g , V o id , S t r i n g s {
@ O verride
p r o t e c t e d S t r in g d o ln B a c k g r o u n d ( S t r i n g . . . u r l s ) {
/ / param s v i e n e d e l m étodo e x e c u t e O c a l l : p a r a m s[0 ] e s l a u r l .
try {
retu rn d e s c a r g a U r l(u r ls [0 ]);
} c a t c h (I O E x c e p tio n e) {
r e t u r n " I m p o s i b l e c a r g a r l a w eb ! URL m a l f o r m a d a " ;
}
}
/ / o n P o stE x ecu te v i s u a l i z a l o s r e s u lt a d o s d e l A syncT ask.
@ O verride
p r o te c te d v o id o n P o stE x ecu te(S tr in g r e s u lt ) {
tx tD esca rg a . s e t T e x t (r e s u lt);
}
j *★
E s t e m étodo l e e e l in p u t s tr e a m c o n v i r t i é n d o l o en una cadena
a y u d á n d o n o s c o n u n B y t e A r r a y O u t p u t S t r e a m ()
*/
p r iv a t e S tr in g L eer(In p u tstream is ) {
try {
B y t e A r r a y O u t p u t S t r e a m b o = new B y t e A r r a y O u t p u t S t r e a m ( ) ;
in t i = i s . r e a d ();
w h i l e ( i != - 1 ) {
b o .w r ite ( i ) ;
i = i s . r e a d ();
}
retu rn b o .to S tr in g O ;
} c a tc h (IO E x cep tio n e) {
r e t u r n "";
}
}
// D ada u n a URL, e s t a b l e c e u n a c o n e x i ó n H t t p U r l C o n n e c t i o n y d e v u e l v e
// e l c o n t e n i d o d e l a p á g i n a web c o n u n I n p u t s t r e a m ,
Capítulo 3. Comunicaciones 145

/ / y q u e s e t r a n s f o r m a a un S t r i n g .
p r i v a t e S t r i n g d e s c a r g a U r l ( S t r i n g m yu rl) th r o w s IO E x c e p tio n {
InputStream i s = n u ll;

try {
URL u r l = new U R L ( m y u r l ) ;
H ttp U R L C o n n ectio n con n =
(H ttp U R L C on n ection ) u r l . o p e n C o n n e c t i o n ( ) ;
c o n n .s e t R e a d T im e o u t (10000 / * m i l i s e g u n d o s * / ) ;
c o n n .s e t C o n n e c t T i m e o u t (15000 / * m i l i s e g u n d o s * / ) ;
c o n n . s e t R e q u e s t M e t h o d ( "GET") ;
conn. setD oIn p u t(tru e);
/ / com ien za l a c o n s u lt a
con n . c o n n e c t();
i n t r e sp o n s e = c o n n .g e tR e sp o n se C o d e ();
i s = c o n n .g e tln p u tS tr e a m O ;

/ / c o n v e r t ir e l InputStream a s t r in g
retu rn L e e r (is );

//N o s aseg u ra m o s de c e r r a r e l in p u tS tr e a m .
}
fin a lly {
if (is != n u l l ) {

Por último, el método callback que responde al click del botón, que ejecuta la tarea
asincrona

p u b l i c v o i d D e s c a r g a r (V ie w v ) {
ed U R L = (E d itT ex t)fin d V iew B y ld (R .id .ed U R L );
tx tD esca rg a = (T ex tV iew ) fin d V ie w B y ld (R .id .tx tD e s c a r g a );
t x t D e s c a r g a . setM ovem en tM eth od (n ew S c r o l l in gM ovem en tM eth od ( ) ) ;

C o n n e c t i v i t y M a n a g e r connM gr = ( C o n n e c t i v i t y M a n a g e r )
g e t S y s t e m S e r v i c e ( C o n t e x t . CONNECTIVITY_SERVICE);
N e tw o rk ln fo n e tw o r k ln fo = c o n n M g r .g e tA c tiv e N e tw o r k ln fo ();

if ( n e t w o r k l n f o != n u i l && n e t w o r k l n f o . i s C o n n e c t e d ( ) ) {
n ew D e s c a r g a P a g i n a W e b ( ) . e x e c u t e ( e d U R L . g e t T e x t ( ) . t o S t r i n g O ) ;
}
e lse
edURL. s e t T e x t ( "No s e h a p o d i d o e s t a b l e c e r c o n e x ió n a in t e r n e t " ) ;
}

Finalmente, tienes que registrar los permisos pertinentes en el fichero de manifiesto:


c u s e s - p e r m i s s i o n a n d r o i d :n a m e= " a n d r o i d . p e r m i s s i o n . INTERNET" / >
< u s e s - p e r m i s s i o n a n d r o i d :n a m e= " a n d r o i d . p e r m i s s i o n . ACCESS_NETWORK_STATE" / >

A C T IV ID A D 3.4.
Puedes ver y analizar este caso práctico en el siguiente fichero: ConexionesH T T P .rar del m aterial
complementario.
146 Programación en Android

3.6. LAS N O TIFIC A C IO N ES


Las notificaciones son alertas que el usuario recibe en la parte superior de la pantalla
de su dispositivo para informarle de un suceso (has recibido un whatsapp, tienes un
evento pendiente en el calendario, etc.)
Generalmente, cuando el usuario recibe una notificación, este desliza la barra de
notificaciones hacia abajo obteniendo una vista detallada de cada notificación. Para
crear una notificación se utiliza un objeto NotificationCompat.Builder. A este objeto se
le tiene que dotar de tres propiedades:
• Icono pequeño: mediante el método setSmallIcon()
• Un título: mediante el método setContentTitle()
• Texto de detalle: mediante el método setContentText()

■■
vie, 2.1 notdemóre &
O
▼ #) i
Wt-Fi Ubicación Son»do Modo manos Bluetooth
libres

-5 V " Ají

Notificaciones
'loti B orrar

Conectado como dispositivo m.


y

,/ A y - , Serranillos Playa, E sp añ a a las w a s


'Ve* Muy soleado T °0
. PnPCam 2014 - 1 1"11 1 5 4 9 :5 8

■’ running

Lista Negra

Jorge Martin (cole)

2 aplicaciones actualizadas

De manera opcional, puedes incluir en la notificación una acción. Una forma directa
acceder a la Actividad pulsando en el panel de notificaciones. A partir de la versión 4.1
también se le pueden añadir botones para realizar acciones particulares en la
notificación, por ejemplo, cancelar una cita, responder a un mensaje, etc. La acción se
Capítulo 3. Comunicaciones 147

programa pasando un objeto Pendinglntent (un contenedor de Intents para arrancar la


actividad que procesa la acción) a través del método setContentIntent().
La notificación tiene un nivel de p rioridad establecido en el rango PRIORITY_MIN
.. PRIORITY_MAX (de -2 a 2). Por defecto, la prioridad es PRIORITY_DEFAULT
(0 ).

Para poder utilizar las notificaciones hay que agregar al proyecto las Support
Libraries de Google, que previamente deben estar instaladas con el SDK Manager en la
sección “Extras”. En el fichero build.gradle de tu proyecto, agrega la dependencia
siguiente:
compile "com.android.support:support-v4:18.0.+"

Después, sigue estos 3 pasos para enviar una notificación:

P A S O 1: C rea la n otificación con su s propiedades: Con el objeto


constructorNotif de la clase NotificationCompat.Builder, se establecen las propiedades.

i n t n o t i f I d = l ; / / I d e n t i f i c a d o r de l a n o t i f i c a c i ó n , p a r a f u t u r a s m o d i f i c a c i o n e s .
N o t i f i c a t i o n C o m p a t . B u i l d e r c o n s t r u c t o r N o t i f = new
N o tific a tio n C o m p a t. B u i l d e r ( t h i s ) ;
c o n s t r u c t o r N o t i f . s e t S m a l l l c o n (R .d r a w a b le . i c _ s t a t _ a c t i o n _ i n f o _ o u t l i n e ) ;
c o n s t r u c t o r N o t i f . s e t C o n t e n t T i t l e ( "Mi n o t i f i c a c i ó n " ) ;
c o n s t r u c t o r N o t i f . s e t C o n t e n t T e x t ( "Has r e c i b i d o u n a n o t i f i c a c i ó n ! ! " ) ;

P A S O 2: C ream os un in ten t para abrir la a ctiv id a d cu an d o se p u lse la


notificación: Se utiliza una estructura de datos de tipo pila para asegurarnos de que el
botón de "Atrás" del dispositivo nos lleva desde la Actividad a la pantalla principal, y se
establece el intent que creará la notificación a través de un objeto Pendinglntent.

In ten t r e su lta d o ln te n t = new I n t e n t ( t h i s , M a in A c tiv ity .c la s s );

/ / E l o b j e t o s t a c k B u i l d e r c r e a un b a ck s t a c k que
/ / n o s a s e g u r a que e l b o t ó n de "A trás" d e l
/ / d i s p o s i t i v o nos l l e v a desde la A c tiv id a d a la p a n t a lla p r in c ip a l
T a sk S ta ck B u ild er p i l a = T a sk S ta c k B u ild e r . c r e a t e ( t h i s ) ;

/ / El padre d e l s ta c k se r á la a c tiv id a d a c r e a r
p i l a . a d d P a r e n tS ta c k (M a in A c tiv ity . c l a s s ) ;
/ / Añade e l I n t e n t que c o m ie n z a l a A c t i v i d a d a l i n i c i o de l a p i l a
p i l a .a d d N e x tln te n t(r e su lta d o ln te n t);
P e n d in g ln te n t r e s u lta d o P e n d in g ln te n t =
p i l a . g e t P e n d i n g l n t e n t ( 0 , P e n d i n g l n t e n t . FLAG_UPDATE_CURRENT);
c o n str u c to r N o tif. se tC o n te n tln te n t(r e su lta d o P e n d in g ln te n t);

P A S O 3: E n v ía la n otificación
N o tific a tio n M a n a g e r n o t if ic a d o r =
( N o t i f i c a t i o n M a n a g e r ) g e t S y s t e m S e r v i c e ( C o n t e x t .NOTIFICATION_SERVICE);
n o tific a d o r .n o t i f y (n o tifld , c o n str u c to r N o tif.b u ild ());
148 Programación en Android

Las notificaciones se eliminan cuando el usuario pulsa la opción de borrar en el panel


de notificaciones, o si programaste la notificación con la opción s etAuto Cancel ().

3.6.1. NOTIFICACIONES AVANZADAS


Es posible programar notificaciones más elaboradas, por ejemplo, puedes programar
notificaciones con un layout expandido:

w •

20.08 V!t ! DE NOVIEMBRE

!'>
N o tific a c ió n e x p a n d ib le
L s t o e s i a p n rr-er a i ; n e a
L s l o e s la s e g u n d a l i n c a
■1
K-

1 i
P A S O E X T R A : [Opcional] C rear n otificación con la y o u t exp an d ib le: Antes de
enviar la notificación, hay que crear un objeto NotificationCompat.InboxStyle y definir
sus propiedades. Estas propiedades son el BigContentTitle y las líneas de la descripción,
tal y como puedes ver en la figura anterior:

N o tific a tio n C o m p a t.In b o x S ty le in b o x S ty le =new N o t i f i c a t i o n C o m p a t . I n b o x S t y l e (} ;


S t r i n g ! ] e v e n t o s = new S t r i n g [5] ;

/ / T ít u lo d e l expanded la y o u t
in b o x S ty le . se tB ig C o n te n tT itle (" N o tific a c ió n e x p a n d ib le :" );
e v e n t o s [ 0 ] ="E sto e s l a p rim era l í n e a " ;
e v e n t o s [ 1 ] ="E sto e s l a seg u n d a lí n e a " ;
e v e n t o s [ 2 ] = " E sto e s l a t e r c e r a lí n e a " ;
e v e n t o s [ 3 ] = " E sto e s l a c u a r t a l í n e a " ;
e v e n t o s [ 4 ] ="E sto e s l a q u it a lí n e a " ;

/ / Mueve e v e n t o s d e n t r o d e l e x p a n d e d l a y o u t
f o r ( i n t i= 0 ; i < e v e n t o s . l e n g t h ; i+ + )
in b o x S t y l e . a d d L i n e ( e v e n t o s [ i ] );

/ / Mueve e l e x p a n d e d l a y o u t a l a n o t i f i c a c i ó n .
c o n str u c to r N o tif. s e tS ty le (in b o x S ty le );

/ / D a r m áxima p r i o r i d a d y p o n e r l o e n l a c im a d e l a s n o t i f i c a c i o n e s
c o n s t r u c t o r N o t if . setW hen(0 );
c o n s t r u c t o r N o t i f . s e t P r i o r i t y ( N o t i f i c a t i o n . PRIORITY_MAX);

ACTIVIDAD 3.5.
Puedes ver el código de una notificación en el siguiente fichero: DemoNotificaciones.rar del
m aterial complementario.
Capítulo 3. Comunicaciones 149

A partir de Android 5.0 (API Level 21), es posible crear notificaciones flotantes
cuando el dispositivo está activo (también llamadas notificaciones Heads Up). T am bién
se pueden crear notificaciones que aparezcan en la pantalla de bloqueo. Para más
información lee la documentación al respecto:
(l i tt p : / /d e v e lo D e r.a n d ro id .c o m /g u id e /to p ic s /u i/n o tifie rs /n o tific a tio n s .h tm ll

3.7. R EC IBIEN D O B R O A D C A ST S
Un receptor de multidifusión, en inglés, Broadcast Receiver, es un componente de
una App que recibe notificaciones de eventos que se han producido en el sistema. Por
ejemplo, podemos programar una clase que obtenga información de cuándo el sistema
operativo ha terminado de cargar después de encender el dispositivo, o que el dispositivo
se está quedando sin batería, etc.
Para poder recibir estas notificaciones, tenemos que hacer dos cosas.
1. Crear una subclase de la clase BroadcastReceiver e implementar el método
onReceive() que recibirá un intent a través del cual se puede saber qué
notificación hemos recibido. Por ejemplo para programar un receptor que nos
avise de haber recibido un SMS:

p u b lic c l a s s R ecep tor ex te n d s B r o a d c a stR e c e iv e r {


p r iv a te f in a l S trin g
SMS_RECEIVED="a n d r o i d . p r o v i d e r . T elep h o n y .S M S _ R E C E IV E D " ;
© O v errid e
p u b lic v o id o n R e c e iv e (C o n te x t c o n t e x t , I n t e n t in t e n t ) {
i f ( i n t e n t . g e t A c t i o n O . eq u a ls (S M S _ R E C E I V E D ))
T o a s t . m a k e T e x t ( c o n t e x t , " R e c i b i d o SMS", T o a s t . LENGTH_LONG). s h o w ( ) ;
}
}

2. Registrarla subclase en el fichero de manifiesto de nuestra App como candidata


a recibir ciertas notificaciones. Se especificará, mediante la etiqueta XML
< receiver> un filtro de intent con la acción que se espera recibir, por
ejemp\o:android.intent.action.BOOT_COMPLETED. Recuerda que registrar
un componente en el fichero de manifiesto implica crearlo de manera estática,
es decir, se creará automáticamente un objeto de la clase Receptor nada más
iniciar la App. Además, hay que solicitar los permisos necesarios. Por ejemplo,
para poder recibir SMSs, nuestro fichero de manifiesto tiene esta pinta:

•cuses-p e rm issio n a n d ro id :name ="a n d ro id . p e rm is s io n . INTERNET">


</u s e s-p e rm issio n >
c u s e s -p e rm issio n a n d ro id :name="a n d ro id . p e rm is s io n . RECEIVE_SMS">
< /u ses-p e rm issio n >
« a p p lic a tio n

« re c e iv e r a n d ro id :name=".R eceptor">
150 Programación en Android

<intent-filter>
« a c t i o n a n d r o i d : n a m e = "a n d r o i d . p r o v i d e r . T e l e p h o n y . SMS_RECEIVED" / >
< /in te n t-filte r >
</receiver>
</application>

ACTIVIDAD 3.6.
Puedes ver y analizar el código de un broadcast receiver en: DemoBroadcastReceiver.rar del
m aterial complementario.

3.7.1. LISTA COMPLETA DE BROADCAST RECEIVERS


A continuación te mostramos algunas de las constantes más utilizadas para recepción
de broadcast

a n d ro id .a p p .a c tio n .A C T IO N _ P A S S W O R D _ C H A N G E D a n d ro id .in te n t.a c tio n .D A T E _ C H A N G E D


a n d ro id .a p p .a c tio n .A C T IO N _ P A S S W O R D _ E X P IR IN G a n d ro id .in te n t.a c tio n . D E V IC E _ S T O R A G E _ L O W
a n d ro id .a p p .a c tio n .A C T IO N _ P A S S W O R D _ F A IL E D a n d ro id .in te n t.a c tio n .D E V I C E _ S T O R A G E _ O K
a n d ro id .a p p .a c tio n .A C T IO N _ P A S S W O R D _ S U C C E E D E D a n d ro id .in te n t.a c tio n .D O C K _ E V E N T
a n d ro id .a p p .a c tio n .D E V IC E _ A D M IN _ D IS A B L E D android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE
an d ro id . a p p .a c tio n .D E V IC E _ A D M IN _ D IS A B L E _ R E Q U E S T E D android.intent.action.EXTERNAL__APPLICATIONS_UNAVAILABLE

a n d ro id .a p p .a c tio n .D E V IC E _ A D M IN _ E N A B L E D a n d ro id .in te n t.a c tio n .F E T C H _ V O IC E M A IL


android.bluetooth.a2dp.profile.action.CONNECTION_STATE__CHANGED a n d ro id .in te n t.a c tio n .G T A L K _ C O N N E C T E D
android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED a n d r o id .in te n t.a c tio n . G T A L K _ D IS C O N N E C T E D
android. bluetooth.adapter.action.CONNECTION_STATE_CHANGED a n d ro id .in te n t.a c tio n .H E A D S E T _ P L U G
a n d ro id .b lu e to o th .a d a p te r.a c tio n . D IS C O V E R Y _F IN IS H E D a n d r o id .in te n t.a c tio n .I N P U T _ M E T H O D _ C H A N G E D
a n d ro id .b lu e to o th .a d a p te r.a c tio n .D IS C O V E R Y _ S T A R T E D a n d ro id .in te n t. a c tio n . L O C A L E _ C H A N G E D
a n d ro id .b lu e to o th .a d a p te r.a c tio n .L O C A L _ N A M E .C H A N G E D a n d r o id .in te n t.a c tio n .M A N A G E _ P A C K A G E _ S T O R A G E
a n d ro id .b lu e to o th .a d a p te r.a c tio n .S C A N _ M O D E _ _ C H A N G E D a n d ro id .in te n t.a c tio n .M E D IA . _ B A D _ R E M O V A L
a n d ro id .b lu e to o th .a d a p te r.a c tio n .S T A T E _ C H A N G E D a n d r o id .in te n t.a c tio n .M E D I A _ B U T T O N
a n d ro id .b lu e to o th .d e v ic e .a c tio n . A C L _ C O N N E C T E D a n d r o id .in te n t .ac tio n . M E D I A _ C H E C K IN G
a n d ro id .b lu e to o th .d e v ic e .a c tio n . A C L _ D IS C O N N E C T E D a n d r o id .in te n t.a c tio n .M E D I A _ E J E C T
android.bluetooth.device.action.ACL_DISCONNE€T_REQUESTED a n d ro id .in te n t.a c tio n .M E D IA _ M O U N T E D
a n d ro id .b lu e to o th . device. action.B O N D ... S T A T E _ C H A N G E D a n d r o id .in te n t .ac tio n . M E D I A _ N O F S
an d ro id .b lu e to o th .d e v ic e .a c tio n .C L A S S _ C H A N G E D a n d ro id .in te n t. a c tio n . M E D I A „ R E M O V E D
a n d ro id .b lu e to o t h . d ev ice.a c t ion .F O U N D a n d r o id .in te n t.a c tio n .M E D IA _ S C A N N E R _ F IN IS H E D
a n d ro id .b lu e to o t h . d ev ice.ac tio n . N AME_. .C H A N G E D a n d ro id .in te n t. a c tio n . M E D IA . _ S C A N N E R ._ S C A N _ F IL E
a n d ro id .b lu e to o th , d ev ice.ac tio n . U U ID a n d ro id .in te n t.a c tio n .M E D IA _ S C A N N E R _ S T A R T E D
a n d ro id , b lu e to o th .d e v ic e p ic k e r.a c tio n .D E V IC E _ S E L E C T E D a n d ro id .in te n t. a c tio n .M E D IA _ S H A R E D
an d ro id .b lu e to o th .d e v ic e p ic k e r.a c tio n .L A U N C H a n d ro id .in te n t.a c tio n .M E D IA _ U N M O U N T A B L E
android.bluetooth. headset.action.VENDOR_SPECIFIC_HEADSET_EVENT
an d ro id .in te n t.a c tio n .M E D IA _ _ U N M O U N T E D
android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED
a n d ro id .in te n t.a c tio n .M Y _ P A C K A G E _ _ R E P L A C E D
android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED
android.bluetooth.input.profile.action.CONNECTION_STATE„CHANGED a n d ro id .in te n t.a c tio n .N E W _ O U T G O I N G _ C A L L
android.bluetooth.pan.profile.action.CONNECTION_STATE__ CHANGED a n d r o id .in te n t.a c tio n .N E W _ V O I C E M A IL
a n d ro id .h a rd w a re .a c tio n .N E W _ P IC T U R E a n d r o id .in te n t.a c tio n .P A C K A G E _ A D D E D
a n d ro id .h a rd w a re .a c tio n .N E W _ V ID E O a n d ro id .in te n t.a c tio n .P A C K A G E „ C H A N G E D
a n d ro id .h a rd w a re .in p u t.a c tio n . Q U E R Y _ K E Y B O A R D _ L A Y O U T S a n d ro id .in te n t.a c tio n .P A C K A G E _ D A T A _ C L E A R E D
an d ro id . in te n t.a c tio n .A C T IO N _ P O W E R _ C O N N E C T E D a n d ro id .in te n t.a c tio n .P A C K A G E _ F I R S T _ L A U N C H
a n d ro id , in te n t, a c tio n . A C T IO N _ P O W E R _ D IS C O N N E C T E D a n d r o id .in te n t.a c tio n .P A C K A G E _ F U L L Y _ R E M O V E D
an d ro id , in te n t, ac tio n . A C T IO N _ S H U T D O W N a n d ro id .in te n t.a c tio n .P A C K A G E _ IN S T A L L
a n d ro id .in te n t.a c tio n . A IR P L A N E _ M O D E a n d r o id .in te n t.a c tio n .P A C K A G E _ N E E D S _ V E R I F I C A T I O N
an d ro id , in te n t. a c tio n .B A T T E R Y _ C H A N G E D a n d ro id .in te n t.a c tio n .P A C K A G E _ R E M O V E D
a n d ro id .in t e n t. a c tio n . B A T T E R Y _ L O W a n d r o id .i n t e n t.ac tio n . P A C K A G E _ R E P L A C E D
a n d ro id .in te n t.a c tio n .B A T T E R Y _ O K A Y a n d ro id .in te n t .a c tio n . P A C K A G E _ R E S T A R T E D
a n d ro id .in te n t.a c tio n .B O O T _ C O M P L E T E D a n d ro id .in te n t. a c tio n . P H O N E _ S T A T E
a n d ro id .in te n t.a c tio n .C A M E R A _ B U T T O N a n d ro id , in te n t. a c tio n .P R O V ID E R _ C H A N G E D
a n d ro id .in te n t .ac tio n . C O N F IG U R A T IO N _ C H A N G ED a n d ro id .in te n t.a c tio n .P R O X Y _ C H A N G E
a n d ro id .in te n t.a c tio n .D A T A SM S R E C E IV E D a n d ro id .in te n t.a c tio n .R E B O O T
Capítulo 3. Comunicaciones 151

a n d ro id .i n t e n t.ac tio n . S C R E E N _ O F F an d ro id .n et..w ifi.S T A T E _ C H A N G E


a n d ro id .i n t e n t.ac tio n . S C R E E N _ O N a n d ro id .n e t.w ifi.W IF I_ S T A T E _ C H A N G E D
a n d ro id , in te n t. actio n .T IM E Z O N E _ _ C H A N G E D a n d r o id .n e t.w if i.p 2 p .C 0 N N E C T I 0 N _ S T A T E _ C H A N G E
a n d ro id .in te n t.a c tio n .T IM E _ S E T a n d ro id .n e t.w if i.p 2 p .D I S C 0 V E R Y _ S T A T E _ C H A N G E
a n d ro id , in te n t. a c tio n .T IM E _ T IC K a n d ro id .n e t. w ifi.p 2 p .P E E R S _ C H A N G E D
a n d ro id .i n t e n t.a c tio n .U ID _ R E M O V E D a n d ro id .n e t. w ifi.p 2 p .S T A T E _ C H A N G E D
a n d ro id .in te n t.a c tio n .U S E R _ P R E S E N T a n d ro id . n e t.w ifi.p 2 p .T H I S _ D E V I C E _ C H A N G E D
a n d ro id .in te n t.a c tio n .W A L L P A P E R _ C H A N G E D a n d ro id . n e t.w ifi.s u p p lic a n t.C O N N E C T I O N . C H A N G E
a n d ro id .m e d ia .A C T IO N _ S C O _ A U D IO _ S T A T E _ U P D A T E D a n d ro id .n e t. w ifi.s u p p lic a n t.S T A T E _ C H A N G E
a n d ro id .m e d ia .A U D IO _ B E C O M IN G _ N O IS Y a n d ro id .p ro v id e r.T e le p h o n y .S IM _ F U L L
a n d ro id .m e d ia .R IN G E R _ M O D E _ C H A N G E D an d ro id , p ro v id e r. T e lep h o n y . S M S _ C B _ R E C E I V E D
a n d ro id .m e d ia .S C O _ A U D IO _ S T A T E _ C H A N G E D android.provider .Telephony.SMS_ EM ERGENCY_CB_RECEI VED
a n d ro id .m e d ia .V IB R A T E _ S E T T IN G _ C H A N G E D a n d ro id .p ro v id e r. T ele p h o n y . S M S _ R E C E I V E D
android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION an d ro id , p ro v id e r. T ele p h o n y . S M S _ R E J E C T E D
android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSlON a n d ro id .p ro v id e r.T e le p h o n y .S M S _ S E R V IC E _ C A T E G O R Y _ P R O
android.net.conn.BACKGROUND_DATA_SETTING_CHANGED G R A M _ D A T A _ R E C E IV E D
a n d ro id .n e t.n s d .S T A T E _ C H A N G E D an d ro id .p ro v id e r.T e le p h o n y . W A P _ P U S H _ R E C E I V E D
an d ro id , n e t. w ifi.N E T W O R K _ ID S _ C H A N G ED
a n d ro id .s p e e c h .tts .T T S _ Q U E U E _ P R O C E S S I N G _ C O M P L E T E D
an d ro id , n e t. wifi. R S S I_ C H A N G E D
a n d ro id .sp e ech .tts .e n g in e . T T S _ D A T A _ I N S T A L L E D
a n d ro id .n e t. w ifi.S C A N _ R E S U L T S

3.7.2. BROADCAST PERSONALIZADOS


Por supuesto, además de todos estos Broadcast Receivers, puedes crear los tuyos
propios personalizados, tan solo tienes que definirlos en tu archivo de manifiesto y
enviarlos a través del método sendBroadcast:
p u b lic v o id e j e m p lo B r o a d c a st(V ie w v ie w )
{
In ten t in te n t = new I n t e n t ();
intent.setAction("com.miapp.BRDCAST_PERSONALIZADO");
sendBroadcast(intent);
}
/ / d e f i n e e l f i l t r o en e l a r c h iv o de m a n if ie s t o
< r e c e i v e r a n d r o i d : n a tn e = " M i R e c e i v e r ">
<i n t e n t - f i l t e r >
< a c t i o n a n d r o i d : n a m e = "c o m . m i a p p . BRDCAST_PERSONALIZADO">
< /a c tio n >
< /in te n t-filte r >
</r e c e iv e r >

3.8. LOS M EN SA JES DE TEXTO . E N V IA R Y


R EC IBIR SMS A TR A V ÉS DE CÓDIGO
Para enviar mensajes SMS (Short Message Service) hay que utilizar la clase
SMSManager. Esta clase tiene un método estático llamado getDefault para obtener una
referencia a un objeto de este tipo. Con ese objeto, podemos invocar al método
sendTextMessage para enviar fácilmente un mensaje corto de texto. Para poder realizar
esta operación, necesitamos, además, los permisos necesarios en el fichero de manifiesto:
cuses-permission android:name="android.permission.SEND_SMS"/>

El código que puedes utilizar para enviar un mensaje es el siguiente:


152 Programación en Android

p u b l i c v o i d E n viarS M S (V iew v ) {
E d itT ex t tx t T e le f o n o = ( E d it T e x t ) f in d V ie w B y ld ( R .id .t x t T e le f o n o ) ;
L o g , i ( t a g , " E n v ia n d o S M S . . . . " ) ;

S tr in g t e le f o n o = t x t T e le f o n o .g e t T e x t ( ) .t o S t r i n g ();
S t r i n g m e s s a g e = "Te f e l i c i t o l a n a v i d a d a u t o m á t i c a m e n t e " ;

try {
S m sM an ager sm sM a n a g e r = S m s M a n a g e r . g e t D e f a u l t ( ) ;
sm sM anager. s e n d T e x tM e s s a g e ( t e l e f o n o , n u l l , m e s s a g e , n u l l , n u ll);
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t(),
"SMS e n v i a d o . " , T o a s t . LENGTH_LONG). s h o w ( ) ;
} c a tc h (E x c e p tio n e) {
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t(),
"SMS n o e n v i a d o , p o r f a v o r , i n t é n t a l o o t r a v e z . " ,
T o a s t . LENGTH_LONG). s h o w ( ) ;
e . p r in tS ta c k T r a c e ();
}
}
Como ves, es muy sencillo, tan solo es necesario enviar el número de teléfono y el
mensaje en un String y voilá.
Pero recibir un mensaje de texto no es tan sencillo: Primero, tienes que programar un
Broadcast Receiver tal y como se indica en la Sección 3.7, pero en lugar de registrarlo
estáticamente en el fichero de manifiesto, debes hacerlo dinámicamente. De esta manera
podrás establecer una interfaz de comunicación entre el Broadcast Receiver y la
Actividad principal. Después de filtrar por el tipo de acción, tienes que leer los datos
extras que recibes en el intent que detecta la acción para poder obtener el texto y el
emisor del SMS. Para eso, se utiliza la clase Budle, que es capaz de leer todos los datos
extras de un Intent y transformarlos en un objeto SmsMessage.
De acuerdo al estándar SMS (Estándares ETSI GSM 03.401 y 03.382), un mensaje de
texto está formado por PDUs (Protocol Description Unit), puedes ver un esquema de la
estructura de una PDU:

Octet(s) Description format In this example


07 Length of the SMSC information hex-octet 7 octets
91 Type of address of SMSC hex-octet alternation format
13 26 04 00 00 F0 SMSC number decimal semi-octets b 31624000000
04 First octet of this SMS-DELIVER message. hex-octet TP-MMS
OB Lenght of the sender address hex-octet 11 (decimal)
91 Type of address of the sender number hex-octet
13 46 61 00 $9 F6 Sender number decimal semi-octets 31641600986
00 Protocol identifier hex-octets
00 Data encoding scheme hex-octets
20 80 62 91 73 14 08 Time stamp c decimal semi-octets 06-08-02 29:17:31
oc Length of User data (SMS message) hex-octets 12 (decimal)
C S F 7 ID 14 96 97 S-bit octets
User data How are you?
41 F9 77 FD 07 respresentins 7-bit data

Por tanto, los datos extras que se envían en el Intent son las PDUs que compongan el
mensaje.Estas PDU’s se pueden obtener con el objeto Bundle y transformarlas a un
objeto SmsMessage, mucho más manejable desde código:
Capítulo 3. Comunicaciones 153

Examina el siguiente código, es una aplicación que envía SMSs y que muestra la
recepción de los que llegan. Fíjate particularmente en el método onReceive, que obtiene
un objeto Bundle para leer las pdus. Se puede utilizar el método CreateFromPdu de la
clase SmsMessage para obtener los datos de origen y texto del SMS (métodos
getOriginatingAddress y getMessageBody):
p u b l i c c l a s s R eceptorSM S e x t e n d s B r o a d c a s t R e c e i v e r {
p r iv a te f in a l S trin g
SMS_RECEIVED="a n d r o i d . p r o v i d e r . T e l e p h o n y . SMS_RECEIVED";

/ / I n t e r f a z (L is te n e r ) p a ra com u n icarn os
/ / c o n l a a c t i v i d a d que c r e ó a l B r o a d ca st R e c e iv e r
p r i v a t e o n R e c ib e S M S r e s p u e s t a ;
p u b lic v o id se tO n R e c ib e S M S L iste n e r (A c tiv ity x ) {
resp u esta = (o n R ecib eS M S ) x ;
}
© O v errid e
p u b lic v o id o n R e c e iv e (C o n te x t c o n te x t . I n te n t in t e n t ) {
i f (in te n t.g e tA c tio n O .e q u a ls(S M S _ R E C E IV E D )) {
// E s t o a b orta n o t i f ic a c i o n e s a o t r o s . . .
t h i s . ab ortB road cast();

/ / -----g e t t h e SMS m e s s a g e p a s s e d i n -----


B u n d le b u n d le = i n t e n t . g e t E x t r a s ( ) ;
S m s M e s s a g e [] m s g s = n u l l ;
S tr in g o r ig e n = n u ll;
S t r i n g msg = n u l l ;

if ( b u n d l e != n u l l ) {
/ / o b t e n e m o s e l m e n s a j e o r i g i n a l SMS:
O b j e c t [] p d u s = ( O b j e c t [ ] ) b u n d l e . g e t ( " p d u s " ) ;
m s g s = n ew S m s M e s s a g e [ p d u s . l e n g t h ] ;
f o r ( i n t i = 0; i < m s g s . l e n g t h ; i+ + ) {
m s g s [ i ] = S m sM essage. C reateF rom P d u ( ( b y t e [] ) p d u s[i]);
o r ig e n = m s g s [ i ] .g etO r ig in a tin g A d d r e ss0 ;
msg = m s g s [ i ] . g e t M e s s a g e B o d y () . t o S t r i n g O ;
}
//in fo r m a m o s a l a a c t i v i t y de l a l l e g a d a d e l m en saje
r e s p u e s t a . o n R ecib eS M S ( o r i g e n , m s g ) ;
T o a st.m a k e T e x t(c o n te x t,
"SMS R e c i b i d o ! " , T o a s t . LENGTH_LONG) . s h o w ( ) ;

/ / c o n t i n u a e l p r o c e s o n orm al de b r o a d c a s t
/ / e s d e c i r , l l e g a e l sm s y s e a l m a c e n a
/ / e n la b an d eja de en tra d a
t h i s . cle a r A b o r tB r o a d c a st();
}
}
}
/ / i n t e r f a z para l a a c tiv id a d e n tr e e l b ro a d ca st R e c e iv e r y la a c tiv id a d
p u b l i c i n t e r f a c e on R ecib eS M S {
p u b l i c v o i d o n R ecib eS M S ( S t r i n g o r i g e n , S t r i n g m e n s a j e ) ;
}

}
154 Programación en Android

El código de la actividad es el siguiente. Fíjate sobre todo en:


• Cómo se registra dinámicamente el broadcast receiver para poder enlazarlo a la
actividad a través de una interfaz y una función de callback.
• Cómo se desregistra el broadcast receiver para evitar pérdidas de memoria en
el sistema.
p u b lic c la ss S M S A ctiv ity e x te n d s A c t i v i t y im p le m e n t s R ecep to rS M S . o n R ecib eS M S {

p u b l i c f i n a l S t r i n g tag=" D em oS M S " ;
R eceptorSM S r e c e p t o r ;

© O v errid e
p r o t e c t e d v o id o n C r e a te (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p er. o n C r e a te (sa v e d ln sta n c e S ta te );
se tC o n te n tV ie w (R .la y o u t. a c t i v it y _ s m s ) ;
// c r e a m o s y r e g i s t r a m o s e l r e c e p t o r de s m s ' s de m anera d in á m ic a
r e c e p to r = n e w R eceptorSM S();
r e g is te r R e c e iv e r (r e c e p to r ,
new I n t e n t F i l t e r (" a n d r o i d . p r o v i d e r . T e l e p h o n y . SMS_RECEIVED") ) ;
r e c e p t o r . setO n R ecib eS M S L ist e n e r ( t h i s ) ;

© O v errid e
p r o te c t e d v o id o n D estroyO {
u n re g iste r R e c e iv e r (r e c e p to r ); / / p a r a q u e n o h a y a p é r d i d a s d e m e m o r ia
r e c e p to r = n u ll;
}
//M é to d o c a ll b a c k para p u ls a r e l b o tó n
p u b l i c v o i d E n v ia r S M S ( V ie w v ) {
E d itT ex t tx tT e le fo n o = (E d itT e x t)fin d V ie w B y ld (R . i d . t x t T e l e f o n o ) ;
E n v ia S M S (tx tT elefo n o . g e t T e x t ( ) . t o S t r i n g () ,
"Te f e l i c i t o l a n a v i d a d a u t o m á t i c a m e n t e " ) ;
}
p u b l i c v o i d E n v ia S M S (S trin g t e l e f o n o , S t r i n g m e n s a j e ) {
try {
Sm sM anager sm sM a n a g er = S m s M a n a g e r . g e t D e f a u l t ( ) ;
sm sM anager. s e n d T e x tM e s s a g e ( t e l e f o n o , n u l l , m e n s a je , n u l l , n u ll);
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t(),
"SMS e n v i a d o . " , T o a s t . LENGTH _LO NG ).show();
} c a t c h ( E x c e p t io n e) {
T o a st.m a k e T e x t(g e tA p p lic a tio n C o n te x t(),
"SMS n o e n v i a d o , p o r f a v o r , i n t é n t a l o o t r a v e z . " ,
T o a s t . LENGTH_LONG). s h o w ( ) ;
e .p r in tS ta c k T r a c e ();
}

© O v errid e
p u b l i c v o i d o n R ecib eS M S ( S t r i n g o r i g e n , S t r i n g m e n s a je ) {
T ex tV iew t = ( T e x t V ie w ) f in d V ie w B y ld ( R . i d . t x t R e c i b i r S M S ) ;
t . s e t T e x t ( "M ensaje de " + o r ig e n + " : " + m e n s a j e ) ;
}
}
Capítulo 3. Comunicaciones 155

3.9. LOS PR O V EED O R ES DE C O N T E N ID O


(C O N T E N T PR O V ID E R )
Un Proveedor de Contenido, en inglés, content provider, maneja y regula el acceso a
un conjunto de datos. En tu dispositivo hay multitud de aplicaciones con datos que
pueden ser de utilidad para tus App. Por ejemplo, los contactos es una fuente de datos
que tu aplicación puede aprovechar, y que puede acceder a través de estos proveedores
de contenido.
De igual manera si quieres compartir los datos de tu App, puedes programar un
proveedor de contenido para que otras App puedan acceder a esos datos contenidos.
Los proveedores de contenido presentan la información de manera similar a como lo
hace un sistema gestor de base de datos relaciónales, es decir, a través de ta b la s. Los
datos de cualquier Proveedor de Contenido se acceden a través de un objeto llamado
ContentResolver. Este objeto actúa como cliente del proveedor e implementa unos
métodos de comunicación estándar para acceder a los datos del ContentProvider, que
son los siguientes:
• insert(Uri uri, ContentValues cv) que añade nuevos datos al proveedor de
contenido.
• update (Uri uri, ContentValues cv, String selección, String[] args_seleccion)
que actualice datos del proveedor.
• delete(Uri uri, String selección, String[] args_seleccion) que borra datos
• query(Uri uri, String [] proyección, String selección, String[] args_seleccion,
String ordenación) para consultar datos del proveedor.
El primer parámetro que reciben estos métodos es un Uri (Universal Resource
Identifier) que indicará el tipo de objeto que se desea leer, por ejemplo, para leer el
email de los contactos de la agenda, se usará:
ContactsContract.CommonDataKinds.Email.CONTENT_URI.
En el caso de insert y update hay que proporcionar también los valores a
insertar/actualizar mediante un objeto ContentValues.
En el caso de update y delete, hay que proporcionar un filtro para obtener las filas de
datos que se van a actualizar/borrar (selección y args_selección), al más puro estilo
WHERE en una cláusula SELECT de SQL. La selección sería algo del estilo: WHERE
colum nal= ?, columna2= ? . Y args_seleccion es un array con valores para cada
símbolo ? . Las interrogaciones se utilizan a modo de valor para cada parámetro para
evitar posibles ataques estilo SQL Injection.
Y en el caso de query, recibe también un argumento llamado proyección, donde se
indican los campos de datos a obtener. Sería el equivalente a la lista de columnas de una
consulta SQL (Select coll,col2 ...) Los resultados devueltos por un método query se
pueden recorrer mediante un bucle y un cursor.
156 Programación en Android

Por ejemplo, un tipo de content provider es el proveedor de contactos Contad


Provider, que puedes ver a través de la aplicación de contactos de tu dispositivo móvil.
Este proveedor ofrece tres tablas, la tabla de contactos Contad (representada mediante
la clase C on tacts C ontract. C ontacts), que contiene una sola fila con cada contacto.
La tabla de contactos o Raw contacts (representada por
C on tacts C on tract. R aw C ontacts), que contiene una fila por cada cuenta de cada
contacto, y la tabla de datos de cada cuenta o Data (representada por
C on tacts C on tract. D ata). El nivel de detalle de los datos va estructurado a través de
la clase C o n ta c tsC o n tra ct.C o m m o n D a ta K in d s, pudiéndose llegar a consultar, por
ejemplo, los datos del nombre del contacto de forma estructurada (Nombre de pila,
Apellido, Tratamiento, etc) a través de la clase
C on tacts C on tract. C o m m o n D a ta K in d s.S tru ctu red N a m e.

Contact

(Thomas Htoginson)
------ ► emiIy.dickinson@gmail.com -
Google
(Thomas Higginson)
------- ► emilyd@gmall.com
Google
(colonel_tom)
------ ► amherstoelie
Twitter
R a w C o n ta c ts

Thomas Higginson
StructuredName

imágenes de : Email
developer.android,com
Email
D a ta

DemoContentProvnder
C aso práctico: Crea una App que muestre en una lista los
contactos que tengan teléfono y cuyo nombre contenga una Juan Carlos buscar
Juan cados P e#
cadena de caracteres que se introduzca desde un campo de JuancanosManrique
texto. Cuando se haga una pulsación larga en uno de los
elementos de la lista de contactos, se le enviará un SMS con el
contenido de una caja de texto. escribe aquí el lexto de! mensaje

Para resolver el caso práctico, debes realizar las siguientes operaciones:


P a so 1: Da a tu App los permisos que necesitas en el fichero de manifiesto:
« u s e s - p e r m i s s i o n a n d r o i d : name= " a n d r o i d . p e r m i s s i o n . READ_CONTACTS" / >
< u s e s - p e r m i s s i o n a n d r o i d :n a m e=" a n d r o i d . p e r m i s s i o n . SEND_SMS" / >

P a so 2: Crea un fichero XML con un campo de texto para la listView que contendrá
los contactos.
< T e x t V i e w x m l n s : a n d r o i d = "h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d
Capítulo 3. Comunicaciones 157

a n d r o id :o r ie n ta tio n = " v e r t í c a l "


a n d r o id : la y o u t_ w id th = " m a tch _ p a ren t"
a n d r o i d : l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d ro id :lo n g C lick a b le= " tru e"
a n d r o i d : h e i g h t = "3 Odp"

P a so 3: Programa el método “Buscar”:


Obtén una referencia al ContentResolver para que a través del método Query, poder
hacer una consulta sobre la Uri ContactsContract.Contacts.CONTENT_URI. Esta Uri
nos da acceso a la tabla de contactos, de la que consultaremos los campos:
S t r i n g p r o y e c c i ó n [ ] = { C o n t a c t s C o n t r a c t . C o n t a c t s ._ ID ,
C o n t a c t s C o n t r a c t . C o n t a c t s . DISPLAY_NAME,
C o n t a c t s C o n t r a c t . C o n t a c t s . HAS_PHONE_NUMBER,
C o n t a c t s C o n t r a c t . C o n t a c t s . PHOTO_ID};

Con el content resolver, ejecuta el método query con el filtro obtenido de la caja de
texto que el usuario ha rellenado:
S t r i n g f i l t r o = C o n t a c t s C o n t r a c t . C o n t a c t s . DISPLAY_NAME + " l i k e ? " ;
S t r i n g a r g s _ f i l t r o []= {" % " + t x t N o m b r e . g e t T e x t ( ) . t o S t r i n g ()+" % "};

C o n ten tR eso lv er c r = g e tC o n te n tR e s o lv e r ();


C u r s o r c u r = c r . q u e r y ( C o n t a c t s C o n t r a c t . C o n t a c t s . CONTENT_URI,
p ro y ecció n , f i l t r o , a r g s _ f ilt r o , n u i l ) ;

P a so 4: Con el cursor, recorre la lista de contactos extraída, agregando los elementos a


un ArrayList. Con ese ArrayList construye un adaptador para el listView.
List<String> lista_contactos=new ArrayList<String> () ,-
ContentResolver cr = getContentResolver () ,-
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_DRI,
proyección, filtro, args_filtro, nuil) ,-

if (cur.getCount () > 0 ) {
while (cur.moveToNext()) {
//obtener id de contacto
String id = cur.getString(
cur.getColumnlndex(ContactsContract.Contacts._ID));
//obtener nombre de contacto
String name = cu r .getString(
cur.getColumnlndex(ContactsContract.Contacts,DISPLAY_NAME));
//si tiene teléfono, lo agregamos a la lista de contactos
if (Integer.parselnt(cur.getString(
cur.getColumnlndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
lista_contactos.add(name);
}
1
1
//mandamos el adapatador a la lista de contactos
ListView 1 = (ListView)findViewByld(R.id.IstContactos);
1.setAdapter(new ArrayAdapter<String>(this,R .layout.fila_lista,lista_contactos));
cur.close(); //cerrar el cursor

P a so 5: Programa el método onItemLongClick() de la listView para buscar el contacto


seleccionado en el content provider:
L i s t V i e w 1;
p r iv a te f in a l S t r i n g tag="SM S:";
158 Programación en Android

@ O verride
p u b l i c b o o le a n o n ltem L o n g C lick (A d a p terV iew < ? > p a r e n t,
V iew v ie w , i n t p o s i t i o n , lo n g id ) {

T ex tV iew t= ( T e x t V ie w ) v ie w ;
S tr in g n om b reC on tacto= t. g e t T e x t ( ) . t o S t r i n g O ;

S tr in g p r o y e c c ió n []= {C o n ta c tsC o n tr a c t. C o n ta c ts._ ID };


S t r i n g f i l t r o = C o n t a c t s C o n t r a c t . C o n t a c t s . DISPLAY_NAME + " = ? " ;
S tr in g a r g s _ f i l t r o [ ] = {n om b reC on tacto};

L ist< S trin g > lista _ c o n ta c to s= n e w A r r a y L ist< S tr in g > ();


C o n ten tR eso lv er c r = g e tC o n te n tR e s o lv e r ();
C u r s o r c u r = c r . q u e r y ( C o n t a c t s C o n t r a c t . C o n t a c t s . CONTENT_URI,
p ro y ecció n , f i l t r o , a r g s _ f ilt r o , n u l l ) ;
i f ( c u r . g e t C o u n t () > 0) {
w h i l e (c u r . m o v e T o N e x t ( ) ) {
S trin g id e n t ific a d o r = c u r .g e t S t r in g (
c u r . g e tC o lu m n ln d e x (C o n ta c tsC o n tr a c t. C o n t a c t s ._ I D ) );
E n v ia r S M S (id e n tific a d o r );
}
}
c u r . c l o s e ();
retu rn tru e;
}
P a so 6: Programa el método enviar SMS. Para obtener el número de teléfono hay que
efectuar una segunda consulta filtrando por el nombre del contacto:
/ / e n v i a u n SMS a l o s t e l é f o n o s d e u n c o n t a c t o
p r i v a t e v o i d E n v ia rS M S ( S t r i n g i d e n t i f i c a d o r ) {
C o n ten tR eso lv er c r = g e tC o n te n tR e s o lv e r ();
Sm sM anager sm sM a n a g er = S m s M a n a g e r . g e t D e f a u l t ( ) ;
S t r i n g m ensaje=
( (E d itT e x t)fin d V ie w B y ld (R .id .tx tS M S )) . g e t T e x t ( ) . t o S t r i n g ();
C ursor c u r so r T e le fo n o = c r . q u e r y (
C o n t a c t s C o n t r a c t . C o m m o n D a ta K in d s. P h o n e . CONTENT_URI, n u i l ,
C o n t a c t s C o n t r a c t . C o m m onD ataK inds . P h o n e . CONTACT_ID +" = ? " ,
n ew S t r i n g [ ] { i d e n t i f i c a d o r } , n u i l ) ;
w h ile (c u r so r T e le fo n o .m o v e T o N e x t()) {
S trin g te le fo n o = c u r s o r T e le fo n o .g e tS tr in g (
c u r s o r T e le fo n o . g etC o lu m n ln d e x (
C o n t a c t s C o n t r a c t . C o m m o n D a ta K in d s. P h o n e . DATA)) ;
try {
sm sM a n a g e r .sen d T ex tM essa g e(telefo n o , n u i l ,
m en saje, n u l l , n u i l ) ;
L o g . d ( t a g , "SMS e n v i a d o . " ) ;
} c a tc h (E x c e p tio n e) {
L o g . d ( t a g , "No s e p u d o e n v i a r e l S M S ." ) ;
e . p r i n t S t a c k T r a c e () ;
}

1
c u r so r T e le fo n o . c l o s e ();

ACTIVIDAD 3.7,
Puedes ver y analizar el código fuente de este caso práctico en: D em oC ontentProvider.rar del
m aterial complementario.
Capítulo 3. Comunicaciones 159

3.10. ACCESO A BA SES DE D A TO S SQLite


SQLite es un pequeño gestor de base de datos que forma parte del sistema operativo
Android y que permitirá a tu aplicación almacenar datos de forma estructurada al más
puro estilo base de datos relacional. Acceder a SQLite en Android es realmente sencillo,
con un objeto y un par de métodos tienes todo el potencial de un lenguaje SQL al
alcance de tu aplicación.
Para poder utilizar SQLite tienes que utilizar un objeto de la clase SQLiteDatabase.
Este objeto se consigue invocando al método estático
openOrCreateDatabase(nombre,modo,cursor), que crea una nueva base de datos si no
existe previamente y devuelve un objeto para poder acceder a esa base de datos. Con
este objeto se pueden ejecutar sentencias SQL para acceder a una base de datos a través
del método execSQL, y se pueden realizar consultas con el método rawQueryQ.

# MisDiscos

Grupo: Lady Gaga


Titulo de! disco:

Añadir Borrar

blur-Parklife
Lady Gaga-Monster

El caso práctico que te proponemos a continuación utiliza estos dos métodos para
mantener una tabla de artistas musicales (Grupos) y sus álbumes (Discos).
Para solucionarlo te proponemos que sigas estos pasos:
P a so 1: En el método onCreate() de la actividad, obtén una referencia a
SQLiteDatabase y crea la base de datos “MisDiscos”.
E d itT e x t tx tG r u p o ,tx tD is c o ;
L istV ie w l i s t a D i s c o s ;
S Q L i t e D a t a b a s e db;

@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) (
su p er.o n C rea te(sa v ed ln sta n ceS ta te);
se tC o n te n tV ie w (R .la y o u t . a c t i v it y _ m a in ) ;
tx tG r u p o = (E d itT e x t)fin d V ie w B y ld (R .id .tx tG r u p o );
tx tD isc o = (E d itT e x t)fin d V ie w B y ld (R .id .tx tD is c o );
lista D is c o s= (L istV ie w )fin d V ie w B y ld (R .id .lista D isc o s );
d b = o p e n O r C r e a t e D a t a b a s e ( " M i s D i s c o s " , C o n t e x t . MODE_PRIVATE, n u i l ) ;
d b . e x e c S Q L ( "CREATE TABLE I F NOT EXISTS m i s D i s c o s (G rupo VARCHAR,
D i s c o VARCHAR); " ) ;
L i s t a r ();
}
160 Programación en Android

P a so 2: Programa el método de callback Añadir para responder al clic del botón


Añadir. Este método realizará un INSERT a través de la función execSQL.
p u b l i c v o i d A ñ a d i r ( V ie w v ) {
d b . e x e c S Q L ("INSERT INTO M i s D i s c o s VALUES
( '" + txtG ru p o. g e t T e x t ( ) . t o S t r i n g () + " ','" +
t x t D is c o .g e tT e x t( ) . t o S t r in g ()+ " ')" );
T o a s t . m a k e T e x t ( t h i s , "Se a ñ a d i ó e l d i s c o "+
t x t D i s c o . g e t T e x t ( ) . t o S t r i n g ( ) , T o a s t . LENGTH_LONG). s h o w ( ) ;
L is t a r ();
}
P a so 3: Programa el método Borrar para responder al clic del botón Borrar. Este
método realizará un DELETE a través de la función execSQL.
p u b l i c v o i d B o r r a r (V ie w v ) {
try {
d b . e x e c S Q L ("DELETE FROM M i s D i s c o s WHERE G rupo = +
t x t G r u p o . g e t T e x t ( ) . t o S t r i n g () + AND D i s c o = ' " +
t x t D i s c o . g e t T e x t ( ) . t o S t r i n g () +
T o a s t . m a k e T e x t ( t h i s , "Se b o r r ó e l d i s c o " +
t x t D i s c o . g e t T e x t ( ) . t o S t r i n g ( ) , T o a s t . LENGTH_LONG). s h o w ( ) ;
}
c a t c h (S Q L E xcep tion s ) {
T o a st.m a k e T e x t( t h i s , "Error a l b o r r a r !" . T o a s t . LENGTH_LONG). s h o w ( ) ;
}
L is ta r ();
}

P a so 4: Programa el método listar, que realizará una consulta SELECT a la base de


datos a través del método rawQuery. Este método devuelve un cursor con el que se
puede recorrer la lista de registros que devuelve la SELECT y, a través de un ArrayList
crea un adaptador para el List View que muestra el contenido de la tabla:
p u b lic v o id L is t a r (){
A rra y A d a p ter< S trin g > a d a p ta d o r;
L i s t < S t r i n g > l i s t a = new A r r a y L i s t < S t r i n g > ( ) ;
C u r s o r c = d b . r a w Q u e r y ( "SELECT * FROM M i s D i s c o s " , n u l l ) ;
i f ( c . g e t C o u n t ( ) ==0)
l i s t a . a d d ( "No h a y r e g i s t r o s " ) ;
e lse {
w h i 1 e (c . m o v e T o N e x t ( ) )
l i s t a . a d d ( c .g e t S t r in g (0 )+ " -"+c. g e t S t r in g (1 ));
}
ad ap tad or= n ew A r r a y A d a p te r < S tr in g > (
g e tA p p lic a tio n C o n te x t( ) , R. la y o u t . l i s t a _ f i l a , l i s t a ) ;
lis t a D is c o s . setA d ap ter(ad ap tad or);
c . c l o s e () ;
}

No te olvides de cerrar los cursores y la base de datos cuando ya no estén en uso.


¡Esencial para liberar recursos!
c . c lo s e ();
d b . c lo s e ();

ACTIVIDAD 3.8.
Puedes ver el código de este caso práctico en: MisDiscos.rar del m aterial complementario.
Capítulo 3. Comunicaciones 161

3.11. LAS C O NEXIO NES BLUETO O TH


Android incluye librerías para la creación de aplicaciones con la tecnología
inalámbrica Bluetooth, tanto clásicas (con alto consumo de baterías), como “Low
Energy” con consumo bajo de batería (IEEE 802.15.4).
Utilizando esta características podrás detectar la presencia de otros dispositivos
cercanos con Bluetooth, detectar los que están emparejados e intercambiar información
con ellos.
Para poder utilizar Bluetooth en tu App, tienes que incluir los permisos
BLUETOOTH:
c u s e s -p e rm issio n a n d ro id :name="a n d ro id . p e rm is s io n . BLUETOOTH"/>
Si además quieres permitir que tu App gestiones las conexiones y descubra nuevos
dispositivos Bluetooth tienes que agregar los permisos BLUETOOTH_ADMIN
c u s e s -p e rm issio n a n d ro id :name="a n d ro id . p e rm is s io n . BLUETOOTH_ADMIN"/>
Las conexiones Bluetooth en Android se manejan a través de la clase
BluetoothAdapter. Esta clase es la base para interacción entre aplicaciones que utilizan
conexiones inalámbricas a través del estándar Bluetooth. Con esta clase tienes que
verificar si el dispositivo tiene bluetooth, y si lo tiene, si está o no activado.
p u b lic v o id In ic ia r B lu e to o th (V ie w v ) {
b tA d ap ter = B lu e to o th A d a p te r .g e tD e fa u ltA d a p te r ();
i f (btA dapter= = n u l l ) {
/ / El d i s p o s i t i v o no so p o r ta B lu e to o t h
T o a s t . m a k e T e x t ( t h i s , R . s t r i n g . s i n b t , T o a s t . LENGTH_LONG) . s h o w ( ) ;
}
e lse
i f ( ¡ b t A d a p t e r . i s E n a b l e d () ) {
/ / El d i s p o s i t i v o s o p o r ta B lu e to o t h , p ero no e s t á a c tiv a d o
/ / s e s o l i c i t a su co n e x ió n
I n t e n t e n a b l e B t l n t e n t = new
I n t e n t ( B l u e t o o t h A d a p t e r . ACTION_REQUEST_ENABLE);
s t a r t A c t i v i t y F o r R e s u l t ( e n a b l e B t l n t e n t , HA BILITA_BT);
}
e lse {
m A ctiv a d o = tru e;
}
}
Si no está activado, se lanza la actividad con un intent de tipo
Bluetooth Adapter. ACTION_REQUEST ENABLE para solicitar al usuario que lo
active. Al terminar la solicitud, se retorna ejecutándose onActivityResult, y si el usuario
activó el bluetooth se resultCode vuelve con valor RESULT__OK:

© O v e r r id e
p r o t e c t e d v o id o n A c t i v i t y R e s u l t ( i n t req u estC o d e, i n t r e s u ltC o d e , I n t e n t d ata) {
i f (req u es tC o d e= = H A B I L I T A _ B T )
i f (resu ltC od e= = R E S U L T _ O K ) {
/ / E l d is p o s it iv o a c tiv ó e l b lu e to o th
T o a s t . m a k e T e x t ( t h i s , R . s t r i n g . a c t i v a d o , T o a s t . LENGTH_LONG).show();
m A ctivad o = t r u e ;
}else{
162 Programación en Android

T o a s t.m a k e T e x t(th is, R .s t r i n g . n o a ctiv a d o ,


T o a s t . LENGTH_LONG). s h o w ( ) ;
}
}
Para encontrar dispositivos Bluetooth puedes buscar en la lista de d isp o sitivo s
em parejados (Bonded) que se obtiene con el método getBondedDevices() de
BluetoothAdapter o descubriendo nuevos dispositivos a través del método
startDiscovery(), también de la clase BluetoothAdapter:
El método getBondedDevices() devuelve un conjunto (Set) de objetos de la clase
BluetoothDevice, y se puede recorrer mediante un iterador:
p u b lic v o id E n cu en traE m p arejad os(){
S e t< B lu e to o th D e v ic e > p a ir e d D e v ic e s = b tA d a p te r .g e tB o n d e d D e v ic e s();
/ / S i hay d i s p o s i t i v o s em parejados
i f ( p a i r e d D e v i c e s . s i z e () > 0 ) {
/ / Ite r a m o s p o r l o s d i s p o s i t i v o s em parejados
fo r (B lu eto o th D ev ice d e v ic e : p a ir e d D e v ic e s) {
a r r a y D i s p o s i t i v o s . a d d (d e v i c e . g e t N a m e () + " \n " +
d e v ic e .getA d d ress());
}
}
}
Los dispositivos encontrados se agregan a un ArrayAdapter (arrayDispositivos) para
luego agregarlos a un ListView.
Para descubrir los dispositivos que están usando Bluetooth, tan solo hay que invocar
al método startDiscovery() de la clase BluetoothAdapter. Este método lanzará un
broadcast ACTION_FOUND por cada dispositivo que encuentre, por lo que habrá que
programar uno para recibir estos broadcast. Los dispositivos encontrados también se
pueden agregar al ArrayAdapter.
/ / C r e a u n B r o a d c a s t R e c e i v e r p a r a ACTI0N_F0UND
p r i v a t e f i n a l B r o a d c a s t R e c e i v e r m R e c e i v e r = new B r o a d c a s t R e c e i v e r () {
p u b lic v o id o n R e c e iv e (C o n te x t c o n t e x t . I n te n t in t e n t ) {
S tr in g a c tio n = in t e n t .g e t A c t i o n ();
/ / C uando s e d e s c u b r e u n d i s p o s i t i v o
i f (B lu eto o th D ev ice.A C T I0 N _ F 0 U N D . e q u a l s ( a c t i o n ) ) {
/ / O btener e l B lu e to o th D e v ic e d e l I n t e n t
B lu e to o th D e v ic e d e v ic e =
i n t e n t . g e t P a r c e l a b l e E x t r a ( B l u e t o o t h D e v i c e . EXTRA_DEVICE);
/ / A ñ a d i r e l n o m b re y s u d i r e c c i ó n a l a l i s t a
a r r a y D i s p o s i t i v o s . a d d ( d e v i c e . g e t N a m e () + " \n " +
d e v ic e .g e tA d d r e ss());
}
}
};

p u b lic v o id D escu b re(){


i f (b t A d a p t e r .is D is c o v e r in g ()) {
b tA d a p ter. c a n c e lD is c o v e r y ();
}
b tA d ap ter. s t a r tD is c o v e r y ();
Capítulo 3. Comunicaciones 163

Para establecer una conexión debe haber dos Apps, una que haga de servidor y otra
que haga de cliente. El servidor abrirá un socket y el cliente iniciará la conexión usando
la dirección MAC del servidor. Cuando tanto el cliente como el servidor obtengan un
socket en el mismo canal RFCOMM se considerará una conexión establecida.
DEL LADO DEL SERVIDOR
Para que una App sea servidora debe mantener abierto un objeto del tipo
BluetoothServerSocket obtenido con la llamada a
listenUsingRfcommWithServiceRecord(NombreServicio,UUID) método del
BluetoothAdapter. Este método pone a la escucha un socket con un nombre de servicio
y un UUID, un identificador único universal para tus conexiones.
La llamada al método accept() del objeto BluetoothServerSocket dejará el proceso en
espera de una conexión entrante y, al recibir una conexión entrante, accept devolverá un
objeto del tipo BluetoothSocket con el que poder comunicarse con el cliente.
DEL LADO DEL CLIENTE
Para iniciar una conexión, primero, has de hacerte con un objeto de tipo
BluetoothDevice como el que has recibido cuando descubrías los dispositivos. Este
BluetoothDevice representa el servidor remoto y puedes obtener un objeto
BluetoothSocket a partir de él invocando al método
createRfcommSocketToServiceRecord(UUID). EL UUID debe coincidir con el del
servidor para poder conectar.
ENVIANDO Y RECIBIENDO DATOS
Cuando ambos dispositivos hayan obtenido un BluetoothSocket ya se puede enviar y
recibir datos a través de un Stream. Para recibir datos se ha de obtener el stream de
entrada (método getInputStream() del objeto BluetoothSocket), del cual se leerán los
bytes recibidos a través del método read(byte[]). Para enviar datos, se obtendrá un
stream de salida, a través del método getOutputStream() del objeto BluetoothSocket, y
en el cuál se escribirá con el método write(bytes []).

El caso práctico que te mostramos a continuación consiste en una pequeña Aplicación


que interactúa con las A PI’s de Bluetooth para enviar mensaje entre dispositivos. El
objetivo es ejecutar la misma App en los dos dispositivos emparejados mediante
Bluetooth:
• El botón de Iniciar activará el bluetooth y buscará los dispositivos
164 Programación en Android

• Tiene una lista de dispositivos entre los vinculados y encontrados, dependiendo


de si la checkbox está seleccionada o no
• “Iniciar Servidor” lanzará un Thread para aceptar conexiones.
• “Iniciar Cliente” lanzará un thread para conectar al dispositivo que el usuario
hay seleccionado en la lista.
La arquitectura de la aplicación consistirá en tres subclases de Thread:
• AcceptThread: Pondrá un socket Bluetooth a la escucha de conexiones.
• ConnectThread: Se arrancará para iniciar una conexión Bluetooth a un
dispositivo que esté escuchando mediante AcceptThread.
• ConnectedThread: Lo arrancarán tanto AcceptThread como ConnectThread
cuando hayan obtenido un socket válido, habiéndose producido la conexión.
Este socket será el encargado de recibir y enviar datos del socket.
Estos Threads se comunicarán con la interfaz principal a través de un objeto de la
clase Handler, una estructura de datos de tipo Cola para procesar mensajes. Los threads
depositan mensajes en la cola para que la interfaz de usuario los lea y actúe en
consecuencia. Los mensajes a enviar serán los cambios de estado en la conexión
(Conectando, Conectado, Desconecta y Mensaje Recibido y Mensaje Enviado). Todos
estos tipos de mensajes se definen en una interfaz mediante constantes de tipo estático y
entero:
public interface Constantes {
public Static final int CAMBIAR_ESTAD0=1;
public Static final int ESTAD0_C0NECTAND0=2;
public static final int ESTAD0_C0NECTAD0=3;
public static final int MENSAJE_ENVIADO=4;
public static final int MENSAJE_RECIBIDO=5;
public static final int SIN_C0NECTAR=6;
public static final int C0NEXI0N_PERDIDA=7;
public static final String NOMBRE_DISPOSITIVO="NOMBRE_DISPOSITIVO";
}
Al iniciar la App como servidor (“Iniciar Servidor”) se lanzará el Thread
AcceptThread que consiste en el siguiente código:
p u b li c c l a s s A ccep tT h rea d e x te n d s Thread {
p r i v a t e s t a t i c f i n a l UUID MY_UUID =
U UID . f r o m S t r i n g ( " f a 8 7 c 0 d 0 - a f a c - l l d e - 8 a 3 9 - 1 1 1 1 1 1 1 1 1 1 1 1 " ) ;
p r i v a t e f i n a l s t a t i c S t r i n g NOM B RE _SE RV IC IO ="m iA ppBluetooth";

p r iv a te fin a l B lu e to o t h S e r v e r S o c k e t m m S erverS ock et;

p r i v a t e H a n d l e r m H a n d le r ;
p r iv a te A c tiv id a d B lu e to o th a c tiv id a d ;

p u b lic A c c e p tT h rea d (B lu eto o th A d a p ter b tA d a p ter,


H an d ler h , A c t i v i d a d B l u e t o o t h a c t ) {
m H a n d le r = h ;
a c tiv id a d = a c t;
B l u e t o o t h S e r v e r S o c k e t tmp = n u l l ;
Capítulo 3. Comunicaciones 165

try {
tmp = b t A d a p t e r . l i s t e n ü s i n g R f c o m m W i t h S e r v i c e R e c o r d (
NOMBRE_SERVICIO, MY_UUID);
} c a tc h (IO E x cep tio n e) { }
m m S e r v e r S o c k e t = tm p;
}
p u b l i c v o i d r u n () {
B lu e to o th S o c k e t so c k e t = n u ll;
/ / s e g u i r e sc u c h a n d o h a s t a que o c u r r a una e x c e p c i ó n
/ / o s e a c e p t e un s o c k e t
w h ile (tru e) {
try {
E n v i a r C a m b i o E s t a d o ( C o n s t a n t e s . ESTADO_CONECTANDO,null);
s o c k e t = m m S erverS ock et. a c c e p t ();
} c a t c h (I O E x c e p tio n e) {
break;
}
// S i l a co n e x ió n fu e acep tad a
if ( s o c k e t != n u l l ) {
sy n ch ro n ized (t h is ) {
E n v i a r C a m b i o E s t a d o ( C o n s t a n t e s . ES TADO_CONE CTADO,
s o c k e t . g e tR e m o te D e v ic e ( ) ) ;
a c t i v id a d .C o n e c t a r ( s o c k e t , s o c k e t . g e tR e m o te D e v ic e ( ) ) ;
}
break;
}
}
}

p u b lic v o id c a n c e lO {
try {
m m S erverS ock et. c l o s e ();
} c a t c h (I O E x c e p tio n e) { }
}
p u b lic v o id E n v ia r C a m b io E sta d o (in t i , B lu e to o t h D e v ic e d e v i c e ) {
M e s s a g e m sg = m H a n d l e r . o b t a i n M e s s a g e ( C o n s t a n t e s . CAMBIAR_ESTADO,i,- 1 ) ;
/ / S i h a y d i s p o s i t i v o a e n v i a r , s e e n v i a como B u n d l e
i f ( d e v i c e != n u ll) {
B u n d l e b u n d l e = n ew B u n d l e d ;
b u n d l e . p u t S t r i n g ( C o n s t a n t e s ,NOMBRE_DISPOSITIVO, d e v i c e . g e t N a m e ( ) ) ;
m sg .se tD a ta (b u n d le );
}
m H a n d ler. s e n d M e s s a g e ( m s g ) ;
}
}

El constructor del Thread AcceptThread recibe como parámetros el adaptador de


Bluetooth, para poder establecer una conexión RFComm, un Handler para comunicarse
con la interfaz de usuario, una referencia a la Actividad para poder lanzar el thread
“ConnectedThread” a través del método “Conectar".
ConnectThread para el dispositivo Cliente, inicia la conexión a un BluetoothDevice
de la siguiente forma:
p u b l i c c l a s s C on n ectT h read e x t e n d s T hread {
p r i v a t e s t a t i c f i n a l UUID MY_UUID =
166 Programación en Android

U U ID . f r o m S t r i n g ( " f a 8 7 c O d O - a f a c - l l d e - 8 a3 9 - 1 1 1 1 1 1 1 1 1 1 1 1 " ) ;
p r i v a t e f i n a l s t a t i c S t r i n g N Q M B RE _SE RV IC IO ="m iA ppBluetooth";
p r i v a t e f i n a l B l u e t o o t h S o c k e t m m S o c k e t;
p r i v a t e f i n a l B l u e t o o t h D e v i c e m m D e v ic e ;
p r i v a t e B lu e t o o t h A d a p t e r m btA dapter;
p r iv a te A ctiv id a d B lu eto o th a c tiv id a d ;
p r i v a t e H a n d l e r m H a n d le r ;

p u b li c C o n n ectT h rea d (B lu eto o th D ev ice d e v ic e ,B lu e to o th A d a p te r


b tA d a p te r ,H a n d le r h ,A c t iv id a d B lu e t o o t h act)
{
m b tA d ap ter= b tA d ap ter;
m H a n d le r = h ;
a c tiv id a d = a c t;
B l u e t o o t h S o c k e t tmp = n u l l ;
m m D ev ice = d e v i c e ;

/ / O b t e n e r un B l u e t o o t h S o c k e t p a r a c o n e c t a r c o n e l B l u e t o o t h D e v i c e
try {
tm p = d e v i c e , c r e a t e R f c o m m S o c k e t T o S e r v i c e R e c o r d ( M Y _ U U I D ) ;
} c a t c h (IO E x cep tio n e) { }
m m S o ck et = t m p ;

p u b lic v o id ru n () {
/ / Se c a n c e l a l a búsqueda de d i s p o s i t i v o s p a ra
/ / n o r a l e n t i z a r la co n ex ió n
m b tA d a p te r .c a n c e lD isc o v e r y O ;

try {
m m Socket. c o n n e c t ( ) ;
} c a t c h (IO E x cep tio n c o n n e c tE x c e p tio n ) {
try {
m m S ock et. c l o s e ( ) ;
} c a t c h (IO E x cep tio n c lo s e E x c e p t io n ) { }
retu rn ;
}
syn ch ro n ized (th is ) {
E n v i a r C a m b i o E s t a d o ( C o n s t a n t e s . ESTADO_CONECTADO,
m m S o c k et.g etR em o teD ev ice( ) ) ;
a c t i v i d a d . C o n e c t a r (m m S o ck et, m m S o c k e t . g e t R e m o t e D e v i c e ( ) ) ;
}
}
p u b lic v o id c a n c e lO {
try {
m m Socket. c l o s e ( ) ;
} c a t c h (I O E x c e p tio n e) { }
}
p u b li c v o i d E n v ia r C a m b io E sta d o (in t i,B l u e t o o t h D e v ic e d e v i c e ) {
M e s s a g e m sg = m H a n d l e r , o b t a i n M e s s a g e ( C o n s t a n t e s . CAMBIAR_ESTADO,i,- 1 ) ;
/ / S i h a y d i s p o s i t i v o a e n v i a r , s e e n v i a como B u n d l e
i f ( d e v i c e != n u ll) {
B u n d l e b u n d l e = new B u n d l e O ;
b u n d l e . p u t S t r i n g ( C o n s t a n t e s .NOMBRE_DISPOSITIVO, d e v i c e . g e t N a m e ( ) ) ;
m sg. se tD a ta (b u n d le );
}
m H a n d ler. s e n d M e s s a g e (m s g );
Capítulo 3. Comunicaciones 167

}
}
El constructor de ConnectThread se programa de la misma manera que el de
AcceptThread. Fíjate que a diferencia de AcceptThread, se llama a la función
createRfcommSocketToServiceRecord del objeto BluetoothDevice para iniciar la
comunicación, y cuando ya está ejecutándose, el Thread intenta conectar el socket al
servidor mediante el método connect.
Si AcceptThread y ConnectThread han obtenido un socket válido, los dos lanzan un
ConnectedThread, para que este lea y escriba en los sockets:
p u b l i c c l a s s C on n ected T h read e x te n d s T hread {
p r i v a t e f i n a l B l u e t o o t h S o c k e t m m S o ck et;
p r i v a t e f i n a l I n p u t S t r e a m m m ln S tr e a m ;
p r i v a t e f i n a l O u t p u t S t r e a m m m OutStream;
p r i v a t e H a n d l e r m H a n d le r ;

p u b l i c C o n n e c te d T h r e a d (B lu e to o th S o c k e t s o c k e t . H an d ler h a n d le r ) {
m m S o ck et = s o c k e t ;
m H a n d ler= h a n d ler;
In p u tS tr e a m tm p ln = n u l l ;
O u t p u t S t r e a m tm pOut = n u l l ;
/ / O btener d e l B lu e to o th S o c k e t l o s strea m s de e n tra d a y s a l i d a
try {
tm p ln = s o c k e t . g e t l n p u t S t r e a m O ;
tm pOut = s o c k e t . g e t O u t p u t S t r e a m O ;
} c a t c h ( I O E x c e p t i o n e ) {}
m m ln S tr e a m = t m p l n ;
mm OutStream = tm p O u t;
}
p u b l i c v o i d r u n () {
b y t e [ ] b u f f e r = new b y t e [ 1 0 2 4 ] ;
in t b ytes;
w h ile (tru e) {
try {
/ / L eer d e l InputStream
b y t e s = m m ln S tr e a m .r e a d (b u ffe r );
/ / M ensaje r e c i b i d o ! !
m H a n d l e r . o b t a i n M e s s a g e (C o n s t a n t e s . MENSAJE_RE CIB IDO,
b y te s , -1 , b u f f e r ) . sen d T oT arget();
} c a t c h (I O E x c e p tio n e) {
E n v i a r C a m b i o E s t a d o ( C o n s t a n t e s . CONEXION_PERDIDA);
break;
}
}
}

p u b li c v o id E n v ia r C a m b io E sta d o (in t i ) {
M e s s a g e msg = m H a n d l e r . o b t a i n M e s s a g e ( C o n s t a n t e s . CAMBIAR_ESTADO,i, - 1 ) ;
m H a n d ler. s e n d M e s s a g e ( m s g ) ;
}

/* E s c r ib e en e l O u tS tream . */
p u b l i c v o i d w r i t e ( b y t e [] b u f f e r ) {
try {
m m O u tS tr e a m .w r ite (b u ffe r );
168 Programación en Android

//M e n sa je e n v ia d o !!
m H a n d l e r . o b t a i n M e s s a g e ( C o n s t a n t e s . MENSAJE_ENVIADO, -1, -1, b u ffer)
, sen d T oT arget();
} c a t c h ( I O E x c e p t i o n e ) {}
}
p u b lic v o id c a n c e lo {
try {
m m S ock et. c l o s e ( ) ;
} c a t c h (I O E x c e p tio n e) {}
}
}
En cuanto a la actividad principal, la codificación de los métodos principales sería la
siguiente:
• El clic sobre el botón Iniciar Servidor, crea un AcceptThread y lo ejecuta:
//B o t ó n I n i c i a r S e rv id o r
p u b l i c v o i d I n i c i a r S e r v i d o r (V ie w v ) {
i f (b tA d a p ter!= n u ll) {
m A c c e p t T h r e a d = new A c c e p t T h r e a d ( b t A d a p t e r , m H a n d le r , th is);
m A ccep tT h read . s t a r t () ;
}
e ls e {
T o a s t .m a k e T e x t(th is,
"P or f a v o r , d a l e a l b o t o n d e i n i c i a r " , T o a s t . LENGTH_SHORT). s h o w ( ) ;
}
}
• El botón para enviar mensaje, que utiliza la función enviarMensaje y que a su
vez ejecuta el método write del ConnectedThread
/ / b o t ó n E n v i a r M e n s a j e ( - >) y l a f u n c i ó n p a r a e n v i a r m e n s a j e
p u b l i c v o i d E n v i a r (V iew v ) {
e n v i a r M e n s a j e (t x t E n v i a r . g e t T e x t ( ) . t o S t r i n g ( ) ) ;
}
p r iv a t e v o id e n v ia r M e n s a je (S t r in g m ensaje) {

if ( e s t a d o = = C o n s t a n t e s . SIN_CONECTAR) {
T o a s t .m a k e T e x t ( t h i s , " c o n e c ta p r im e r o a un s e r v i d o r ! " ,
T o a s t . LENGTH_SH0RT). s h o w ( ) ;
retu rn ;
}
// C omprobamos s i h a y a l g o q u e e n v i a r
if ( m e n s a j e . l e n g t h () > 0) {
b y t e [] s e n d = m e n s a j e . g e t B y t e s ( ) ; //o b te n e r
m C onnectedT hread. w r i t e ( s e n d ) ;
}
}
• El código para "Iniciar Cliente”, crea un Thread de tipo ConnectThread
pasando como parámetro el dispositivo Bluetooth al que se va a intentar
conectar a partir de la MAC del mismo (17 últimos caracteres):
p u b l i c v o i d I n i c i a r C l i e n t e (V ie w v ) {
i f (se le c c io n a d o = = -l)
T o a s t .m a k e T e x t ( t h i s , " e l i g e un d i s p o s i t i v o a l que c o n e c t a r t e p r im ero " .
T o a s t . LENGTH_LONG) . s h o w ( ) ;
e ls e {
Capítulo 3. Comunicaciones 169

S trin g x =
l i s t a _ d i s p o s i t i v o s .g e tlte m A tP o s itio n ( s e le c c io n a d o ). t o S t r in g ();
S t r i n g a d d r e s s = x . s u b s t r i n g ( x . l e n g t h () - 1 7 ) ;
d is p o s itiv o C o n e c ta d o = b tA d a p te r .g e tR e m o te D e v ic e (ad d ress) ;
i f (d isp o sitiv o C o n e c ta d o != n u ll) {
m C o n n e c t T h r e a d = n ew C o n n e c t T h r e a d ( d i s p o s i t i v o C o n e c t a d o , b t A d a p t e r ,
m H a n d le r , t h i s ) ;
m C onnectT hread. s t a r t () ;
}
e lse
T o a s t . m a k e T e x t ( t h i s , "no p u d e o b t e n e r e n l a c e a l d i s p o s i t i v o " ,
T o a s t . LENGTH_LONG). s h o w ( ) ;
}
}

• El método conectar, que lanza el ConnectedThread, corazón de la transmisión.


Fíjate que el método es syncronized, es decir, define una región crítica para
lanzar el ConnectedThread antes de que terminen los AcceptThread y
ConnectThread:
p u b lic sy n ch ro n ized v o id C o n e c ta r (B lu eto o th S o ck et s o c k e t,
B lu e to o th D e v ic e d e v ic e ) {
/ / C o m ie n z a l a c o n e x i ó n ! !
m C o n n e c t e d T h r e a d = new C o n n e c t e d T h r e a d ( s o c k e t , m H a n d l e r ) ;
m C o n n e c te d T h r e a d .sta r t();
}
Por último, el Handler que maneja los mensajes que generan los Threads
(CAMBIAR_ESTADO, MENSAJE_ENVIADO Y MENSAJE _RECIBIDO)

p r i v a t e f i n a l H a n d l e r m H a n d le r = new H a n d l e r () {
© O v errid e
p u b l i c v o i d h a n d l e M e s s a g e ( M e s s a g e msg) {
s w i t c h (m sg .w h a t) {
c a s e C o n s t a n t e s . CAMBIAR_ESTADO:
i f ( m s g . a r g l = = C o n s t a n t e s . ESTADO_CONECTADO)
C a m b ia rE sta d o (m sg . a r g l ,
m s g . g e t D a t a ( ) . g e t S t r i n g ( C o n s t a n t e s ,NOMBRE_DISPOSITIVO)) ;
e lse
C a m b ia r E s ta d o (m s g . a r g l , "") ;
break;
c a s e C o n s t a n t e s . MENSAJE_ENVIADO:
C a m b i a r E s t a d o ( C o n s t a n t e s . MENSAJE_ENVIADO," " ) ;
break;
c a s e C o n s t a n t e s . MENSAJE_RECIBIDO:
b y t e [] r e a d B u f = ( b y t e l l ) m s g . o b j ;
/ / c o n s t r u y e una ca d en a de c a r a c t e r e s a
/ / p a r t i r de lo s c a r a c t e r e s d e l b u f f e r
S t r i n g r e a d M e s s a g e = new S t r i n g ( r e a d B u f , 0 , m s g , a r g l ) ;
tx t R e c ib ir . setT ext(read M essage);
C a m b i a r E s t a d o ( C o n s t a n t e s . M E N S A J E _ R E C I B I D O ," " ) ;
break;
}
}
};
170 Programación en Android

ACTIVIDAD 3.9.
Puedes ver y analizar el código de ejemplo
de: ServidorBluetooth.rar del material
complementario
¡Ten en cuenta que necesitas dos
dispositivos reales y con bluetooth para
poder probarlo!

3.12. LAS ALARM AS


Android dispone de un gestor de alarmas llamado AlarmManager. Permite programar
alarmas que despierten componentes de tu App incluso cuando no se está ejecutando, o
incluso cuando el dispositivo móvil está bloqueado/durmiendo. Con las alarmas es
posible disparar Intents en una hora o un intervalo de tiempo determinado. Recibiendo
ese intent, tu aplicación puede responder apropiadamente a esa alarma.
Por ejemplo, para crear una alarma que se ejecute en base a una hora pasada como
parámetro, podemos programar el siguiente código:

p u b lic v o id S e tA la r m a (in t h o r a ,i n t m in u to s) {

A la r m M a n a g e r a la rm M g r;
P e n d in g ln te n t a la r m ln te n t;

/♦ c o n fig u r a r ca le n d a r io * /

C a len d a r c a le n d a r = C a l e n d a r . g e t l n s t a n c e () ;
c a le n d a r .se tT im e ln M illis(S y ste m .c u r r e n tT im e M illis());
c a l e n d a r . set(C alen d ar.H 0U R _0F _D A Y , h o r a ) ;
c a l e n d a r . set(C a len d a r.M IN U T E , m i n u t o s ) ;

/♦ C r e a r a la r m a * /
I n t e n t i n t e n t = new I n t e n t ( m C o n t e x t o , A l a r m a . c l a s s ) ;
a la r m ln t e n t = P e n d in g ln t e n t .g e t B r o a d c a s t ( m C o n t e x t o , 0, in te n t, 0) ;

a la rm M g r = (A la rm M a n a g er)
m C o n t e x t o . g e t S y s t e m S e r v i c e ( C o n t e x t . ALARM_SERVICE);
ala rm M g r. s e t l n e x a c t R e p e a t i n g (
AlarmManager.RTC_WAKEUP, c a l e n d a r . g e t T i m e l n M i l l i s ( ) ,
A l a r m M a n a g e r . INTERVAL_DAY, a l a r m l n t e n t ) ;
Capítulo 3. Comunicaciones 171

La alarma requiere un P e n d in g ln te n t para poder funcionar. Un P e n d in g ln te n t es


un tipo especial de Intent que no se ejecuta al instante (está pendiente), sino
posteriormente cuando sea necesario ejecutarlo. Es una descripción de un Intent y de la
acción que debe realizar, que se pasa a otro proceso de tal manera que si la App no se
está ejecutando, el sistema pueda todavía reconocer la acción a realizar cuando la
alarma salta. El ejemplo utiliza el método setlnexactRepeating del AlarmManager para
establecer a qué hora se ejecutará el Pendinglntent y especificar qué tipo de alarma se
programa, en este caso AlarmManager.RTC_WAKEUP exige que el dispositivo se
“despierte” para disparar el intent especificado en el tiempo determinado.

public class Alarma extends BroadcastReceiver {


©Override
public void onReceive(Context context, Intent intent) {

//Alarma disparada!

3.13. PU B L IC A C IÓ N DE A PLIC A C IO N ES EN
GOOGLE PL A Y STORE
Google Play Store es la tienda online de Google. Es una aplicación incluida en la
mayoría de distribuciones de Android que permite descargar las aplicaciones que los
desarrolladores publican. Google Play Store es la forma que tiene google de filtrar
aquellas Apps que puedan ser potencialmente dañinas para tu dispositivo, además, los
usuarios colaboran activamente dejando comentarios y valoraciones sobre las
aplicaciones que han descargado y probado de tal manera que un usuario puede estar
más o menos seguro de que cuando se descarga una App de Google Play Store no se
descarga, por ejemplo, un virus.
Para publicar una App en la play Store tienes que publicarlas conforme a los
requisitos que te exige Google, que son:
• Debes publicar varios pantallazos (Screenshot) de tu App.
• Debes escribir una descripción detallada sobre el funcionamiento de tu App.
• Debe estar firmada.
• El desarrollador tiene que someterse a una larga, aunque razonable, lista de
condiciones de las políticas del programa de desarrolladores de google.
172 Programación en Android

f C piay.googie.com :: : =
Aplicaciones O Tutonel Amplitube .. Añadiendo nuevas O Bgetois First TutoriaL. Rehafci!it*ctón_LCA:... | | Samsung UE32M500... " i LA BOLSA EN TUS... LO ULTIMO EN n2b... »

G o o g le play Develops*Console LupeSoft . :■ cenar sesión Q

w TUS APLICACIONES ■f Añadí i nueve apficeeión

m
Pagina 1 de 1

0 NOMBRE DELA APLICACIÓN PRECIO INSTALACIONES VALORACIÓN MEDIA ERRORES Y ANRS ÚLTIMA ESTADO
ACTUALESrrOTALES /TOTAL 0 ACTUALIZACIÓN

A 0
Encuentra Hipotenochas 1.0 Gratuita 24 i 34 19/10/2014 Publicada
fey¡
Página 1 de 1

A parte de estas pequeñas exigencias,


publicarlo es fácil, tan solo tienes que AÑADIR NUEVAAPLICACIÓN
registrarte como desarrollador en la Google Idioma predeterminado
Play Developer Console Español (Latinoamérica; - es-419 *

https://play.google.com /apps/publish) Nombre


Llamando a Walter Wbrte|
23 de 30 caracteres
J
y seguir los pasos de un asistente. Por el
módico precio de 25$ (en la fecha en la que ¿Cómo te gustaría empezar?

se escribió este texto) podrás publicar tantas Subir APK 1 Preparar ficha de Pisy Store

Apps como desees. Si quieres ponerle a tu


App un precio por descarga para recuperar
esta pequeña inversión deberás, además, configurar Google Wallet.
Cuando pulsas en “Añadir nueva aplicación”, tienes que completar el nombre de la
aplicación, preparar una ficha de información de tu app y subir el fichero apk que se
genera al compilar desde en el entorno de desarrollo:

y. Google play 1 Developer Console LupeSüf? Cerrar sesión Q

'í' LLAMANDO A WALTER WHITE / Borrador ▼

s »
APK FICHA DE PLAY STORE
¡ Ficha de Play Store
0 INFO RM ACIÓ N DEL PRODUCTO p3ta p“! f e a ' b “ c" mp“
Precio y distribución
A Español {Latinoamérica) - es-4i& Añadir traducciones
Productos integrados
aplicación
o Título' Llamando a Walter While
Servicios y APIs Español {Latinoamérica) - es-419
23 de 30 caracteres

Descripción breve
' '' W
0 de 80 caracteres

Descripción completa
Español (Latinoamérica) - es-41S

El apk se puede subir en tres modos: Producción (producto terminado), Beta Testing
o Alpha testing (pruebas). El modo dependerá de la fase de desarrollo en la que se
encuentra la aplicación:
Capítulo 3. Comunicaciones 173

G oogk play : Developer Console LupeSoft ilopezmor^n/grraii corn

M
LLAMANDO A WALTER WHITE
W

=- ; APK APK

Ficha de Play Store


O PRODUCCIÓN BETA TESTING ALPHA TESTING
Precio y distribución Publica? tu aplicación en
Google Píay de !u aplicación alpha o* tu aplica;icr
Productos integrados en la

□ aplicación

Servicios y APis

Ahora ias cla v es d e licencia s e adm inistran d e forma individual para cada aplicación.
Si tu aplicación utiliza servicios de licencias (por ejemplo, si s e trata de una aplicación de pago o
Sugerencias de optimización
utiliza la facturación integrada en aplicaciones o archives de expansión APK; puedes obtener la
nueva clave de licencia en la página AP

Subir tu primer archivo APK en tase de producción

Antes de subir tu apk, debes generarlo/construirlo en modo release. De esta manera


Android Studio elimina los símbolos extras añadidos que requiere el debugger para
poder realizar depuraciones, generando un paquete más pequeño que en modo debug.
Para generar el apk en modo release, pulsa en la barra que hay en la parte derecha de
Android studio donde pone “Gradle”.
Después selecciona “All tasks”- > ”app”->assambleRelease:

Gradle tasks O"

C5 + - ___
Recent tasks

► EncuentraHipotenochas:app [assembleRelease]

All tasks

▼ l ? :»PP
O androidDependencies
O assemble
O assembleDebug
O assembleDebugTest
assembleRelease
O build
O buildDependents
O buildNeeded
O check

Después, compila el proyecto de nuevo seleccionado la opción “Build-> Generate


Signed apk”:
174 Programación en Android

r , EncuentraHipotenochas - [C:\Users\ilm\ArxiroidStudioPfojectoiEncuentraHipotenochas]
File Edit View Navigate Code Analyse Refactor | Ü H | RMn lo o ls VCS Wind

D m CD ~~X Q) ¡f ©i f t <=■ M a k e P r o je c t C tr h - B
Make Module ‘app'
EncuentraH ipotenochas app src ma¡

ti B Project
¿ EncuentraH ipotenochas
*+ ► E l .idea

En la pantalla que sale a continuación, crea una firma:

, Generate Signed APK Wizard

Finalmente, pulsa en Next y después en “Finalizar” y obtendrás el preciado apk


firmado necesario para subirlo a google play store. Súbelo, espera unas horas y
comprueba que está disponible en la tienda de google.
174 Programación en Android

EncuentraH ipotenochas - [C :\U se-s\flm \A ndroidStudioProjects\EncuentraH ipotenochasj

File Edit V ie* N avigate C ode Analyze Refactor Run J o o ls VCS Wind

a m £5 - JU Si e i& T . Make Project Ctrl+F9


Make M odule 'app'
E n cu e n tra H ip o te n o ch a s app src mai
Rebuild Project
Ü" Project
Clean Project
Lt E n c u e n tra H ip o te n o ch a s \Users\ilm\Androi
► C3 .idea

En la pantalla que sale a continuación, crea una firma:

Finalmente, pulsa en Next y después en “Finalizar” y obtendrás el preciado apk


firmado necesario para subirlo a google play store. Súbelo, espera unas horas y
comprueba que está disponible en la tienda de google.
Capítulo 3. Comunicaciones 175

P R Á C T I C A 3. E L B I R T H D A Y H E L P E R
El objetivo de esta práctica es utilizar los tipos de componentes de una aplicación que
has aprendido en esta unidad: Actividades, BroadcastReceiver, Servicios y
ContentProviders. También podrás practicar con otro tipo de elementos como Alarmas
y AsyncTask, todo en una simple aplicación de gestión de felicitaciones de cumpleaños:
El BirthdayHelper. Una aplicación que ayuda a tener los cumpleaños de tus contactos
controlados, y que te avisará y/o enviará un SMS de felicitación cuando sea el
cumpleaños de uno de tus contactos.

Aquí te presentamos un diagrama con los casos de uso que tiene que tener tu App.
Es tu decisión, qué arquitectura diseñar y qué componentes elegir para cada una de
las funciones que programes.

Los req u isito s de la ap licación son los sigu ien tes:

R E Q U IS IT O 1: La aplicación guardará la información extraída de la aplicación de


contactos de Android en la siguiente tabla de SQLite:
Tabla miscumples:
CREATE TABLE IF NOT EXISTS miscumples(
ID integer,
TipoNotif char(l),
Mensaje VARCHAR(160),
Telefono VARCHAR(15),
FechaNacimiento VARCHAR(15),
Nombre VARCHAR(128)
);
176 Programación en Android

REQUISITO 2 (Caso de uso “Consulta Contactos”): La aplicación mostrará, mediante


una list View, una lista de contactos con su foto de perfil (extraída de la foto de los contactos), su
número de teléfono, su fecha de nacimiento y el tipo de notificación que el usuario recibirá
cuando sea su cumpleaños (SMS o sólo notificación en la barra de notificaciones).

Birthday Helper

Iv Bender
(435) 353-55
U i** Aviso: Sólo notificación
íÜfTv Billy
J S W (342)424-234
ÉWfc^BAviso: Enviar SMS
¡Bruce
| l 982-12-13 (888)777-444
■Aviso Enviar SMS
SPCristiano R. 7
(543) 535-345
Emk Aviso: Sólo notificación
M F H ja m e s
® | ¿ ^ 2 0 0 0 - 12-13 32424234234
i 1Aviso: Sólo notificación
B * J B ju a n Tamariz, Jr.
736348734647
H P ^ H U v is o : Sólo notificación
isteve
2010-12-13 (423)424-244
A w ie w C A In n n l i f i e a r i A n

REQUISITO 3 (Caso de Uso (Ver contacto)): Al pulsar un contacto se obtendrá una


pantalla con los datos de tu contacto, pudiendo realizar las siguientes acciones:

EditarC ontato

lomore ...
Ver el nombre y la fecha de nacimiento (si la tiene)
Walter

Teléfono
□ ¿Enviar SMS? Seleccionar de un spinner el teléfono al que se enviará
34234234234 j

34234234234
un sms (un contacto puede tener varios teléfonos)
Fecha de I (377) 374-666
(424) 234-3424 Seleccionar si queremos enviarle un SMS
(444) 444-444
Mensaje: ... ............................ ~ automáticamente y si es así, escribir el mensaje que irá en el
Feliz cumpleaños!! Y que SMS.
cumplas muchos más...

Guardar
Capítulo 3. Comunicaciones 177

REQUISITO 4: (Caso de uso (Ver Contacto)): A la derecha del nombre debe haber un
botón que lleve a la pantalla de edición del contacto de la propia aplicación de contactos de
Android, de tal modo que el usuario pueda meter el cumpleaños del contacto. Ten en cuenta que
para poder introducir el cumpleaños la App de contactos debe estar sincronizada con un servidor
(por ejemplo, gmail).
EditarContato

¿Enviar SMS?
34234234234

Fecha d e Nacimiento

Mensaje.

Feliz cumpleaños!! Y que


cumplas muchos más... 34234234234
&*»&£
Guardar
(377) 374-566
WOSS?.
(424) 234-3424
HOM E
(444) 444-444
MAIN
EVENTS

REQUISITO 5 (caso de uso (EditaPreferencias)):


El usuario podrá seleccionar la hora a la que se envían las notificaciones. A través de un diálogo
TimePickerDialog, el usuario podrá elegir la hora a la que se comprueba si hoy es el cumpleaños
de algún contacto.
j j | Birthday Helper £5? •

Configurar Felicitaciones -.
biiiy
] (342)424-234
viso Enviar SMS
Bruce
11982-12-13 (888)777-444
*víso Enviar SMS
Cristiano R 7
(543)535-345
* Aviso Sólo notificación
James
52000-12-13 32424234234
iAviso: Solo notificación
J u a n Tamariz, Jr.
| 736348734647
3 Sólo notificación

iS te v e
|2010-12-13 (423)424-244
«viso Sólo notificación
V a lla r

REQUISITO 6 (Casos de uso “Alarma Cumpleaños”/E nvía SM S/Envía


Notificación):
La Alarma se ejecuta y se envía una notificación a la barra de las notificaciones especificando de
quién son los cumpleaños.
178 Programación en Android

n :
3 :0 4 SAT. DECEMBER 13 -T " ■ ■■

A v is o d e c u m p l e a ñ o s
hoy es ei cum pleaños de
Bi1fy(SMS) ;Bruce(SM S);J a m e s ;Sí e ve•.Waite

4, 2 0 1 4 -1 2 -1 3 -1 3 -2 5 -1 5 -5 8 2 0 4 2 35 >JM
Download com plete

l
2 0 1 4 -1 2 - 1 3 - 1 3 - 2 5 - 3 1 - 1 5 6 2 8 :
Download complete.

3 new m e ssag e s 1 22 PM
k P
androideperez! 5@gmail.com 3 21

K e e p p h o t o s & v id e o s b a c k e
A Touch to get free p r i v a t e storage on Googie

REQUISITO 7 (Casos de uso “Alarma Cumpleaños”/E nvía SM S/Envía


Notificación): La Alarma se ejecuta y se envía el SMS de felicitación a aquellos contactos a los
que se les haya marcado la casilla “¿Enviar SMS?”

CRITERIOS DE CORRECCIÓN:
Program ar requisito 1 correctam ente -> 2 puntos
Program ar requisito 2 correctam ente -> 3 puntos
Program ar requisito 3 correctam ente -> 1 punto
Program ar requisito 4 correctam ente -> 1 punto
Program ar requisito 5 correctam ente -> 1 punto
Program ar requisito 6 correctam ente -> 1 punto
Program ar requisito 7 correctam ente -> 1 punto
UNIDAD 4

PERSISTENCIA DE LOS DATOS Y


CONTENIDO MULTIMEDIA

CONTENIDOS
r
4.1 La persistencia de los datos
4.2 Contenido multimedia
4.3 Reproducción de audio y vídeo. La clase MediaPlayer
4.4 Captura de fotos
4.5 Tratamiento de imágenes escaladas
4.6 Captura de vídeo
4.7 Almacenamiento de contenido multimedia (en la galería)

V
180 Programación en Android

4.1. LA PE R SIST E N C IA DE LOS DATO S


Para cualquier tipo de aplicación, guardar y cargar datos es de suma importancia.
Desde guardar las páginas web más visitadas por un usuario, hasta almacenar grandes
volúmenes de información en complejas bases de datos.
Una aplicación para móviles debería, como mínimo, guardar el estado que tenía la
última vez que fue usada por el usuario. Para que cuando la vuelva a usar, la encuentre
tal como la dejó.
Android tiene muchas formas de almacenar datos de forma permanente, algunas de
ellas son: usando un sistema gestor de bases de datos como es SQLite (tal como
estudiaste en el capítulo anterior), Content Providers, preferencias y ficheros.
En este capítulo aprenderás a trabajar con dos de ellos, las preferencias (preferences)
y los ficheros estáticos (static files).

4.1.1. PREFERENCIAS
Las preferencias (preferences) son una forma sencilla y ligera de guardar información
simple. Con “simple” nos referimos a los tipos de datos primitivos: enteros (integer),
booleanos (boolean), cadenas de caracteres (String), etc. Dicha información se almacena
según un par nombre/valor (key/value, name/value).
Usando las preferencias podemos: guardar el tono de llamada, de notificación o de
mensajes WhatsApp; establecer el rechazo de llamadas; activar la ocultación de
identidad en las llamadas, etc. Sin embargo, aunque estos ejemplos sean opciones de
configuración conocidas, las preferencias nos permiten guardar los datos que queramos,
dándole el significado que deseemos.

4.1.1.1. Almacenamiento de datos simples


Para crear el par nombre/valor comentado en el punto anterior, tenemos a nuestra
disposición la clase SharedPreferences.
La forma de conseguir acceso a las preferencias es muy sencilla, tan solo hay que invocar
al método getSharedPreferencesQ:
SharedPreferences misPreferencias = getSharedPreferences("prefs", MODE_PRIVATE);

Los dos parámetros que se le pasan a getSharedPreferences tienen el siguiente


significado:
• “prefs” es el nombre asignado al fichero de preferencias. Podemos tener tantos
como queramos.
Capítulo 4. Persistenica de los datos y contenido multimedia 181

• M ODE_PRIVATE se refiere al modo de creación del fichero de preferencias.


De esta manera, las preferencias creadas solo pueden ser accesibles desde la
propia aplicación. Existen otros dos modos (MODE_WORLD_READABLE y
MODE_WORLD_W RITEABLE), los cuales son totalmente desaconsejados
debido a los problemas de seguridad que puedan provocar
(http: / / developer.android.com/reference/android/content / Context. html#M O
DE_„WORLD_READ ABLE).

Las preferencias pueden ser compartidas por los distintos componentes de una
aplicación. Sin embargo, no están disponibles para el resto de aplicaciones.
Una vez que tenemos acceso a las preferencias, para crear, modificar o borrar valores
se usa el interfaz SharedPreferences.Editor. La forma de obtener un objeto de dicha
interfaz es:
SharedPreferences.Editor editor = misPreferencias.editQ;
Fíjate que invocamos el método edit() con el objeto misPreferencias de la clase
SharedPreferences.
La interfaz SharedPreferences.Editor proporciona un conjunto de métodos para
establecer, modificar o eliminar los pares nombre/valor que definirán nuestras
preferencias:
e d i t o r . p u t S t r i n g ( "nom bre", " R a istlin " );
e d ito r .p u tS tr in g (" a p e llid o s" , "M ajere" );
e d i t o r .p u t l n t ( "edad", 36);
e d ito r .p u tB o o le a n (" e s ta V iv o " , t r u e ) ;

Sin embargo, hay que tener en cuenta que nuestras preferencias no se guardarán
hasta que llamemos al método commitQ o apply(). La diferencia entre uno y otro es que
commit () escribe las preferencias de forma síncrona y devuelve un valor booleano
indicando el éxito o fracaso de la operación de escritura. Mientras que apply() escribe las
preferencias de forma asincrona y no informa del éxito de la escritura. Debido al
funcionamiento asincrono, apply() es el método recomendado para guardar las
preferencias. Siguiendo con nuestro ejemplo:
e d i t o r . a p p ly ();

Tan solo nos queda conocer cómo recuperar las preferencias. Para ello, utilizamos el
objeto creado misPreferencias de la clase SharedPreferences. Del mismo modo que
tenemos los métodos put para establecer valores, la clase SharedPreferences nos ofrece
los métodos get para recuperar los pares nombre/valor:
S t r i n g n om b re = m i s P r e f e r e n c i a s . g e t S t r i n g ( " n o m b r e " , " en b l a n c o " ) ;
S trin g a p e llid o s = m is P r e fe r e n c ia s .g e tS tr in g (" a p e llid o s" , " en b l a n c o " ) ;
in t edad = m i s P r e f e r e n c i a s . g e t l n t ( "edad", 0 );
b o o lea n e s ta V iv o = m is P r e f e r e n c ia s . g e tB o o le a n (" e sta V iv o " , tr u e ) ;

El primer parámetro de los métodos get es el nombre asignado con los put
(putString, putlnt, etc.) Y el segundo parámetro es un valor por defecto usado cuando
todavía no se haya establecido un valor para la preferencia correspondiente.
182 Programación en Android

ACTIVIDAD 4.1.
Im plem enta una App que le pida al usuario su nombre, apellidos y edad. Dichos valores se deben
alm acenar en forma de preferencias. Además ten en cuenta los siguientes requisitos:
Un botón perm itirá m ostrar las preferencias en cajas de texto.
Las preferencias se guardarán cuando la aplicación se detenga.
P ara ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
PreferenciasOl.rar del m aterial complementario

¿Te acuerdas del ciclo de vida de una aplicación?

R esum ed
T\
(visible)

4 on
onResuneQ onPause()
onResumeQ
_ L
S ta rte d Paused
(visible)
y (partially visible)

onStano ; t 1 onStopfi
3 onStartQ

Stopped
C re a te d 2 -cmRestartQ-
(h id d e n )
onCreateO onDestroyO

D e s tr o y e d

Imagen extraída de d e v e lo p e r.a n d ro id .c o m

ACTIVIDAD 4.2.
Modifica la aplicación anterior para prescindir del botón Cargar Preferencias, y que dichas
preferencias se m uestren siempre que la aplicación sea visible.

4.1.1.2. El sistema de preferencias de Android


Hasta ahora hemos visto cómo crear nuestras propias preferencias; sin embargo, esto
tiene sus ventajas y sus inconvenientes. Por un lado, podemos personalizar nuestras
preferencias hasta donde queramos. Por otro, todos los usuarios están acostumbrados a
interactuar con las preferencias de cualquier aplicación, ¡aún sin darse cuenta! Por
ejemplo silenciando el tono de grupos en WhatsApp, activando/desactivando la Wi-Fi o
Bluetooth, o modificando el tiempo de espera de la pantalla. Y si lo piensas un
momento, te darás cuenta de que el entorno es común a todas las aplicaciones. Con lo
que hemos visto hasta ahora, podríamos llegar a hacerlo, pero nos costaría mucho e
innecesario trabajo. ¿Por qué? Pues porque Android ofrece a los desarrolladores un
potente y ágil armazón para trabajar con las preferencias. Sus ventajas son enormes,
Capítulo 4. Persistenica de los datos y contenido multimedia 183

aparte de que el usuario está familiarizado con dicho entorno, nos permite integrar
configuraciones de otras aplicaciones en nuestras propias preferencias.
La versión Android 3.0 (Honeycomb, API 11) introdujo cambios importantes en
cuanto a las preferencias se refiere. Hasta dicha versión, se trabajaba con Preference
Screen, y a partir de Honeycomb, se empezó a trabajar con Preference Fragment.
Aunque el modo de trabajo de ambas es similar, debemos tener presente para qué tipo
de móviles estamos desarrollando nuestra aplicación. Y si queremos abarcar todo el
abanico de móviles con sus diferentes versiones de Android, tendremos que programar
nuestras aplicaciones para que sean compatibles con ambos entornos de preferencias,
Preference Screen y Preference Fragment.

P r e fe r e n c e S creen
Vamos a ver cómo trabajar con Preference Screen en cuatro sencillos pasos:
P A S O 1: C rear un fichero de recursos x m l
En el directorio de recurso (res) creamos un directorio que se llame xml. Dentro de
dicho directorio (res/xml) es donde creamos el fichero de recursos xml.

▼ Cüapp
► t ¡ manifests
► ti?:java
▼ C i res
► E3 drawable
▼ E l layout
5* activity_main.xml
« activityjTiis_preferencias.xml
► B menu
► El values
▼ El xml
tss preferendasjcml
• ' Gradle Scripts

Al fichero lo hemos llamado preferencias.xml, y su contenido será el siguiente:


<?xml v e r s i o n = " 1 .0 " en c o d in g = " u tf-8 " ? >
< P referen ceS creen
x m l n s : a n d r o i d = "h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d ">
< E d itT e x tP r e fe r e n c e
a n d r o i d : k e y = "n i c k n a m e "
a n d r o id : title = " N ic k n a m e "
a n d r o i d : sum mary="Tan s o l o e s c r i b e t u a p o d o ">
< /E d itT ex tP referen ce>
< C h eck B oxP referen ce
a n d r o i d : k e y = "g u s t a n S u p e r h e r o e s "
a n d r o id :title = " ¿ T e g u sta n lo s superheroes?"

r
184 Programación en Android

a n d r o i d : su m m a ry = " P a ra g u s t o s lo s c o lo r e s"
a n d r o i d : d e f a u l t V a l u e = " t r u e ">
< /C h eck B o x P referen ce>
< /P r e fe r e n c e S c r e e n >

La finalidad de este fichero es definir el contenido y el formato que tendrán nuestras


preferencias. Su contenido es muy fácil de entender:
• La etiqueta Preferences creen es la raíz y contiene el resto de etiquetas (más
adelante veremos un ejemplo en el que usamos varias etiquetas
Preferences creen).
• Con EditTextPreference creamos una caja de texto. Y el tipo de dato que
contendrá será un String.
• Como es de suponer, CheckBoxPreference es un CheckBox. Y contendrá un
tipo de dato boolean.
Dentro de cada etiqueta especificamos los atributos:
• android:key - El nombre de la preferencia (par nombre/valor).
• android:title El texto mostrado al usuario para representar la preferencia.
• android:summary - Una descripción más larga de la preferencia, mostrada en
un tamaño de fuente más pequeño.
• android:defaultValue - Valor por defecto que se mostrará al usuario. Y que
también será asociado como valor de la preferencia en caso de que el usuario
no asigne ninguno.

ACTIVIDAD 4.3.
Puedes ampliar información sobre el contenido de PreferenceScreen y sus atributos en:
h ttp ://devoloncr.android.com/reference/android/preforence/Preferencc.html

P A S O 2: C rear u na n u ev a a ctiv id a d
En el paso anterior hemos definido la estructura de nuestras preferencias. Ahora
tenemos que crear una nueva actividad, la cual será invocada cada vez que el usuario
quiera editar las preferencias de nuestra aplicación.
La nueva actividad se llamará MisPreferencias y hereda de PreferenceActivity:

p u b lic c la s s M isP r e fe r e n c ia s ex ten d s P r e fe r e n c e A c tiv ity {

}
Capítulo 4. Persistenica de los datos y contenido multimedia 185

Todavía nos falta hacer algo referente a esta nueva actividad, y es declararla en el archivo
A ndroidManifest. xml:

| |í ?xml v e r s i o n = !' l , 0 ” en co d in g = 'u tf -8 " ?>


“ « m a n ife st x m ln s:a n d ro id =" h ttp ;/ / s c h e m a s . an d ro id .co m /a p k /res/an d roid "
package=”com. «oc . p r e f e r e n c i a s 0 2 >

< a p p lication
a n d r o id :allowBacfeup="t r u e "
a n d r o id :ic o n = " g d r a w a b le /ic _ la u n c h e r ”
a n d r o id : la b e l= " P r e f e r e n c ia s0 2 "
android :them e=,,|style/AppTheme" >
$ « a ctiv ity
: a n d r o id :name="com.mmc .p A'vre f e rene
WvWvV' A'VvW'AiV'as
AV0 2 , MainAc t i v i t y "
android: l a b e l = !’P r e f e r e n c i a s 0 2 11 >
9 < in ten t-filter>
< a c t i o n an d roid :n a m e= " a n d ro id ,in ten t.actio n .M A IN 1' />

■«category android.:nam e="android.intent, c a t e g o r y . LADHCHER" />


él < /in ten t-filter>

é -« a c t iv it y
a n d r o id :name=" com. mac .^refejrenciasO 2. l i is P r e f e r e n c i a s "
android; la b e l = ' ' f i i s P r e f e r e n c i a s " >
é < / a c t i v i t y > ____________________ __________________________________
é < /a p p lica tio n >

é < /m a n ife s t>

P A S O 3: Cargar el layou t (o in terfaz) d efin id o en r es/x m l/p r e f e r en cía s.xm l


en el p aso 1
Hay que modificar el método onCreate() de la nueva actividad:

p u b lic c l a s s M is P r e fe r e n c ia s e x te n d s P r e f e r e n c e A c t iv it y {
© O v errid e
p r o t e c t e d v o id o n C r e a te (B u n d le sa v ed ln sta n ceS ta te) {
su p er . on C reate ( s a v e d ln s t a n c e S t a t e )
/ / se tC o n te n tV ie w (R .la y o u t. a c t iv it y _ m is _ p r e f e r e n c ia s ) ;

a d d P refe r e n c e sF r o m R e so u r c e (R .x m l. p r e f e r e n c i a s ) ;
}
Fíjate que está comentada la línea que establece la interfaz de usuario, ya no la
necesitamos. Y como te puedes imaginar, el archivo
res/layout/activity_mis_preferencias lo puedes borrar. Pues el nuevo layout (creado en
el paso 1) lo cargamos a través de la línea:
186 Programación en Android

a d d P r e fe r e n c e sF r o m R e so u r c e (R .x m l.p r e fe r e n c ia s);

Observarás que Android Studio te indica que este método está obsoleto. Así es desde
la API Level 11 (Honeycomb).
PA SO 4: Lanzar la n u ev a activ id a d (M isP referencias) para ed itar las
p referencias
La forma de lanzar la nueva actividad será a través de un Intent. En nuestro ejemplo,
dicho Intent se activará a través de un botón, aunque podría ser a través de cualquier
otro medio, por ejemplo a través de un menú. El interfaz principal de nuestra aplicación
será el siguiente:

Nickname: <campo va cio


¿Te gustan los superheroes? true

Editar preferencias

Las etiquetas que están encima del botón “Editar preferencias” nos sirven de debug
para mostrar el contenido de las preferencias.
El evento Click del botón lo definimos a través del atributo onClick (del widget
Button):
android:onClick="editarPreferencias"

El código asociado al botón sería el siguiente:


p u b l i c v o i d e d i t a r P r e f e r e n c i a s (V ie w v i e w ) {
sta r tA c tiv ity (n e w In te n t(th is, M is P r e f e r e n c i a s .c l a s s ) );
}

Cuando el usuario pulse en el botón “Editar preferencias ”verá la siguiente interfaz:


Capítulo 4. Persistenica de los datos y contenido multimedia 187

MisPreíerencias

Nickname
Tan solo estribe tu apodo

¿Te gustan los superheroes?


Para gustos los colores

¡Hasta aquí los cuatro pasos! Ha sido sencillo, ¿verdad? Añadimos un quinto paso
para hacer el debug antes mencionado.

PA SO 5: D E B U G . M ostrar las p referen cias en la a ctiv id a d p rin cipal


Si le echas un vistazo a la imagen del ciclo de vida de una aplicación, apreciarás que
hay que implementar el método onResume() si queremos que se muestren las
preferencias cada vez que nuestra aplicación esté en primer plano de ejecución:
p u b li c v o i d onR esum e() {
s u p e r . onR esum e();
S t r i n g n ick n a m e;
b o o lea n gu sta n ;
188 Programación en Android

T e x tV ie w tv _ n ic k n a m e = (T ex tV iew )
fin d V ie w B y ld (R .id .te x tV ie w N o m b r e );
T e x tV ie w t v _ g u s t a r = (T ex tV iew )
fin d V ie w B y ld (R .id .te x tV ie w G u sta r );

S h ared P referen ces p r e fs =


P r e fe r e n c e M a n a g e r .g e tD e fa u ltS h a r e d P r e fe r e n c e s( t h i s ) ;

n ick n a m e = p r e f s . g e t S t r i n g ( " n ick n a m e" , "<campo v a c i o " ) ;


g u s t a n = p r e f s . g e t B o o l e a n ( " g u s t a n S u p e r h e r o e s ", tru e);

t v _ n i c k n a m e . s e t T e x t ( "N ick n am e: " + n ic k n a m e ) ;


t v _ g u s t a r . s e t T e x t ( " ¿Te g u s t a n lo s superheroes? "
+ new B o o l e a n ( g u s t a n ) . t o S t r i n g O ) ;
}
Los métodos get ya los conoces del apartado anterior. La única novedad aquí es el
método:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

Fíjate que:
• Le pasamos como parámetro tan solo el contexto (this).
• Es un método estático que pertenece a la clase PreferenceManager.
• Devuelve un objeto de la clase SharedPreferences.

E sta es la mejor opción, usar el método getDefaultSharedPreferences() en lugar de


getSh aredPrefe rences().
Y para terminar... ¿hay algo que te haya llamado la atención? ¿o qué eches en falta? Piensa
unos minutos...

¿Dónde está el commit() o apply ()1 ¡Has acertado! Al heredar de PreferenceActivity, los
cambios se escriben autom áticam ente.

ACTIVIDAD 4.4.
Puedes ver el código de este ejemplo en el fichero Preferencias02.rar del m aterial complementario.

ACTIVIDAD 4.5.
Profundiza en el conocimiento de Preference Screen. P a ra ello investiga: PreferenceCategory y
PreferenceScreerx anidados. B asándote en el ejemplo anterior o im plementando una nueva
aplicación, diseña un entorno de preferencias más avanzado que contenga tanto etiquetas
PreferenceCategory como PreferenceScreen anidados.
En los ejemplos anteriores has conocido elementos de Preference Screen como
CheckBoxPreference y EditTextPreference, Prueba elementos nuevos, por ejemplo:
ListPreference, RingtonePreference, etc._____________________________________________________
Capítulo 4. Persistenica de los datos y contenido multimedia 189

P r e f e r e n c e fr a g m e n t

Tal como hemos comentado anteriormente, Android 3.0 (Honeycomb, API 11)
cambió la forma de trabajar con las preferencias.
Analizando lo que hemos hecho hasta ahora, observamos que en un fichero de
recursos XML (res/xml/preferencías.xml) se define la estructura de las preferencias, las
cuales se muestran en la pantalla desde una clase que hereda de PreferenceActivity (con
el método addPreferencesFromResource()).
A partir de Android 3.0, la clase que hereda de PreferenceActivity carga unas
cabeceras (headers), las cuales apuntan a subclases de PreferenceFragment que son las
encargadas de mostrar las preferencias en la pantalla.
¡Lo sé! Es confuso, pero solo al principio. Para que empieces a tenerlo claro, en la
siguiente imagen tienes un ejemplo de “lo que se ve” usando PreferenceFragment:

Aficiones Correo electrónico


¿Cuáles son tus aficiones’ ¿Estas en el siglo XXI?

¿Tienes Intérnete en casa?

QUEREMOS CONOCER ALGO MÁS SOBRE TI

¿Qué zona de Castilla-La Mancha te gusta más?


Elige una zona de las que te ofrecemos

Visualización de preferencias usando P r e fe r e n c e F r a g m e n t

Si has hecho la actividad anterior, en la que te sugeríamos que investigarás el uso de


PreferenceCategory y Preferences creen anidados. Lo primero que vas a pensar en
cuanto conozcas Preference Headers es que es lo mismo que PreferenceCategory. Que
quede claro desde el principio, no es lo mismo. Preference Headers son unos niveles de
agrupación superiores, que permiten una más clara y mejor visualización de las
preferencias.
En la imagen anterior puedes ver los headers a la izquierda, y cuando se hace clic
sobre alguno de ellos, se muestra a la derecha el conjunto de preferencias que contiene
dicho header.
190 Programación en Android

Para definir Preference Headers hay que crear un fichero de recursos XML en el
directorio res/xml. En nuestro ejemplo lo llamaremos preferences_headers.xml, y su
contenido será:
<?xml version="l.0" encoding="utf-8"?>
«preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
«header
android:fragment="com.mmc.preferencefragmentOl.DatosPersonales"
android:icon="@drawable/datos_personales"
android:title="Datos personales"
android:summary="No es por cotillear, tan solo necesitamos tus datos" />
«header
android:fragment="com.mmc.preferencefragmentOl.PersonajesFavoritos"
android:icon= "@drawable/persona jes_f avori tos11
android:title="Personajes favoritos"
android:summary="¿Cuáles son tus personajes ficticios favoritos?" />
«header
android:fragment="com.mmc.preferencefragmentOl.Aficiones"
android:i con="@drawable/aficiones"
android:title="Aficiones"
android:summary="¿Cuáles son tus aficiones?" />
«/preference-headers>

Cada elemento header contiene los siguientes atributos:


• android.-fragment - indica cuál es la subclase de PreferenceFragment que
mostrará las preferencias
• android:icon - icono para el header
• android:title - título que dota de significado al header
• android:summary - una descripción más amplia, y en una fuente menor, para
el header
¿Cómo cargamos los headers? En el ejemplo anterior, visualizábamos las preferencias
usando el método addPreferencesFromResourcef). Pues bien, ahora invocaremos al
método loadHeadersFromResource(). Siguiendo con nuestro ejemplo:

p u b lic c l a s s M isF ra g m en tP refe r e n c i a s exten d s P r e fe r e n c e A c tiv ity {


© O v errid e
p u b lic v o id o n B u ild H ea d ers(L ist« H ea d er> t a r g e t ) {
su p e r . o n B u ild H e a d e r s(ta r g e t);
lo a d H e a d e r sF r o m R e s o u r c e (R .x m l.p r e fe r e n c e _ h e a d e r s , ta rg et);
}

}
Tan solo nos queda implementar las clases DatosPersonales, PersonajesFavoritos y
Aficiones. Veamos cómo quedaría DatosPersonales:
Capítulo 4. Persistenica de los datos y contenido multimedia 191

p u b lic c l a s s D a to sP e r so n a le s e x te n d s P referen ceF ragm en t {


@ O verride
p u b l i c v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p er. o n C rea te(sa v ed ln sta n ceS ta te);
a d d P refer e n c e sF r o m R e so u r c e (R .x m l. d a t o s _ p e r s o n a l e s ) ;
}
}

Por fin, aquí tenemos la clase que hereda de PreferenceFragment. Y su código es muy
sencillo, desde el método onCreatef) llamamos a nuestro, ya conocido, método
addPreferencesFromResource().
Te habrás dado cuenta que cargamos el contenido de un fichero de recursos, ¿verdad?
Pues su contenido ya lo conoces, son los elementos Preferences creen que hemos
estudiado.

ACTIVIDAD 4.6.
¡Ah! Otra cosa que observarás es que Android Studio no te marca
addPreferencesFromResource() como obsoleto, ya que para la clase PreferenceFragment no lo
está.
Puedes consultarlo en: http://developer.android.com/reference/android/preference/PrefereiiceFragment.html.

Por último, en la documentación de Android Developer para la clase


PreferenceActivity encontramos el método isV a lid F ra g m e n t() cuya documentación
dice:
Subclasses should override this method and verify that the given
fragment is a valid type to be attached to this activity. The default
implementation returns true for apps built
for android:targetSdkVersionolder than K ITK A T. For later versions, it
will throw an exception.
Puedes consultarlo en:
littp://developer.android.com /reference/android/preference/PreferenceA ctivitv.Iitnil#isV alidFra
guien t (iava.lang. String')

Esto quiere decir que si ejecutas tu app en dispositivo con una versión anterior a
KITKAT, por ejemplo con una JELLY BEAN (Android 4.2, API Level 17), sin
sobrescribir el método isV a lid F ra g m e n tf), todo funcionará perfectamente. Sin
embargo, si ejecutas tu app en un dispositivo con una versión LOLLIPOP (Android 5.0,
API Level 21), se producirá la siguiente excepción:
java.lang.RuntimeException: Unable to start activity
Componentlnfofcom.mmc.preferencefragmentOl/com.mmc.preferencefr
agmentOl.MisFragmentPreferencias}: java. lang.RuntimeException:
Subclasses of PreferenceActivity must override isValidFragmentfString)
192 Programación en Android

to verify that the Fragment class is valid!


com.mmc.preferencefragmentOl.MisFragmentPreferencias has not
checked if fragment com.mmc.preferencefragmentOl.DatosPersonales is
valid.
De modo que, para subsanar este pequeño error, habrá que sobrescribir el citado
método:
protected boolean isValidFragment (String fragmentName) {
if ([NOMBRE_DE_TU_FRAGMENT].class.getName().equals(fragmentName))
return true;
return false;
}

En nuestro ejemplo, el código quedaría de la siguiente manera:


public class MisFragmentPreferencias extends PreferenceActivity {

©Override
protected boolean isValidFragment (String fragmentName) {
if (Aficiones.class.getName().equals(fragmentName)) return true;

else if (DatosPersonales.class.getName().equals(fragmentName))

return true;
else if (PersonajesFavoritos.class.getName().equals(fragmentName))

return true;

return false,-

1
}
Y para terminar este apartado, comentar que la visualizado!) de las Preference
Headers depende del tamaño y resolución de la pantalla. La imagen que has visto al
principio de este apartado corresponde a una resolución de 1280x800. La misma
aplicación, para una resolución de 1024x600, se vería así:

^ i 17.56

A I MisFragmentPreterencias

0 Datos personales
No e s por cotillea?, tan solo necesitam os tus datos

• 0 . Personajes favoritos
mm ¿C uáles son sis personajes ficticios favoritos?

Aficiones
¿C uáles son tus aficiones?

Visualización de Preference Headers para una resolución de 1024x600


Pierden elegancia, estilo, glamour...
Capítulo 4. Persistenica de los datos y contenido multimedia 193

ACTIVIDAD 4.7.
B asándote en el ejemplo de este apartado (código e imágenes), desarrolla una aplicación que
implemente las preferencias a través de PreferenceFragment y Preference Headers. Debe tener 3
headers (Datos Personales, Personajes favoritos y Aficiones), y cada uno de ellos cargará sus
correspondientes preferencias.
E ntre otros, usa los elementos MultiSelectListPreference y ListPreference dentro de
PreferenceScreen.
Y recuerda, p ara una correcta visualization, modifica la resolución del emulador.
P ara ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
PreferenceFragm entO l.rar del m aterial complementario.

C o m p a tib ilid a d h a c ia a tr á s
Si queremos que nuestras aplicaciones se puedan ejecutar en cualquier teléfono móvil,
independientemente de su versión de Android, tendremos que añadir algunas líneas de
código.
La idea es sencilla: crear dos clases que hereden de PreferenceActivity, una para las
versiones anteriores a Honeycomb, y otra para las versiones posteriores.
Basándonos en el ejemplo del apartado anterior, creamos una nueva actividad:
p u b lic c la ss M isV ie ja s P r e fe r e n c ia s exten d s P r e fe r e n c e A c tiv ity {
@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p er.o n C rea te(sa v ed ln sta n ceS ta te);

a d d P r e fe r e n c e sF r o m R e so u r c e (R .x m l.p r e fe r e n c ia s);
}
}
El fichero de recursos XML que contiene las preferencias (res/xml/preferencías, xml)
para las versiones anteriores a Honeycomb, tiene que ser diseñado para que agrupe todas
las preferencias que existan usando Preference Headers. En nuestro ejemplo debe
contener las preferencias de tres ficheros:
datos personales, xml, personajes favoritos.xml y aficiones, xml.
Para comprobar la versión del sistema operativo Android, implementamos dentro del
botón (o el widget que sea) que carga las preferencias, el siguiente código:
public void editarPreferencias(View view){
if (Build.VERSION.SDK_INT<Build.VERSION_CODES.HONEYCOMB)
startActivity(new Intentithis, MisViejasPreferencias.class));
else
startActivity(new Intentithis, MisFragmentPreferencias.class));

El código es bastante claro, ¿verdad? Se comprueba si la versión del sistema


operativo es anterior a Honeycomb, en caso afirmativo se lanzan las “viejas”
preferencias. Por el contrario, si estamos usando una versión posterior a Honeycomb, se
lanzan las “nuevas” preferencias.
194 Programación en Android

ACTIVIDAD 4.8.
Modifica la aplicación de la actividad anterior para que sea compatible con versiones anteriores a
Honeycomb.

4.1.2. FICHEROS ESTÁTICOS


Para los casos en que haya que almacenar información, y no sea necesaria ni una base
de datos ni el uso de las preferencias, Android ofrece ficheros estáticos, los cuales son
empaquetados con la aplicación.
La característica principal de los ficheros estáticos es que solo se pueden realizar
operaciones de lectura sobre ellos, es decir, no se pueden crear ni modificar en tiempo de
ejecución. Sería necesaria una actualización de la app para actualizar el contenido de
dichos ficheros.
Si en una determinada situación se necesita un acceso de lectura/escritura sobre un
fichero, Android proporciona todos los mecanismos de trabajo con ficheros de Java.
Para empezar se debe añadir el fichero como un recurso (res/raw):
T E ¡ jp p
P D manifests
► Cu java
V D i res
► SU drawable
▼ SU layout
artrv¡ty_rna¡n.xml
P SU menu
▼ SU raw
palabras
▼ S3 values
► SU d¡mens.xml 2)
i> stringsjcml
► SU styles.xml (2)
► Gradle Scripts

El contenido del recurso res/raw/palabras es un fichero de texto, que pondría


contener un diccionario, frases, etc.
Conseguir el acceso al fichero es sencillo, tan solo necesitamos un objeto de la clase
InputStream:
R esources r = g e tR e so u r c e s();
InputStream in = r . op en R aw R esou rce(R . ra w . p a l a b r a s );

Desde este momento se puede leer el contenido del fichero usando todas las clases y
métodos proporcionados por Java.

ACTIVIDAD 4.9.
Im plem enta una sencilla App que lea el contenido de un fichero estático y lo m uestre en pantalla.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el
FicherosEstaticos.rar del m aterial complementario.
Capítulo 4. Persistenica de los datos y contenido multimedia 195

4.2. CO N TEN ID O M U LTIM ED IA


Android proporciona de forma nativa una serie de formatos multimedia, codec y protocolos de
red. Sin embargo, hay que tener en cuenta que algunos de ellos, solo son soportados por Android
a p artir de una determ inada versión.

• Protocolos de red usados para el stream ing de audio/vídeo:

- RTSP (R TP, SDP)


H T T P /H T T P S stream ing progresivo
H T T P /H T T P S stream ing en vivo (a partir de Android 3.0)
Nota: H TTPS no está soportado para versiones anteriores a Android 3.1.
• Audio:

- AAC LC
- FLAC
- MP3
- MIDI
Ogg Vorbis
• Imagen:
- JPE G
- GIF
- PNG
- BMP
- WebP
• Vídeo:
- H.263
- H.264 AVC
- MPEG-4 SP
- VP8

ACTIVIDAD 4.10.
Puedes leer toda la documentación relacionada con los formatos multim edia soportados en:
Iittp: / / develouer.android.com / guide/appendix/m edia-form ats.litm l

4.3. R E PR O D U C C IÓ N D E A U D IO Y VÍDEO . LA
CLASE M ediaPlayer
La clase M ed ia P la y er es la encargada de controlar la reproducción de audio/vídeo y
streams. Dicha reproducción se gestiona como una máquina de estados. De forma muy
sencilla, podemos identificar los siguientes estados en la reproducción (Media Player):
196 Programación en Android

1. Idle
2. Inicialización del reproductor (Media Player) con contenido para reproducir:
in itia lize d
3. Preparación del Media Player: p rep a red
4. Comienzo de la reproducción: sta rte d
5. Pausa o parada de la reproducción: p a u se or stop
6. La reproducción se ha completado: com pleted

Imagen extraída de developer.android.com


Capítulo 4. Persistenica de los datos y contenido multimedia 197

Como veremos a continuación, es importante tener en cuenta dichos estados a la hora


de programar nuestras aplicaciones.

ACTIVIDAD 4.11.
Puedes ampliar información sobre la clase MediaPlayer, la máquina de estados, métodos,
excepciones, etc., en el enlace:
http://developer, android, coin/reference/android/media/MediaPlayer.html

4.3.1. INICIALIZACIÓN DEL REPRODUCTOR


Para poder reproducir contenido multimedia es necesario que estemos en el estado
P re p a re d , pero primero hay que pasar por el estado Idle, para ello basta con crear un
objeto de la clase MediaPlayer.
M e d i a P l a y e r m e d i a P l a y e r = new M e d i a P l a y e r ( ) ;

A continuación debemos pasar al estado Initialized:


m e d ia P la y e r . se tD a ta S o u r c e ();

El método setDataSource() está sobrecargado y admite varias formas de invocarlo. Lo


importante es que podemos pasarle contenido multimedia de cuatro formas distintas:
• A través de un identificado!- de recurso (audio o vídeo almacenado en el
directorio de recursos res/raw)
• Mediante una ruta local a un archivo almacenado en el sistema, por ejemplo en
nuestra tarjeta SD,
• Usando una URI (Uniform Resource Identifier) para reproducir contenido
online (streaming).
• Y desde un Content Provider.

4.3.2. PREPARACIÓN DEL CONTENIDO


Para pasar al estado P rep a red tan solo hay que invocar al método:
m e d ia P la y e r .p r e p a r e ();

En este momento podemos comenzar la reproducción. Pero antes debemos conocer


otra forma de crear nuestro objeto MediaPlayer.
M e d ia P la y er m e d ia P la y e r = M e d ia P la y e r . c r e a t e () ;

El método createf) es estático y también está sobrecargado. Permite que le pasemos


el contenido multimedia a través de las cuatro formas que hemos comentado en el
apartado anterior (res/raw, fichero del sistema, URI y Content Provider). Sin embargo,
hay que tener en cuenta que el objeto devuelto por create() ya se encuentra en el estado
Prepared, luego no debemos invocar al método prepare() otra vez.
198 Programación en Android

4.3.3. CONTROLANDO LA REPRODUCCIÓN


Para controlar la reproducción, tenemos a nuestra disposición principalmente tres
métodos:
m ed iaP layer. s t a r t ();
m ed iaP layer.p a u s e ();
m ed ia P la y er. s t o p ();

Si te fijas en el diagrama de estados, podrás observar que tenemos un estado


Stopped y un estado P aused. Para regresar al estado S ta rte d desde P a u se d tan solo
tenemos que llamar al método start(), sin embargo, desde el estado S to p p ed no
podemos pasar directamente al estado Started, sino que debemos pasar por P repared.
para ello, tal como indica el diagrama habrá que invocar primero al método preparef) y
a continuación start().
Además de estos tres métodos, la clase MediaPlayer nos ofrece una enorme
funcionalidad para controlar nuestra reproducción: getCurrentPosition(), getDuration(),
isLooping(), isPlaying(), seekTo(), selectTrack(), etc.

4.3.4. FINALIZACIÓN DE LA REPRODUCCIÓN


Es muy importante que una vez que haya acabado la reproducción y no se necesite el
objeto MediaPlayer, invocar el método release(), para liberar todos los recursos
asociados a dicha reproducción.
mediaPlayer.release<);
Vamos a ver todo este funcionamiento con un sencillo reproductor de audio, tan
sencillo que solo vamos a reproducir una canción. El contenido multimedia lo vamos a
cargar desde el directorio de recursos (res). Entonces lo primero será crear un directorio
dentro de “res” que se llame “raw”. Clic derecho sobre “res” y New / New Directory, en
la imagen tienes el resultado.

▼ C i res
► El] drawable
▼ ED layout
r i. . .
<> activrty_main.xml
► E3 menu
► E3 raw
▼ EH values
► E] dimens.xml 2)
f'.
«> strings.xml
► E l styles.xml (2)
C re a c ió n de u n d ir e c to rio de re c u rso s
Capítulo 4. Persistenica de los datos y contenido multimedia 199

Ahora tenemos que copiar la canción dentro del directorio “raw


(M ediaPlayer01\app\src\main\res\raw). El resultado será el siguiente:

▼ Ca res
► ED drawable
▼ ED layout
<> activity_main.xml
► [ill menu
▼ EH raw
¡ adrenalina.mp3
▼ E j values
► El! dimens.xml ( 2)
n>
i> strings.xml
► El styles.xml (2)

Archivo de audio dentro de res/raw

Nuestra App va ser muy sencilla, tres widget Button (play, stop y pause) y un
widget TextView que nos permita mostrar en qué estado estamos, es decir, nos servirá
de debug.

© 1 MediaPlayerOl

PLAY STOP PAUSE

Estado de tu reproductor de audio

Usaremos la propiedad onClick del widget Button para invocar los distintos métodos.
<B u tton
a n d r o i d : l a y o u t _ w i d t h = "w r a p _ c o n t e n t "
a n d r o i d : l a y o u t _ h e i g h t = "w r a p _ c o n t e n t "
a n d r o id :te x t= " @ str in g /p la y "
a n d r o i d : id = " @ + id /p la y B u tto n "
a n d r o i d : o n C l i c k = " p la y "
a n d r o i d : l a y o u t _ a l i g n P a r e n t T o p = "t r u e "
a n d r o id :la y o u t_ a lig n P a ren tS ta rt= " tr u e " />
200 Programación en Android

Recuerda que lo primero es inicializar y preparar el reproductor. En este caso


usaremos el método create(), más adelante veremos ejemplos con setDataSource().

p u b lic c l a s s M a in A c tiv ity ex ten d s A c t i v i t y !


M ed ia P la y er m e d ia P la y e r ;
@ O verride
p r o t e c t e d v o id o n C rea te(B u n d le sa v ed ln sta n ceS ta te) {
s u p e r .o n C r e a te (s a v e d ln sta n c e S ta te );
setC o n ten tV iew (R . la y o u t . a c t i v it y _ m a in ) ;
m e d ia P la y er = M e d ia P la y e r . c r e a t e ( t h i s , R .r a w .a d r e n a l i n a ) ;
}
}
El código asociado al Button PLAY y PAUSE quedaría así:

public void play(View view){


TextView t = (TextView) findViewByld(R.id.textView);
if (mediaPlayer.isPlaying()){
t .setText("Ya estás escuchando música, ¿qué más quieres chaval?") ,-
1
else {
mediaPlayer.start();
t .setText("Tu MP está parado, tranqui que le hago un start()");
}
}
public void pau s e (View view){
TextView t = (TextView) findViewByld(R.id.textView);
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause 0;
t .setText("Acabas de pausar tu MP");
1
else
t .setText ("Tu MP no está en ejecución, luego no lo puedes pausar"),-
1

Dentro del método que controla el STOP tenemos que tener cuidado con las
excepciones que puede provocar el método prepare().

public void stop(View view) throws IOException {


TextView t = (TextView) f indViewByld (R.i d .textView) ,-
if (mediaPlayer!=null && mediaPlayer.isPlaying()) {
mediaPlayer.stop () ,-
try {
mediaPlayer.prepare 0;
t .setText ("La música estaba sonando pero acabas de hacer un stopO y
un prepare 0 a tu MP');
} catch (IOException e) {
e .printStackTrace();
Capítulo 4. Persistenica de los datos y contenido multimedia 201

} catch (IllegalStateException e) {
e .printStackTrace();
}
}
else {
t .setText("La música no suena, el MP está parado, ¿por qué haces
un stop ()?") ;
}
}
Como puedes observar, el código es sencillo, tan solo hay que tener cuidado con el
estado en que nos encontramos.

A C T IV ID A D 4.12.
¿Te has dado cuenta que hay un estado que nos ha faltado? Piénsalo un momento... ¡eso es! El
estado Completed, es decir, cuando se ha term inado la reproducción. Tal como te hemos
explicado, es muy im portante que liberes todos los recursos asociados a tu reproductor una vez
que hayas term inado con él. Añade al ejemplo anterior el código necesario para liberar esos
preciosos recursos del sistema.
PISTA: en el diagram a de estados tienes el método y el Listener que debes usar.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consultando el fichero
M ediaPlayerOl.rar del m aterial complementario.

4.3.5. AÑADIENDO CONTROLES ESTÁNDAR. LA CLASE


MediaController
Para facilitarnos la vida en cuanto al control de la reproducción se refiere, Android
incluye la clase MediaController. Dicha clase nos ofrece los botones típicos de
reproducción: play, pause, rewind (rebobinar), fast forward (avance rápido) y la barra
de progreso.
MediaController también se encarga de sincronizar los controles con el estado de
nuestro MediaPlayer. Además, cuando desarrollamos una aplicación siempre debemos
tener en cuenta al usuario, facilitándole la vida en lugar de complicársela.
Aquí MediaController también nos ayuda, pues nos ofrece la interfaz típica al que
todo usuario está acostumbrado cuando reproduce vídeo o audio en su teléfono móvil.
Aunque podemos insertar el MediaController desde la paleta (sección Expert), la
forma correcta de trabajar con esta clase es instanciarla desde el código:
MediaController mediaController = new MediaController(Context
context);
Y no debemos olvidar que para nosotros es un widget, luego tendremos que realizar
la siguiente importación:
import android.widget.MediaController;
202 Programación en Android

Si queremos usar MediaController con la clase MediaPlayer, tenemos que


implementar el interfaz MediaController. MediaPlayerControl. Los métodos que
tendremos que implementar son:

p u b lic b o o le a n canP auseO { }

p u b l i c b o o l e a n c a n S e e k B a c k w a r d () { }

p u b l i c b o o l e a n c a n S e e k F o r w a r d () { }

p u b lic i n t g e t A u d i o S e s s i o n l d () { }

p u b lic i n t g e t B u f f e r P e r c e n t a g e () { }

p u b lic i n t g e t C u r r e n t P o s i t i o n () { }

p u b lic i n t g e t D u r a t i o n () { }

p u b lic b o o le a n is P la y in g O { }

p u b l i c v o i d p a u s e () { }

p u b lic v o id s e e k T o fin t pos) { }

p u b lic v o id s t a r t () { }

Además necesitamos invocar a otros tres métodos de la clase MediaController: para


especificar el MediaPlayer que se controlará, para especificar la vista (view) que
determinará el ancho del control y, por último, para mostrar el control en la pantalla.
m e d i a C o n t r o l l e r . s e t M e d i a P l a y e r ( M e d i a C o n t r o l l e r . Medi aPl aye rCont rol p l a y e r )
m e d i a C o n t r o l l e r . s e t A n c h o r V i e w ( V ie w v i e w ) ;
m e d ia C o n tr o lle r . show (in t timeout) ;

ACTIVIDAD 4.13.
Puedes ampliar información sobre la clase MediaContoller, en el enlace:
http://developer.android.com /reference/android/w idget/M ediaC ontroller.htm l

ACTIVIDAD 4.14.
Desarrolla una App que reproduzca una canción. Ten en cuenta los siguientes requisitos:
• Debes usar la clase MediaPlayer.
• Usa en esta ocasión el m étodo setDataSource() p ara cargar el contenido a reproducir.
Igual que en el ejemplo anterior, la canción a reproducir estará en el directorio de
recursos res/raw.
• P a ra controlar la reproducción tienes que utilizar la clase MediaController.
• Usa un listener p ara comenzar la reproducción cuando el contenido m ultim edia esté
preparado (M ediaPlayer.OnPreparedListener).
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
M ediaPlayer05.rar del m aterial complementario.
Capítulo 4. Persistenica de los datos y contenido multimedia 203

4.3.6. REPRODUCCIÓN DE VÍDEO


La reproducción de vídeo, en comparación con la de audio, requiere de algún paso
extra. Lo primero que necesitamos es un contenedor donde poder reproducir dicho vídeo.
Tenemos dos posibilidades. La primera, y más sencilla, es usar la clase V id eoV iew .
Dicha clase dispone de un conjunto de métodos que nos permiten cargar contenido desde
distintas fuentes y controlar la reproducción.

La segunda posibilidad consiste en crear nosotros mismos el contenedor y controlar la


reproducción usando la clase MediaPlayer. Esto se hace a través de la clase
Sur face V iew .

VideoView
e xte n d s S u rfa ceV ie w
im p le m e n ts M e d ia C o n tro ile r M e d ia P la ye rC o n tro l

ja v a .la n g .O bject
L android, view . V iew
L an d ro id . vie w .S u rfa ce V ie w
L an d ro id . w id g e t.V id e o V ie w

Imagen extraída de developer.android.com

Como podemos apreciar en la imagen, la clase VideoView hereda de SurfaceView e


implementa MediaController. MediaPlayer Control. Esto nos da mucha ventaja, pues no
tenemos que preocuparnos por diseñar el contenedor donde reproduciremos el vídeo, y
tenemos a nuestra disposición todos los métodos que hemos estudiado en el apartado
anterior.
Sin más dilación, ¡manos a la obra!
Comenzamos declarando las variables de las clases VideoView y MediaController:
p u b lic c l a s s M a in A c tiv ity e x te n d s A c t i v i t y {
V id eo V iew v id e o V ie w ;
M ed ia C o n tro ller m e d ia C o n tr o lle r ;

Dentro del método onCreate ():


@Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);
// Obtenemos la r eferenda al widget VideoView
videoView = (VideoView) findViewByld(R.id.videoView);
204 Programación en Android

// Creamos el objeto MediaController


mediaController = new MediaController(this);
// Anclamos el widget
mediaController.setAnchorView(videoView);
// Al contenedor VideoView le añadimos los controles
videoView.setMediaController(mediaController);
// Cargamos el contenido multimedia (el vídeo) en el VideoView
videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/"
+ R.raw.magia));
// Registramos el callback que será invocado cuando el vídeo esté cargado y
// preparado para la reproducción
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaController.show (10000)
videoView.start() ;
}
}>;
}

Y para terminar programamos el método onTouchEvent(), para que se muestre el


MediaControl cuando el usuario pulse en la pantalla:
@ O verride
p u b l i c b o o le a n on T ou ch E ven t(M otion E ven t e v e n t) {
m e d ia C o n tr o lle r . show ();
retu rn fa lse;
}
Merece especial atención el uso que se ha hecho del método setVideoURI() para
cargar el contenido a reproducir:
videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName()
+ "/" + R.raw.magia));

• Mediante un string se indica el recurso: (“android, resource://" +


getPackageName() + "/" + R.raw. magia)
• Con el método Uri.parse() se convierte ese string a un objeto de la clase Uri,
que es lo que espera el método setVideoURI().
No es ta n difícil como en un principio podría parecer, ¿verdad?

ACTIVIDAD 4.15.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
VideoView.rar del m aterial complementario.

4.3.7. STREAMING DE AUDIO Y VÍDEO


El streaming (reproducción online) es una tarea que en un principio podría parecer
compleja. Nada más lejos de la realidad, ya que en Android es tremendamente sencilla.
Capítulo 4. Persistenica de los datos y contenido multimedia 205

Se puede reproducir cualquier contenido publicado en Internet, por ejemplo, un vídeo


de Youtube. Sin embargo, el contenido que nosotros reproduciremos está alojado en un
hosting web, es decir, un alojamiento web donde se puede almacenar cualquier tipo de
fichero (páginas web, ficheros php, vídeos, imágenes, etc.). Dicho hosting tiene asociado
un nombre de dominio para poder acceder a él. Pero no te preocupes porque no tendrás
que hacer nada de esto, nosotros ya lo hemos hecho por ti. El dominio con el que
trabajaremos es mim.zz.mu, y para acceder al contenido de este capítulo la URL es
http://mim.zz.mu/ut-4 multimedia/

En el momento de la escritura de este libro, la URL h ttp ://m im .zz.m u /u t4 m ultim edia/ está
operativa. Si cuando estés leyendo estas líneas, dicha URL no estuviera funcionando, en Google
podrás encontrar m ultitud de sitios para poder reproducir contenido online.

Vamos a trabajar sobre los dos ejemplos anteriores: VideoView para reproducir vídeo,
y MediaPlayer05 para audio. La magia es que tan solo tenemos que modificar 2 líneas.
Lo primero que tenemos que hacer es incluir el permiso necesario para que nuestra
aplicación pueda acceder a Internet. En el manifiesto (AndroidManifest.xml) añadimos
la siguiente línea:
c u s e s - p e r m i s s i o n a n d r o i d :n a m e = " a n d r o i d .p e r m i s s i o n .I N T E R N E T " / >

<?xml v e r s i o n = " l ,0" e n c o d in g = " u t f - 8 " ?>


í<m anifest x m ln s:android="h t t p : / / s c h e m a s . a n d r o i d .c o m / a p k / r e s / a n d r o i d 11
pack a g e = 11com, i k .VsWt Vr ^e 'Aa 'm
*■ í í v v
in g v id ec" >
V V ^ 'V V '.'W V W
v v

<uses-pemission a n d r o i d :name="a n d r o i d , p e r m i s s i o n . IWTERIJET"/>

I A p p lica tio n
andró id : allowB ackup="t r u e 11
a n d r o i d :icon = "gd raw ab le / 1 c _ la u n c h e r "
a n d r o i d :l a b e l = "Stream: ngV ideo*
a n d r o i d : theme=" * s t y le/AppTheme11 >
I A c tiv ity
a n d r o id :name='' com, mase . s t r e a m i n g v i d e o . M a i n A c t i v i t y "
an d ro id : la h e l ^ S t r e a m i n g V i d e o " >
I < in te n t-filte r >
A c t i o n a n d r o i d :name="android.in te n t.a c tio n .M A I N " />

< c a te g o r y android:name="android, i n t e n t . c a t e g o r y . LAUNCHER" / >


I < /in te n t-filte r >
I < /a ctiv ity >
l < /a p p lica tio n >

K /ia a n i f e s t >

AndroidManifest.xml
206 Programación en Android

Y lo segundo y último es modificar el origen del contenido a reproducir:


• En el ejemplo VideoView, tendríam os que cam biar la línea:
videoView.setVideoURI(Uri.parse("android.resource://" +
getPackageName() + "/" + R.raw.magia));

Por:
videoView.setVideoURI(Uri.parse("http://mim.zz.mu/ut4_multimedia/magia.webm"));

Puedes ver el código en el fichero Stream ingVideo.rar del m aterial complementario.

• Y en el ejemplo MediaPlayer05, cambiamos la línea:


mediaPlayer.setDataSource(this, U r i .parse("android.resource://" +
getPackageName() + "/" + R.raw.metodo_para_escapar));

Por:
mediaPlayer.setDataSource(this,
Uri.parse("http://mim.zz.mu/ut4_multimedia/cirujano/04_aire.ogg"));

A C T IV ID A D 4.16.
Desarrolla una App que haga un stream ing de audio desde la URL
httT)://mim.zz.mu/ut4 multimedia/ciruiano.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el archivo
Stream ingAudio.rar del m aterial complementario.

4.4. C A P T U R A DE FOTOS
Hoy en día, cualquier móvil de gama media, lleva integrada una cámara con la que
muchos hubiéramos soñado hace unos años.
Trabajar con la cámara de fotos/vídeo en Android es relativamente fácil. Para
capturar fotos dentro de nuestra aplicación disponemos, básicamente, de dos
mecanismos:
1. Usar un intent, delegando a la aplicación nativa de Android todo el trabajo
sucio.
2. Programar nosotros mismos una aplicación que controle directamente la cámara
de fotos.
Como te puedes imaginar, la primera opción es la más adecuada en la mayoría de las
circunstancias. Y es la que explicaremos en este apartado. Si eres un apasionado del
mundo de la fotografía y no estás contento con la aplicación que Android te ofrece para
hacer fotos, puedes profundizar en el uso del balance de blancos, del flash, del
autoenfoque, etc.
NOTA: Si no recuerdas muy bien cómo funciona un intent, échale un vistazo a los apartados
correspondientes del capítulo anterior.
Capítulo 4. Persistenica de los datos y contenido multimedia 207

El siguiente código muestra como lanzar el intent


static final CAPTURA_IMAGEN = 1;
private void hacerFoto(){
Intent hacerFotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (hacerFotoIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(hacerFotoIntent, CAPTURA_IMAGEN);
}
}
La comprobación del if es importante, ya que el método resolveActivity() devuelve la
actividad que debe manejar el intent que vamos a lanzar. Así evitamos que nuestra
aplicación falle inesperadamente, en caso de que en el móvil donde se ejecute nuestra
aplicación no disponga de una aplicación capaz de manejar la cámara.

4.4.1. OBTENCIÓN DE THUMBNAILS


Por defecto, la foto es devuelta como un thumbnail, ¿qué es un thumbnail? Pues son
esas miniaturas de las imágenes (más ligeras) que sirven para una mejor organización y
visualización. Dicho thumbnail va integrado dentro del propio intent como datos extras,
el cual recibimos a través del método onActivityResult().
@0verride
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURA_IMAGEN_THUMBNAIL && resultCode == RESULT_0K) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
imageView.setImageBitmap(imageBitmap);

4.4.2. GUARDAR FOTOS A TAMAÑO COMPLETO


Para guardar la foto a tamaño real hay que proporcionar una ruta completa donde
almacenarla. Normalmente, todas las fotos se almacenan en un directorio de acceso
público para el resto de aplicaciones. Para obtener dicho directorio tenemos el método
getExternalStoragePublicDirectoryO, pasándole como argumento
DIRECTOR Y_PIC TU RES
(http://developer.android.com/reference/android/os/Environment.html#DIRECTORY PICTURES).

Tal como se ha comentado, este directorio es de acceso público, luego es necesario


otorgar permisos de escritura y lectura a nuestra aplicaciones
(RE AD _E X T.E R N A L_STO R .4GE y W RITE_EXTERNAL__STORAGE). El permiso
de escritura lleva implícito el permiso de lectura.
208 Programación en Android

< ?x2l1 version="l.0" encodings utf-8" ?>


«manifest xmlns:android= http://schemas.android.com/apt/res/android''
package=Bccai.s!^c.ihacieirfofotos01'' >

I^Orses^eiTOJSiOT^ndroi^naa&i^andrD^^pcra^s^^^MTE^XTrailA^^TCHA^^^^

<application
android: allowBackup=”true"
android:icon="§drawafcle/ic_launeher"
android: label="HaciendoFotos01"
android:theae= >3style/AppTheoe1 >
I <activity
android: name=" coas.asme■haciendofotosOl .MainActivity "
android:label=Haciendo?otosCi" >
I <±ntent-filter>
«action android:naaje= 'android.intent.action.MAIN" />

•«category android:naiae= "android, intent, category .LAUNCHER" />


</intent-filter>
I </activity>
I </application>

¡</iaanifest>|

A ndroidM anifest.xml

Si no queremos que cada vez que hagamos una foto, esta sobrescriba la anterior,
habrá que pensar un mecanismo para nombrar las nuevas fotos. Una buena idea es
hacerlo en base a la fecha en que se tomó la foto, tal como muestra el siguiente código
(extraído de Android Developer, h ttp : / /developer.android.com/ I :
S t r i n g m C urrentP hotoP ath;
p r iv a te F ile c r e a t e l m a g e F i l e () th ro w s IO E x cep tio n {
// C r e a t e a n im a g e f i l e name
S t r i n g t i m e s t a m p = new
S i m p l e D a t e F o r m a t ( "yyyyMMdd_HHmmss") . f o r m a t ( n e w D a t e ( ) ) ;
S trin g i m a g e F i l e N a m e = "JPEG_" + t i m e s t a m p + ;
F ile s t o r a g e D ir = E n v iro n m en t. g e t E x t e r n a l S t o r a g e P u b li c D i r e c t o r y (
E n viron m en t,D IR E C T O R Y _P IC T U R E S );
F ile im a g e = F i l e . c r e a t e T e m p F i l e (
im a g e F ile N a m e , /* p refix */
" .jp g " , /* su ffix */
sto ra g eD ir /* d ir e c to r y */
);

// Save a f i l e : p a t h f o r u s e w i t h ACTION_VIEW i n t e n t s
m C u rren tP h otoP ath = " f i l e : " + im a g e. g e t A b s o lu t e P a t h ();
retu rn im a ge;
}
Capítulo 4. Persistenica de los datos y contenido multimedia 209

Una vez hecho esto, solo nos queda crear e invocar el intent (código extraído de
Android Developer, http://developer.android.eom /~):

static final int CAPTURA_IMAGEN_TAMAÑO_REAL = 2;


private void dispatchTakePicturelntent() {
Intent takePicturelntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePicturelntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createlmageFile();
} catch (IOException ex) {
// Error occurred while creating the File
I I ...
}
// Continue only if the File was successfully created
if (photoFile != null) {
takePicturelntent.putExtra(MediaStore.EXTRA_OUTPUT,
U r i .fromFile(photoFile));
startActivityForResult(takePicturelntent,
CAPTURA_IMAGEN_TAMAÑO_REAL) ;
}

En la línea de código:

takePicturelntent.putExtra(MediaStore.EXTRA_OUTPUT, ü r i .fromFile(photoFile));

La foto (en tamaño real) se añade como dato extra al intent con el método
putExtra(). MediaStore es el proveedor de contenido (content provider) de los archivos
multimedia (audio, vídeo y fotos). Y con su constante E X TR A _O U TP U T, se indica el
intent que contiene la Uri que se usará para almacenar la foto.
Por último, resaltar que en este caso no se devuelve el thumbnail, y los datos del
intent recibido serán nuil. Luego no se puede hacer data.getExtras()como en el ejemplo
de “Obtención de thumbnails'’.
Se me olvidaba, no busques la foto en la galería porque no estará, todavía no lo
hemos explicado. Debes buscarla donde tu dispositivo almacene los archivos
(Aplicaciones!Mis archivos... o algo parecido).
210 Programación en Android

A C T IV ID A D 4.17.
Implementa una App con la siguiente interfaz:

|(S ¡ 1 H a c ie n d o F o to sO l
fe *

Hacer foto H a cer fo to

Y obtener thumbnail... Y guardarla en su tamaño real...

• El botón de la izquierda hará una foto y devolverá un thumbnail, el cual será mostrado
en un widget ImageView (que debe estar debajo de los botones).
• El botón de la derecha hará una foto y la guardara a tamaño completo en el sistema de
archivos del dispositivo. Cuando lo haga se debe mostrar un mensaje (Toast) indicando
la ruta donde se ha guardado.
Para ver el código completo del ejemplo con la solución de esta actividad, consulta
HaciendoFotosOl.rar del material complementario.

4.5. TRATAM IENTO DE IM ÁGENES ESCALADAS


Estarás de acuerdo conmigo en que cualquier usuario de un Smartphone o Tablet,
quiere que su dispositivo haga fotos de alta calidad. Es más, para muchos usuarios la
calidad de la cámara es un factor determinante en la compra del móvil. Por eso, lo
normal es configurar la cámara para que haga las fotos en máxima resolución, “¿qué las
fotos ocupan mucho? ¡No pasa nada! Me compro una tarjeta de memoria más grande”.
Hoy en día, el espacio no es un problema en Smartphones y Tablets.
Sin embargo, para los desarrolladores esto sí que es un problema. ¿Por qué? Pues
debido a que cargar en memoria dinámica imágenes de gran tamaño es muy costoso. La
memoria en cualquier tipo de dispositivo informático es un recurso muy valioso y, a la
vez, muy escaso.
Pensemos en la “ya no tan poderosa” Samsung Galaxy Tab 3: 1GB de RAM y 16GB
de almacenamiento interno. Con ese 1GB de RAM hay que hacer malabarismos para
que el sistema no se quede colgado. Pero no porque la Samsung Galaxy Tab 3 se esté
quedando vieja, sino porque las aplicaciones cada vez demandan más memoria dinámica.
Capítulo 4. Persistenica de los datos y contenido multimedia 211

Y la Tab 3 te deja hacer fotos de una resolución máxima de 2048x1536, con un


tamaño medio por foto de 1,9MB. Llegando un incluso a los 3MB. Si cada vez que el
usuario quiera ver sus fotos, se tienen que ir alojando esos 3MB en la memoria, esta se
agotará rápidamente. Y la pregunta es... ¿para qué queremos fotos con esa resolución? Si
vamos a hacer algún trabajo fotográfico con ellas, obviamente la resolución es
importante. Pero para visualizarlas en el móvil, pues no. Además tú no quieres que tu
aplicación lance una excepción del tipo “out of memory”, ¿verdad?
Pues si tu aplicación va a trabajar con imágenes, cada vez que las muestres deberás
escalarlas a un tamaño razonable.
Vamos a estudiar la solución que aportada por los desarrolladores de Android (código
extraído de Android Developer, http: //developer.android.com/'):

private void setPicO {


// Get the dimensions of the View
int targetW = mlmageView.getwidth()
int targetH = mlmageView.getHeight ()

// Get the dimensions of the bitmap


BitmapFactory.Options bmOptions = new BitmapFactory.Options 0 ;
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outwidth;
int photoH = bmOptions.outHeight ;

// Determine how much to scale down the image


int scaleFactor = Math.min (photoW/targetW, photoH/targetH)

// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;

Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);


mlmageView.setlmageBitmap(bitmap);

En la parte del código donde se obtienen las dimensiones de la imagen:


• BitmapFactory. Options es una clase estática con un conjunto de campos o
variables para trabajar con imágenes (Bitmap):
B i t m a p F a c t o r y . O p t i o n s b m O p t i o n s = new B i t m a p F a c t o r y . O p t i o n s ( ) ;

• La variable inJustDecodeBounds con valor true permite obtener los valores que
determinan el tamaño original de la imagen:
b m O p tio n s. in J u stD e c o d e B o u n d s = t r u e ;
212 Programación en Android

• Con el decodeFile() tan solo se obtiene en bmOptions información relativa a la


imagen (dimensiones reales):
B itm a p F a c to r y . d eco d eF ile(m C u rren tP h o to P a th , b m O p tio n s );
i n t p h otoW = b m O p t i o n s . o u t W i d t h ;
i n t p hotoH = b m O p tio n s. o u t H e i g h t ;

Una vez que se han calculado las dimensiones que debe tener la imagen para
visualizarse en el ImageView. Se escala la imagen con el nuevo tamaño:
• En este caso se establece inJustDecodeBounds con valor false para devolver
con decodeFile() la imagen escalada:
b m O p tio n s. in J u stD eco d eB o u n d s = f a l s e ;

• Factor de escala calculado previamente:


b m O p tio n s. in S a m p le S iz e = s c a l e F a c t o r ;

• El campo inPurgeable se ha quedado obsoleto desde la API 21. Desde Lollipop


en adelante es ignorado. Y para versiones anteriores (hasta Kitkat), si este
campo es true, el sistema se puede deshacer de la memoria usada para alojar el
bitmap en caso de que la necesite:
b m O p tio n s. in P u r g e a b le = tru e;

• Finalmente se invoca a decodeFilef), que devuelve un bitmap con la imagen


escalada. Ya solo queda visualizarla donde corresponde, en este caso en el widget
Image View.
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
mlmageView.setlmageBitmap(bitmap);

A C T IV ID A D 4.18.
Para profundizar consulta el siguiente enlace:
http://developer, android.com/training/displaving-bitmaps /'load-bit map, lit m isread-bit map

4.6. C A P T U R A DE VÍDEO
Al igual que ocurre con la captura de fotos, Android ofrece dos posibilidades para
grabar vídeo:
1. Usar intents para delegar la captura a la aplicación de grabación por defecto.
2. Usar la clase Media Recorder si lo que se pretende es reemplazar la aplicación
nativa de Android para grabación de vídeo.
La primera es la más sencilla y la más apropiada en la mayoría de las ocasiones. Y es
la que aquí explicaremos.
Lo primero que hay que hacer es crear el intent con la acción
M ediaStore.ACTION_VIDEO_CAPTURE. A continuación lanzarlo, lo que iniciará la
aplicación de grabación del sistema.
Capítulo 4. Persistenica de los datos y contenido multimedia 213

prívate final static int GRABAR_VIDEO = 1;


public void comenzarGrabacion(View view){
// Creación del intent
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

// El vídeo se grabará en calidad baja (0)


intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);

// Limitamos la duración de la grabación a 5 segundos


intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 5);

// Nos aseguramos de que haya una aplicación que pueda manejar el intent
if (intent.resolveActivity(getPackageManager()) != null) {
// Lanzamos el intent
startActivityForResult(intent, GRABAR_VIDEO);
}
}
En el ejemplo se añaden unos datos extras y, opcionales, para personalizar la grabación:
• MediaStore.EXTRA_VIDEO_QUALITY permite establecer la calidad de
grabación del vídeo. Hay dos posibles valores: 0 para baja calidad, y 1 para
alta calidad. Por defecto, los vídeos se graban en alta calidad.
i n t e n t . putExtra(M ediaStore.EXTRA_V IDEO _Q U ALITY , 0 ) ;
• MediaStore.EXTRA_DURATION_LIMIT establece, en segundos, la duración
máxima de la grabación.
i n t e n t . putExtra(M ediaStore.EX TRA _D U RA TIO N _LIM IT, 5 ) ;
• MediaStore.EXTRA_OUTPUT para elegir donde se guardará el vídeo. Por
defecto, los vídeos se almacenan en la galería.
Una vez que el usuario haya terminado su grabación, el intent devuelto contendrá,
como dato extra, una Uri al vídeo grabado.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == GRABAR_VIDEO && resultCode == RESULT_0K) {
VideoView videoView = (VideoView) findViewByld(R.id.videoView);
videoView.setVideoURI(data.getData());
videoView.start();
1

A C T IV ID A D 4.19.
Desarrolla una app que permita grabar vídeo. El funcionamiento será el siguiente:
• Un botón para comenzar la grabación.
• Un widget VideoView donde se visualizará el vídeo.
Para ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
GrabandoVideo.rar del material complementario.
214 Programación en Android

4.6.1. CÓMO A N U N C IA R QUE N U E S T R A APLIC A CIÓ N


D E P E N D E DE LA C Á M A R A
Aunque resulte extraño, puede que haya móviles que no tengan la cámara disponible.
Para evitar este tipo de situaciones, las cuales dejarían nuestra App en mal lugar,
conviene anunciar en Google Play que nuestra aplicación depende de la existencia de la
cámara en el dispositivo. Para ello en AndroidManifest.xml escribimos:
< u ses-featu re
a n d r o i d :n atne=" a n d r o i d . h a r d w a r e . c a m e r a "
a n d r o id :r e q u ir e d = " tru e" />

Si nuestra App usa la cámara, pero no es estrictamente necesaria para su


funcionamiento, se puede establecer android:required a false. De esta manera, Google
Play permitirá que dispositivos sin cámara descarguen nuestra App.
Es nuestra responsabilidad comprobar la disponibilidad de la cámara en tiempo de
ejecución llamando al método
hasSystemFeature(PackageManager.FEATURE_CAMERA). Y en caso de que no lo
esté, habrá que deshabilitar las partes de nuestro código que dependan de la cámara.
Todo esto también es aplicable a aplicaciones que usen la cámara de fotos. Pues
estamos a hablando de “la existencia de una cámara”, y no diferenciando si es de vídeo o
de fotos.

4.7. A L M A C EN A M IEN T O DE C O N TEN ID O


M U LTIM ED IA (EN LA G ALERÍA)
La mayoría de usuarios si una foto o vídeo no está en la galería de Android, no saben cómo
encontrarlo. Hemos visto que los vídeos y thumbnails se almacenan en la galería directamente,
mientras que las fotos a tamaño completo no.
El siguiente código (extraído de Android Developer, littu: / /developer.android.com /l muestra
como añadir una. foto a la galería. Sin embargo, puede utilizarse para cualquier tipo de fichero.
private void galleryAddPic() {
Intent mediaScanlntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = U r i .fromFile(f);
mediaScanlntent.setData(contentUri);
this.sendBroadcast(mediaScanlntent);
}
Para conseguirlo se usa un intent con una acción de la clase Media Scanner
(.A C TIO N _M ED IA_SC AN N ER_SC AN _FILE).
Capítulo 4. Persistenica de los datos y contenido multimedia 215

P R Á C T IC A 4. E L IG E D E E N T R E D O S E J E R C IC IO S

Desarrolla las aplicaciones de esta tarea para el siguiente SDK mínimo: API 17 (Android
4.2 Jelly Bean)

Implementa tan solo 2 ejercicios. Tienes total libertad para elegir los 2 que más te
gusten.

1. Implementa una aplicación que almacene información del usuario usando


Preferencias. La temática es totalmente libre. Debe cumplir los siguientes requisitos:
• Se implementarán las preferencias a través de Preference Fragment y
Preference Headers.
• Se crearán un mínimo de 4 headers.
• Se usará el elemento PreferenceCategory con el fin de tener una mejor
clasificación.
• Se usarán los siguientes elementos: MultiSelectListPreference, ListPreference,
RingtonePreference, SwitchPreference, CheckBoxPreference y
EditTextPreference.
• Cada preferencia debe tener asociado un icono.

2. Crea una aplicación que permita al usuario escribir una URL (donde habrá
almacenado un vídeo o una canción) y reproduzca el contenido.

3. Desarrolla una aplicación que permita la grabación de vídeo, ofreciendo al usuario la


posibilidad de elegir:
• La calidad de la grabación (alta o baja).
• La duración máxima de la grabación (0 para no establecer límite).
También se deberá anunciar que la aplicación depende de la existencia de la cámara.

4. Implementa una aplicación que permita tomar fotografías. El usuario podrá:


• Elegir el nombre con que se guardará la fotografía. En caso de que no lo haga,
se guardará la fotografía con un nombre único generado automáticamente.
• Elegir el nombre de la carpeta donde se guardará la fotografía, en caso de que
no exista, se creará dicha carpeta. Si el usuario no especifica ninguna carpeta,
la fotografía se guardará en la galería.
También se deberá anunciar que la aplicación depende de la existencia de la cámara.
216 Programación en Android

C R IT E R IO S D E C A L IF IC A C IÓ N

Ejercicio 1: 5 puntos
• Se implementarán las preferencias a través de Preference Fragment y
Preference Headers: 1,5 puntos
• Se crearán un mínimo de 4 headers: 1 punto
• Se usará el elemento PreferenceCategory con el fin de tener una mejor
clasificación: 1 punto
• Se usan todos los elementos mencionados: 1 punto
• Cada preferencia debe tener asociado un icono: 0,5 puntos

Ejercicio 2: Que se reproduzca el contenido correctamente: 5 puntos


Ejercicio 3: 5 puntos
• La aplicación funciona y permite la grabación de vídeo: 2 puntos
• La calidad de la grabación (alta o baja): 1 punto
• La duración máxima de la grabación (0 para no establecer límite): 1 punto
• También se deberá anunciar que la aplicación depende de la existencia de la
cámara: 1 punto
Ejercicio 4: 5 puntos
• La aplicación funciona y permite tomar fotografías: 2 puntos
• Elegir el nombre con que se guardará la fotografía. En caso de que no lo haga,
se guardará la fotografía con un nombre único generado automáticamente: 1
punto
• Elegir el nombre de la carpeta donde se guardará la fotografía, en caso de que
no exista, se creará dicha carpeta. Si el usuario no especifica ninguna carpeta,
la fotografía se guardará en la galería: 1 punto
• También se deberá anunciar que la aplicación depende de la existencia de la
cámara: 1 punto
UNIDAD 5

PROGRAMACIÓN DE
VIDEOGUEGOS

CONTENIDOS
5.1 Introducción
5.2 La arquitectura de un videojuego
5.3 El canvas de Android
5.4 Los dibujables (Drawables)
5.5 El framework de animaciones de Android
5.6 Las animaciones en tiempo real: el bucle de un videojuego
5.7 La interacción con el jugador
5.8. Creación de un videojuego sencillo: the Xindi Invasion
5.9. Los motores para programación de videojuegos
218 Programación en Android

5.1. IN T R O D U C C IÓ N
La industria del videojuego es una industria muy lucrativa. Desde que en los años 70
la empresa Atari popularizara los videojuegos hasta la actualidad, donde empresas como
Electronic Arts, Blizzard, Activision o Konami son gigantes de una industria que
factura miles de millones de dólares.
La base de la creación de estos videojuegos está en la programación y aunque en
ocasiones el éxito de un videojuego reside más en la producción pseudo-cinematográfica
y la publicidad, está claro que el núcleo de un videojuego siempre será la programación.
El director que guía todo lo que sucede en un videojuego es un algoritmo. Cosas como
qué animaciones ocurren y qué interacciones puede tener el usuario, qué respuestas
inteligentes produce el videojuego ante determinadas situaciones están basadas en
algoritmos programados en el núcleo del videojuego.
Para la creación de videojuegos Android nos proporciona ciertos mecanismos para
realizar animación y tratar gráficos tanto en 3d como en 2d. Entre los más importantes
están:

• Canvas; Un canvas, en inglés lienzo, permite realizar aplicaciones que tienen


mayor control sobre la interfaz de usuario, permitiendo dibujar cualquier
objeto (líneas, rectángulos, óvalos, incluir bitmaps, etc.) en una superficie
(Surface) que se volcará a la pantalla de tu dispositivo.

• Animadores (Animators): Permite animar prácticamente cualquier objeto


fácilmente con el uso de propiedades y estilos de animación preprogramados.

• Drawable Animation: Permite visualizar recursos Drawable, por ejemplo, una


imagen, uno detrás de otro como si fuera un rollo de película.

• Los Dibujables (Drawables): Android tiene una librería 2D para dibujar formas
e imágenes.

• Open GL: Es una librería de funciones de alto rendimiento para el tratamiento


de gráficos en 3D. Es una librería estándar y multiplataforma que permite
dibujar escenas tridimensionales complejas utilizando geometría de puntos,
líneas y triángulos.
Capítulo 5. Programación de videojuegos 219

5.2. LA A R Q U IT E C T U R A DE U N V ID EO JU EG O

LOGICA DEL VIDEOJUEGO

EVENTOS U 1R CONTROLES
DE
GAMELOOP

ACTUALIZACIÓN
RENDERIZAR CANVAS
O
GENERACIÓN
DE DEL ESTADO
GENERAR DE SONIDO
USUARIO VIDEOJUEGO (UPDATE) PROCESAR
FRAME DE
SPRITES
ANIMACIÓN

ANDROID FRAMEWORK O P E N GL

A continuación te proponemos esta arquitectura como una posibilidad sencilla para


que apliques a tus videojuegos. Verás que aunque sencilla, también tiene mucha
flexibilidad. Los bloques que encuentras en el diagrama representan cada uno de los
bloques funcionales que tendrá el videojuego.
Si abstraes el bloque central “Lógica del videojuego” y piensas en él como en un
cerebro que lo controla todo, la arquitectura sería muy sencilla: Los eventos de usuario
(toques y arrastres del dedo, interacción con controles hardware) son enviados al
videojuego, el cual los procesa y genera una salida en forma de animación. Este proceso
dura y se repite el tiempo que el videojuego está en ejecución, de tal manera que se
forma un bucle recibiendo eventos de usuario y generando la salida. Este bucle se llama
bucle de juego o “Game Loop”. El game loop es, por tanto, el cerebro del videojuego,
procesa ios eventos del usuario transformándolos en controladores de acciones, que
sirven para tomar decisiones en cuanto a la animación a generar.
Para dibujar todas las cosas que suceden en una escena de nuestro videojuego, se
utiliza el Framework de Android para animaciones 2D y 3D y en especial, se puede
utilizar la librería estándar Open GL para estos menesteres. Con estas librerías se
pueden tratar sprites (imágenes con transparencia) para componer escenas y generar
animaciones.
El sonido es parte importante de un videojuego. En el game loop también se deben
tomar las decisiones referentes a los efectos de sonido que se van a generar en cada
momento y se utiliza el framework de android para reproducir estos efectos.
220 Programación en Android

5.3. EL C A N V A S DE A N D R O ID
Cuando se necesita generar gráficos de forma dinámica, se dibujan estos gráficos en
un objeto “Lienzo” o C anvas. El canvas se repinta regularmente para dar forma a la
animación. El objeto Canvas tiene asociado un bitmap donde se escribe lo que se dibuja
en el canvas. Además, para pintar en un canvas, se necesitan:
• Las coordenadas x,y donde se va a pintar el objeto
• Una primitiva de dibujo, es decir, qué dibujar. Por ejemplo Rect (Rectángulo),
Text (Texto), Bitmap (gráfico)
• Un objeto Paint para describir los colores y estilo para el dibujo
El sistema de coordenadas de Android está pensado para preservar el aspecto
independientemente del tamaño de pantalla que se esté utilizando. La coordenada (0,0)
corresponde al borde superior derecho de la pantalla, mientras que la coordenada
(x_max, y_m ax) corresponde al borde inferior izquierdo.

Para calcular x__max e y_max, se utiliza el objeto Display que calcula el tamaño de
la pantalla en pixels.
D i s p l a y m d isp = getW in d ow M an ager( ) . g e t D e f a u l t D i s p l a y ( ) ;
P o i n t r a d i s p S i z e = new P o i n t ( ) ;
m d isp . g e tS iz e (m d is p S iz e );
i n t maxX = m d i s p S i z e . x ;
i n t maxY = m d i s p S i z e . y ;

Canvas tiene métodos para dibujar todo tipo de objetos. Por ejemplo:
• drawARGB: Rellena todo el bitmap del canvas del color ARGB que se pasa
como parámetro.
d r a w A R G B (in t a , in t r, in t g, i n t b)
• drawArc: Dibuja un arco, acorde a los parámetros que se le pasan:
d raw A rc(R ectF o v a l , flo a t sta rtA n g le, flo a t sw eep A n gle,
Capítulo 5. Programación de videojuegos 221

b o o lea n u seC en ter, P a in t p a in t)


d r a w A r c (flo a t l e f t , f l o a t to p , f l o a t r i g h t , f l o a t b ottom ,
f l o a t s t a r t A n g le , f l o a t sw eep A n g le, b o o le a n u s e C e n te r , P a in t p a in t)
• drawBitmap: Dibuja un objeto de formas distintas
draw B itm ap (B itm ap b itm a p , M a trix m a tr ix , P a in t p a in t)
draw B itm ap (B itm ap b itm a p , R ect s r c , R ectF d s t , P a in t p a in t )
draw B itm ap (B itm ap b itm a p , f l o a t l e f t , f l o a t to p , P a in t p a in t)
draw B itm ap (B itm ap b itm a p , R ect s r c , R ect d s t , P a in t p a in t)
• drawCicle: Dibuja un círculo con un determinado radio
d r a w C ir c le (flo a t ex, flo a t cy, flo a t r a d iu s. P a in t p a in t)
• drawLine/drawLines: Dibuja una o varias líneas
d ra w L in e(flo a t sta rtX , flo a t startY , flo a t stopX , flo a t stopY ,
P a in t p a in t)
d r a w L i n e s ( f l o a t [] p t s , P a in t p a in t)
d r a w L i n e s ( f l o a t [] p t s , in t o f f s e t , in t cou n t, P a in t p a in t)
• drawOval: Dibuja un óvalo encuadrado en un rectángulo
d ra w O v a l(f l o a t l e f t , f l o a t top , f l o a t r ig h t, f l o a t bottom , P a in t p a in t)
d raw O va l(R ectF o v a l , P a in t p a in t)
• drawPath: Dibuja un camino con la pintura especificada
d raw P ath (P ath p a th , P a in t p a in t)
• drawPicture: Dibuja una imagen cualquiera
d ra w P ictu re(P ictu re p ic tu r e , R ectF d s t )
d r a w P ic tu r e (P ic tu r e p ic t u r e )
d r a w P ic tu r e (P ic tu r e p ic t u r e , R ect d st)
• drawPoint/drawPoints: Dibuja uno o varios puntos
d r a w P o in t(flo a t x, f l o a t y , P a in t p a in t)
d r a w P o i n t s ( f l o a t [] p ts , in t o f f s e t , in t cou n t, P a in t p a in t)
d r a w P o i n t s ( f l o a t [] p t s , P a in t p a in t)
• drawRect: Dibuja un rectángulo
d ra w R ect(R ectF r e c t , P a in t p a in t )
d raw R ect(R ect r . P a in t p a in t )
d r a w R e c t(flo a t l e f t , f l o a t to p , f l o a t r ig h t, f l o a t bottom , P a in t p a in t)
• drawRoundRect; Dibuja un rectángulo con los bordes redondeados
d ra w R o u n d R ect(flo a t l e f t , f l o a t to p , f l o a t r i g h t , f l o a t b ottom ,
f l o a t rx , f l o a t ry . P a in t p a in t)
draw R oundR ect(R ectF r e c t , f l o a t r x , f l o a t r y , P a in t p a i n t )
• drawText: Permite dibujar un texto
d ra w T ex t(S trin g t e x t , flo a t x , f l o a t y . P a in t p a in t)
d ra w T ext(C h arS eq u en ce tex t, i n t s t a r t , i n t end, f l o a t x , flo a t y,
P a in t p a in t)
d r a w T e x t ( c h a r [] t e x t , in t in d e x , in t cou n t, flo a t x, flo a t y.
P a in t p a in t)
d ra w T ex t(S trin g t e x t , in t sta rt, in t end, flo a t x, flo a t y. P a in t p a in t)
• drawTextOnPath: Permite escribir un texto siguiendo una ruta a través de la
pantalla
draw T extO nP ath( S t r in g t e x t . P ath p a th , f l o a t h O f f s e t , f l o a t v O f f s e t ,
P a in t p a in t)
d r a w T e x t O n P a t h ( c h a r [] t e x t , i n t i n d e x , i n t c o u n t , P a t h p a t h ,
f l o a t h O ffs e t, f l o a t v O f fs e t , P a in t p a in t)
222 Programación en Android

D em o C a n v a s

Aquí un texto

Por ejemplo, imagina que vas a programar un método llamado render que se ejecuta
cada cierto tiempo y que te pasa como parámetro un objeto de tipo canvas, podrías
hacer dibujos de la siguiente manera:

p u b li c v o id ren d er(C an vas can vas) {


P a i n t m y P a i n t = new P a i n t () ;
m y P a i n t . s e t S t y l e ( P a i n t . S t y l e . STROKE);

//T o d o e l c a n v a s en r o j o
c a n v a s.d r a w C o lo r (C o lo r .R E D );

/ / D i b u j a r muñeco de a n d r o id
bmp = B i t m a p F a c t o r y . d e c o d e R e s o u r c e ( g e t R e s o u r c e s ( ) ,
R .d r a w a b le .ic _ la u n c h e r );
c a n v a s . draw B itm ap(bm p, 5 0 0 , 5 0 0 , n u l l ) ;

/ / C a m b i a r c o l o r y ta m a ñ o d e b r o c h a
m y P a in t. s e t S tr o k e W id t h (1 0 );
m y P a in t. se tC o lo r (C o lo r .B L U E );

//d ib u j a r rectá n g u lo
c a n v a s . d r a w R e c t(450, 450, 300, 300, m y P a in t);

//d ib u j a r ó v a lo y arco
R e c t F r e c t F = n ew R e c t F ( 5 0 , 2 0 , 2 0 0 , 1 20);
c a n v a s . d ra w O v a l(rectF , m y P a in t);
c a n v a s . drawArc ( r e c t F , 9 0 , 4 5 , t r u e , m y P a in t);

/ / d i b u j a r un t e x t o
m y P a in t. s e t S t y l e ( P a i n t . S t y l e . F IL L );
m y P a in t. s e t T e x t S i z e (1 0 0 );
c a n v a s . d r a w T e x t ( "Aquí un t e x t o " , 5 0 , 200, m y P a in t);

5.4. LOS D IB U JA B L E S (D R A W ABLES)


Un dibujable es una abstracción para definir “cualquier cosa que se pueda dibujar”.
Estos son los objetos que querrás utilizar cuando necesites cargar una imagen de un
fichero. Android es capaz de manejar bitmaps de los siguientes tipos:
• Png: El preferido

• Jpg: Aceptable
Capítulo 5. Programación de videojuegos 223

• Gif: Desaconsejado

Para referenciar a estos elementos desde el código hay que escribir


R.drawable.nombrefichero. Tan sólo tiene una restricción, que el nombre tiene que estar
en minúscula y que como caracteres especiales solo acepta el guión bajo.
En XML se referencia mediante la expresión @drawable/nombrefichero.
Para dibujar en un canvas un bitmap se utiliza la clase BitmapFactory y el método
drawBitmap:
/ / D i b u j a r muñeco de a n d r o id
bmp = B i t m a p F a c t o r y . d e c o d e R e s o u r c e ( g e t R e s o u r c e s () , R . d r a w a b l e . i c _ l a u n c h e r ) ;
c a n v a s . draw Bitm ap(bm p, 5 0 0 , 5 0 0 , n u i l ) ;

5.4.1. LOS SPRITES


Los sprites son imágenes con fondo transparente para poder ser integrada de manera
natural en una escena. Tan sólo se dibujan encima de la escena los píxeles que no son
transparentes. Para poder hacer un sprite necesitas un programa de edición de imágenes
como por ejemplo Gimp. Gimp es un programa open source con multitud de
herramientas para retocar y editar imágenes. Con Gimp puedes dibujar, recortar,
convertir a diferentes formatos y, por supuesto, crear transparencias, elemento esencial
para crear sprites. Para crear una transparencia en una imagen, se debe añadir un
“Canal Alfa” para que actúe como transparencia en la imagen.

SPRITES DE ROBOTS sprite de Mario

Puedes descargar toneladas de bitmaps y sprites libres de la página


h t t p : //o p e n g a m e a r t .o r g /

Por ejemplo, de h t t p : //o p e n g a m e a r t .o r g /c o n t e n t / r o b o t - 2 puedes descargar un bonito


robot posando en diferentes posturas e intercalar las imágenes para animarlo:
Verás que el paquete robot_c-toy.zip contiene una imagen por cada postura del
robot. Para secuenciar cada una de las imágenes deberás cargar todas en memoria e ir
dibujando una cada vez.
224 Programación en Android

1 2 3

O tra estrategia es tomar una única imagen con todas las posturas y cargarlas de una
vez. Después, se puede ir tomando pedazos de la imagen en secuencia para formar la
animación mediante la instrucción:
c a n v a s . d raw B itm a p (b itm ap , sou rceR ect, d estR ect, n u il);
donde bitmap es la imagen con todas las posturas, sourceRect es un objeto de tipo
rectángulo con las coordenadas del recorte y destRect son las coordenadas donde se va a
dibujar.

5.5. EL FR A M EW O R K DE A N IM A C IO N E S DE
A N D R O ID
Android incorpora un potente sistema de animaciones que permiten realizar cualquier
tipo de animaciones automáticas sin tener excesivos conocimientos de física o de
matemáticas.
Pare crear animaciones se utiliza la clase Animation, que representa una animación
que se puede aplicar a Vistas, Superficies u otros objetos. Además se puede utilizar la
clase AnimationSet, que representa un conjunto de animaciones que serán visualizadas
al mismo tiempo o unas detrás de otras.
Android proporciona varios sistemas para crear animaciones:
• Property Animations
• View Animations
• Drawable Animations

5.5.1. LAS PROPERTY ANIMATIONS


Introducidas en el API nivel 11, permite animar propiedades de cualquier objeto. Por
ejemplo podrías hacer crecer automáticamente el tamaño de un botón (estilo zoom-in
seguido de zoom-out), rotarlo o incluso crear traslaciones por la pantalla.
Capítulo 5. Programación de videojuegos 225

Por ejemplo, para crear una animación en la que trasladas un botón desde la
izquierda de la pantalla al mismo tiempo que lo haces aparecer poco a poco (fade in), se
puede usar este código:

p u b lic v o id A n im a cio n B o to n (){


A n i m a t o r S e t a n i m a d o r B o t o n = n ew A n i m a t o r S e t () ,-

/ / I a a n im a ció n , t r a s l a d a r d e sd e l a iz q u ie r d a
/ / (8 0 0 p i x e l e s m e n o s h a s t a l a p o s i c i ó n i n i c i a l (0)
O b jectA n im a to r t r a s l a d a r =
O b jectA n im a to r. o f F l o a t (b o to n J u e g o ," t r a n s la t io n X " , - 8 0 0 , 0 ) ;
t r a s l a d a r . s e t D u r a t io n (5 0 0 0 ); //d u r a c ió n 5 segundos

/ / 2 a A n im a ción f a d e i n de 8 se g u n d o s
O b jectA n im a to r fa d e=
O b je c tA n im a to r . o f F l o a t (b o to n J u eg o , " a lp h a " , O f, If);
f a d e . s e t D u r a tio n (8000);

/ / s e v i s u a l i z a n l a s dos a n im a cio n es a la v ez
a n im a d o r B o to n .p la y ( t r a s l a d a r ) .w i t h ( f a d e ) ;

//c o m e n z a r a n im a c ió n
a n im a d o rB o to n . s t a r t ( ) ;
}
Cada objeto ObjectAnimator se crea para crear un tipo de animación. Por ejemplo, la
propiedad translationX sirve para cambiar la coordenada X del objeto y alpha para
cambiar la transparencia. El método ofFloat indica el cambio progesivo en valores de
coma flotante que sufrirán los parámetros de la propiedad para generar la animación.

5.5.2. VIEW ANIMATIONS


Permiten dotar de animación a las vistas. Las animaciones se calculan con
información del punto de inicio, punto de fin, tamaño, rotación y otros parámetros. Por
ejemplo, puedes programar una ImageView con un sprite para calcular una trayectoria
de un objeto, rotarlo, ampliarlo o reducirlo progresivamente para dar sensación de zoom.
Puedes crear estas animaciones en XML o en código fuente.

Con un Animation se pueden crear dos tipos principales de efectos:


226 Programación en Android

Animadores: Realiza cambios en una vista para producir una animación. Algunas de
las animaciones más útiles que puedes encontrar son las siguientes.

RotateAnimation Una animación que controla la rotación de un objeto


ScaleAnimation Una animación que controla la escala de tamaño objeto
Define la transformación que se aplicará a un objeto en algún
Transformation punto de la animación
T ranslateAnimation Una animación que controla la posición del objeto
Una animación que controla el nivel alfa (transparencia) de un
AlphaAnimation objeto

Interpoladores: Controlan cómo se producen cambios en la animación, como por


ejemplo

Un interpolador donde el ratio de cambio comienza y acaba


AccelerateDeceleratelnterpolator lentamente, pero acelera en el medio
Un interpolador donde el ratio de cambio comienza
Acceleratelnterpolator lentamente y luego acelera
Bouncelnterpolator Interpolador donde el cambio bota al final
Interpolador que repite la animación un determinado número
Cyclelnterpolator de veces
Un interpolador que cambia rápidamente al principio y luego
Deceleratelnterpolator decelera
Linearlnterpolator Un interpolador donde el ratio de cambio es lineal

Estos efectos se pueden combinar a través de AnimationSet, donde se agrupan


cualquier conjunto de animaciones.

5.5.2.1. Definición de animaciones view en XML


Por ejemplo, para definir en XML una combinación de rotación constante y
traslación, definimos un recurso de tipo animación en el directorio res/anim de nuestro
proyecto:

<?xml v e r s i o n = " 1 .0 " e n c o d in g = " u t f - 8 " ? >


< se t a n d r o id :sh a r e In te r p o la to r = " fa lse "
x m l n s : a n d r o i d = " h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d ">
< rotate
a n d r o i d : d u r a t i o n = " 1600"
a n d r o id : from D egrees="0"
a n d r o i d : t o D e g r e e s = "358"
a n d r o i d : i n t e r p o l a t o r " @ a n d ro id : a n i m / l i n e a r _ i n t e r p o l a t o r "
a n d r o i d : p i v o t X = "50%"
a n d r o i d : p i v o t Y = " 50%"
a n d r o i d : r e p e a t C o u n t = "i n f i n i t e "
Capítulo 5. Programación de videojuegos 227

/>
« tr a n s ía te
a n d r o i d : d u r a t i o n = "10000"
a n d r o i d : fro m X D elta = " -1 200"
a n d r o i d : f r o m Y D e l t a = "0"
a n d r o i d : t o X D e l t a = " 1 2 00 "
a n d r o i d : t o Y D e l t a = "40"
a n d r o id :repeatM ode= "reverse"
a n d r o id :r e p e a tC o u n t="i n f i n i t e "
/>

< /set>

En el conjunto de la animación “Set” del ejemplo anterior se definen dos animaciones


que tendrán lugar al mismo tiempo:
Rotate: Se rotará desde los 0 hasta los 359 grados en una duración 1600 milisegundos
de forma lineal (interpolator), con la línea de pivote en 50% para que el objeto gire
sobre sí mismo, repetido infinitas veces.
Translate: La animación durará 10 segundos en la que se trasladará el objeto de la
coordenada (-1200,0) hasta la (1200,40) y, cuando llegue al final, volverá
(repeat Mode= ”re verse ”)
Puedes consultar la sintaxis completa de las animaciones en XML en
http://developer.android.com/guide/topics/resources/animation-resource.html
Para aplicarlo a un sprite, tan solo hay que cargarlo vía código creando la animación
a través de la clase AnimationUtils y después invocando al método startAnimation del
objeto Animation que se acaba de crear:

I m a g e V i e w m e t e o r i t o = (I m a g e V i e w ) f i n d V i e w B y l d (R. i d . i m g M e t e o r i t o ) ,-
m e t e o r it o . setv isib ility (Im a g e V ie w .V IS IB L E ) ;
A n im a tio n m e te o r ito A n im = A n i m a t i o n U t i l s . lo a d A n im a t io n ( t h i s , R . a n i m .m e t e o r i t o ) ;
m e t e o r ito . sta rtA n im a tio n (m eteo rito A n im );

5.5.2.2. Definición de animaciones view en código fuente


Si quisiéramos mover un objeto de izquierda a derecha de la pantalla definiendo la
animación a través de código fuente, y después de derecha a izquierda puedes utilizar el
siguiente código:

I m a g e V i e w i m a g e n = (I m a g e V i e w ) fin d V ie w B y ld (R .id .im g P la n e ta );

T ra n sla teA n im a tio n a n im a tio n =


n ew T r a n s l a t e A n i m a t i o n ( - 4 0 0 . O f , 8 0 0 . O f, 8 0 . O f, 4 0 0 . 0 f ) ;
a n im a tio n .s e tD u r a tio n (10 0 0 0 ); / / d u r a c ió n de l a a n im a c ió n
a n im a tio n .setR ep ea tC o u n t(5 ); / / r ep etir 5 veces
a n im a tio n .setR ep eatM od e(2 ); / / r e p e t i r de iz d a a deha y de deha a iz q u ie r d a

im a g e n .sta r tA n im a tio n (a n im a tio n ); // com enzar a n im a ció n


228 Programación en Android

En el ejemplo anterior se ha utilizado la TheXindilnvasion


clase TranslateAnimation para generar la
translación de un objeto, en este caso el
sprite del planeta que puedes ver en el
gráfico adjunto. De esta manera, se
describirá una trayectoria desde las
coordenadas (-400,80) a la coordenada
(800,400). Las coordenadas negativas
indican que el objeto se sitúa fuera de la
pantalla.
Las propiedades que puedes utilizar son
las mismas que en los archivos XML y
puedes utilizar cualquier tipo de Animador
(TranslateAnimation, RotateAnimation,
ScaleAnimation, etc).
Desgraciadamente, todas estas
animaciones creadas a través del
frameword de animaciones de Android, son
solo eso, simples animaciones y, aunque
existen funciones de callback para detectar
su comienzo, repetición y finalización
(onAnimationStart, onAnimationRepeat, onAnimationEnd) su capacidad de interacción
con el usuario es muy limitada. Por tanto, para poder interactuar con el usuario en un
videojuego y responder con otras animaciones a las acciones del jugador, deberás crear
tus animaciones en tiempo real, las cuales te explicamos en la siguiente sección. No
obstante, no creas que has perdido el tiempo leyendo esta sección puesto que estas
animaciones automáticas sí que te permitirán crear todo el marco de opciones de menú
tu videojuego, dándole un toque verdaderamente profesional. Por ejemplo, puedes
animar la selección de tipos de nivel, la selección de personajes e incluso crear
transiciones entre escenario y escenario.

5.5.3. DRAWABLE ANIMATIONS


Con estas animaciones se pueden encadenar bitmaps uno detrás de otro para formar
una animación como si fuera una película. Es muy fácil definirlas en XML mediante la
etiqueta < animation-list>. Por ejemplo, crea un fichero de recursos en el directorio de
tu proyecto res/drawable con el nombre a n im a c ió n _ro b o t.xm l e inserta el siguiente
código:
« a n i m a t i o n - l i s t x m l n s : a n d r o i d = "h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d "
a n d r o id :o n e sh o t= " fa lse " >
« ite m a n d r o id :d r a w a b le = " @ d r a w a b le /r o b o tl" a n d r o id :d u r a tio n = " 2 0 0 " />
« i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t 2 " a n d r o i d : d u r a t i o n = " 20 0" / >
« i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t 3 " a n d r o i d : d u r a t i o n = " 20 0" / >
Capítulo 5. Programación de videojuegos 229

< ite m a n d r o id : d r a w a b le = " @ d r a w a b le /r o b o t4 " a n d r o i d : d u r a tio n = " 2 0 0 " />


< i t e m a n d r o i d : d r a w a b l e = "@ d r a w a b l e / r o b o t 5 " a n d r o i d : d u r a t i o n = "200" />
< i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t 6" a n d r ó i d : d u r a t i o n = "2 0 0" />
< i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t 7" a n d r ó i d : d u r a t i o n = "2 0 0" />
< i t e m a n d r o i d : d r a w a b 1 e ="@ d r a w a b l e / r o b o t 8 " a n d r o i d : d u r a t i o n = "200" />
< i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t 9" a n d r o i d : d u r a t i o n = "200" />
< i t e m a n d r o i d : d r a w a b l e = " @ d r a w a b l e / r o b o t l O " a n d r o i d : d u r a t i o n = "200 />
< /a n im a tio n -list>

Cada elemento “ítem” de la <animation-list> representa las imágenes que se irán


intercalando en la animación con una duración en milisegundos establecida mediante la
propiedad android ¡durat ion.
Si establecemos a false el atributo android:oneshot="false", la animación se repetirá
una y otra vez, mientras que si le damos el valor "true" terminará al llegar al último
elemento.
Para lanzar la animación, por ejemplo, al pulsar la pantalla, añadiremos el siguiente
código a la actividad:
A n im a tio n D ra w a b le a n im a c io n _ r o b o t ; / / O b j e t o a n im a c ió n

@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p er.o n C rea te(sa v ed ln sta n ceS ta te);
se tC o n te n tV ie w (R .la y o u t . a c t i v it y _ m a in ) ;

//Im a g e V ie w a l a que s e a ñ a d ir á l a a n im a c ió n
Im ageV iew im gR obot = (Im ageV iew ) f i n d V i e w B y l d f R . i d . i m g R o b o t ) ;
im gR ob ot. se tB a c k g r o u n d R e s o u r c e (R .d r a w a b le . a n im a c io n _ r o b o t ) ;
a n im a c io n _ r o b o t= (A n im a tio n D r a w a b le ) im g R o b o t .g e t B a c k g r o u n d ( ) ;
im gR ob ot. s e t O n T o u c h L i s t e n e r ( t h is ) ; / / l i s t e n e r p a ra to u c h
}
© O v errid e
p u b l i c b o o l e a n on T o u ch (V iew v , M o tio n E v e n t e v e n t ) {
/* A l t o c a r l a p a n t a l l a com enzará l a a n im a c ió n * /
i f ( e v e n t . g e t A c t i o n O == M o t i o n E v e n t . ACTION_DOWN) {
a n im a c io n _ r o b o t. s t a r t ();
retu rn tru e;

retu rn fa lse ;
}

El objeto AnimationDrawable es el que arranca la animación desde el objeto


imgRobot de tipo ImageView. Previamente se ha cargado la animación mediante el
método setBackgroundResource(recurso).

ACTIVIDAD 5.1.
Puedes ver el código de este caso práctico en el fichero AnimationDrawable.rar del material
complementario.
230 Programación en Android

5.6. LAS A N IM A C IO N E S E N TIEM PO REAL: EL


BU CLE DE U N V ID EO JU EG O
Para poder generar animaciones en tiempo real se necesita un mecanismo repetitivo
para poder procesar los datos y generar las distintas situaciones que se produzcan en el
juego. Este mecanismo repetitivo, llamado bucle de juego o gameloop tiene la
característica de que se ejecuta mientras el juego siga en ejecución.

El bucle del juego consiste en tres acciones:


A ctualizar: Se realizan todos los cálculos y cambios necesarios en el estado. Entre estos
cálculos se puede incluir:
• La física aplicada a la transformación de las animaciones que se están
produciendo.
• Comprobación de los toques del usuario en la pantalla del dispositivo móvil y
en los botones hardware para controlar la interacción con el usuario, y en base
a la acción de los controles, poder variar la acción de la animación
• Contar puntuaciones.
• Conectar con la red y recibir paquetes que describan los protocolos diseñados
para que el videojuego funcione en red. Etc.
• Calcular la condición de fin de juego o de paso al siguiente nivel.
R en d er izar: Se dibuja en el canvas todos los nuevos elementos de la escena y se
visualizan en la pantalla del dispositivo. Anteriormente se comentó que el canvas se
vuelca a un bitmap. Ahora bien, este bitmap se puede encapsular dentro de una View,
tal y como lo hace un botón o un Spinner, o se puede encapsular dentro de una
SurfaceView. Si necesitas programar juegos que no tengan mucha dinámica de gráficos,
por ejemplo un juego de tablero como el ajedrez o algún tipo de videojuego que no
necesite mucho procesamiento en términos de velocidad fps (frames per second), puedes
Capítulo 5. Programación de videojuegos 231

utilizar una View (Como hiciste en la unidad 2 con la práctica del encuentra
hipotenochas).
Sin embargo, si necesitas más control sobre el entorno gráfico, necesitas utilizar un
SurfaceView. En ambos casos, la vista se repinta frecuentemente invocando a un método
llamado onDraw que recibe como parámetro el canvas a pintar. La diferencia entre
ambas está en que SurfaceView utiliza su hilo propio para no tener que esperar hasta
que la jerarquía de la vista tenga que repintarse. En lugar de esperar, si estamos
utilizando una SurfaceView , el canvas puede dibujar a su propio ritmo. La clase view
invoca automáticamente a onDraw mientras que SurfaceView no.
Como la clase SurfaceView utiliza su propio hilo para repintar la pantalla cada cierto
tiempo, la velocidad a la que se repinta la SurfaceView la decide el propio hilo a través
de un timer. Si una SurfaceView se repinta para formar una animación 30 veces por
segundo (cada frame tarda 33.33 milisegundos) se dice que la animación se visualiza a
30fps.
Este redibujo en un momento dado del canvas sobre la SurfaceView es lo que produce
la sensación de animación. En cada iteración entre repintado y repintado se puede hacer
todos los cálculos necesarios para que la aplicación genere el siguiente frame.
D orm ir: El bucle del juego itera cada cierto tiempo, cuanto más rápido itere, más
rápida será la animación y más definición tendrá, no obstante, esto también dependerá
de la capacidad de procesador del dispositivo móvil, puesto que si entre iteración e
iteración hay que hacer muchos cálculos y hay muchas cosas que pintar en una escena,
es posible que en algunos dispositivos más lentos nuestro videojuego sufrirá retardos en
la ejecución de ese bucle haciendo que la jugabilidad se reduzca. En dispositivos más
rápidos ocurrirá lo contrario, que los cálculos serán tan rápidos que entre estado y
estado, hay que esperar para que el usuario pueda ver la animación sin que parezca la
típica escena acelerada de una película de Benny Hill. Esta espera, o tiempo en el que el
bucle del juego D uerm e, ha de hacerse sincronizada al reloj del sistema.
El siguiente gráfico muestra tres ciclos del game loop que suceden en 1 segundo (3
FPS). Aunque 3 FPS resultaría en una pobre animación, es suficiente para ilustrar las
acciones que deben suceder en el game loop. Un ratio mínimo de 30 fps está considerado
como aceptable.

3 Frames por segundo

UPDATE RENDER DORMIR UPDATE RENDER DORMIR UPDATE RENDER DORMIR

« 1 Segundo----------------------------------- ►
232 Programación en Android

Para sincronizar la animación con la capacidad de cálculo del procesador, hay que
realizar en cada iteración del bucle del juego un cálculo del tiempo que nos lleva
realizar cada iteración, pudiendo saltar algunos repintados para ahorrar tiempo. Es
mucho más rápido actualizar solo el estado del videojuego que actualizarlo y pintarlo,
por lo que si el procesador no puede mantener el ritmo de fps que le exigimos, podemos
saltar algún proceso de render para no tener que pintar toda la escena y así, ahorrar
tiempo para que el procesador pueda ponerse al día del proceso exigido. Para que la
animación de la escena y la jugabilidad no se vean afectada por este fenómeno, la ratio
de FPS debe ser suficientemente alta y el número de frames por segundo que saltamos
no muy alto. Por ejemplo, 30 FPS y un máximo de 5 Frames no dibujados por segundo.

3 Frames por segundo

; D
0
R
UPDATE RENDER UPDATE DORMIR UPDATE R EN D ER
M

1______ R

Salta la renderización para


Tarda m ás en renderizar la
poder ponerse al día
escen a que el tiempo asignado
■1 Segundo-

C on e ste G am e Loop se co n sig u e u n a an im ación en tiem p o real con


in teracción d el usuario.

A continuación te mostramos con un ejemplo en Android, el esqueleto de una


arquitectura genérica con un game loop controlado para generar un número determinado
de FPS que te puede servir para utilizar en tus videojuegos. El proyecto básico de
esqueleto consiste en tres ficheros:
• A ctiv id a d Ju ego.java: Representa la actividad principal. Esta actividad
tendrá como contenido una instancia del juego. Lo único que cambia con
respecto a la actividad creada por defecto por Android Studio es la línea
setContentView(new Juego(this)) en el método onCreate:
public class ActividadJuego extends ActionBarActivity {

@Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState) ;
setContentView(new Juego(this));
}

©Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_actividad_juego, menu);
return true;
}
Capítulo 5. Programación de videojuegos 233

@Override
public boolean onOptionsItemSelected(Menultem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest .xml.
int id = item.getltemldO;

//noinspection SimplifiablelfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}_____________________________ _____

• Juego.java: Es una subclase de la clase SurfaceView. Incorpora los métodos


de actualizar() para calcular el estado del juego en cada iteración del game
loop y renderizar para pintar cada frame del juego. Cuando la superficie se
crea, se inicia el game loop al que se le pasa como parámetro el objeto
SurfaceHolder, necesario para obtener acceso al canvas de la SurfaceView y
una referencia al objeto Juego para que el bucle pueda invocar a los métodos
actualizar() y renderizar (). Fíjate que estos dos métodos están vacíos para que
puedas implementar aquí tu videojuego.
public class Juego extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder holder;


private BucleJuego bucle;

private static final String TAG = Juego.class.getSimpleName();

public Juego(Context context) {


super(context);
holder = getHolderO;
holder.addCallback(this);
1
©Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
©Override
public void surfaceCreated(SurfaceHolder holder) {
II se crea la superficie, creamos el game loop

// Para interceptar los eventos de la SurfaceView


getHolderO .addCallback(this);

II creamos el game loop


bucle = new BucleJuego(getHolder() , this);

// Hacer la Vista focusable para que pueda capturar eventos


setFocusable(true);

//comenzar el bucle
bucle.start();

}
I★ ★
* Este método actualiza el estado del juego. Contiene la lógica del videojuego
* generando los nuevos estados y dejando listo el sistema para un repintado.
*/
234 Programación en Android

public void actualizar() {


//
}
/★ ★
* Este método dibuja el siguiente paso de la animación correspondiente
*/
public void renderizar(Canvas canvas) {

}
©Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.dtTAG, "Juego destruido!");
// cerrar el thread y esperar que acabe
boolean retry = true;
while (retry) {
try {
bucle.fi n ();//se finaliza el bucle
bucle .join ()
retry = false;
} catch (InterruptedException e) {
//tratar posible fallo al finalizar el bucle

• B u cle Ju ego, java: Es el propio game loop. Es un hilo independiente, por


tanto, esta clase es una subclase de la clase thread. El método run itera de
manera infinita hasta que el juego se acaba.
public class BucleJuego extends Thread {
// Frames por segundo deseados
private final static int MAX_FPS = 30;
// Máximo número de frames saltados
private final static int MAX_FRAMES_SALTADOS = 5;
/ / E l periodo de frames
private final static int TIEMPO_FRAME = 1000 / MAX_FPS;
private Juego juego;
public boolean JuegoEnEjecucion=true;
private static final String TAG = Juego.class .getSimpleName () ,-
private Surf aceHolder surfaceHolder
BucleJuego(SurfaceHolder sh, Juego s){
juego=s;
surfaceHolder=sh;
}
©Override
public void run() {
Canvas canvas;
Log.d(TAG, "Comienza el game loop");
long tiempoComienzo; // Tiempo en el que el ciclo comenzó
long tiempoDiferencia; // Tiempo que duró el ciclo
int tiempoDormir; // Tiempo que el thread debe dormir(<0 si vamos mal de tiempo)
int framesASaltar; // número de frames saltados
tiempoDormir = 0;
while (JuegoEnEjecucion) {
canvas = null;
// bloquear el canvas para que nadie más escriba en el
try {
canvas = this.surfaceHolder.lockC a n v a s O ;
synchronized (surfaceHolder) {
tiempoComienzo = System.currentTimeMillis();
framesASaltar = 0 ; // resetear los frames saltados
// Actualizar estado del juego
ju e g o . a c t u a l i z a r ();
// renderizar la imagen
ju eg o .ren d eriz ar(canvas);
// Calcular cuánto tardó el ciclo
Capítulo 5. Programación de videojuegos 235

tiempoDiferencia = System.currentTimeMillis() - tiempoComienzo;

// ¿cuánto debe dormir el thread antes de la siguiente iteración?


tiempoDormir = (int)(TIEMPO_FRAME - tiempoDiferencia);

if (tiempoDormir > 0 ) {
// si sleepTime > 0 vamos bien de tiempo
try {
// Enviar el thread a dormir
// Algo de batería ahorramos
Thread.sleep(tiempoDormir);
} catch (InterruptedException e) {}
}
while (tiempoDormir < 0 && framesASaltar < MAX_FRAMES_SALTADOS) {
// Vamos mal de tiempo: Necesitamos ponernos al día
juego.actualizar(); // actualizar si rendering
tiempoDormir += TIEMPO_FRAME; // actualizar el tiempo de dormir
framesASaltar++;
}
}
} finally {
// si hay excepción desbloqueamos el canvas
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
L o g .d(TAG, "Nueva iteración!");
}
}
public void fin(){JuegoEnEjecucion=false;}
}

En cada iteración calcula el tiempo que debe dormir, invoca al método actualizar()
del juego y decide si en función del tiempo que tardó la iteración en ejecutarse debe o no
saltar el render. Fíjate que el bucle espera a través de una llamada a la función sleep() y
que antes de dibujar se bloquea el acceso al canvas para que no haya interferencia de
otras aplicaciones.

ACTIVIDAD 5.2.
El código de este esqueleto lo puedes ver en el fichero Esqueleto_juego.rar del m aterial
complementario.

5.6.1. UN EJEMPLO DE BUCLE DE JUEGO


A continuación te mostraremos cómo aplicar ese esqueleto de juego para hacer unos
pequeños dibujos, y formar una animación desplazando esos elementos hacia abajo y
hacia arriba constantemente:
Primero define, dentro de la clase juego, las variables con las coordenadas que quieras
utilizar para tu animación, por ejemplo:

p r iv a te i n t x = 0 ,y = 0 ; //C o o rd en a d a s x e y para d e s p la z a r

//C o o r d e n a d a s i n i c i a l e s de l o s o b j e t o s a d ib u j a r
p r iv a t e s t a t i c f i n a l in t b m p ln icia lx = 5 0 0 ;
236 Programación en Android

p r iv a te sta tic fin al in t b m p ln ic ia ly = 5 0 0 ;


p r iv a te sta tic fin al in t r e c tln ic ia lx = 4 5 0 ;
p r iv a te sta tic fin al in t r e c tln ic ia ly = 4 5 0 ;
p r iv a te sta tic fin al in t a r c o ln ic ia lx = 5 0 ;
p r iv a te sta tic fin al in t a r c o ln ic ia ly = 2 0 ;
p r iv a te sta tic fin al in t te x to ln ic ia lx = 5 0 ;
p r iv a te sta tic fin al in t te x to ln ic ia ly = 2 0 ;

/ / C o o r d e n a d a s m á x im a s d e l a p a n t a l l a
p r i v a t e i n t maxX=0;
p r i v a t e i n t maxY=0;

//C o n t a d o r de fram es
p r iv a t e in t con tad orF ram es= 0;

// in d i c a d o r de d ir e c c ió n
p r iv a t e b o o lea n h a c ia _ a b a jo = tr u e ;

Después, modifica el constructor del juego para calcular las coordenadas máximas y
mínimas:
p u b lic J u eg o (A c tiv ity co n tex t) {
su p er(co n tex t);
h o ld e r = g e tH o ld e r O ;
h o ld e r .a d d C a llb a c k (th is);
D i s p l a y m d isp = c o n t e x t . getW in d ow M an ager( ) . g e t D e f a u l t D i s p l a y ( ) ;
P o i n t m d i s p S i z e = n ew P o i n t ( ) ;
m d isp . g e t S iz e ( m d is p S iz e ) ;
maxX = m d i s p S i z e . x ;
maxY = m d i s p S i z e . y ;
} ___________________ ___________________________________________________________

A continuación programa el método actualizar. Este método incrementará en cada


iteración las coordenadas x e y para ir dando la sensación de animación. Si la
coordenada llega al límite, cambiará de dirección.

p u b l i c v o i d a c t u a l i z a r () {
i f (x>maxX)
h a c ia _ a b a jo = fa lse;

i f (x== 0)
h a cia _ a b a j o = t r u e ;

i f (h a cia _ a b a jo ) {
x = x + 1;
y = y + 1;
}
e ls e {
X = X - 1 ;

y = y - i;
}
contadorF ram es+ + ;
}________ _______

Finalmente programa el método renderizar(), sumando las coordenadas x e y a las


coordenadas iniciales.
Capítulo 5. Programación de videojuegos 237

p u b l i c v o i d r e n d e r i z a r (C anvas c a n v a s ) {
i f ( c a n v a s != n u ll) {
P a i n t m y P a i n t = new P a i n t ( ) ;
m y P a i n t . s e t S t y l e ( P a i n t . S t y l e . STROKE);

//T o d a e l c a n v a s en r o j o
c a n v a s . d r a w C o l o r ( C o l o r . R E D );

/ / D i b u j a r muñeco de a n d r o id
bmp = B i t m a p F a c t o r y . d e c o d e R e s o u r c e ( g e t R e s o u r c e s ( ) ,
R .d r a w a b le . i c _ l a u n c h e r ) ;
c a n v a s.d ra w B itm a p (b m p , b m p l n i c i a l x + x , b m p l n i c i a l y + y , n u ll);

/ / C a m b i a r c o l o r y ta m a ñ o d e b r o c h a
m y P a in t. s e tS tr o k e W id th (1 0 );
m y P a i n t . s e t C o l o r ( C o l o r . BLUE) ;

/ / d i b u j a r r e c tá n g u lo de 300x300
ca n v a s. d r a w R e c t(r e c tln ic ia lx + x , r e c t ln ic ia ly + y , 300, 300, m y P a in t);

//d ib u j a r ó v a lo y arco
R e c t F r e c t F = n ew R e c t F ( a r c o l n i c i a l x + x , a r c o l n i c i a l y + y , 200, 120);
c a n v a s . d ra w O v a l(rectF , m y P a in t);
m y P a i n t . s e t C o l o r ( C o l o r . BLACK);
c a n v a s . d r a w A r c (r e c tF , 9 0 , 4 5 , t r u e , m y P a in t);

/ / d i b u j a r un t e x t o
m y P a in t. s e t S t y l e ( P a i n t . S t y l e . F IL L );
m y P a in t. s e t T e x t S i z e (4 0 );
c a n v a s . d r a w T e x t ( " F ram es e j e c u t a d o s : " + c o n t a d o r F r a m e s ,
t e x t o l n i c i a l x , t e x t o l n i c i a l y + y , m y P a in t);

}
}________________

Por ultimo, puedes jugar con el parámetro del game loop M AX_FPS para ver cómo
de rápida se ejecuta la animación, por ejemplo, puedes cambiarlo a 60:

p r iv a te fin a l sta tic in t MAX_FPS = 6 0 ;

ACTIVIDAD 5.3.
Puedes ver el código de ejemplo en DemoCanvas.rar del material complementario.

5.7. LA IN T E R A C C IÓ N CO N EL JU G A D O R
5.7.1. LOS EVENTOS TOUCH
Cuando se trabaja con una SurfaceView el evento de usuario más importante que se
puede capturar es el evento Touch (OnTouchEvent), o evento de tocar uno o varios
puntos en la pantalla con los dedos. Actualmente, los dispositivos móviles permiten
pulsar con varios dedos en la pantalla, es decir, hacer múltiples Touch en la pantalla. El
sistema que Android utiliza para procesar los eventos de toque se llama Touch
238 Programación en Android

Framework y es bastante complejo. Supon, por ejemplo, que tienes en tu actividad


varias layouts y vistas anidadas, ¿Cuál de los layouts o de las vistas procesa el evento
Touch? La respuesta es que todos, algunos o ninguno, depende. Esto se decide mediante
el ciclo de Touch, que consiste en sucesivas llamadas a tres métodos:
• dispatchTouchEvent
• onlnterceptTouchEvent
• onTouchEvent
El método dispathTouchEvent de la actividad recorre todos los elementos gráficos
hijos de la actividad dando la oportunidad de procesar el evento onTouch. Cada uno de
los hijos de la actividad recibe una llamada a dispatchTouchEvent de manera que pueda
interceptarlo. El método onlnterceptTouchEvent decide si un hijo particular va a
procesar el método, y si es así, se invocará al método de callback onTouchEvent.
En una SurfaceView, con cada toque de la pantalla, Android envía un MotionEvent a
través del método de Callback onTouch, es decir, un objeto que define las características
de la acción del toque, por ejemplo, las coordenadas de donde se produjo y el tipo de
acción que se efectuó, que pueden ser las siguientes.
• ACTION_DOWN: se efectuó la presión sobre la pantalla.
• ACTION__MOVE: El dedo se movió después de hacer un ACTION_DOWN
sin levantarlo.
• ACTION_UP: se levantó el dedo de la pantalla.
• ACTION_CANCEL: Se abortó el gesto actual.
• Otras, necesarias para detectar más de una pulsación a la vez. Se verán más
adelante.
De esta manera se definen los gestos (en inglés, gestures): Se define como gesto la
acción que comienza como un ACTION_DOWN y termina con un ACTION_CANCEL.
Un MotionEvent, además del código de acción, contiene valores como la posición y
las propiedades de los movimientos, como por ejemplo, el momento en el que sucedió,
origen, ubicación en coordenadas o presión.
Para implementar un evento onTouch, por ejemplo, para dibujar un círculo cuando
pulse con el dedo la pantalla, debes seguir los siguientes pasos.
1. Añade a tu clase la implementación del listener OnTouchListener de la clase
SurfaceView:
p u b lic c la ss J u ego e x te n d s S u rfa ceV iew im p lem en ts S u r f a c e H o l d e r . C a llb a c k ,
S u r fa c e V ie w .O n T o u c h L iste n e r
2. Crea variables para almacenar las coordenadas de donde se produjo el toque y
una variable para detectar cuándo se ha levantado el dedo de la pantalla.
/♦ C o o r d e n a d a s d e l t o u c h * /
i n t to u ch X , touchY ;
Capítulo 5. Programación de videojuegos 239

b o o lea n h ay T o q u e= fa lse;

3. En el constructor de la clase, registra el listener:


/ / l i s t e n e r p a r a onTouch
se tO n T o u c h L iste n e r (th is);

en renderizar:
4. Programa en el método renderizarQ, Si ha ocurrido un toque en la pantalla
"Touch”, dibuja un círculo.
i f (h a y T o q u e){
c a n v a s . d ra w C ir c le (to u c h X , touchY , 20, m y P a in t);
}

5. Programa el método de callback onTouch, que dectecta cuándo se pulsa con el


dedo en la pantalla (ACTION_DOWN) y cuándo se levanta (ACTION UP). Se
actualiza siempre el valor de las coordenadas para que cuando se produzcan otras
acciones, por ejemplo, ACTION_MOVE, se refleje el efecto del movimiento en
los sucesivos redibujos del círculo.

@ O verride
p u b l i c b o o l e a n o n T o u c h ( V ie w v , M otion E ven t e v e n t ) {

s w i t c h ( e v e n t . g e t A c t i o n M a s k e d () ) {
c a s e M o t i o n E v e n t . ACTION_DOWN:
h a y T o q u e =t r u e ;
break;
c a s e M o ti o n E v e n t .A C T I O N _ U P :
h a y T o q u e= fa lse;
break;
}
tou ch X = (i n t ) e v e n t .g e t X ();
to u c h Y = (i n t ) e v e n t . g e t Y ( ) ;

retu rn tru e;
}

Observa que los métodos getX() y getYQ retornan floats, según la documentación de
Android por si la App se está ejecutando en un dispositivo de precisión sub-pixel, por
tanto, hay que convertirlo a entero.

ACTIVIDAD 5.4.
Puedes ver el código de ejemplo en Dem oCanvasConTouch.rar del m aterial complementario.
240 Programación en Android

5.7.2. LOS EVENTOS MULTITOUCH

Para detectar varios touch al mismo tiempo, con diferentes


dedos en distintas partes de la pantalla, hay que definir el
concepto de pointer. Un pointer es un objeto que representa la
pulsación de un dedo en una parte de la pantalla. Está
identificado por cuatro parámetros (Index, Id, x, y) donde x e
y son las coordenadas donde se pulsó, id el identificador del
pointer e index es el orden en el que se pulsó la pantalla. Las
acciones de pulsaciones se guardan en un array e index, es la
posición del array donde se guardó la acción. Ten en cuenta
que los dedos se pueden levantar (ACTION_UP) en un orden
distinto al que se pulsaron (ACTION_DOWN).
Cuando ocurren varios toques a la vez en la pantalla, ocurre
lo siguiente:
El primer dedo en la pantalla provoca un evento onTouch con la acción
ACTION_DOWN.
Los dedos sucesivos en la pantalla provocan un evento onTouch por cada nuevo dedo
con la acción ACTION_POINTER_DOW N.
Si uno de los dedos se mueve provoca un evento onTouch con la acción
ACTION_MOVE.
Cuando uno de los dedos que no sean el primero se levanta, se produce un evento
ACTIO N _PO INTER_U P
Cuando el último dedo se levanta, se produce el evento ACTION_UP.
Para que puedas procesar todos los toques de la pantalla puedes utilizar los siguientes
consejos:
Se puede contar el número de toques en pantalla con la función getPointerCount()
del objeto MotionEvent que se pasa como parámetro al método onTouch.
Se puede obtener el índice del dedo que ha pulsado la pantalla con el código:

index = MotionEventCompat.getActionlndex(event);

Se puede obtener las coordenadas del toque mediante el código:


x = (int) MotionEventCompat.getX(event, index);
y = (int) MotionEventCompat.getY(event, index);

Puedes probar el funcionamiento de los toques múltiples en pantalla modificando la


aplicación DemoCanvas de la siguiente forma: Crea una clase llamada Toque que
almacene las coordenadas de cada toque y el índice del pointer.
Capítulo 5. Programación de videojuegos 241

p u b l i c c l a s s Toque {
p u b lic in t x; //co o rd en a d a x d e l to q u e
p u b lic in t y; //co o rd en a d a y
p u b lic i n t in d ex ; / / i n d i c e d el p o in te r
T o q u e ( i n t m l n d e x , i n t mX, i n t m Y ){
in d e x = m ln d e x ;
x=mX; y=mY;
}

Crea una lista (ArrayList) de toques, para almacenar todos los toques que se
produzcan:
/ * A rray de Touch * /
p r i v a t e A r r a y L i s t < T o q u e > t o q u e s = new
A rra y L ist< T o q u e> ();
f S í 3 a « T . I U 21:00
En el método renderizar, dibuja un círculo y el
texto con el índice del pointer por cada elemento DemoCanvas ■
de la lista de toques:

i f (h a y T o q u e){
sy n ch ron ized (t h is ) {
f o r (T o q u e t : t o q u e s ) {
c a n v a s. d r a w C ir c le (t.x , t .y ,
100, m y P a in t);
c a n v a s . d r a w T e x t(t. in d ex +
t . x , t . y , m y P a in t2 );
}
}
}

Reprograma el método onTouch de manera,


que cada vez que se produzca un nuevo toque se
agregue a la lista el nuevo pointer generado.
Cuando se levanta uno de los dedos, se elimina de la lista justo el pointer del dedo que
se levantó y cuando se levante el último dedo, se desactive el dibujo de los pointers en el
renderizado:

s w it c h ( e v e n t . g etA ctio n M a sk ed () ){
c a s e M o t i o n E v e n t . ACTION_DOWN:
c a s e M o t i o n E v e n t . ACTI0N_P0INTER_D0WN:
hayT oq u e= tru e;
x = ( i n t ) M otionE ventC om pat. g e t X ( e v e n t , in d e x ) ;
y = ( i n t ) M otionE ventC om pat. g e t Y ( e v e n t , in d e x ) ;
sy n ch ro n ized (th is) {
t o q u e s . a d d ( i n d e x , new T o q u e ( i n d e x , x , y ) ) ;
}
L o g . i ( J u e g o . c l a s s . g e t S i m p l e N a m e () , " P u l s a d o d e d o "+ i n d e x + " . " ) ;
break;

c a s e M o t i o n E v e n t . ACTION_POINTER_UP:
sy n ch ro n ized (th is) {
t o q u e s . rem o v e(in d e x ) ;
}
L o g . i ( J u e g o . c l a s s . g etS im p leN a m e( ) , " S o lta d o dedo " + in d ex + " ." );
242 Programación en Android

break;

c a s e M o t i o n E v e n t . ACTION_UP:
sy n ch ro n ized (th is) {
to q u e s . rem ove(in d e x ) ;
}
L o g . i ( J u e g o . c l a s s . g etS im p leN a m e( ) , " S o lt a d o dedo "+ i n d e x + " . u l t i m o . " ) ;
h a y T o q u e= fa lse;
break;
}

Fíjate que hay que proteger el acceso a la lista con bloques synchorized para crear
secciones críticas y proteger la lista de toques de accesos concurrentes.

ACTIVIDAD 5.5.
Puedes ver el código de ejemplo en D em oCanvasConM ultitouch.rar del m aterial complementario.

5.7.3. LOS GESTOS


Android tiene un sistema de APIs que ayudan al programador a crear y detectar
gestos que el usuario realiza en una aplicación a partir de los toques que se producen en
la pantalla. Estos gestos se basan en los MotionEvent que se disparan a través de los
Eventos Touch y se capturan con la clase GestureDetector, capaz de detectar gestos y
eventos a través de eventos MotionEvent.
Esta clase GestureDetector nos proporciona una interfaz llamada OnGestureListener
para detectar los siguientes gestos:
• onDown (MotionEvent e): Callback para notificar que se ha tocado la pantalla
con un dedo.
• onSingleTapUp(MotionEvent e): Callback para notificar que se ha producido
un golpecito (tap) y se ha levantado el dedo.
• onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocity):
Callback para detectar que el usuario ha realizado un gesto de deslizar,
obteniendo la velocidad del gesto en pixels por segundo.
• onLongPress(MotionEvent e): Detecta un toque de larga duración a modo de
“Long Click”.
• onScroll(MotionEvent el, MotionEvent e2, float distanceX, float distanceY):
Detecta un gesto de tipo “Scroll” para desplazarse por la pantalla en horizontal
o en vertical.
• onShowPress(MotionEvent e): Detecta que el usuario ha realizado un toque
presionando la pantalla y no ha levantado el dedo.
Además, puedes implementar la interfaz OnDoubleTapListener para escuchar,
además:
Capítulo 5. Programación de videojuegos 243

• OnDoubleTap(MotionEvent e): Se llama cuando se produce el primer toque


(tap) de un doble tap.
• OnDoubleTapEvent(MotionEvent e): Notifica un doble toque (tap)
• OnSingleTapConfirmed(MotionEvent e): Se invoca cuando el sistema está
seguro de que el gesto que se ha producido es un tap simple y no uno doble.
La suma de estos eventos se recopila también en la interfaz
SimpleOnGestureListener.
Para crear gestos, se debe programar un evento OnTouchEvent y delegar el
procesamiento del MotionEvent al detector de gestos “GestureDetector”.
Examina el siguiente código, verás que el evento onTouchEvent invoca al
onTouchEvent del detector de gestos.

p u b l i c b o o le a n on T ou ch E ven t(M otion E ven t e v e n t ) {


/ / c a p t u r a co n d e t e c t o r de g e s t o s
r e tu r n d e te c to r G e sto s.o n T o u c h E v e n t( e v e n t ) ;
}

El detector de gestos puede ser una clase que extienda de


GestureDetector.SimpleOnGestureListener como la siguiente:

p r iv a t e c l a s s C o n tr o la d o r G e sto s e x te n d s
G e s tu r e D e te c to r . S im p le O n G e stu r e L iste n e r {

@ 0 v errid e
p u b lic v o id on S h ow P ress(M otion E ven t e) {
L o g . i (TAG, " e v e n t o o n S h o w P r e s s ! ! " ) ;
}
@ 0 v errid e
p u b l i c v o i d o n L o n g P ress(M o tio n E v en t e) {
L o g . i (TAG, " e v e n t o o n L o n g P r e s s ! ! " ) ;
}
@ 0 v errid e
p u b lic b o o le a n o n S c r o ll(M o tio n E v e n t e l , M otion E ven t e 2 ,
f lo a t d ista n c e , f lo a t d ista n c e Y ){
L o g .i(T A G , " e v e n to o n S c r o l l ! ! " ) ;
retu rn f a ls e ;
}
@ O verride
p u b l i c b o o l e a n on D o w n (M o tio n E v en t e) {
L o g .i(T A G , " e v e n to on D ow n !! " ) ;
retu rn tru e;
}
@ O verride
p u b l i c b o o le a n o n S in g le T a p U p (M o tio n E v e n t e) {
L o g .i(T A G , " e v e n to o n S i n g l e T a p U p ! ! " ) ;
retu rn tru e;
}
244 Programación en Android

© O v errid e
p u b lic b o o lea n o n F lin g (M o tio n E v en t e l , M otion E ven t e 2 , f lo a t v e lo c ity X ,
f l o a t v elo city Y ) {
L o g .i(T A G , " e v e n t o o n F l i n g ! ! " ) ;
retu rn f a ls e ;
}
}

ACTIVIDAD 5.6.
Puedes ver el código de este ejemplo en G estos.rar del m aterial complementario.

5.8. CREACIÓ N DE U N V ID EO JU EG O
SENCILLO: THE X IN D I IN V A SIO N
En esta sección programaremos un pequeño y sencillo videojuego, “The Xindi
Invasion”. Los Xindi son una raza alienígena muy cruel que ha invadido nuestro sistema
solar y quiere destruirnos. Nosotros, superhéroes protagonistas del videojuego deberemos
con una sola nave, aniquilar a todos los enemigos que amenazan nuestra existencia.
El material gráfico del videojuego (sprites e imágenes), lo podemos obtener
libremente de h ttp://ope ngam eart.org/content/space-gam e-starter-set.

5.8.1. DISEÑANDO EL VIDEOJUEGO


Lo primero que debes hacer es pensar muy bien en qué va a consistir tu videojuego.
Por ejemplo, “The xindi invasión” será un Arcade clásico en el que una nave se desliza a
través de un escenario disparando a todos los que salen a su paso.
A continuación debes decidir la estructura de pantallas que va a tener el videojuego.
Por ejemplo, primero un menú principal de bienvenida donde el usuario pueda elegir las
opciones, personajes, niveles, etc. y después las pantallas subyacentes de haber
seleccionado estas acciones. Es decir, debes realizar un mapa de pantallas con todas las
transiciones posibles que vaya a hacer el usuario.
Puedes aprovechar el editor de navegación para hacer las transiciones entre las
diversas pantallas de tu videojuego:
1 2 5 3 VCS Window Help
• Selecciona el menú Tools->Android- Iasks & Contexts ►

> Navigation Editor Generate lava Doc...


Save Project as Template...
Manage Project Templates...
• En el editor de navegación, pulsa botón Q Groovy Console...

derecho del ratón y selecciona “New Navigation Editor


ps Open Terminal... fa| Memory Monitor
Activity” £ Sync Project with Gradle Files
- Android Device Monitor

• A continuación elige un tipo de actividad. ?, AVD Manager


O SDK Manager
V Enable ADB Integration
• Finalmente, enlaza la nueva actividad
arrastrando desde uno de los botones de tu
actividad principal hasta la nueva actividad pulsando la tecla de “Control".
Capítulo 5. Programación de videojuegos 245

En el caso de "The Xindi Invasion" serán dos pantallas, una principal con un botón
de “Comenzar’’ y otra pantalla con una SurfaceView para poder pintar la animación en
tiempo real con el transcurso de la acción.

5.8.2. EL MODO DE PANTALLA


Lo siguiente que debes decidir es en qué espacio de pantalla se va a mostrar el
videojuego. Deberás, además, responder a las siguientes preguntas:
• ¿Mi videojuego se va a poder jugar solo con orientación de pantalla en modo
landscape (horizontal) o con orientación portrait (vertical)? Si la respuesta es
que sí, deberás indicar en el fichero manifiesto que la actividad tiene una
android:screenOrientation= "portrait11 o android:screenOrientation=Tandscape"
• ¿Se puede jugar en dos las orientaciones? Si la respuesta es que sí, deberás
programar el método de callback onConfigurationChanged() de la actividad
con código para guardar el estado del juego y ejecutar un nuevo cálculo de las
coordenadas de pantalla para poder proseguir con la ejecución normal de tu
videojuego. Para poder programar este método, debes añadir al fichero de
manifiesto la opción android:configChanges=”orientation”.
• ¿Mi videojuego se ejecutará en pantalla completa? Si es que sí, deberás crear
una Activity de pantalla completa “Full Screen”. También puedes crear una
actividad en blanco “Blank Activity” y añadir al método onCreate de la
activity una llamada a hideSystemlU:
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
s u p e r .o n C r e a te (sa v e d ln sta n c e S ta te );
j=new J u e g o (t h i s ) ;
h id e S y ste m U I();
s e t C o n t e n t V i e w (j );
}
246 Programación en Android

El método HideSystemlU es un snippet que nos proporciona Google para esconder


todos los elementos de la interfaz de usuario obteniendo todo el espacio de nuestra
pantalla para nuestra SurfaceView:

// T h is s n ip p e t h id e s th e sy s te m b a r s ,
p r i v a t e v o i d h i d e S y s t e m U I () {
/ / S e t t h e IMMERSIVE f l a g .
/ / S e t th e c o n te n t t o appear under th e sy stem b a rs so th a t th e c o n te n t
/ / d o e s n ' t r e s i z e w h en t h e s y s t e m b a r s h i d e a n d s h o w ,
j . se tS y ste m U iV isib ility (
V i e w . SYSTEM_UI_FLAG_LAYOUT_STABLE
| V i e w . SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| V i e w . SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| V i e w . SYSTEM_UI_FLAG_HIDE_NAVIGATION / / h i d e n a v b a r
| V i e w . SYSTEM_UI_FLAG_FULLSCREEN / / h i d e s t a t u s b a r
| View.SYSTEM_UI_FLAG_IMMERSIVE);

/ / c u a n d o s e p r e s i o n a v o lu m e n , p o r e j e m p lo , s e cam b ia l a v i s i b i l i d a d ,
/ / h ay que v o l v e r a o c u l t a r
j . se tO n S y ste m U iV isib ility C h a n g e L iste n e r (n e w
V i e w . O n S y s t e m U i V i s i b i l i t y C h a n g e L i s t e n e r () {
@ O verride
p u b lic v o id o n S y stem U iV isib ility C h a n g e(in t v i s i b i l i t y ) {
h id eS y stem U I() ;
}
}>;
}

Este método funciona únicamente a partir de la API Level 19, por lo que para que
funcione en otras versiones hay que hacer operaciones extras que consisten en incluir las
siguientes líneas en el método hideSystemUI:

if ( B u i l d . VERSION.SDK_INT < B u i l d . VERSI0N_C0DES. JELLY_BEAN) {


/ / P r e - J e l l y B ean , h a y que o c u l t a r m an u alm en te l a a c t i o n b a r
g etA ctio n B a r( ) . h id e ();
}
/ / P a r a v e r s i o n s a n t e r i o r e s a K itK at
getW in d ow O . s e t F l a g s (
W in d o w M a n a g e r . L a y o u t P a r a m s . FLAG_FULLSCREEN,
W in d o w M a n a g e r . L a y o u t P a r a m s . FLAG_FULLSCREEN);

Precisamente aquí radica la dificultad del modo de pantalla completa, la guerra que
hay que librar con las diferentes versiones de Android para que en todas funcione sin
problemas.

5.8.3. AJUSTANDO TU VIDEOJUEGO AL TAMAÑO DE LA


PANTALLA
Uno de los problemas que encontrarás es cómo ajustar tus sprites a un tamaño de
pantalla variable. Es decir, no es lo mismo ejecutar un videojuego en un dispositivo con
coordenadas de pantalla 1900x1080 que en uno de 600x480.
Capítulo 5. Programación de videojuegos 247

Por tanto, tienes que proporcionar gráficos para todos los tamaños de pantalla,
android elegirá la imagen apropiada dependiendo del tamaño de pantalla (small, normal,
large, x-large) y de la densidad (ldpi, mdpi, xhdpi, xxhdpi, xxxhdpi) tal y como se
comentó en la Sección 2.2.8.
Los objetos deberán aparecer con una proporción adecuada al tamaño de pantalla,
por ejemplo, imagina la nave de “The Xindi Invasion” con un tamaño de 50x50 píxeles.
Quizá este tamaño sea adecuado para una pantalla de 1024x600 pero si no lo escalamos,
será muy pequeño para una pantalla de 1900x1080 y muy grande para otras de menor
tamaño.

#1
I 8 i A
1 i i ^ | | Ü , A
i li8r.i
i • * • * « • *

xxh dp i xhdpi hdpi m dpi


100x100 75x75 50x50 25x25

Los sprites elegidos para la nave de “The Xindi Invasion” son los que ves a la
derecha, con su correspondiente resolución y densidad de pantalla.
Puedes leer más sobre el soporte de múltiples pantallas en la documentación de
Android “Supporting Multiple Screens”, dentro de las API Guides:
http://developer.android.com /guide/practices/screens support.htm l

A
248 Programación en Android

No obstante, no puedes predecir el alto y ancho de pantalla que encontrará tu juego


cuando sea ejecutado. Así que te proponemos el siguiente Snippet para calcular el alto y
ancho de pantalla y así poder escalar imágenes al tamaño adecuado a la pantalla. (>API
Level 13)

p u b lic in t A lto P a n ta lla ;


p u b lic i n t A n ch o P a n ta lla ;
p u b lic v o id C a lc u la T a m a ñ o P a n ta lla (){
D i s p l a y d i s p l a y = a c t i v i d a d . getW in d o w M a n a g er( ) . g e t D e f a u l t D i s p l a y ( ) ;
P o i n t s i z e = new P o i n t ( ) ;
d isp la y .g e tS iz e (siz e );
A n ch o P a n ta lla = s i z e . x ;
A lto P a n ta lla = s i z e . y ;
}

Una vez calculado el ancho y alto, puedes escalar una imagen de la siguiente forma:

im a g en = B i t m a p F a c t o r y . d e c o d e R e s o u r c e ( g e t R e s o u r c e s ( ) , R . d r a w a b l e . im agen) ;
i m a g e n _ e s c a la d a = im a g e n . c r e a t e S c a l e d B i t m a p ( im a g en , A n c h o P a n t a lla ,
A lto P a n ta lla , t r u e ) ;

En “The Xindi Invasion" te mostramos las dos alternativas, por un lado, el sprite con
la nave se escala automáticamente proporcionando varios sprites y el fondo, que se
escala al tamaño de la pantalla.
Repasa la Sección 4.5 para conseguir imágenes escaladas con un factor de escala
determinado.

5.8.4. EL ESCENARIO
En videojuegos de tipo arcade, es muy común que en lugar de moverse el objeto
principal del videojuego (en nuestro caso la nave protagonista), se mueva el fondo. De
esta manera se crea la sensación de movimiento de forma que el centro de la acción es el
propio protagonista formando un escenario dinámico.
En nuestro videojuego “The Xindi Invasion"’, optaremos por esta opción. La nave se
desplazará avanzando en vertical a lo largo de un escenario “infinito” matando cuantos
enemigos salgan al paso. Para crear esta sensación de infinitud, lo que se moverá será el
fondo y la nave permanecerá inmóvil. Dibujaremos una serie de imágenes una detrás de
otra, que compondrán el circuito que la nave deba recorrer. Cuando el circuito se acabe,
es decir, se haya pintado la última imagen volverá a aparecer la primera.
Capítulo 5. Programación de videojuegos 249

En cada momento, se pintarán dos imágenes. La primera imagen del fondo se


posicionará inicialmente en la coordenada 0,0 de la pantalla (esquina superior izquierda)
y en cada iteración del bucle del videojuego se incrementará en 1 la coordenada y. A
esta imagen la llamaremos img_actual, que irá seguida de img_siguiente, cuyas
coordenadas iniciales serán -A ltoPantalla, para que vaya apareciendo poco a poco.
Según vaya desapareciendo img_actual iremos pintando la img_siguiente para que la
sensación de scroll sea constante. Cuando la segunda imagen se encuentre totalmente
pintada y haya desparecido la primera por completo, incluiremos la tercera imagen. En
ese momento, la imagen que acaba de desaparecer se convierte en img_actual y la
tercera imagen pasa a ser img_siguiente. Así sucesivamente hasta que se pinte la última
imagen. Cuando se esté pintando la última imagen, se volverá a pintar la primera.
Para implementar este mecanismo vamos a seguir los siguientes pasos:
1Q, Declara las variables necesarias: Definiremos el número de imágenes que
compondrán el escenario (MAX_IMAGENES_FONDO), un array de enteros para
guardar los identificadores de los dibuj ables (drawables) que compondrán el escenario y
un array de bitmaps para guardarlos. Además serán necesarios dos contadores para
incrementar la posición de las dos imágenes que se están pintando (yImgActual=0,
yImgSiguiente=-AltoPantalla). Finalmente, se requieren dos índices que apunten en
cada momento a las dos imágenes del array que se están pintando (img_actual e
img_siguiente):

p r i v a t e s t a t i c f i n a l i n t MAX_IMAGENES_FONDO= 6; / / i m á g e n e s d e l e s c e n a r i o
Bitmap i m á g e n e s [ ] =new B itm a p [MAX_IMAGENES_FONDO]; / / Arrays de im ágenes

/ * Array de r e c u r s o s que componen e l e s c e n a r i o * /


i n t r e c u r s o s _ i m a g e n e s [ ] = { R . d r a w a b l e . b g l , R . d r a w a b le .b g 2 , R. d r a w a b le .b g 3 ,
R. d r a w a b le . b g 4 , R. draw able . b g 5 , R. draw able .bg6}
i n t y lm g A c t u a l, y l m g S i g u i e n t e ; / / c o o r d e n a d a s y d e l fondo a c t u a l y d e l s i g u i e n t e

/ * í n d i c e s d e l a r r a y de im ágenes para a l t e r n a r e l fondo * /


250 Programación en Android

i n t i m g _ a c t u a l= 0 , i m g _ s i g u i e n t e = l ;

2Q. Carga las imágenes a escala en el array: Con un bucle, carga los recursos en un
bitmap y escálalos:

p u b l i c v o i d CargaBackground(){
//c a r g a m o s t o d o s l o s fo n d o s en un a rra y
f o r (in t i= 0 ;i< 6 ;i+ + ) {
fondo = B itm a p F a c t o r y . d e c o d e R e s o u r c e ( g e t R e s o u r c e s ( ) ,
r e c u r s o s _ i m a g e n e s [ i ] );
im á g e n e s [ i ] = f o n d o . c r e a t e S c a l e d B i t m a p ( f o n d o , A n c h o P a n t a lla ,
A lto P a n ta lla , t r u e ) ;
f o n d o . r e c y c l e () ,- / / r e c i c l a l a imagen a u x i l i a r para a h o r r a r memoria
1
1

3Q Programa el cambio de estado en cada iteración del game loop: Para esto, crea un
método llamado a c t u a l i z a _ f ondo () que sea invocado desde la función actualizar del
bucle del videojuego. Este método incrementará de uno en uno la imagen actual y la
siguiente, y cuando la imagen actual vaya a desparecer (yImgActual>AltoPantalla), que
actualice las variables para que aparezca la nueva imagen:
p u b l i c v o i d a c t u a l i z a _ f o n d o (){
/ / n u e v a p o s i c i ó n d e l fondo
y Im gA ctu al++;
y I m g S ig u ie n t e + + ;

/ * S i l a imagen de fondo a c t u a l ya ha b a jad o c o m p leta m en te* /


i f (y Im gA ctu al> A ltoP an talla){
/ / S e a c t u a l i z a l a imagen a c t u a l a l a s i g u i e n t e d e l a r r a y de im ágenes
i f (img_ac t u a 1==MAX_IMAGENES_FONDO-1)
i m g _ a c t u a l= 0 ;
e lse
i m g _ a c t u a l+ + ;

/ / S e a c t u a l i z a l a imagen s i g u i e n t e
i f (img_s i g u i e n t e ==MAX_IMAGENES_FONDO-1)
im g _ sig u ie n te = 0 ;
e lse
im g _ sig u ien te+ + ;

/ / N u e v a s co ord en a d a s
y Im g A ctu a l= 0 ;
y Im g S ig u ien te= -A lto P a n ta lla ;
1
1

4Q Renderiza el fondo: En la función renderizar dibuja la imagen actual y la imagen


siguiente en las coordenadas que se acaban de actualizar.
c a n v a s . d r a w B i t m a p ( im á g e n e s [ im g _ a c t u a l] , 0 , y I m g A c t u a l , n u l l ) ;
c a n v a s.d ra w B itm a p (im á g en es[im g _ sig u ien te], 0 ,y I m g S ig u ie n t e ,n u ll ) ;

5.8.5. LA NAVE DEL JUGADOR


La nave quedará fija siempre en la misma coordenada Y y solo se moverá en
coordenada X con los gestos del dedo. Para establecer la posición Y fija de la nave,
podemos pensar en visualizar a 4/5 del total de la pantalla. Una posición clásica a la vez
Capítulo 5. Programación de videojuegos 251

que jugable. La coordenada X inicial será justo la mitad del ancho de la pantalla,
calculada con la fórmula AnchoPantalla/2-nave.getW idth()/2:

x N a v e = A n c h o P a n ta lla /2 -n a v e .g e tW id th ()/2 ; / / p o s i c i ó n i n i c i a l d e l a Nave


y N a v e= A lto P a n ta lla /5 * 4 ; / / p o s ic ió n f i j a a 4 /5 de a l t o y l a m ita d de ancho

//d ib u j a la nave
c a n v a s . d r a w B i t m a p (n a v e , x N a v e , y N a v e , n u i l ) ;

5.8.6. LOS CONTROLES


Nuestro videojuego contará con tres botones de control muy básicos. Una flecha
izquierda para mover la nave hacia la izquierda (restando píxeles a la coordenada X de
la nave), una flecha para mover la nave hacia la derecha (sumando píxeles) y otro botón
para disparar.
Para programar los controles hemos creado la clase “Control”. Un objeto control
tiene las siguientes responsabilidades:
Sabe cargar su imagen
Sabe pintarse en un canvas (incluso de forma transparente)
Sabe si lo han pulsado o lo han soltado.
Sabe su ancho y su alto.

d isparo
flecha flecha
izquierda d e re c h a

Examina la clase control para ver cómo están programados estos comportamientos:

p u b lic c l a s s C on trol {
/ / i n d i c a s i e l c o n t r o l e s t á p u ls a d o o no
p u b lic b o o lea n p u ls a d o = fa ls e ;
252 Programación en Android

//c o o r d e n a d a s donde s e d ib u j a e l c o n t r o l
p u b lic in t coordenada_x, coordenada_y;
p r i v a t e B itm ap im a g en ; / / i m a g e n d e l c o n t r o l
p r i v a t e C o n te x t m C on texto;
p u b li c S t r i n g nom bre;

//c o n str u c to r
p u b lic C o n tr o l(C o n te x t c , in t x, in t y ) {
coordenada_x=x;
coordenada_y=y;
m C on texto= c;
}
/ / c a r g a s u im a g e n
p u b lic v o id C argar(in t r e c u r s o ){
im a g en = B itm a p F a c to r y .d e c o d e R e so u r c e (m C o n te x to .g e tR e so u r c e s( ) , recu rso );
}
/ / s e d i b u j a e n un c a n v a s c o n un p i n c e l ( i n c l u s o t r a n s p a r e n t e )
p u b l i c v o i d D i b u j a r (C anvas c , P a i n t p ) {
c . d ra w B itm a p (im a g e n ,c o o r d e n a d a _ x ,c o o r d e n a d a _ y ,p );
}
//c o m p r u e b a s i s e ha p u ls a d o
p u b lic v o id com p ru eb a _ p u lsa d o (in t x , i n t y ) {
i f ( x > c o o r d e n a d a _ x && x < c o o r d e n a d a _ x + A n c h o () &&
y > c o o r d e n a d a _ y && y < c o o r d e n a d a _ y + A l t o ( ) ) {
p u lsa d o = tr u e ;
L o g . i ( J u e g o . c l a s s . g e t S im p le N a m e ( ) , nombre + " p u l s a d o " ) ;
}
}
p u b li c v o id co m p ru eb a _ so lta d o (A rra y L ist< T o q u e> l i s t a ) {
b o o le a n a u x = fa ls e ;
f o r (T oq u e t : l i s t a ) {
i f ( t . x > c o o r d e n a d a _ x && t . x < c o o r d e n a d a _ x + A n c h o () &&
t . y > c o o r d e n a d a _ y && t . y < c o o r d e n a d a _ y + A l t o ( ) ) {
aux = tr u e ;
}
}
i f (!au x ){
p u ls a d o = fa lse ;
}

/ /d e v u e lv e su ancho
p u b lic in t A ncho(){
r e t u r n im a g e n . g e t w i d t h () ;
}
//d e v u e lv e su a l t o
p u b lic in t A l t o (){
retu rn im a g e n .g e tH e ig h t();
}

Fíjate en el método comprueba pulsado: recibe dos parámetros que son las
coordenadas donde el usuario pulsó. Si estas coordenadas se encuentran encuadradas en
el rectángulo que forma la imagen, se considera que el control se pulsó:
Capítulo 5. Programación de videojuegos 253

if(x>coordenada_x && x<coordenada_x+Ancho() &&y>coordenada_y &&y<coordenada_y+Alto())

coordenada_x, coordenada_y coordenada_x+Ancho(), coordenada_y


\ ----- x /

coordenada_x, coordenada_y+Atto() coordenada_x+Ancho(),coordenada_y+Afto()

Para comprobar si se soltó el control, basta con comprobar si en la lista de toques de


pantalla, hay alguno que coincida en coordenadas con las coordenadas del control.
Para dar vida a la nave, por tanto, hay que dar los siguientes pasos:
1. Crea la clase Juego anterior.
2. Declara las siguientes variables para cargar los controles.

/* C o n tro les * /
p r i v a t e f i n a l i n t IZQUIERDA=0;
p r i v a t e f i n a l i n t DERECHA=1;
p r i v a t e f i n a l i n t DISPARO=2;
/ / v a r i a b l e p a r a m e d i r como d e r á p i d o s e d e s p l a z a l a n a v e e n h o r i z o n t a l
p r i v a t e f i n a l i n t VELOCIDAD_HORIZONTAL=10; / / 1 0 p i x e l s p o r fr a m e
C o n t r o l c o n t r o l e s [ ] =new C o n t r o l [ 3 ] ;

3. Crea una función para crear los objetos de la clase control y cargar sus imágenes y
coordenadas:

p u b lic v o id C a r g a C o n tr o le s (){
f l o a t aux;

/ / fle c h a _ iz d a :
/ / c o o r d e n a d a s 0 , 4 / 5 de l a a l t u r a de l a p a n t a l l a + a l t o de l a n a v e + 5 p i x e le s
con troles[IZ Q U IE R D A ]= n ew
C o n tro l(g etC o n tex t 0 , 0 , A lto P a n ta lla /5 * 4 + n a v e . g etH eig h t 0 + 5 ) ;
con troles[IZ Q U IE R D A ]. C a r g a r ( R . d r a w a b le . f l e c h a _ i z d a ) ;
c o n tr o l e s [ I Z Q U I E R D A ] .n o m b r e = " I Z Q U I E R D A " ;

/ / f l e c h a _ d e r e c h a : c o o r d e n a d a s j u s t o a c o n t i n u a c ió n de l a f l e c h a iz q u i e r d a
con troles[D E R E C H A ]= new C o n t r o l ( g e t C o n t e x t ( ) ,
c o n tr o le s[I Z Q U I E R D A ].A n c h o ( ) + c o n tro les[IZ Q U IE R D A ]. c o o r d e n a d a _ x ,
con troles[IZ Q U IE R D A ]. c o o r d e n a d a _ y );
co n tro les[D E R E C H A ]. C a r g a r (R .d r a w a b le . f l e c h a _ d c h a ) ;
c o n t r o l e s [ D E R E C H A ] . nombre="DERECHA";

/ / d i s p a r o : c o o r d e n a d a s X = 5 /7 d e l a n c h o de l a p a n t a l l a ,
/ / Y = l a m ism a q u e l a s f l e c h a s
a u x = 5 . O f/ 7 . O f* A n c h o P a n t a l l a ; / / e n l o s 5 / 7 d e l a n c h o
con troles[D IS P A R O ]= n ew C o n t r o l ( g e t C o n t e x t ( ) ,
( i n t ) a u x ,c o n t r o le s [ 0 ] .coord en ad a_y);
co n tro les[D IS P A R O ]. C a r g a r (R .d r a w a b le . d i s p a r o ) ;
c o n tro les[D IS P A R O ].n o m b re= " D IS P A R O " ;
254 Programación en Android

4. Agrega a las acciones ACTION_DOWN y ACTION_POINTER_DOW N del


evento onTouch, las comprobaciones para detectar si algún control se ha presionado:

/ / s e com prueba s i s e ha p u ls a d o
f o r ( i n t i = 0 ; i < 3 ; i+ + )
c o n t r o l e s [ i ] . c o m p r u e b a _ p u lsa d o (x ,y );

5. Agrega a las acciones ACTION_UP y ACTION_POINTER_UP del evento


onTouch, las comprobaciones para ver si algún control ha dejado de presionarse.

/ / s e com prueba s i s e ha s o l t a d o e l b o tó n
f o r ( i n t i= 0 ;i< 3 ;i+ + )
c o n t r o l e s [ i ] . c o m p r u e b a _ so lta d o (x ,y );

6. Por último, modifica el método actualizar del bucle del videojuego para actualizar
las coordenadas de la nave si se ha detectado pulsación en cualquiera de las flechas
de control:

i f (c o n tr o le s[IZ Q U IE R D A ].p u lsa d o ){


i f (xN ave>0)
x N a v e = x N a v e - VELOCIDAD_HORIZONTAL;
}
i f (co n tr o le s[D E R E C H A ].p u lsa d o ){
i f (x N a v e < A n c h o P a n ta lla -n a v e .g e tw id th ())
xNave=xNave+VELOCIDAD_HORIZONTAL;
}

5.8.7. LOS ENEMIGOS


La diversión comienza cuando el jugador ve aparecer enemigos y empieza a
dispararles para destruirlos. En “The Xindi Invasion” existen dos tipos de enemigos:
- Los enemigos tontos: Son naves enemigas que simplemente pululan por la pantalla con
una dirección calculada al azar. Si llegan a una esquina de la pantalla, simplemente
rebotan. Se mueven a una velocidad inferior a los listos. Si los matas sumas solo 10
puntos. Hay más probabilidad de que haya enemigos tontos que listos.
- Los enemigos listos: Este tipo de naves son capaces de seguir a la nave del jugador
para ofrecer más dificultad. Las naves enemigas “buscan” las coordenadas X de la nave
del jugador, actualizando la dirección de la coordenada Y al llegar a los bordes de la
pantalla. Se mueven a una velocidad superior a los tontos. Si los matas sumas 50
puntos.
Capítulo 5. Programación de videojuegos 255

enemigo enemigo
listo tonto

Para implementar la dirección en la que se mueven los enemigos se utilizan dos


variables, direccion__vertical y direccion_horizontal, ambas con posibles valores -1 y +1.
Estas variables directamente se sumarán a las coordenadas del enemigo para seguir esa
dirección.

direccion_vert¡cal=-l

Í direccion_vertical=l

d ir e c c io n _ h o r iz o n t a l = - l
dirección h o r iz o n t a l = l

Para programar estos enemigos, se crea una nueva clase donde se defíne el
comportamiento de cada enemigo:

p u b l i c c l a s s Enemigo {
p u b lic f i n a l in t ENEMIGO_INTELIGENTE=0; / / e n e m i g o que s i g u e al a nave
p u b lic f i n a l in t ENEMIG0_T0NT0=1; / / e n e m i g o que s e mueve a l e a t o r i a m e n t e

p u b lic f i n a l in t VELOCIDAD_ENEMIGO_INTELIGENTE=5;
p u b lic f i n a l in t VEL0CIDAD_ENEMIG0_T0NT0=2;
p u b lic i n t v e lo c id a d ;

p u b l i c i n t co ord en a d a _ x, coord en ad a_y; / / c o o r d e n a d a s donde s e d i b u j a e l c o n t r o l


p u b l i c i n t t i p o _ e n e m ig o ; // i m a g e n d e l c o n t r o l

p u b l i c i n t d i r e c c i o n _ v e r t i c a l = l ; / / i n i c i a l m e n t e h a c i a a b a jo
p u b lic i n t d ir e c c io n _ h o r iz o n t a l= l; / / i n i c i a l m e n t e derecha

p r i v a t e Ju ego ju e g o ;

p u b l i c Enem igo(Juego j ) {
ju eg o = j;

/ / p r o b a b i l i d a d de enemigo t o n t o 80%, enemigo l i s t o 20%


i f (Math, random() > 0 .2 0 ) {
t i p o _ e n e m ig o = ENEMIGO_TONTO;
v e l o c i d a d = VELOCIDAD_ENEMIGO_TONTO;
}
e lse {
tip o _ e n e m ig o = ENEMIGO_INTELIGENTE;
256 Programación en Android

v e l o c i d a d = VELOCIDAD__ENEMIGO_INTELIGENTE;
}
/ / p a r a e l enemigo t o n t o s e c a l c u l a l a d i r e c c i ó n a l e a t o r i a
i f ( M a t h .r a n d o m ( ) > 0 . 5 )
d ir e c c io n _ h o r iz o n ta l= l; //d erech a
e lse
d i r e c c i o n _ h o r i z o n t a l = - l ; / / iz q u i e r d a

i f ( M a t h .r a n d o m ( ) > 0 . 5 )
d ir e c c io n _ v e r tic a l= l; //a b ajo
e lse
d irecc io n _ v ertica l= -l; //a r r ib a

C a lc u la C o o r d e n a d a s ( ) ;
}
p u b l i c v o i d C a lc u la C o o r d e n a d a s (){
d o u b le x ; / / a l e a t o r i o
/ * P o s i c i o n a m i e n t o d e l enemigo * /
/ / e n t r e 0 y 0 . 1 2 5 s a l e por l a i z q u i e r d a (x=0, y = a l e a t o r i o ( 1 / 5 ) p a n t a l l a )
/ / e n t r e 0 . 1 2 5 y 0 .2 5 s a l e p o r l a d erech a
/ / (x =A n ch o P a n ta lla -a n ch o b itm a p , y = a l e a t o r i o ( 1 / 5 ) )
/ / > 0 .2 5 s a l e p o r e l c e n t r o (y=0, x = a l e a t o r i o e n t r e 0 y A nchoPan talla-A n ch oB itm ap )

x=Math. random( ) ;

i f (x<= 0. 2 5 ) {
//25% de p r o b a b i l i d a d de que e l enemigo s a l g a p o r l o s l a d o s
i f ( x < 0 . 1 2 5 ) / / s a l e por l a i z q u i e r d a
coord en ad a_x = 0;
e lse
coord en ad a_x = j u e g o . A n c h o P a n t a l l a - j u e g o . e n e m i g o _ t o n t o . g e t w i d t h ( ) ;
coord en ad a_y = ( i n t ) (Math.random( ) * j u e g o . A l t o P a n t a l l a / 5 ) ;
}else{
c o o r d e n a d a _ x = ( i n t ) (Math.random()*
(ju eg o .A n ch o P a n ta lla -ju eg o . en e m ig o _ to n to .g e tw id th ( ) ) ) ;

coordenada_y= 0;
}
}
/ / A c t u a l i z a l a coord en ad a d e l enemigo con r e s p e c t o a l a coord en ad a de l a nave
p u b l i c v o i d A c tu a li z a C o o r d e n a d a s (){
i f (tipo_enemigo==ENEMIGO_INTELIGENTE) {

if (ju e g o .x N a v e > coordenada_x)


coordenada_x+=VELOCIDAD_ENEMIGO_INTELIGENTE;
e l s e i f ( j u e g o .x N a v e < coordenada_x)
coordenada_x-=VELOCIDAD_ENEMIGO_INTELIGENTE;

i f (Math.abs(coordenada_x-juego.xNave)<VELOCIDAD_ENEMIGO_INTELIGENTE)
c o o r d en a d a_ x= ju ego .x N a ve; / / s i e s t á muy c e r c a s e pone a su a l t u r a

i f ( c o o r d e n a d a _ y > = ju e g o . A l t o P a n t a l l a - j u e g o . e n e m i g o _ l i s t o . g e t H e i g h t ()
&& d i r e c c i o n _ v e r t i c a l = = l )
d ir e c c io n _ v e r tic a l= -l;

i f (coordenada_y<=0 && d i r e c c i o n _ v e r t i c a l = = - l )
d ir e c c io n _ v e r tic a l= l;

coordenada_y+=direccion_vertical*VELOCIDAD_ENEMIGO_INTELIGENTE;
}
else{
/ / e l enemigo t o n t o h a ce c a s o om iso a l a p o s i c i ó n de l a n a ve,
//sim p le m e n te p u lu la por la p a n t a lla
coordenada_x+=direccion_horizontal*VELOCIDAD_ENEMIGO_INTELIGENTE;
coordenada_y+=direccion_vertical*VELOCIDAD_ENEMIGO_INTELIGENTE;
Capítulo 5. Programación de videojuegos 257

//Cambios de direcciones al llegar a los bordes de la pantalla


i f (coordenada_x<=0 && direccion_horizontal==-l)
direccion_horizontal=l;

i f (coordenada_x>juego.AnchoPantalla-juego.enemigo_tonto.getWidth()
&& direccion_horizontal==l)
direccion_horizontal=-l;

i f (coordenada_y>=juego.AltoPantalla && direccion_vertical ==1)


direccion_vertical=-l;

i f (coordenada_y<=0 && direccion_vertical==-l)


direccion_vertical=l;

}
}
public void Dibujar(Canvas c. Paint p ) {
i f (tipo_enemigo==ENEMIGO_TONTO)
c .drawBitmap(juego.enemigo_tonto,coordenada_x,coordenada_y,p );
else
c .drawBitmap(juego.enemigo_listo,coordenada x,coordenada y,p);
}
public int Anch o O {
i f (tipo_enemigo==ENEMIGO_TONTO)
return juego.enemigo_tonto.getWidth();
else
return juego.enemigo_listo.getWidth();
}
public int Alto(){
i f (tipo_enemigo==ENEMIGO_TONTO)
return juego.enemigo_tonto.getHeight();
else
return juego.enemigo_listo.getHeight();
}
}
El comportamiento del enemigo incluye las siguientes responsabilidades:
• Un enemigo sabe de qué tipo es, si es tonto o listo, (constructor).
• Un enemigo sabe qué coordenadas tiene, calculadas al azar.
CalculaCoordenadas ().
• Conocer su alto y su ancho. Alto() y AnchoQ.
• Un enemigo sabe cómo moverse. ActualizaCoordenadas().
• Un enemigo sabe dibujarse. Dibujar().
Ahora hay que decidir cuántos enemigos habrá y con qué frecuencia aparecerán. Para
esto, definimos, dentro de la clase Juego, las siguientes variables y objetos:

/ * E n e m ig o s * /
B itm a p e n e m i g o _ t o n t o , e n e m ig o _ lis to ;

//E n e m ig o s p a r a a c a b a r e l ju e g o
p u b l i c f i n a l i n t TOTAL_ENEMIGOS=1000;

/ /n ú m e r o d e e n e m ig o s p o r m in u to
p r i v a t e i n t e n e m ig o s _ m in u to = 5 0 ;
258 Programación en Android

/ / f r a m e s que r e s t a n h a s t a g e n e r a r n u evo en em igo


p r i v a t e i n t fra m es_ p a ra _ n u ev o _ en em ig o = 0 ;

//C o n t a d o r de en em ig o s d e s t r u i d o s
p r i v a t e i n t en em ig o s_ m u erto s= 0 ;

/ / n ú m e r o d e e n e m i g o s c r e a d o s h a s t a e l m om ento
p r iv a t e in t en em ig o s_ crea d o s= 0 ;

La variable enemigos_minuto nos dirá cuántos enemigos debemos mostrar por


minuto, inicialmente establecida en 50. Esta variable se puede ir incrementando
conforme avance el juego para incrementar la dificultad.
La variable frames para nuevo enemigo mide el número de frames que han de
pasar hasta que aparece un nuevo enemigo. Este valor, irá en proporción al número de
enemigos que queramos mostrar por minuto (60 segundos):

f ram e s _ p a r a _ n u e v o _ e n e m i g o = b u c 1 e . MAX_FPS *6 0 / e n e m i g o s _ m i n u t o ;

La variable contador_enemigos se incrementará cuando nuestro jugador destruya a


un enemigo. El objetivo del juego será que m atar enemigos hasta que el contador llegue
a TOTAL_ENEMIGOS. Se usarán dos bitmaps, enemigo__tonto y enemigo_listo, que
serán dibujados tantas veces como enemigos haya en pantalla en ese momento y se
cargarán mediante la función CargaEnemigos():

p u b l i c v o i d C a rg a E n em ig o s( ) {
f rame s _ p a r a _ n u e v o _ e n e m i g o = b u c l e . MAX_FPS * 6 0 / e n e m i g o s _ m i n u t o ;
e n e m ig o _ to n to = B itm a p F a c to r y . d e c o d e R e s o u r c e (
g e t R e s o u r c e s () , R . d r a w a b le . e n e m ig o _ to n to );
e n e m ig o _ lis to = B itm a p F a c to r y .d e c o d e R e so u r c e (g e tR e so u r c e s( ) ,
R .d r a w a b le . e n e m i g o _ li s t o ) ;
}
Para almacenar todos los enemigos que van apareciendo en pantalla, se puede crear
una lista “ArrayList” con objetos del tipo Enemigo:

p r i v a t e A rrayL ist< E n em igo> lis ta _ e n e m ig o s = n e w A r r a y L ist< E n e m ig o > ();

cuando llegue el momento de lanzar un nuevo enemigo, se invocará el método


CrearNuevoEnemigo() para incluir el nuevo enemigo en la lista:

p u b l i c v o i d C r e a r N u e v o E n e t n i g o () {
if ( T O T A L _ E N E M I G O S - e n e m ig o s _ c r e a d o s > 0 ) {
/ / a ú n quedan en em ig o s p o r c r e a r
l i s t a _ e n e m i g o s . add(new E n e m ig o ( t h i s ) );
en em igos_cread os+ + ;
}
}
Capítulo 5. Programación de videojuegos 259

Para decidir cuándo crear al enemigo hay que meter en el método actualizar del bucle
del videojuego, el siguiente código:

/♦ E n em ig o s* /
i f (fra m es_ p ara_n u ev o _en em igo= = 0){
C rearN u evoE n em igo( ) ;
// n u e v o c i c l o de en em igos
f r a m e s _ p a r a _ n u e v o _ e n e m i g o = b u c 1 e . MAX_FPS * 6 0 / e n e m i g o s _ m i n u t o ;
}
fra m es_ p ara_n u evo_en em igo--;

De esta manera, en cada iteración del bucle se decrementará el contador de frames


hasta que tenga que aparecer en pantalla el nuevo enemigo. Además, también en el
método actualizar hay que actualizar las coordenadas de todos los enemigos. Para hacer
esto, se recorre la lista de enemigos invocando al método ActualizaCoordenadas() de
cada uno de los objetos que contiene la lista:

// L o s en em igo s p e r s ig u e n a l ju g a d o r
fo r (E n e m ig o e: l i s t a _ e n e m i g o s ) {
e . A c t u a l i z a C o o r d e n a d a s () ;
}

Para terminar, en el método renderizar(), hay que invocar al método dibujar de cada
uno de los enemigos:
/ / d i b u j a l o s en em igos
f o r (E n e m ig o e : l i s t a _ e n e m i g o s ) {
e .D ib u j a r (c a n v a s, m y P a in t);
}

5.8.8. EL DISPARO
Para los disparos seguiremos exactamente la misma estrategia que para los enemigos,
es decir, vamos a crear una clase Disparo que tenga el comportamiento de un disparo y
una lista de objetos Disparo que guarde los disparos que se dibujan en cada pantalla.
Con estas dos listas podremos comprobar si hay colisiones entre los disparos y los
enemigos, eliminando ambos objetos cuando colisionen. Además, hay que añadir a los
enemigos la responsabilidad de saber si han colisionado con la nave del jugador,
terminando así con la partida.

A la clase Disparo, por tanto, le daremos las siguientes responsabilidades:


• Establecer sus coordenadas iniciales (constructor)
• Conocer y actualizar sus coordenadas
(ActualizaCoordenadas() )
• Conocer su alto y su ancho (Alto() y Ancho) imagen del disparo

• Saber dibujarse (DibujarQ )


260 Programación en Android

En definitiva, es una clase muy sencilla formada por el siguiente código:

p u b lic c l a s s D isp a ro {
//c o o r d e n a d a s donde s e d ib u j a e l c o n t r o l
p u b lic in t coordenada_x, coordenada_y;
p r iv a t e Juego ju eg o ; // r e f e r e n c i a a la c la s e p r in c ip a l

/ ♦ C o n s t r u c t o r c o n c o o r d e n a d a s i n i c i a l e s y número d e d i s p a r o * /
p u b lic D isp a ro (J u eg o j , i n t x , in t y ) {
ju eg o = j;
coordenada_x=x;
c o o r d e n a d a _ y = y -j. d i s p a r o .g e t H e i g h t ()+15;

/ / s e a c t u a l i z a l a c o o r d e n a d a y n a d a más
p u b lic v o id A c tu a liz a C o o r d e n a d a s(){
co o rd en ad a_y-= 10;
}
p u b l i c v o i d D i b u j a r ( C a n v a s c . P a i n t p) {
c . d ra w B itm ap (ju ego. d is p a r o , coordenada_x, coordenada_y, p) ;
}
p u b lic in t A ncho(){
retu rn ju e g o .d is p a r o .g e tW id th O ;
}
p u b lic in t A lto ( ){
retu rn ju e g o . d is p a r o .g e tH e ig h t();
}
}

Al igual que para los enemigos, creamos una lista de disparos y algunas variables
más:
p r iv a t e A rra y L ist< D isp a ro > lis ta _ d is p a r o s = n e w A r r a y L ist< D isp a r o > ();

B itm ap d i s p a r o ;
p r iv a t e i n t fr a m es_ p a ra _ n u ev o _ d isp a ro = 0 ;
/ / e n t r e d i s p a r o y d i s p a r o d e b e n p a s a r a l m en o s
/ / MAX_FRAMES_ENTRE_DI S PARO
p r i v a t e f i n a l i n t MAX_FRAMES_ENTRE_DISPARO=20;
p r iv a t e b o o le a n n u e v o _ d is p a r o = fa ls e ;

El bitmap disparo cargará la imagen utilizada


para el disparo, la variable siguiente
frames para nuevo disparo limita el número,
necesario para saber cuándo permitir realizar un
disparo. De esta manera, haremos más jugable el
juego: Cuando se efectúa un disparo, se espera
MAX__FRAMES_ENTRE_JDISPARO frames, para
no permitir que la nave dispare un tiro por cada
frame. La variable nuevo_disparo nos permitirá
saber, si el jugador pulsó en algún momento el control de disparo y, de esta manera,
Capítulo 5. Programación de videojuegos 261

cuando llegue el momento (haber esperado MAX JFRAMES_ENTRE_DISPARO),


efectuar el disparo.
Todavía queda programar la parte en la que el usuario presiona el control de disparo
y se genera. Para ello, hay que modificar el método actualizar, donde se detecta si hubo
disparo (mediante c o n t r o l e s [DISPARO] .p u ls a d o ) , se averigua cuándo (en qué
frame) hay que generarlo:

/* D isp a ro * /
if (c o n tr o le s[D IS P A R O ].p u lsa d o )
n u e v o _ d isp a r o = tr u e ;

if ( f r a m e s _ p a r a _ n u e v o _ d i s p a r o == 0 ) {
i f (n u ev o _ d isp aro) {
C rea D isp a ro ();
n u ev o _ d isp a ro = f a l s e ;
}
// n u e v o c i c l o de d is p a r o s
f r a m e s _ p a r a _ n u e v o _ d i s p a r o = MAX_FRAMES_ENTRE_DISPARO;
}
fr a m es_ p a ra _ n u ev o _ d isp a ro --;

p u b lic v o id C re a D isp a r o (){


l i s t a _ d i s p a r o s . add(new D i s p a r o ( t h i s , x N a v e , y N a v e ) );
}

No hay que olvidarse de que en cada iteración del bucle del videojuego, hay que
modificar las coordenadas del disparo, por tanto, agregaremos este código al método
actualizar. La novedad aquí es que hay que recorrer la lista con un iterador, puesto que
si el disparo se encuentra fuera de pantalla, hay que eliminarlo, y para poder eliminarlo
sin fallos de concurrencia en la lista hay que ejecutar el método remove del iterador de
la lista, que borra tanto del iterador como de la lista original el disparo que ya no
necesitamos:

/ / L o s d i s p a r o s s e mueven
f o r ( I t e r a t o r < D i s p a r o > i t _ d i s p a r o s = l i s t a _ d i s p a r o s . i t e r a t o r () ;
it _ d is p a r o s .h a sN ex t();) {

D isp a ro d = i t _ d is p a r o s . n e x t ();
d . A c t u a l i z a C o o r d e n a d a s () ;

/ / e l i m i n a e l d is p a r o que y a no s e d ib u j a
i f (d .F u e r a D e P a n t a lla ())
i t _ d i s p a r o s . r e m o v e () ;
}

Finalmente, solo queda dibujar los disparos existentes, y para esto modificamos el
método renderizar de nuestro videojuego incorporando al método este código:

//d ib u ja lo s d isp a ro s
262 Programación en Android

fo r(D isp a ro d : l is t a _ d is p a r o s ) {
d .D ib u ja r (c a n v a s ,m y P a in t);
}

5.8.9. LAS COLISIONES


¿Y cómo matamos a los enemigos? Pues muy fácil,
recorriendo las dos listas que tenemos, la de disparos y
la de enemigos, y con un par de bucles anidados
comprobando si se encuentran en las coordenadas de
influencia de ambos sprites. colisión disparo-enemigo
Si ambos sprites colisionan se borrarán tanto el
disparo como el enemigo y se creará un objeto de la clase Explosion que crearemos para
poder dibujar el efecto de la colisión.
La explosión se pintará con un único sprite que contendrá todas las imágenes que
formarán la animación:

& '•& % $ C/5

Este sprite se cargará entero, pero se mostrará únicamente una imagen de la


secuencia por frame. La imagen original tiene 2176x128 pixeles, y 17 imágenes para
formar la animación. No obstante, tienes que tener en cuenta que Android escalará la
imagen para adaptarla a la pantalla. Por lo tanto, si cargar la imagen en un Bitmap
llamado explosión, cada uno de los subsprites que componen explosión medirá:

a n c h o _ s p r i t e = e x p l o s i o n . g e t W i d t h ( ) /NUMERO_IMAGENES_EN_SECUENCIA;

Dicho de otra forma, habrá que ir recortando el sprite entero en cada iteración del
bucle del videojuego formando imágenes de ancho_sprite x explosión.getHeight()
pixeles.
Los objetos Explosión tendrán las siguientes responsabilidades:
Conocerán sus coordenadas (Constructor).
Conocerán su estado de evolución, es decir la porción del sprite que se tiene que
pintar en cada momento hasta que desaparezca (ActualizarEstado).
Sabrán dibujarse conforme a su estado. (Dibujar).
Conocer si ha terminado de dibujarse toda la animación de la explosión
(HaTerminado()).
El código de la clase es el siguiente:

public class Explosion {


Capítulo 5. Programación de videojuegos 263

private int ancho_sprite;


private int alto_sprite;
private final int NUMER0_IMAGENES_EN_SECUENCIA=17;
public int coordenada_x, coordenada_y; //coordenadas donde se dibuja el control
private Juego juego;
private int estado;

/♦Constructor con coordenadas iniciales y número de explosión*/


public Explosion(Juego j, int x, int y ) {
coordenada_x=x;
coordenada_y=y;
juego=j;
ancho_sprite=juego.explosion.getwidth()/NUMERO_IMAGENES_EN_SECUENCIA;
alto_sprite=juego.explosion.getHeight();
estado=-l; //recien creado
}
public void ActualizarEstado(){
//incrementamos el estado al siguiente momento de la explosión
estado++;

public void Dibujar(Canvas c. Paint p ) {


int posicionSprite=estado*ancho_sprite;

i f (¡HaTerminado()) {
//Calculamos el cuadrado del sprite que vamos a dibujar
Rect origen = new Rect(posicionSprite, 0,
posicionSprite + ancho_sprite, alto_sprite);
//calculamos donde vamos a dibujar la porción del sprite
Rect destino = new Rect(coordenada_x, coordenada_y,
coordenada_x + ancho_sprite, coordenada_y + juego.explosion.getHeight ())
c .drawBitmap(juego.explosion, origen, destino, p ) ;
}
}
public boolean H a T e r m i n a d o O {
return estado>= NUMERO_IMAGENES_EN_SECUENCIA;
}

Como ves, es una clase muy sencilla, pero con un método bastante ingenioso. El
método dibujar, que va recortando los sprites originales con respecto a un contador
llamado estado que se incrementará en cada iteración del bucle del videojuego. Este
estado mide el estado de evolución de la animación y contará de 0 (primer sprite) a 16
(último sprite). Cuando este contador llega a 17, ha terminado de dibujarse la explosión
y puede eliminarse.

| |
1 ^ 1| ¥ ] i * i
e -,
□ 1 1□ 1 1□ ■ □ 1 1□ 1 1□ I I 0
264 Programación en Android

posicionSprite.G

alto_sprite

posicionSprite=estado'ancho_sprite;

( s p r i t e A . x ;s p r i t e A . y )

A
diferenciaY j(s p r ite B .x ,s p r ite B .y )
V s p r ite ^ '-'

o ------ c>
difísrenciaX s p r i t e B

Ahora queda programar la lógica de la colisión para poder generar objetos de la clase
explosión. Para saber cómo detectar la colisión hay que calcular el valor absoluto de la
diferencia de coordenadas:

Con las variables diferenciaX y diferenciaY calculadas como Math.abs(spriteA.x-


spriteB.x) y como Math.abs(spriteA.y-spriteB.y) sabemos la distancia tanto en el eje de
las X como en el eje de las Y, y podemos determinar si ambos objetos han colisionado.
Tan solo hay que comparar diferenciaX con el menor de los anchos de los dos objetos y
diferenciaY con el menor de los altos de los dos objetos. Si ambas comparaciones
resultan verdaderas, entonces existirá colisión:

public boolean Colisión(Enemigo e, Disparo d ) {


int alto_mayor=e.A l t o ()>d.Alto()?e.Alto() :d.Alto O ;
int ancho_mayor=e.Ancho()>d.Ancho()?e.Ancho():d.Ancho();
int diferenciaX=Math.abs(e.coordenada_x-d.coordenada_x);
int diferenciaY=Math.abs(e.coordenada_y-d.coordenada_y);
return diferenciaX<ancho_mayor &&diferenciaY<alto_mayor;
}

Como hemos comentado, hay que hacer dos bucles anidados recorriendo los enemigos
que hay en pantalla y los disparos que se han efectuado. Si hay colisiones entre ellos,
hay que eliminar ambos objetos, tanto de la lista de enemigos como de la de disparos.
Para poder borrar, recuerda que tienes que recorrer las listas con un iterador, de lo
contrario obtendrás una excepción java.util.ConcurrentModificationException.
Capítulo 5. Programación de videojuegos 265

//colisiones
fo r (Iterator<Enemigo> it_enemigos= lista_enemigos.iterator();it_enemigos.hasNext 0;){
Enemigo e = it_enemigos .next () ,-
fo r (Iterator<Disparo> it_disparos=lista_disparos.iterator();it_disparos.hasNext();){
Disparo d=it_disparos.next();
if (Colisión(e, d) ) {
/* Creamos un nuevo objeto explosión */
lista_explosiones.add(new Explosion(this,e .coordenada_x, e .coordenada_y));
/* eliminamos de las listas tanto el disparo como el enemigo */
try {
it_enemigos.remove();
it_disparos.remove();
}catch(Exception ex){}
enemigos_muertos++; //un enemigo menos para el final
}
}
}

5.8.10. LA MÚSICA
La música siempre ha sido esencial para captar jugadores. Tanto en el cine como en
los videojuegos, una secuencia de acción no es lo mismo si no va acompañada de una
buena banda sonora. Qué sería de la famosa escena de la ducha de Psicosis, de Alfred
Hitchcock sin la terrible música de fondo, o de los Xwing de startrek si no disparan sin
ese peculiar sonido.
En este punto, por tanto, tenemos que dotar a “The Xindi Invasion” de una buena
banda sonora. En opengameart.com puedes encontrar miles de contribuciones gratuitas
y libres en forma de efectos y canciones para tu videojuego. Si quieres hacer tus propias
creaciones, puedes utilizar un software libre y gratuito como Hydrogen para crear tus
propios sonidos.
Es muy fácil añadir efectos a todos los sucesos de nuestro videojuego si tenemos estos
sonidos en un formato como mp3. En este punto, te recomendamos que repases el
capítulo de contenido multimedia para refrescar tus conocimientos.
Vamos a escoger para nuestro videojuego 4 ficheros mp3:
• Canción para la introducción (intro.mp3).
• Canción para el transcurso del juego (juego.mp3).
• Sonido para el disparo (disparo.mp3).
• Sonido para la explosión (explosión.mp3).

Para agregar estos sonidos a nuestro videojuego tenemos que seguir los siguientes
pasos:
1. Inserta los 4 ficheros mp3 en la carpeta res/raw
2 . Modifica el método onCreate de la actividad principal (menú) y escribe una llamada
al método IniciaMusicaIntro() para reproducir la canción:

public void IniciaMusicalntro(){


mediaPlayer = MediaPlayer.create(this, R.raw.intro);
mediaPlayer.setOnCompletionListener(
266 Programación en Android

new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
m p .start ();
}
}>;
mediaPlayer.start();

3. Añade código para que la música deje de sonar cuando se abandona la aplicación o
cuando se inicia la acción. Modifica la creación de la actividad del videojuego para
añadir la parada y liberación del media player:

findViewByld(R.id.btnNuevoJuego).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mediaPlayer.stop () mediaPlayer .reset O ;
MenuPrincipal.this.startActivity(new Intent(MenuPrincipal.this,
ActividadJuego.class));
}
}>;
©Override
protected void onDestroyO {
super.onDestroy();
mediaPlayer.stop(),-
mediaPlayer.release();
1

4. Sigue los mismos pasos para el juego. Crea el objeto para reproducir la música de
fondo del juego, añade una llamada al método IniciarMusicaJuego() en el constructor del
juego y en el método fin(), añade la parada y liberación del media player:

/* sonidos */
MediaPlayer mediaPlayer; //para reproducir la música de fondo

private void IniciarMusicaJuego(){


//música de fondo
mediaPlayer = MediaPlayer.create(actividad, R.raw.juego);
mediaPlayer.setOnCompletionListener(new
MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
m p .start();
}
}>;
mediaPlayer.start O ;
}
public void fin(){
mediaPlayer.release();

5. Repartamos responsabilidades: En el constructor de las clases Explosion y Disparo


añade un par de líneas para cargar el recurso correspondiente y reprodúcelo.

mediaPlayer=MediaPlayer.create(j.getContext(), R.raw.XXXX); //XXXX=disparo o explosion


mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
Capítulo 5. Programación de videojuegos 267

public void onCompletion(MediaPlayer mp) {


m p .release();
}
}>;
mediaPlayer.start ()

5.8.11. EL NIVEL DE DIFICULTAD


“The Xindi Invasion” es hasta ahora, bastante fácil, tenemos disparos ilimitados y los
enemigos ni son rápidos ni muy listos. Debemos Implementar un sistema de puntuación
que incremente automáticamente el nivel de dificultad, a medida que el jugador vaya
adquiriendo puntos.

Una forma muy sencilla de hacer esto es


incrementando la velocidad de los enemigos a
medida que se van sumando puntos, por ejemplo,
que cada X puntos se incremente en un punto la
velocidad de los enemigos y que el ratio de
enemigos por minuto crezca.

Necesitaremos tres variables:

private int Puntos=0; //contador de puntos


private int Nivel=0; //contador de niveles
private int PUNTOS_CAMBIO_NIVEL=2000; //cada cuanto se cambia de nivel

Empezamos modificando el constructor de la clase Enemigo añadiendo la personalización para


el nivel:
public Enemigo(Juego j, int n ) {
juego=j; Nivel=n;
//probabilidad de enemigo tonto 80%, enemigo listo 20%
if (Math, random () >0.20) {
tipo_enemigo = ENEMIGO_TONTO;
velocidad = VELOCIDAD_ENEMIGO_TONTO+Nivel;
1
else {
tipo_enemigo = ENEMIGO_INTELIGENTE;
velocidad = VELOCIDAD_ENEMIGO_INTELIGENTE+Nivel;

1
Y modificar la creación de objetos de tipo enemigo en el método
CrearN uevoEnemigo ():
public void CrearNuevoEnemigo(){
i f (TOTAL_ENEMIGOS-enemigos_creados>0) { //aún quedan enemigos por crear
lista_enemigos .add (new Enemigo (this, Nivel) ),-
enemigos_creados++;

En nivel del juego se puede cambiar en el método a c t u a l i z a r () :


//cada PUNTOS_CAMBIO_NIVEL puntos se incrementa la dificultad
268 Programación en Android

if (Nivel!=Puntos/PUNTOS_CAMBIO_NIVEL) {
Nivel = Puntos / PUNTOS_CAMBIO_NIVEL;
eneraigos_minuto += (20 * Nivel);
}

Y finalmente se contabilizan los puntos cuando se m ata a un enemigo:


if (Colisión(e, d)) {

/♦Puntos*/
i f (e .t ipo_enemigo==e .ENEMIGO_INTELIGENTE)
Puntos+=50;
else
Puntos+=10 ;
}

No te olvides de dibujar en el método renderizar(), los puntos que lleva el jugador y


el nivel en el que está jugando:

//escribe los puntos


myPaint.setTextSize(60);
canvas.drawText ("PUNTOS " + Puntos + " - Nivel " + Nivel, 50, 50, myPaint) ,-
canvas.drawText("Enemigos por matar "+ (TOTAL_ENEMIGOS-enemigos_muertos),
50, 100, myPaint);

5.8.12. EL FIN DEL JUEGO


Ganar o no ganar. He ahí la cuestión. Para termina “The Xindi Invasion”, tan solo
nos queda programar una condición de fin de juego:
• VICTORIA : Si hemos acabado con todos nuestros enemigos
• DERROTA: Si alguna nave enemiga chocó contra la nave del jugador
Capítulo 5. Programación de videojuegos 269

Tienes que tener en cuenta que aunque el juego haya terminado, la animación
generada por los métodos actualizar-renderizar tiene que continuar. Con algunos
matices, pero tiene que continuar. Por ejemplo, si la nave del jugador ha sido destruida,
aparecerá un cartel indicando la condición de DERROTA, aunque los enemigos seguirán
saliendo y moviéndose (no vas a dejar todo parado por haber terminado). Y al revés, si
hemos destruido a todos los enemigos, deja que el jugador siga moviendo la nave por el
escenario disfrutando del merecido cartel de VICTORIA.
Para detectar las condiciones de derrota o victoria se crean dos variables de tipo
booleano:

/* Fin de juego */
private boolean victoria=false,derrota=false;

Estas se darán cuando:


victoria = true, si el número de enemigos totales es igual al de enemigos destruidos,
derrota = true, si nuestra nave ha colisionado con algún enemigo.

Para hacer estas comprobaciones, programa un método llamado


CompruebaFinJuego() que se invoque desde el método actualizar, tan solo si aún no se
ha producido la condición de victoria o derrota:

i f (!derrota) {
if (controles[IZQUIERDA].pulsado) {
if (xNave > 0)
xNave = xNave - VELOCIDAD__HORIZONTAL;
}
if (controles[DERECHA].pulsado) {
if (xNave < AnchoPantalla - nave.getwidth())
xNave = xNave + VELOCIDAD_HORIZONTAL;
}
/* Disparo */
if (controles[DISPARO].pulsado)
nuevo_disparo = true;

if (frames_para_nuevo_disparo == 0) {
if (nuevo_disparo) {
CreaDisparo();
nuevo_disparo = false;
}
//nuevo ciclo de disparos
frames_para_nuevo_disparo = MAX_FRAMES_ENTRE_DISPARO;
}
frames_para_nuevo_disparo--;
}
//actualizar: comprobación de fin de juego
i f (¡derrota && ¡victoria)
CompruebaFinJuego();

public void CompruebaFinJuego(){


fo r (Enemigo e :lista_enemigos)
i f (ColisionNave(e)){
lista_explosiones.add(new Explosion(this,e .coordenada_x, e .coordenada_y));
270 Programación en Android

derrota=true
}
if (¡derrota)
i f (enemigos_muertos==TOTAL_ENEMIGOS)
victoria=true;
}
//Comprueba si un enemigo ha chocado con la nave
public boolean ColisiohNave(Enemigo e ) {
int alto_mayor=e.Alto O >nave.getHeight()?e.Alto():nave.getHeight();
int ancho_mayor=e.Ancho()>nave.getWidth()?e.Ancho():nave.getwidth();
int diferenciaX=Math.abs(e.coordenada_x-xNave),-
int diferenciaY=Math.abs(e.coordenada_y-yNave);
return diferenciaX<ancho_mayor &&diferenciaY<alto_mayor;
}

Ya sólo te queda retocar el método renderizar. Tan sólo renderizarás los movimientos
de la nave cuando no hayas sido derrotado. Además, deberás incluir el texto final de
VICTORIA o DERROTA:

public void renderizar(Canvas canvas) {

if(canvas!=null) {

i f (!derrota)
//dibuja la nave (posición fija a 4/5 de alto y la mitad de ancho)
canvas.drawBitmap(nave,xNave,yNave,nuil);

i f (victoria){
myPaint.setAlpha(0);
myPaint.setColor (Color .RED)
myPaint.setTextSize(120);
canvas.drawText("VICTORIA!!", 50, AltoPantalla/2-100, myPaint);
myPaint.setTextSize(50);
canvas.drawText("Las tropas enemigas han sido derrotadas",
50, AltoPantalla/2+100, myPaint);
}
i f (derrota) {
myPaint.setAlpha(0);
myPaint.setColor(Color.RED);
myPaint.setTextSize (50) ,-
canvas.drawText("DERROTA!!", 50, AltoPantalla/2-100, myPaint);
canvas.drawText("La raza humana está condenada!!!!",
50, AltoPantalla/2+100, myPaint);
1
1
}

A C T IV ID A D 5.7.
Puedes ver el código videojuego completo en TheXindilnvasion.rar del material complementario.
Capítulo 5. Programación de videojuegos 271

5.8.13. ADAPTANDO LA VELOCIDAD DE LOS OBJETOS


Cuando programes tu videojuego, debes tener en cuenta que el tamaño de la pantalla
y la velocidad del procesador de los dispositivos móviles variarán seguro e influirán en
cómo de rápido se ejecute la acción de tu videojuego.
La velocidad del procesador la hemos controlado implementando el bucle del video y
el mecanismo de actualizar-dibujar para generar “frames por segundo”, pero hasta
ahora, el tamaño de la pantalla solo nos ha preocupado para el tamaño de los sprites.
No obstante, también tenemos que tener en cuenta el tamaño de la pantalla para
calcular la velocidad de los objetos. Por ejemplo, no es lo mismo que “The Xindi
Invasion” se ejecute en una pantalla full hd de 1920x1080 píxeles que en una pantalla de
800x480 píxeles.

Fíjate en cómo hemos controlado la velocidad del disparo:

/ / s e a c t u a l i z a l a c o o r d e n a d a y n a d a más
p u b l i c v o i d A c t u a l i z a C o o r d e n a d a s () {
c o o r d e n a d a _ y -=10;
}

En cada iteración el bucle del videojuego se actualiza y dibuja un frame. Si en cada


actualización movemos el disparo en dirección hacia arriba 10 píxeles, causaremos que
en una pantalla más pequeña el disparo llegue al borde superior mucho más rápido que
en una pantalla con más resolución.
Piensa en un disparo que se mueve a 10 píxeles por frame. En una pantalla de 1920
píxeles, el disparo tardaría en cruzar toda la pantalla 1920 píxeles/10 píxeles/60 fps=3,2
segundos y en una pantalla de 800 píxeles tardaría 1,3 segundos. Es decir, la velocidad
aumentaría casi al triple.
Por tanto, hay que elegir una estrategia adecuada e independiente del tamaño de la
pantalla. Hasta ahora hemos utilizado la medida de pixeles por frame, pero nos falta
adaptar la velocidad de cada objeto al tamaño de la pantalla variando el número de
pixeles que avanza o retrocede un objeto por segundo según la pantalla sea más o menos
grande.

Si el videojuego en este punto tiene una velocidad adecuada para un tamaño de


pantalla de 1920*1080 píxeles, podemos aplicar una sencilla regla de tres para hacerlo
funcionar en cualquier tamaño de pantalla. Por ejemplo, si el disparo funciona bien con
una velocidad de 10 píxeles en una pantalla de 1920 píxeles, adecuar la velocidad al alto
de pantalla AltoPantalla se calcularía como: veloeidad=10*AltoPantalla/1920.

Podemos generalizar esta fórmula para todos los objetos:


272 Programación en Android

V elo cid a d _a d a p ta d a = v elo cid a d * A lto P a n ta lla /1 9 2 0


De esta manera, modificamos las velocidades del disparo (constructor de la clase
Disparo):

/ / a d a p t a r v e l o c i d a d a l ta m a ñ o d e p a n t a l l a
v e lo c id a d = 1 0 * (j.A lto P a n ta lla )/1 9 2 0 ;

y su método actualizar():

/ / A c t u a l i z a l a co o rd en a d a d e l en em igo co n r e s p e c t o a l a coordenada de l a nave


p u b lic v o id A c tu a liz a C o o r d e n a d a s (){
co o rd e n a d a _ y -= v e lo c id a d ;
}

la velocidad de la nave (Constructor de la clase Juego):

/ /VELOCIDAD_HORIZONTAL a d a p t a d a
V ELOCIDAD_HO RIZO NTAL=AnchoPantalla*l0 / 1 0 8 0 ;

Y la velocidad de los enemigos (Constructores de la clase Enemigo):

v e lo c id a d = (VELOCIDAD_ENEMIGO_TONTO+Nivel) * j u e g o . A l t o P a n t a l l a / 1 9 2 0 ;
v e lo c id a d = (VELOCIDAD_ENEMI GO_INTELIGENTE+Nive1 ) * j u e g o . A l t o P a n t a l l a / 1 9 2 0 ;

y su método actualizar que también hay que modificarlo:

p u b lic v o id A c tu a liz a C o o r d e n a d a s(){


i f (t ipo_enemigo==ENEMIGO_IN TELIGENTE) {
i f (ju e g o .x N a v e > c o o rd en a d a _ x )
co o rd en a d a _ x + = v elo cid a d ;
e l s e i f (ju eg o .x N a v e < coord en ad a_x)
co o r d e n a d a _ x -= v e lo c id a d ;

i f (M a th .a b s(c o o r d e n a d a _ x -ju e g o .x N a v e )« v e lo c id a d )
/ / s i e s t á muy c e r c a s e p o n e a s u a l t u r a
coordenada_x= ju e g o .x N a v e ;

i f ( coordenada_y>=
j u e g o . A l t o P a n t a l l a - j u e g o . e n e m i g o _ l i s t o . g e t H e i g h t ()
&& d i r e c c i o n _ v e r t i c a l = = l )
d ir e c c io n _ v e r tic a l= -l;

i f ( c o o r d e n a d a _ y < = 0 && d i r e c c i o n _ v e r t i c a l = = -l)


d ir e c c io n _ v e r tic a l= l;

co o r d e n a d a _ y + = d ir e c c io n _ v e r tic a l* v e lo c id a d ;
}
e lse {
/ / e l en em igo t o n t o h a c e c a s o om iso a l a p o s i c i ó n de l a n a v e,
//sim p lem en te p u lu la por la p a n ta lla
c o o r d e n a d a _ x + = d ir e c c io n _ h o r iz o n ta l* v e lo c id a d ;
c o o r d e n a d a _ y + = d ir e c c io n _ v e r tic a l* v e lo c id a d ;
Capítulo 5. Programación de videojuegos 273

//C a m b io s de d i r e c c i o n e s a l l l e g a r a l o s b o r d e s de l a p a n t a l l a
i f ( c o o r d e n a d a _ x < = 0 && d i r e c c i o n _ h o r i z o n t a l = = - l )
d ir e c c io n _ h o r iz o n ta l= l;

i f ( c o o r d e n a d a _ x > j u e g o . A n c h o P a n t a l l a - j u e g o . e n e m i g o _ t o n t o . g e t W i d t h ()
Sc& d i r e c c i o n _ h o r i z o n t a l = = l )
d ir e c c io n _ h o r iz o n ta l= -l;

i f (co o rd en a d a _ y > = ju eg o .A lt o P a n t a lla && d i r e c c i o n _ v e r t i c a l ==1)


d ir e c c io n _ v e r tic a l= -l;

i f ( c o o r d e n a d a _ y < = 0 && d i r e c c i o n _ v e r t i c a l = = - l )
d ir e c c io n _ v e r tic a l= l;
}
}

IM P O R T A N T E : Para que esta adaptación de velocidad al tamaño de la pantalla sea


efectiva, cambia todas las variables de velocidades de los objetos a float:

p u b lic f i n a l f l o a t VELOCI DAD_ENEMI GO_INTELIGENTE= 5 ;


p u b lic f i n a l f l o a t VEL0CIDAD_ENEMIG0_T0NT0=2;
p u b lic f lo a t v e lo c id a d ;
p u b lic f lo a t coordenada_x, coordenada_y;

5.8.14. LIBERA RECURSOS


En este punto, tu aplicación ha consumido muchos recursos, ha cargado Bitmaps y
música y es esencial liberarlos invocando al método recycle.

Así no consumirás inútilmente toda la memoria del dispositivo. Por tanto, para
terminar, programa un método donde hagas todas las liberaciones de estos recursos e
invócalo cuando la Sur faceView se destruya.

De esta manera, terminarás el bucle del videojuego y liberarás toda la memoria que
has consumido para la ejecución del videojuego:

p u b lic v o id f i n ( ) {
b u c le .fin ();
m e d i a P l a y e r . r e l e a s e () ;

f o r ( i n t i = 0 ; i<MAX_IMAGENES_FONDO; i + + )
im á g e n e s [i]. r e c y c l e ();

n a v e . r e c y c l e ();
e n e m ig o _ lis to .r e c y c le ();
en em ig o _ to n to . r e c y c l e ();
d is p a r o . r e c y c l e ();
}
@ O verride
p u b li c v o id s u r f a c e D e s t r o y e d (S u rfa ceH o ld er h o ld e r ) {
/ / c e r r a r e l th r e a d y e s p e r a r que a ca b e
b o o le a n r e t r y = tr u e ;
274 Programación en Android

w h ile (retry ) {

try {
f i n () ;
b u c le . j o i n ();
retry = fa lse ;
} c a t c h ( I n t e r r u p t e d E x c e p t i o n e) {}

}
}

5.9. LOS M OTORES P A R A PR O G R A M A C IÓ N


DE VIDEO JUEGOS
Aunque Android incluye muchas características para poder programar videojuegos sin
necesidad de utilizar otras herramientas, cierto es que para programar un juego con sólo
estas herramientas puede llevar muchísimo tiempo.
Para realizar videojuegos más avanzados y complejos en menos tiempo existen los
motores de videojuegos. Estos motores te facilitan la generación de animaciones e
interacciones complejas, detectar colisiones entre las distintas animaciones, te evitan
tener que programar la física de un complejo movimiento, o el cálculo de cómo las luces
y las sombras afectan a los objetos de una escena. Te ayudan con la inteligencia
artificial que necesite tu videojuego y te facilitan tareas como la de incluir sonidos o
trabajar con redes.
Hay todo tipo de motores diseñados para todo tipo de videojuegos,
fundamentalmente agrupándose en dos tipos, en dos dimensiones (típico videojuego
tradicional con pantalla plana), y tres dimensiones donde se tra ta de dar una imagen
mucho más real de la escena que el videojuego plantea.

Videojuego en 2D Videojuego en 3D
Capítulo 5. Programación de videojuegos 275

5.9.1. MOTORES PARA VIDEOJUEGOS EN 2D

Cocos2d es un motor para videojuegos en 2d open source, comenzó siendo un motor


para videojuegos en dos dimensiones para el sistema operativo IOS de los Iphone,
Posteriormente apareció Cocos2d-X, evolucionada en paralelo a cocos2d, que también
permite hacer videojuegos para otras plataformas. Desde aquí surgieron numerosas
versiones de Cocos2d, Cocos2d-X incluye un lenguaje de programación en C + + desde
donde se puede dotar de acción a los objetos y animaciones creados con cocos2d.
Actualmente, incluye también un proyecto llamado Cocos2d-android para crear juegos
específicamente para Android en Java.

Con Cocos2d se pueden programar los mecanismos típicos de los juegos 2d con
muchísima facilidad, por ejemplo:
• Reproducir efectos de sonidos
• Incorporar fuentes de texto y realizar efectos
• Tratamiento y animación de Sprites o pequeñas imágenes con fondo
transparente para incluir en tu videojuego
• Incorporar miles de efectos: Rotar, reflejar, paralaje, escalamiento, tintado,
deslizamiento, saltar.
276 Programación en Android

Pero sin duda, la gran ventaja de Cocos2d es que es open source. Y como buen
proyecto Open Source, el soporte de la comunidad es muy alto. Puedes encontrar la guía
del programador de forma gratuita aquí:
http://cocos2d-x.org/programmersguide/ProgrammersGuide.pdf

5.9.2. MOTORES PARA VIDEOJUEGOS EN 3D


Aunque existen numerosos en el mercado, sin duda, el motor de videojuegos que más
éxito tiene en el mercado es Unity. Unity es un motor cross-platform para la creación no
solo de videojuegos en 3d sino de cualquier tipo de aplicación que contenga gráficos
avanzados, incluidos gráficos en 2d. Unity, al contrario que cocos2d, es propietario, no es
Open Source.
La licencia de Unity tiene una parte gratuita (unity Basic) y una parte profesional
(Unity Pro) que cuesta como unos 3000 dólares. La versión gratuita de Unity viene un
poco limitada, pero puedes obtener la licencia Pro de forma gratuita por 30 días.
En estos 30 días puedes experimientar todas las mejores y plugins que lleva, que
harán de tu videojuego todo un éxito en el mercado. Eso sí, la licencia de Unity te
obliga a que compres la versión pro si has ganado más de 100.000 en el año anterior
aunque, si has ganado ese dinero, probablemente puedas permitirte comprar la licencia
pro.
Capítulo 5. Programación de videojuegos 277

PRÁCTICA 5. VIDEO JUEGO O TRABAJO


Para esta tarea os damos dos alternativas:
A L T E R N A T IV A A: Programa un videojuego (temática libre) con los siguientes
requisitos:

• Debe tener al menos 1 animación


• Debe tener música
• Debe usar la estructura de bucle de videojuego propuesta en la Unidad
• Para los controles debes utilizar eventos multitouch o gestos
• Debes incluir una memoria en formato doc contando el funcionamiento del
videojuego

A L T E R N A T IV A B: Realiza un trabajo sobre programación de videojuegos con el


motor Unity.

Con la alternativa A puedes llegar a obtener hasta 100 puntos, mientras que con la
alternativa B, tan sólo puedes aspirar a 50 puntos. ¡Tú decides!
UNIDAD 6

LOCALIZACIÓN GEOGRÁFICA

CONTENIDOS
6.1 Localización geográfica
6.2 ¿Cómo se ha trabajado con la localización geográfica desde el comienzo
de Android?
6.3 Las nuevas APIs de los servicios de localización
6.4 Sensores

rS

V
280 Programación en Android

6.1. LOCALIZACIÓN G EO G R Á FIC A


Una de las cosas más potentes que se puede hacer con un dispositivo móvil es
localizarlo geográficamente, es decir, obtener sus coordenadas exactas (latitud y
longitud). Del mismo modo que los humanos no queremos recordar que 216.58.210.163
es una de las direcciones IP de www.google.es, tampoco nos apetece recordar que
38° 59' 44" N, I o 51' 21" O (o en decimal 38.995556°, -1.855833°) hace referencia a la
maravillosa ciudad de Albacete. Así que, una vez que tenemos las coordenadas de un
dispositivo móvil preferimos mostrar su ubicación en un mapa, o incluso obtener la
dirección exacta (reverse geocoding). Otros prodigios que se pueden hacer es seguir el
movimiento del dispositivo, o detectar cuándo sale o entra de un área específica
(geofence).
La localización geográfica de un dispositivo móvil puede conseguirse a través de
varias tecnologías, siendo la más conocida la localización por GPS ( Global Positioning
System, Sistema de Posicionamiento Global). Pero también se puede conseguir a través
de las antenas de la red de telefonía móvil o mediante redes inalámbricas WiFi (puntos
de acceso).
En este mundo loco de la informática, donde el cambio continuo está a la orden del
día, y los avances son más rápidos que el Halcón Milenario. Un sistema operativo joven
como es Android no puede quedarse al margen. Además, una característica que lo define
muy bien es su constante innovación. De esta manera, los desarrolladores de Android
han introducido recientemente importantes cambios en la programación de localización
geográfica.
Con el fin de poder entender los nuevos cambios introducidos, empezaremos
estudiando cómo se hacían las cosas hasta ahora. Es más, muchas de los conceptos que
aprenderemos nos serán muy útiles pues se siguen utilizando en las nuevas APIs. A
continuación estudiaremos cómo se debe programar, a partir de ahora, la localización
geográfica.
6.2. ¿CÓMO SE HA T R A B A JA D O CON LA
LOCALIZACIÓN G EO G R Á FIC A D ESD E EL
COM IENZO DE A N D R O ID ?
Desde la API level 1, el paquete android.location con sus clases e interfaces ha sido el
usado para programar cualquier aplicación que requiriese de los servicios de localización.
Se puede especificar qué tipo de tecnología queremos usar o especificar una serie de
requisitos y dejar que Android elija la tecnología más apropiada.
A C T IV ID A D 6.1.

Para obtener más información puedes consultar la documentación del paquete android.location:
https://developer.android.com/reference/android/location/package-summary.html
Capítulo 6. Localización Geográfica 281

Las dos clases principales que usaremos son:


• LocationManager: proporciona acceso a los servicios del sistema de localización.
• LocationProvider: un proveedor de localización proporciona información sobre la
localización geográfica del dispositivo. Además, un proveedor de localización hace
referencia a la tecnología específica usada para averiguar dicha ubicación (GPS,
antenas de la red de telefonía móvil y puntos de acceso WiFi).
Para trabajar con la clase LocationManager no es necesario crear una instancia
(objeto); en su lugar, obtenemos una instancia de la siguiente manera:
C o n t e x t . g e t S y s t e m S e r v i c e ( C o n t e x t . LOCATION_SERVICE)

Por ejemplo:
L o c a t ion M an ager l o c a t io n M a n a g e r ;
(L ocationM anager =
( L o c a t i o n M a n a g e r ) g e t S y s t e m S e r v i c e ( C o n t e x t . LOCATION_SERVICE);

El método getSystemService() devuelve un tipo Object, por eso hacemos un casting a


LocationManager.

6.2.1. ESPEC IFIC A C IÓ N DE PER M ISO S DE A PLIC A C IÓ N


P A R A G E ST IO N A R LA U B IC A C IÓ N DEL DISPO SITIV O
Antes de nada debemos añadir a nuestro archivo de manifiesto
(AndroidManifest.xml) los permisos necesarios:

< u se s-p e r m issio n


a n d r o i d :n a m e= " a n d r o i d . p e r m i s s i o n . ACCESS_FINE_LOCATION"/>
« u s e s -p erm issio n
a n d r o i d : n a m e = "a n d r o i d . p e r m i s s i o n . ACCESS_COARSE_LOCATION"/ >

Básicamente, estos permisos nos permiten especificar el nivel de precisión requerido


para calcular la localización del dispositivo:
• ACCESS_FINE_LOCATION: para una precisión alta.
• ACCESS_COARSE_LOCATION: para una precisión baja.
Aunque más adelante veremos cuando se necesita uno u otro permiso, comentar que
la localización por GPS requiere una precisión alta (fine), mientras que para la
localización a través de la red de telefonía móvil es suficiente con una precisión baja
(coarse).
282 Programación en Android

6.2.2. PR O V EED O R ES DE LOCALIZACIÓN


Para poder obtener una ubicación necesitamos un proveedor de localización, este
puede ser el GPS, la conexión inalámbrica a través de puntos acceso, o la red de
telefonía móvil mediante sus antenas.
La clase LocationManager ofrece, entre otras, las siguientes constantes de tipo String:
• GPS_PROVIDER: nombre del proveedor de localización GPS.
• NETW O RK_PROVID ER: nombre del proveedor de localización para la red
(antenas de la red de telefonía móvil y puntos de acceso WiFi).
¿Cómo conseguimos un proveedor de localización? Pues tenemos dos opciones:
Dejar que Android elija el proveedor más apropiado.
Elegir nosotros mismos el proveedor que queramos o más nos convenga.
Aquí debemos pararnos a pensar en las necesidades de nuestra aplicación. En el caso
de que el tipo de proveedor no sea un requisito específico de la aplicación, entonces nos
decantaríamos por la segunda opción (dejar que Android elija el proveedor). Por otro
lado, si nuestra aplicación depende estrictamente del GPS, no nos queda más remedio
que elegir el proveedor nosotros mismos.

6 .2 .2 .1 . D eja r q u e A n d ro id elija el m ejor p ro v eed o r


Mediante la clase Criteria es posible establecer unos requisitos o criterios en base a
precisión, uso de energía, altitud, velocidad, dirección y coste económico.
Por ejemplo:
Criteria criteria = new Criteria (),-
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(true);
criteria. setBearingRequired (false)
criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW,); //Latitud y longitud
criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH); //Altitud
criteria.setSpeedRequired (false)
criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
criteria.setCostAllowed (true)

El método setCostAllowed() permite establecer si el proveedor tiene permitido


incurrir en algún coste económico derivado de la localización de la ubicación.

A C T IV ID A D 6.2.
P a ra obtener más información puedes consultar la documentación de la clase C r i t e r i a
(h ttp : / / d e v e lo p e r.a n d ro id .c o m /re fe re n c e /a n d ro id /lo c a tio n /C rite ria .h tm l)._______________
Capítulo 6. Localización Geográfica 283

Una vez que hemos decidido nuestros requisitos en cuanto al proveedor de


localización se refiere, tan solo hay que invocar al método getBestProvider(). Dicho
método devuelve el nombre del proveedor que mejor coincide con los requisitos
especificados. Su cabecera es:
p u b lic S tr in g g etB estP ro v id er (C r ite r ia c r it e r ia , b o o le a n en a b led O n ly )

Donde:
• criteria es el objeto de la clase Criteria mediante el cual hemos especificado los
requisitos.
• enabledOnly es un valor booleano que sirve para indicar si queremos que
getBestProvider() devuelva un proveedor que esté actualmente activo (true).
Por el contrario, si no nos importa que el proveedor esté activado o
desactivado este parámetro será false.

6 .2 .2 .2 . In sta n c ia r un p ro v ee d o r d e lo ca liza ció n esp e cífico


Elegir un proveedor en concreto no es complicado. Tan solo debemos tener la
precaución de comprobar si está habilitado (activo) en el dispositivo móvil. Si no lo está,
se le puede informar al usuario e incluso solicitarle que lo active para la correcta
ejecución de la aplicación.
Para hacer esta comprobación, la clase LocationManager nos ofrece el método
isProviderEnabled(). Su cabecera es la siguiente:
p u b lic b o o le a n is P r o v id e r E n a b le d (S tr in g p r o v id e r )

El parámetro provider es una de las constantes tipo String de la clase Location


Manager (explicadas anteriormente): GPS_PROVIDER y NETWORK_PROVIDER.

6 .2 .2 .3 . O b te n ie n d o u n a lis ta d e p ro v ee d o r es
Si en alguna ocasión necesitamos conocer todos los proveedores conocidos, disponibles
o que satisfagan unos determinados requisitos, la clase LocationManager nos ofrece los
siguientes métodos:
• public List<String> getAHProviders()
Devuelve una lista de nombres de todos los proveedores conocidos.
• public List<String> getProviders(boolean enabledOnly)
Devuelve todos los proveedores disponibles dependiendo de los permisos que tenga
la aplicación y de las características del dispositivo móvil.
• public List<String> getProviders(Criteria criteria, boolean enabledOnly)
Igual que el anterior pero filtrando aquellos proveedores que cumplan unos
determinados requisitos.
284 Programación en Android

A C T IV ID A D 6.3.
P a ra obtener más información puedes consultar la documentación de la clase L o c a t i o n M a n a g e r
(h ttp : / /developer.android.com /reference / android /location /LocationM anager.htm l-).

6.2.3. R EC EPC IÓ N DE LA ÚLTIM A U B IC A C IÓ N


Vamos a estudiar una primera aproximación al procedimiento para averiguar la
ubicación de un dispositivo. En este caso recurriremos al método
getLastKnowLocation(), el cual devuelve la última localización obtenida por un
proveedor de localización.
L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
L o c a tio n l o c a l i z a c i ó n =
l o c a t i o n M a n a g e r . g e t L a s t K n o w n L o c a t i o n ( L o c a t i o n M a n a g e r . GPS_PROVIDER);

Es importante no perder de vista que gestLastKnownLocation() tan solo devuelve la


última localización, es decir, no solicita al proveedor de localización (GPS en el código
anterior) que actualice la ubicación. Pudiendo darse el caso de que no exista o esté
completamente desactualizada (por ejemplo, mostrando una ubicación que se solicitó
hace dos semanas).
El método gestLastKnownLocation() devuelve un objeto de la clase Location. Dicha
clase dispone de un conjunto de métodos tipo get, mediante los cuales podemos
consultar todas las propiedades del objeto Location, por ejemplo: la precisión de la
localización devuelta, la dirección, la altitud, la velocidad, etc.
En el siguiente ejemplo se implementa una App que averigua la última posición
conocida y la muestra en un TextView.
1. Especificación de los permisos necesarios. Se usará el GPS como proveedor de
localización, como se ha explicado anteriormente para una precisión alta se necesita
A CCESS_FINE_L O CA TION.
< ? x m l v e r s i o n = " l . 0" e n c o d i n g = " u t f - 8 " ? >
< m a n i f e s t x m l n s : a n d r o i d = "h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d "
p a c k a g e = " c o m .m m c .u ltim a u b ic a c io n " >

■ cu ses- p e r m i s s i o n a n d r o i d :n a m e= " a n d r o i d . p e r m issio n .A C C E S S _ F IN E _ L O C A T I O N " />

< /m a n ife st>

2. Creación del objeto LocationManager y obtención de la última posición conocida.


@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
s u p e r .o n C r e a te (sa v e d ln sta n c e S ta te );
se tC o n te n tV ie w (R .la y o u t . a c t i v it y _ u l t i m a _ u b ic a c i o n ) ;
Capítulo 6. Localización Geográfica 285

L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
lo ca tio n M a n a g er = (L ocation M a n a g er)
g e t S y s t e m S e r v i c e ( C o n t e x t . LOCATION_SERVICE);

S t r i n g p r o v e e d o r = L o c a t i o n M a n a g e r . GPS_PROVIDER;
L o c a tio n lo c a tio n = lo c a tio n M a n a g e r .g e tL a s tK n o w n L o c a tio n (p r o v e e d o r );
}

3. Impresión de los resultados. Observa que dentro del método actualizar Localización,
creado expresamente para mostrar los resultados, se comprueba si el objeto Location es
nuil, lo que significaría que, o bien ha sido imposible encontrar la ubicación (porque el
proveedor no esté disponible), o que nunca se haya actualizado la ubicación (recuerda
que estamos usando getLastKnownLocation).

@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p er.o n C rea te(sa v ed ln sta n ceS ta te);
se tC o n te n tV ie w (R .la y o u t . a c t i v it y _ u l t i m a _ u b ic a c i o n ) ;

L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
lo c a tio n M a n a g e r = (L ocationM anager)
g e t S y s t e m S e r v i c e ( C o n t e x t .LOCATION_SERVICE);

S t r i n g p r o v e e d o r = L o c a t i o n M a n a g e r . GPS_PROVIDER;
L o c a tio n lo c a tio n = lo c a tio n M a n a g e r . g e tL a stK n o w n L o ca tio n (p ro v eed o r);

a c tu a liz a r L o c a liz a c io n (lo c a tio n );


}

p r iv a t e v o id a c t u a l iz a r L o c a li z a c io n (L o ca tio n lo c a t io n ) {
T ex tV iew t = (T e x tV ie w )fin d V ie w B y ld (R .id .u ltim a U b ic a c io n T e x t);

if (lo c a tio n != n u l l ) {
d o u b le l a t i t u d = lo c a t io n .g e t L a t it u d e ();
d o u b l e l o n g i t u d = l o c a t i o n . g e t L o n g i t u d e ()
t . s e t T e x t ( " L a titu d : " + la titu d + " \n L o n g itu d : " + lo n g itu d );
}
e ls e t . s e t T e x t ( "No e n c u e n t r o t u u b i c a c i ó n , ¿dónde e s t á s ? " ) ;
}

Aquí tienes unas capturas de pantalla mostrando la depuración en el emulador (se ha


usado Genymotion):
286 Programación en Android

Latitud: 52.37021500000001 Q
Longitud: 4 .8 9 5 1 6 6 6 6 6 6 6 6 6 6 6

F í ja t e qu e a la d e r e c h a e l G P S e s tá a c tiv a d o

oO G e n y m o tio n _ r

GPS

On

Latitude 52.3702

Longitude 4.8 9 5 1 7 Ip3|


A ltitude 947036 m

A ccuracy

0 m •
Bearing
/ Jk \

0
■V
C a r g a m o s la s c o o r d e n a d a s q u e n o s i n t e r e s a n p a r a qu e e x is ta u n a ú ltim a u b ic a c ió n

Como puedes apreciar en las capturas de pantalla, en Genymotion es necesario


activar el GPS, y a través de él podemos introducir manualmente las coordenadas, o lo
que es más sencillo, usar el mapa (botón MAP) y elegir la ubicación. Esta será
considerada como la última ubicación. Esto se hace para poder depurar la aplicación;
obviamente, en un dispositivo real lo único que sería necesario es tener activado el GPS.

A C T IV ID A D 6.4.
Im plem enta la anterior aplicación y com prueba que solo se carga la últim a posición conocida.
P ara ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
UltimaUbicacion.rar, del m aterial complementario.
Capítulo 6. Localización Geográfica 287

6.2.4. R E C E PC IÓ N DE A C TU ALIZA C IO N ES DE U BIC A C IÓ N


En la mayoría de los casos no será suficiente con conocer la última posición, sino que
deberemos estar atentos a cualquier cambio de ubicación, por ejemplo, si queremos
seguir el rastro del usuario.
La clase LocationManager dispone de los métodos requestLocationUpdatesQ que
solicitan al proveedor de localización cualquier cambio que se haya producido.
Una vez que se haya detectado un cambio de localización hay varias posibilidades:
• Implementar un LocationListener, donde se especifique, entre otras cosas, qué
acciones tomar cuando se actualice la ubicación.
• Implementar un Intent que envíe por broadcast cualquier cambio en la
localización.

Para trabajar con el interfaz LocationListener, hay que implementar los siguientes
métodos:
• public abstract void onLocationChanged (Location location)
Este método es invocado cuando la ubicación ha cambiado.
• public abstract void onProviderDisabled (String provider)
Este método es invocado cuando el proveedor de localización es deshabilitado por
el usuario.
• public abstract void onProviderEnabled (String provider)
Se invoca cuando el proveedor de localización es habilitado por el usuario.
• public abstract void onStatusChanged (String provider, int
status, Bundle extras)
Este método es llamado cuando cambia el estado del proveedor:
- OUT_OF_SERVICE: 0
- TEMPORARILY__UNAVAILABLE: 1
- AVAILABLE: 2

A C T IV ID A D 6.5.
P a ra obtener más información puedes consultar la documentación del interfaz L o c a tio n L is te n e r
(http://developer.android.com /reference/android/Iocation/L ocationL istener.htm lh

El método requestLocationUpdates() está sobrecargado. Vamos a estudiar una de sus


posibilidades:
288 Programación en Android

public void requestLocationUpdates (String provider, long minTime,


float minDistance, L o c a t i o n L i s t e n e r listener)

Donde:
• provider es el nombre del proveedor de localización con el que nos
registraremos para obtener las actualizaciones.
• minTime es el intervalo de tiempo mínimo entre actualizaciones de
localización, en milisegundos.
• minDistance es la distancia mínima entre actualizaciones, en metros.
• Listener es el objeto LocationListener cuyo método
onLocationChanged(Location) será llamado por cada actualización.

Para detener las actualizaciones de ubicación hay que llamar al método


removeUpdates() pasándole como parámetro el objeto LocationListener.
El siguiente ejemplo reúne mucho de lo explicado hasta ahora: (1) se establecen los
permisos necesarios, (2) a través de la clase Criteria se especifican una serie de requisitos
que debe cumplir el proveedor de localización, (3) dejamos que Android elija el mejor
proveedor que cumpla dichos requisitos, (4) se actualiza constantemente la ubicación
actual del dispositivo. ¡Al tajo!

1. Establecimiento de los permisos. A priori no sabemos qué proveedor elegirá Android,


luego establecemos los permisos para alta y baja precisión.

<?xml v e r s i o n = " 1 .0 " e n c o d in g = " u tf-8 " ? s


« m a n i f e s t x m l n s :a n d r o id = " h t t p : / / s c h e m a s . a n d r o i d .c o m / a p k / r e s / a n d r o i d "
p a c k a g e = "c o m . mmc. a c t u a l i z a n d o u b i c a c i o n " >

« u s e s - p e r m i s s i o n a n d ro id :n a m e= " a n d ro id .p erm issio n .A C C E S S _ F IN E _ L O C A T IO N " />


« u s e s - p e r m i s s i o n a n d r o id :n a m e= " a n d ro id .p erm issio n .A C C E S S _ C 0 A R S E _ L 0 C A T I0 N " />

« a p p lic a tio n

«/ a p p l i c a t i o n s
«/man i f e s t s

2. Especificación de requisitos para el proveedor de localización.

@0verride
p r o t e c t e d v o i d o n C r e a t e (Bundle s a vedlnstanceState) {
Capítulo 6. Localización Geográfica 289

su p er.o n C rea te(sa v ed ln sta n ceS ta te);


se tC o n te n tV ie w (R .la y o u t . a c t i v it y _ a c t u a li z a n d o _ u b ic a c i o n ) ;

// O b je to de l a c la se L o ca tio n M a n a g er
L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
lo c a t io n M a n a g e r = (L o ca tio n M a n a g er)
g e t S y s t e m S e r v i c e ( C o n t e x t .LOCATION_SERVICE);

// C r e a c ió n d e l c o n ju n to de r e q u i s i t o s que
// deb e c u m p lir e l p r o v e e d o r de l o c a l i z a c i ó n
C r ite r ia c r ite r ia = n ew C r i t e r i a ( ) ;
c r i t e r i a . s e t A c c u r a c y ( C r i t e r i a . ACCURACY_FINE);
c r i t e r i a . s e t P o w e r R e q u i r e m e n t ( C r i t e r i a . POWER_LOW);
c r it e r ia . se tA ltitu d e R e q u ir e d (fa lse );
c r it e r ia .setB ea rin g R eq u ired (fa lse);
c r i t e r i a . se tS p e e d R e q u ir e d (f a l s e ) ;
c r i t e r i a . se tC o stA llo w e d (tr u e );

En este caso hemos establecido los siguientes requisitos:

• Precisión alta para la latitud y longitud.


s e t A c c u r a c y ( C r i t e r i a . ACCURACY_FINE);

• Consumo bajo de energía.


s e t P o w e r R e q u i r e m e n t ( C r i t e r i a . POWER_LOW);

• El proveedor de localización no tiene que proporcionar información sobre


altitud, dirección y velocidad.
se tA ltitu d e R e q u ir e d (fa ls e ) ;
setB ea rin g R eq u ired ( f a l s e ) ;
setS p eed R eq u ired ( f a l s e ) ;

• Se permiten costes económicos asociados a la localización.


se tC o stA llo w e d ( t r u e ) ;

3. Se deja que Android elija el mejor proveedor.

@ O verride
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
su p e r .o n C rea te(sa v ed ln sta n ceS ta te) ;
se tC o n te n tV ie w (R .l a y o u t . a c t i v it y _ a c t u a li z a n d o _ u b ic a c i o n ) ;

// O b jeto de l a c la se L o ca tio n M a n a g er
L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
lo c a tio n M a n a g e r = (L ocation M an ager)
290 Programación en Android

g etS y stem S erv ice(C o n tex t.L O C A T IO N _ S E R V IC E );

// C rea ció n d e l co n ju n to de r e q u i s i t o s que


// deb e c u m p lir e l p r o v e e d o r de l o c a l i z a c i ó n
C r ite r ia c r ite r ia = n ew C r i t e r i a O ;
c r ite r ia .se tA c c u r a c y (C r ite r ia ,A C C U R A C Y _ F IN E );
c r i t e r i a . s e t P o w e r R e q u i r e m e n t ( C r i t e r i a . POWER_LOW);
c r ite r ia .s e tA ltitu d e R e q u ir e d (fa lse );
c r ite r ia .s e tB e a r in g R e q u ir e d (fa lse );
c r i t e r i a . setS p eed R eq u ired (fa lse);
c r i t e r i a . se tC o stA llo w e d (tr u e );
S tr in g proveedor = lo c a tio n M a n a g e r .g e tB e s tP r o v id e r (c r ite r ia , tru e);
}

Llegados a este punto puede que te hayas preguntado: ¿qué ocurre si Android no
encuentra un proveedor que cumpla nuestros requisitos? Bueno, pues que Android,
sabiamente, empieza a suavizar nuestros requisitos hasta encontrar un proveedor. El
orden que sigue es:
• Requisitos de energía.
• Precisión.
• Dirección (orientación).
• Velocidad.
• Altitud.

4. A c tu a liz ació n c o n s ta n te de la u b icació n a c tu a l del disp o sitiv o .

@ 0 v errid e
p r o t e c t e d v o i d o n C r e a t e (B u n d le s a v e d l n s t a n c e S t a t e ) {
s u p e r .o n C r e a te (sa v e d ln sta n c e S ta te );
se tC o n te n tV ie w (R .l a y o u t . a c t i v it y _ a c t u a li z a n d o _ u b ic a c i o n ) ;

// O b je to de l a c la se L o ca tio n M a n a g er
L o ca tio n M a n a g er lo c a t io n M a n a g e r ;
lo c a tio n M a n a g e r = (L ocationM anager)
g e t S y s t e m S e r v i c e ( C o n t e x t . L0CATI0N_SERVIC E);

// C rea ció n d e l co n ju n to de r e q u i s i t o s
// que debe c u m p lir e l p r o v e e d o r de l o c a l i z a c i ó n
C r ite r ia c r ite r ia = new C r i t e r i a O ;
c r i t e r i a . s e t A c c u r a c y ( C r i t e r i a . ACCURACY_FINE);
c r i t e r i a . s e t P o w e r R e q u i r e m e n t ( C r i t e r i a . POWER_LOW);
c r i t e r i a . se tA ltitu d e R e q u ir e d ( f a l s e ) ;
Capítulo 6. Localización Geográfica 291

c r i t e r i a . setB ea rin g R eq u ired ( f a l s e ) ;


c r i t e r i a . setS p eed R eq u ired (fa lse);
c r i t e r i a . se tC o stA llo w e d (tr u e );
S tr in g proveedor = lo c a tio n M a n a g e r .g e tB e s tP r o v id e r (c r ite r ia , tru e);

// Nos r e g i s t r a m o s co n e l p r o v e e d o r de l o c a l i z a c i ó n
// para o b ten er la s a c tu a liz a c io n e s
// Cada 3 s e g u n d o s y c a d a 5 m e t r o s
lo c a tio n M a n a g e r . r e q u e stL o c a tio n ü p d a te s (p r o v e e d o r ,
3000, 5, lo c a tio n L iste n e r );
}

Lo único que queda es implementar el Listener (LocationListener):

p r iv a te f i n a l L o ca tio n L isten er lo c a tio n L is te n e r = n ew L o c a t i o n L i s t e n e r () {


@ 0 v errid e
p u b lic v o id o n L o ca tio n C h a n g ed (L o ca tio n lo c a t io n ) {
//L a u b i c a c i ó n ha cam b iad o,
// lu e g o hay que a c t u a l i z a r la in fo r m a c ió n
// m ostrada a l u s u a r io
a c tu a liz a r L o c a liz a c io n (lo c a tio n );
}
© O v errid e
p u b lic v o id o n S ta tu sC h a n g ed (S trin g p r o v id e r , in t sta tu s, B u n d le e x t r a s ) {}
© O v errid e
p u b lic v o id o n P r o v id e r E n a b le d (S tr in g p r o v id e r ) {}
© O v errid e
p u b lic v o id o n P r o v id e r D isa b le d (S tr in g p r o v id e r ) {}
};

// E s te m étodo m u e str a l o s d a t o s en un T extV iew


p r iv a t e v o id a c t u a l iz a r L o c a li z a c io n (L o ca tio n l o c a t i o n ) !
T ex tV iew l o c a l i z a c i o n T e x t ;
lo c a liz a c io n T e x t = (T e x tV ie w )fin d V ie w B y ld (R .id .L o c a liz a c io n T e x t);

S trin g l a t L o n g = "No s e h a e n c o n t r a d o n i n g u n a l o c a l i z a c i ó n " ;


if (lo c a tio n != n u l l ) {
d o u b le l a t = lo c a t io n .g e t L a t it u d e ();
d o u b le ln g = l o c a t i o n . g e t L o n g it u d e ();
la tL o n g = " L a titu d : " + la t + " \n L o n g itu d : " + ln g ;
}

l o c a l i z a c i o n T e x t . s e t T e x t ( "Tu p o s i c i ó n a c tu a l e s:\n " + la tL o n g );


}
292 Programación en Android

A C T IV ID A D 6.6.
B asándote en el ejemplo anterior im plem enta una aplicación cuya interfaz se componga de los
siguientes widgets:
• B utton para iniciar las actualizaciones de ubicación.
• B utton para detener las actualizaciones de ubicación.
• TextView para m ostrar la ubicación (longitud y latitud).
La aplicación deberá m ostrar un mensaje (por ejemplo m ediante un Toast) cuando el usuario
deshabilite el proveedor de contenidos, y otro mensaje cuando lo vuelva a habilitar.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
ActualizandoUbicacion.rar, del m aterial complementario.

6.3. LAS NUEVAS APIs DE LOS SERVICIOS DE


LOCALIZACIÓN
Lo estudiado hasta el momento nos ha permitido, entre otras cosas, ser conscientes de
las distintas tecnologías para obtener una ubicación (GPS, puntos de acceso WiFi,
antenas de la red de telefonía móvil), y de los permisos necesarios en cada caso.
Desde Android Developer se recomienda encarecidamente implementar las
aplicaciones usando las APIs de localización de Google Play Services, y abandonar las
APIs del framework de localización de Android (paquete android.location).
Las APIs de localización de Google Play Services ofrecen nuevas mejoras y
características. Algunas de ellas son:
• Proveedor de localización Fused (Fused Location Provider) que sustituye a los
anteriores proveedores de localización (GPS_PROVIDER,
NETWORK_PROVIDER, PASSIVE_PROVIDER). Este nuevo proveedor de
localización maneja toda la tecnología subyacente y nos ofrece la mejor
localización de acuerdo con nuestras necesidades (similar a los requisitos que se
establecen con la clase Criteria).
• Independientemente de la finalidad de nuestra aplicación (reconocimiento de la
actividad del usuario, geofences, obtención de direcciones, etc.), la gente de
Android se ha empleado a fondo en mejorar considerablemente el uso de la
batería, minimizando el consumo de batería por parte de nuestras aplicaciones
basadas en las APIs de Google Play Services.
Para poder testear y depurar las aplicaciones usando el SDK de Google Play Services,
se necesita:
• Un dispositivo con Android 2.3 o superior que incluya Google Play Store (más
adelante se explica cómo hacer esta comprobación en el dispositivo).
Capítulo 6. Localización Geográfica 293

• Un emulador con las Google Apps instaladas y con una versión de Android
4.2.2 o superior (más adelante se explica cómo conseguir esto en Genymotion).

6.3.1. GOOGLE PLA Y SERVICES


El primer paso es comprobar si está instalado el SDK de Google Play Services. Para
ello, abrimos el SDK Manager y en la carpeta Extras nos fijamos si está instalado:
Google Play Services y Google Repository. En caso de que no lo esté, procedemos a su
instalación.

4- Android SDK Manager _ □


Packages Tools
SDK Path: C:\Users\Mwolo\AppData\Local\Android\5 dk
Packages
A
<£t Name API Rev. Status
□ £ ; Android 32 (AP113)
D C S Android 3.1 (AP112)
> [ ] [ ^ Android 3.0 (AP111)
. □ ! £ Android 2.3.3 CAP110)
> D C * Android 2 2 (API E)
0 Android 2.1 (API 7)
i> □ £ £ Android 1.6 (API 4)
■ D C S Android 1.5 (API 3)
a Q Extras
0 Q Android Support Repository 11 Bp- Installed
0 O Android Support Library 21.0.3 Installed
F~1 Ü Goodie P lay services f o r F r o y o ^ 12 Í N o t in sta lled
0 j¡3 | Google Play services 22 T ^ ñ s ta ííe ^ ^
0 | 3 Google Repository 15 & Installed 1
J 13 Tooele May ÁhH N o t in sta lle d
0 O Google P lay Billing Library 5 N o t installed
0 £3 Goog le P lay Licensing Library 2 N o t in sta lled
0 0 A n d ro id A u to A P I Sim ulators 1 N o t in sta lled
0 0 Google USB Driver 11 Installed
0 0 Google W eb D river 2 N o t in sta lled
0 O Intel *86 Emulator Accelerator (HAXM installer) 52 Installed
V

Show: @ Updates/New @ Installed Select New or Updates: Installpackages...

0 Obsolete Deselect All Delete packages...

Done loading packages.


O w

A n d r o id S D K M a n a g e r

6 .3 .1 .1 . C ó m o añ ad ir G o o g le P la y S e rv ices a tu p r o y e c to
Para que nuestras aplicaciones puedan disponer de Google Play Services tenemos que:
añadir una dependencia al fichero build.gradíe, sincronizar el proyecto con los ficheros
Gradle (Sync Project with Gradle Files) y añadir una nueva etiqueta al manifiesto de
Android. ¡Empecemos!
1. A ñadir una dependencia al fichero build, gradle. Observarás que existen varios
ficheros con este nombre, el primero que puedes ver en la imagen corresponde al
294 Programación en Android

proyecto, ese NO hay que editarlo. Luego, existe un fichero build.gradle por cada
módulo, ese es el que hay que editar.

ü r Android O T

► C ia p p
▼ •v Gradle Scripts
•s build.gradle' Project: LocationAddress)
build.gradle (Module app)
0 proguard-rules.pro ■ProGuard Rules for app)
fill gradle.properties (Project Properties)
settings.gradle Project Settings)
[ill local,properties (SDK Location)

build, gradle

Hay que añadir una nueva regla de compilación dentro de dependencias


(dependencies) para la última versión de Google Play Services.

a p p ly p lu g in : 'c o m .a n d r o id .a p p lic a tio n '

d e p e n d e n c ie s {
c o m p ile 'c o m .a n d r o id .s u p p o r t:a p p com p at-v7: 2 1 .0 .3 '
co m p ile ' c o m .g o o g le . a n d r o id .g m s :p l a y - s e r v i c e s : 6 . 5 . 8 7 '
}
Es importante cambiar el número de versión (en el Ejemplo 6.5.87) cada vez que se
actualice Google Play Services.

2. Guardar los cam bios y Sync P ro je c t w ith G radle Files.

File Edit View Navigate Code Analyze Refactor Build Run Tools VCS Window Help

CD H £3 X ¡5 & O. & 4» *8 'S'»PP- H U G: j i 1 l a * ?


Capítulo 6. Localización Geográfica 295

3. Añadir una nueva etiqu eta al m anifiesto de A ndroid dentro del elem ento
<application>
« a p p lic a tio n ?

< m e t a - d a t a a n d r o i d :n a m e= " com . g o o g l e . a n d r o i d . gms . v e r s i o n "


a n d r o i d :v a l u e = " @ i n t e g e r / g o o g l e _ p l a y _ s e r v i c e s _ v e r s i o n "
/>
« /a p p lic a tio n ?

6 .3 .1 .2 . C o m p ro b a r la c o m p a tib ilid a d de G o o g le P la y
S erv ices en e l d isp o sitiv o
Google Play Services se actualiza automáticamente a través de G oogle P lay Store
en dispositivos con Android 2.3 o superior. Esto se hace mediante la distribución de un
APK.
Sin embargo, es imposible conocer por adelantado el estado de cada dispositivo. De
modo que, en nuestras aplicaciones, se deberá comprobar la compatibilidad con Google
Play Services antes de hacer uso de sus servicios.
Aquí tenemos dos posibilidades:
• Tal como se explicará más adelante, para establecer la conexión con Google
Play Services, se deben implementar varias interfaces. Una de ellas es
OnConnectionFailedListener, de la cual hay que programar el método
onConnectionFailed(). En caso de que la versión del APK de Google Play
Services no esté actualizada, el método onConnectionFailed() recibirá uno de
los siguientes códigos de error: SERVICE_MISSING,
SERVICE_VERSION_UPDATE_REQUIRED, o SERVICE_DISABLED.
• O tra posibilidad es invocar al método isGooglePlayServicesAvailable() dentro
del método onResume(). Los códigos devueltos son:
• SUCCESS si la versión del APK de Google Play Services está actualizada.
En caso contrario pueden ser devueltos los códigos:
SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED,
o SERVICE_DISABLED.
Si se detecta que la versión del citado APK de Google Play está desactualizada, es
recomendable informar al usuario para que proceda a su instalación y/o actualización.

A C T IV ID A D 6.7.
P a ra obtener más información puedes consultar el siguiente enlace:
https://developer.android.com /google/plav-services/setup.htm l
296 Programación en Android

6 .3 .1 .3 . C ó m o te s te a r las a p lica c io n es
Para poder testear nuestras aplicaciones de geolocalización en Genymotion (el
emulador usado a lo largo de todos los capítulos), es necesario instalar las Google Apps
en los dispositivos virtuales (o máquinas virtuales) creados. Si no, no se podrá establecer
la conexión con Google Play Services.
En Internet hay multitud de sitios donde puedes encontrar las Google Apps, más
conocidas como gapps. Debes descargar las que correspondan con la versión de tu
dispositivo virtual. A continuación podrás ver el proceso de instalación para Android 4.3
(JellyBean), siendo el paquete descargado un zip con el nombre “gapps-jb-20130813-
signed.zip”.
Una vez descargado el paquete gapps, tan solo hay que arrastrarlo al dispositivo
virtual:

Genymotion for personal use - G oogle Nexus 4 - 4... ~ D

- 12:27

APPS WIDGETS

API D em os B row ser C alculator C alendar C am era

0 & “ & t

S e ttin g s S u p e ru se r Voice Dialer

bD C23 CD

Verás que se inicia la transferencia, durante la cual te aparecerá algún aviso o error:
Capítulo 6. Localización Geográfica 297

Fiie ¡nstailation warning


___________________________________________
File gapp5-jb-20130813-Signed.zip seems to be a flashable archive. Do you
want to flash it to the virtual device?
Caution: this operation may corrupt the virtual device.

OK Cancel

A1 final del proceso se mostrará el siguiente mensaje:

File installation result £ S3R

Ríe gapps-jb-20130813-signedzip has been flashed successfully. Please


restart the virtual device.

OK I

En este momento es recomendable reiniciar el dispositivo virtual.


Una vez que se han instalado las Google Apps, tienes que sincronizar tu dispositivo
con una cuenta de Google. Es probable que mientras añades la cuenta de Google,
aparezca un mensaje en pantalla informando de que Google Play Services se ha
detenido.

i \ J

U nfortunately, G oogle Play


se rv ic e s h a s stopped.

OK

1 *
1- )
0

*-=> I
£Z2¡ c rT

Tan solo nos queda solventar este pequeño problema. Para ello, inicia Google Play y
abre las opciones de configuración (Settings):
Al final de las Settings está Build version, sobre la cual debes hacer clic un par de
veces para que se actualice.
298 Programación en Android

late apps
Auto-updale apps over Wi-fi orWy

Add icon to Home screen


For new apps

Clear local search history


Remove searches you have performed from this
device

USER CONTROLS

Content filtering
Set the content filtering level to restrict apps that
can be downloaded

Require authentication for purchases


Every 30 minutes

Open source licenses


License details for open source software
© Open source licenses
License details for open source software

Build version Build version


version .4.3.11 Version 5 2-13

G o o g le A p p s s i n a c t u a l i z a r G o o g le A p p s a c tu a l iz a d a s

Hecho esto, hay que reiniciar el dispositivo. Observarás que ya no aparece el mensaje
indicando que Google Play Services se ha detenido.

6.3.2. O B TEN C IÓ N DE LA Ú LTIM A U BIC A C IÓ N

Antes de estudiar este apartado te recomendamos que releas el apartado “6.2.3.


RECEPCIÓN DE LA ÚLTIMA UBICACIÓN”.
Una vez especificados los permisos en el manifiesto de Android (si no recuerdas cómo
se hace vuelve a leer el apartado “6.2.1. ESPECIFICACIÓN DE PERMISOS”), hay que
establecer una conexión con Google Play Services. Para ello es necesario crear un objeto
de la API cliente de Google:
©Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState) ;
setContentView(R.layout.activity_ultima__ubicación);

// Se crea un objeto de la API cliente de Google Play Services


googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
//Se añade la API de los servicios de localización
.addApi(LocationServices.API)
.build();
}
Al añadir ConnectionCallbacks y OnConnectionFailedListener, la clase debe
implementar dichos interfaces, lo que obliga a programar los siguientes métodos:
Capítulo 6. Localización Geográfica 299

Select Methods to implement

«i com. google.android.gms. com mon.api. GcogleApiClient.Connection Callbacks

# com,google.android.gms,common.api,GoogleApiClient.OnConnectionFailedUstener
jm^^^22!^32^SS!22í^^2!^SS!^S!ii2!221
I__I CopyJavaDoc

0 Insert ©Override m2 K Cancel

E izasssg
M é to d o s a i m p l e m e n t a r d e los i n te r f a c e s C o n n e c tio n C a ü b a c k s y O n C o n n e c tio n F a ile d L is te n e r

El callback onConnected() es invocado cuando el cliente está preparado. Así que es


desde dicho método donde se deberá llamar al método getLastLocation(), pasándole el
objeto creado previamente de la clase GoogleApiClient.

@ O verride
p u b l i c v o i d o n C o n n e c t e d (B u n d le b u n d le ) {
lo c a tio n = L o c a tio n S e r v ic e s . F u se d L o c a tio n A p i.g e tL a stL o c a tio n (
g o o g le A p iC lie n t);
S tr in g u ltim a U b ica cio n S trin g ;

if (lo c a tio n != n u l l ) {
lo n g itu d = l o c a t i o n .g e tL o n g itu d e ();
la titu d = lo c a t io n .g e t L a t it u d e ();
u ltim a U b ic a c io n S tr in g = "Tu ú l t i m a u b i c a c i ó n e s : \ n "
+ " L a titu d : " + la titu d + " \n "
+ " L o n g itu d : " + lo n g itu d ;
u b ic a c io n T e x t. se tT e x t(u ltim a U b ic a c io n S tr in g ) ;
}
}

En los métodos:
• onConnectionSuspended() se puede indicar que la conexión se ha suspendido e
intentar una reconexión.
• onConnectionFailed() se puede mostrar que la conexión ha fallado indicando el
motivo del error. Este es el método comentado en el apartado donde se ha
explicado cómo comprobar la compatibilidad de Google Play Services en el
dispositivo.
300 Programación en Android

Tan solo queda establecer la conexión y cerrarla cuando sea oportuno. Eso se puede
hacer dentro de los métodos onStart() y onStop(). ¿Te acuerdas del ciclo de vida
completo de una actividad? Pues el ciclo de vida visible comienza con el método
onStart() y termina con onStop(). Recuerda que una aplicación es visible aunque no
tenga el foco.

@ O verride
p r o te c te d v o id o n S ta rtO {
su p e r . o n S ta r t();
g o o g le A p iC lie n t. c o n n e c t();
}

@ 0 v errid e
p r o t e c t e d v o id on S top O {
su p e r .o n S to p ();
if (g o o g le A p iC lie n t. is C o n n e c t e d ()) {
g o o g le A p iC lie n t. d is c o n n e c t();
}
}

El resultado debería ser algo así:

Genymotion for personal use - Google Galaxy._


é £

UltimaUb¡cacion_v2

Tu última ubicación es:


Latitud: 39.3919216
Longitud: -3.2220183

A C T IV ID A D 6.8.
Desarrolla una aplicación que m uestre la últim a posición conocida.
P ara ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
U ltim aU bicacion_v2.rar, del m aterial com plem entario
Capítulo 6. Localización Geográfica 301

6.3.3. R E C EPC IÓ N DE A C TU ALIZA C IO N ES DE U B IC A C IÓ N


Antes de estudiar este apartado te recomendamos que releas el apartado “6.2.4.
RECEPCIÓN DE ACTUALIZACIONES DE UBICACIÓN”.
Ya sabes cómo averiguar la última ubicación. Sin embargo, si nuestra aplicación
necesita actualizar constantemente la ubicación del dispositivo, está claro que con
obtener la última localización conocida no es suficiente. El proceso general para recibir
actualizaciones periódicas de la ubicación del dispositivo es el siguiente: especificar los
permisos necesarios, crear y establecer la conexión mediante GoogleApiClient, crear una
petición de actualizaciones de ubicación, lanzar dicha petición, implementar un Listener
para detectar actualizaciones y por último detener las actualizaciones cuando sea
oportuno.
Los dos primeros pasos (especificación de permisos y crear/establecer la conexión
mediante GoogleApiClient) son exactamente iguales que los estudiados en el apartado
anterior (“Obtención de la última ubicación”). De modo que continuaremos con la
programación de las actualizaciones.

1. C re a r u n a p e tició n de ubicación. La clase en cuestión es LocationRequest. A


continuación puedes leer el código necesario para crear dicha petición.

// Se c r e a un o b j e t o de l a c la se L o ca tio n R eq u est
p r iv a te v o id crea rL o ca tio n R eq u est(){
lo c a tio n R e q u e st = new L o c a t i o n R e q u e s t ( ) ;
// Se e s t a b le c e el in t e r v a lo para
// r ecib ir la s a c tu a liz a c io n e s de u b ic a c ió n (en m i l i s e g u n d o s )
lo c a t io n R e q u e s t. s e t l n t e r v a l (8000);

// Se e s t a b le c e el r i t m o más r á p i d o a l q u e s e r e c i b i r á n a c t u a l i z a c i o n e s
lo c a tio n R e q u e s t. s e t F a s t e s t l n t e r v a l (4000);

// Se e s t a b le c e la p r io r id a d de l a p e t i c i ó n
l o c a t i o n R e q u e s t . s e t P r i o r i t y ( L o c a t i o n R e q u e s t . PRIORITY_HIGH_ACCURACY);

Acuérdate que ya no se usa la clase Criteria para elegir el mejor proveedor, ahora el
proveedor usado es Fused Location Provider.
Aunque el código es muy intuitivo, la clase LocationRequest tiene algunos detalles
que conviene conocer. Por un lado están los métodos que permiten establecer unos
“criterios” para la petición:
• setlnterval() establece cada cuanto tiempo (en milisegundos) se quieren recibir
las actualizaciones. Es muy importante tener claro que este intervalo de tiempo
302 Programación en Android

no es exacto, pues se pueden recibir actualizaciones más rápido (en caso de que
otra aplicación solicite actualizaciones a un ritmo superior) o más despacio
independientemente del valor fijado con este método. Tan solo se especifica el
intervalo deseado para recibir las actualizaciones. Una connotación muy
importante es que cuanto menor sea el intervalo de tiempo, mayor será el
consumo de batería. Así que elige sabiamente este valor.
• setFastestInterval() establece el intervalo de tiempo (en milisegundos) más
rápido para recibir actualizaciones. A diferencia de setlnterval() este valor es
exacto, es decir, nunca se recibirán actualizaciones a un ritmo superior al
establecido con setFastestInterval(). Con este método se suele establecer un
valor menor que con setlnterval().
• setPriorityO establece qué tipo de tecnología (GPS, puntos de acceso WiFi o
antenas de telefonía) se usará. Además sirve para controlar el consumo de
batería requerido. Esto se hace en base a unas constantes de la clase
LocationRequest.

Y por otro lado, tenemos las constantes que permiten especificar qué precisión
deseamos, así como el consumo de batería asociado:
• PRIORITY__BALANCED_PO W E R_A CCURA CY. Para un consumo de
batería bajo con una precisión de aproximadamente 100 metros. En este caso
lo más probable es que se usen puntos de acceso WiFi y antenas de telefonía
para averiguar la ubicación.
• PR IO RITY_H IG H _AC C U RAC Y. Para una precisión alta, luego el consume
de batería será elevado. La tecnología usada, probablemente será el GPS.
• P R IO R ITY_L O W _P O W E R . Para un consumo mínimo de batería y,
obviamente, una precisión muy baja (de alrededor de 10 kilómetros).
• P R IO R ITY_N O _P O W E R . Para un consumo nimio de batería. Solo se
recibirán actualizaciones cuando otras aplicaciones las soliciten.

2. Lanzar (iniciar) la petición de actualizaciones. Para ello se debe invocar al


método requestLocationUpdates() dentro del callback OnConnected(). Recuerda que
dicho callback es invocado cuando el cliente está preparado (ha establecido la conexión).

@ O verride
p u b l i c v o i d o n C o n n e c t e d (B u n d le b u n d le ) {
L o c a t io n S e r v ic e s . F u sed L o ca tio n A p i. r e q u e s tL o c a tio n U p d a te s (
g o o g le A p iC lie n t, lo c a tio n R e q u e st, th is);
}
Capítulo 6. Localización Geográfica 303

Observa que se le pasa como parámetros el objeto de la clase GoogleApiClient y la


petición de ubicación.

3. Im plem entar el Listener para detectar actualizaciones en la ubicación. Una


vez que se ha creado la petición y ha sido lanzada, tan solo queda escuchar cambios en
la ubicación. Para conseguir esto se debe implementar el interfaz LocationListener, el
cual nos obliga a sobrescribir el método onLocationChanged(), dicho método es llamado
cuando se detecta cualquier cambio en la localización.

I__! CopyJavaDoc

0 Insert ©Override OK Cancel

MSBSUBIggWB
L o c a t i o n L is te n e r , m éto d o o n L o c a tio n C h a n g e d ( )

@ 0 v errid e
p u b lic v o id o n L o ca tio n C h a n g ed (L o ca tio n lo c a t io n ) {
S tr in g U b ic a c io n S tr in g ;
T ex tV iew u b i c a c i o n T e x t = (T ex tV iew )
fin d V ie w B y ld (R .id . u b ic a c io n te x tV ie w );
d o u b le lo n g it u d , la titu d ;
if (lo c a tio n != n u l l ) {
lo n g itu d = lo c a t io n .g e t L o n g it u d e ();
la titu d = lo c a t io n .g e t L a t it u d e ();
U b i c a c i o n S t r i n g = "Tu u b i c a c i ó n a c t u a l e s : \ n "
+ " L a titu d : " + la titu d + " \n "
+ " L o n g itu d : " + lo n g itu d ;
u b ic a c io n T e x t. se tT e x t(U b ic a c io n S tr in g );
}
}

4. ¿Cuándo detener las actualizaciones de ubicación? Habrás observado que


constantemente hacemos referencia al consumo de batería, tratando de minimizarlo al
máximo. Y tú, como usuario eres consciente de que la localización geográfica agota
304 Programación en Android

rápidamente la batería de tu móvil, ¿verdad? Pues entonces, esta pregunta se puede


responder con otra pregunta: ¿para qué queremos estar recibiendo actualizaciones si
nuestra aplicación no tiene el foco? No se habrá detenido, pero se ha pausado. Pues
justo ahí es donde se deben detener las actualizaciones.

@ O verride
p r o te c te d v o id onP auseO {
su p e r . on P au se();
L o c a t io n S e r v i c e s . F u sed L o ca tio n A p i. rem o v eL o ca tio n U p d a tes(
g o o g le A p iC lie n t, th is);
}
Si se detienen las actualizaciones cuando la aplicación pierda el foco, se deberán
reiniciar cuando lo vuelva a tener.

@ 0 v errid e
p u b l i c v o i d onR esum et) {
s u p e r . onR esum e() ;
L o c a t io n S e r v ic e s . F u sed L o ca tio n A p i. r e q u e s tL o c a tio n U p d a te s (
g o o g le A p iC lie n t, lo c a tio n R e q u e st, t h i s ) ;
}

A C T IV ID A D 6.9.
Revisa tus conocimientos sobre el ciclo de vida de una aplicación. Observando los dos últimos
apartados, compara cuando se inicia/detiene la conexión GoogleApiClient, y cuando se
detiene/inicia la petición de actualizaciones.

A C T IV ID A D 6.10.
Programa una aplicación que muestre constantemente la posición (ubicación) actual del
dispositivo. Observa el interfaz que la aplicación debe tener:
Genyrnotion !of personai use - Google Galaxy.. “ ~ Gcnymotjun loi pcnonet uh Google G jW y - °
I£ 1 1.45 1 * L* 9, |1 4b
B B
Actual¡zandoUbicacion_v2 : ActualizandoUbicacion_v2 :
T
I u ubicación actual es
Hello world1
Q 1 Latitud' 3872569S9 Q
longitud -2.0009983

Iniciar Actualizaciones

Detener Actualizaciones

Para ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
ActualizandoUbicacion_v2.rar, del material complementario.
Capítulo 6. Localización Geográfica 305

6.3.4. VISUALIZ A C IÓ N DE LA D IR EC C IÓ N DE UNA


U B IC A C IÓ N
Estudiando los dos apartados anteriores (averiguar la última ubicación y recepción de
actualizaciones) habrás comprobado que las cosas se hacen de un modo diferente con las
nuevas APIs de los servicios de localización, ¿verdad? Nuestra intención en este
apartado es cómo conseguir una dirección postal (la de toda la vida) partiendo de unas
coordenadas (latitud/longitud), lo que se conoce como “ reverse geocoding”. Traducir
una dirección a coordenadas (latitud/longitud) sería “geocoding”.
Para la explicación que viene a continuación, se considera que ya están
implementados en nuestra aplicación los siguientes puntos:
• Permisos necesarios en el Manifiesto de Android.
• Objeto GoogleApiClient con su respectiva conexión y desconexión.
• También se usara el método onConnected().
En caso de que no tengas del todo claro estos aspectos, te recomendamos que vuelvas
a estudiar los dos apartados anteriores. Lo que viene a continuación, sin ser complicado,
puede resultar confuso.

¡Bueno! Para poder conseguir una dirección “de toda la vida” partiendo de una
latitud y longitud, se puede partir de la última ubicación, calcular en ese preciso
momento donde nos encontramos, pedir al usuario unas coordenadas, etc. En nuestro
ejemplo partiremos de la última ubicación, así que cuando vayas a depurarlo asegúrate
de que existe dicha última ubicación.
El proceso de conseguir una dirección puede resultar costoso en tiempo, luego no
conviene que se ejecute desde el hilo principal de la aplicación. Para solucionar este
problema se usará la clase IntentService. Los clientes (la actividad principal en nuestro
ejemplo) envían las peticiones con el método startService(Intent). Una vez que el
servicio se ha iniciado, se detendrá por sí mismo cuando haya terminado su tarea.
Todas las peticiones son atendidas en el mismo hilo, lo que puede ser lento, pero no
bloquea el hilo principal de la aplicación que lo inició.

1. A ntes de nada, hay que declarar el In ten tS ervice en el m anifiesto de


Android.
« m a n ife s t x m ln s : a n d r o id = " h t t p : / / s c h e m a s . a n d r o i d .c o m / a p k / r e s / a n d r o i d "
p a c k a g e ="c o m . mmc. o b t e n i e n d o d i r e c c i o n " >
« u s e s -p erm issio n
a n d r o i d : n a m e = "a n d r o i d . p e r m i s s i o n . ACCESS_FINE_L0CATI0N"/ >
« a p p lic a tio n
306 Programación en Android

« m e t a - d a t a a n d r o i d : n a m e = "c o m . g o o g l e . a n d r o i d . g m s . v e r s i o n "
a n d r o id :v a lu e = " @ in te g e r /g o o g le _ p la y _ se r v ic e s_ v e r sio n " />
« serv ice
a n d ro id :n a m e= " . A v e r ig u a r D ir e c c io n ln t e n t S e r v ic e "
a n d r o i d : e x p o r t e d = "f a l s e " / >
« /a p p lic a tio n s
« /m a n ife sts

A continuación, nos vamos a centrar en la clase que hereda de IntentService, donde


explicaremos:
• Creación del método onHandlelntent(Intent).
• Obtención de la dirección.
• Control de errores y/o excepciones.
• Extracción de los campos deseados a partir del objeto Address.
• Envío de la dirección a la actividad que inició el servicio.
2. Creación de la clase que hereda de In te n tS e r v ic e y del m étodo
o n H a n d le ln te n t (I n te n t)
p u b lic c la s s A v e r ig u a r D ir e c c io n ln te n tS e r v ic e exten d s In te n tS e r v ic e {
@ 0 v errid e
p r o te c te d v o id o n H a n d le ln te n t(In te n t in te n t) {

}
}
3. O btención de la dirección. Para obtener la dirección se necesita un objeto de la
clase G eocoder.
G e o c o d e r g e o c o d e r = n ew G e o c o d e r ( t h i s , L o c a le .g e tD e fa u lt());

Con Locale.getDefault() se obtiene la zona geográfica y lingüística configurada por el


usuario.
La clase Geocoder proporciona el método g e tF ro m L o c a tio n (), cuya cabecera es la
siguiente:
L ist« A d d ress> g etF ro m L o c a tio n (d o u b le la titu d ,
d o u b le lo n g it u d , i n t m a x R esu lta d o s)

Como se puede observar, dicho método devuelve un array de direcciones (objetos de


la clase Address), y se le pasa como parámetros: la latitud, la longitud, y el número
máximo de direcciones a obtener (se recomienda valores entre 1 y 5).
L ist« A d d ress> a d d r e s s e s = g e o c o d e r .g e tF r o m L o c a tio n (
lo c a tio n .g e tL a titu d e (),
lo c a t io n .g etL o n g itu d e (),
1) ;
Capítulo 6. Localización Geográfica 307

4. C ontrol de errores y /o excepciones.

Se comprueba si el receiver es válido:

R e su ltR e c e iv e r r e s u lt R e c e iv e r ;
r e su ltR e c e iv e r = in t e n t .g e tP a r c e la b le E x tr a (
O b t e n i e n d o D i r e c c i o n . RECEIVER);
if ( r e s u l t R e c e i v e r == n u l l ) {
retu rn ;
}

A partir del Intent se obtiene el objeto Location pasado con un dato extra. Este es
usado para obtener el array de direcciones con getFromLocation().
Y se comprueba que dicho objeto Location sea válido:

L o c a tio n l o c a t io n =
i n t e n t . g e tP a r c e la b le E x tr a (O b te n ie n d o D ir e c c io n .U L T IM A _ U B IC A C IO N _ IS );
if (lo c a tio n == n u l l ) {
m e n s a j e E r r o r = "No s e h a p r o p o r c i o n a d o n i n g ú n u b i c a c i ó n " ;
e n v i a r R e s u l t a d o A l R e c e i v e r ( O b t e n i e n d o D i r e c c i o n . RESULTAD0_FALL0_IS,
m en sajeE rror);
retu rn ;
}

El método getFromLocation() de la clase Geocoder puede provocar las siguientes


excepciones:
• IllegalArgumentException
- Si la latitud es incorrecta (menor que -90 o mayor que 90).
- Si la longitud es incorrecta (menor que -180 o mayor que 180).
• IOException
- Si la red no está disponible o se ha producido algún error de I/O .
En caso de que se produzca algún error, se informa al receiver usando un código de
error definido en la clase principal como una constante.

g e o c o d e r = new G e o c o d e r ( t h i s , L o c a l e . g e t D e f a u l t ( ) ) ;
try {
a d d r e s s e s = g e o c o d e r .g e tF r o m L o c a tio n (
l o c a t i o n . g e t L a t i t u d e () ,
l o c a t i o n . g e t L o n g i t u d e () ,
1) ;
308 Programación en Android

} ca tch (IO E x c e p tio n io E x c e p tio n ) {


// S e ca p tu r a n e r r o r e s en l a red o de I/O
m e n s a j e E r r o r = "En e s t e m o m en to n o h a y n i n g ú n s e r v i c i o d isp o n ib le " ;
} catch (Ille g a lA r g u m e n tE x c e p tio n ille g a lA r g u m e n tE x c e p tio n ) {
// Se c a p tu ra n e r r o r e s de lo n g it u d y l a t i t u d erróneas
m e n s a j e E r r o r = "La l a t i t u d / l o n g i t u d r e q u e r id a e s in c o r r e c ta " ;
}

Si no se encuentra ninguna dirección, getFromLocation() devuelve nuil o una lista


vacía.

if ( a d d r e s s e s == n u l l | | a d d r e s s e s . s i z e () == 0) {
if (m ensaj e E r r o r . is E m p t y ()) {
m e n s a j e E r r o r = "No s e h a e n c o n t r a d o n i n g u n a d i r e c c i ó n " ;
}
e n v i a r R e s u l t a d o A l R e c e i v e r ( O b t e n i e n d o D i r e c c i o n . RESULTADO_FALLO_IS,
m en sajeE rror);

5. E xtracción de los cam pos deseados a partir del objeto A ddress. La clase
Address representa una dirección, y dispone de un conjunto de métodos get() para
extraer la información que nos interese (localidad, país, código postal, calle, etc.).
Acuérdate que el método getFromLocation() devuelve un array de objetos Address. En
el ejemplo se extrae el primer elemento del array.

if ( a d d r e s s e s == n u l l || a d d r e s s e s . s i z e () == 0) {
if (m en saj e E r r o r . i s E m p t y ( ) ) {
m e n s a j e E r r o r = "No s e h a e n c o n t r a d o n i n g u n a d i r e c c i ó n " ;
}
e n v i a r R e s u l t a d o A l R e c e i v e r ( O b t e n i e n d o D i r e c c i o n . RESULTADO_FALLO_IS,
m en sajeE rror);
} e lse {
A ddress a d d ress = a d d r e s s e s .g e t (0);

// En e s t e caso se extraen lo s sig u ie n te s cam pos. A d d ress


// p r o p o r c io n a un c o n j u n t o de m éto d o s g e t / s e t p a ra
// poder tr a b a ja r con d ir e c c io n e s
c a lle = a d d r e s s .g e tT h o r o u g h fa r e (); // C a lle
p o rta l = a d d r e s s .g etF eatu reN am eO ; / / P ortal
cp = a d d r e s s . g e t P o s t a l C o d e () ; // C ó d ig o p o s t a l
lo c a lid a d = a d d r e ss .g e t L o c a lit y O ; / / L o c a lid a d
p a is = a d d r e s s . g etC ou n tryN am e( ) ; // P a ís
d irecc io n P o sta lC o m p leta = c a lle + ", " + p o rta l + " \n " +
cp + " " + l o c a l i d a d + + p a ís;
Capítulo 6. Localización Geográfica 309

en v ia rR esu lta d o A lR eceiv er(O b ten ien d o D ireccio n .R E S U L T A D O _ E X IT O _ IS ,


d ir e c c io n P o sta lC o m p le ta );
}
Aquí termina el contenido del método onHandleIntent(Intent).

6. Envío de la dirección a la actividad que inició el servicio. Cuando se haya


producido algún error y/o excepción, o en caso de haber podido averiguar la dirección,
hay que enviar los resultados a la actividad que invocó el IntentService.
Habrás observado que en ciertas partes del código se llamaba a un método llamado
enviarResultadoAlReceiver(), ¿verdad? Ese método nos sirve para comunicarnos con la
actividad llamante. Y le pasamos dos parámetros:
• Un código de resultado, el cual está definido como una constante en la
actividad llamante. De ahí viene
“ObteniendoDireccion. DIRECCION_OBTENID A_IS ”, donde
ObteniendoDireccion es el nombre de la actividad llamante. Y
DIRECCION_OBTENIDA__IS es el código que queremos devolver indicándole
el resultado del proceso de averiguar la dirección.
• Una cadena de texto que contiene una información descriptiva del resultado.
Estos dos parámetros son los que necesita el método send() de la clase
ResultReceiver, a través del cual se envía el resultado a la actividad que invocó el
servicio.

p r iv a t e v o id e n v ia r R e s u lta d o A lR e c e iv e r (in t c o d ig o R e su lta d o , S t r in g m ensaje) {


B u n d l e b u n d l e = new B u n d l e O ;
b u n d l e . p u t S t r i n g ( O b t e n i e n d o D i r e c c i o n . DIRECCION_OBTENIDA_IS, m e n s a j e ) ;
r e su ltR e c e iv e r .se n d (c o d ig o R e su lta d o , b u n d le );
}

Hasta aquí la clase AveriguarDireccionlntentService (que hereda de IntentService)


con la implementación de los métodos onHandleIntent() y enviarResultadoAlReceiver().
La UI ( User Interface) de nuestro ejemplo es muy sencilla, tan solo contiene dos
widgets: un TextView para mostrar la dirección obtenida o un mensaje de error (que
muestre por qué no se ha podido averiguar la dirección), y un Button que el usuario
pulsará cuando quiera averiguar una dirección.
Se recuerda que para obtener la dirección partimos de la última ubicación, es decir,
no se le pide al usuario que introduzca una latitud y longitud. De modo que, para una
correcta depuración de la aplicación, debemos asegurarnos de que existe dicha última
ubicación. Una manera sencilla de conseguirlo es ejecutando la aplicación del Apartado
6.3.2 como paso previo a la depuración/ejecución de la aplicación que averigua la
dirección.
310 Programación en Android

Aclarado esto, vamos a ver cómo quedaría el UI:

Genymction for personal use - Google Galaxy...

Averiguar dirección

In te rfa z de U su ario (U I) cu an do se ha averig u a d o la d ire c c ió n

Genymotion for personal use - Google Galaxy.

ObteniendoDireccion

Hello world!

dirección

NO existe la última ubicación

Q u é o cu rre cuan do n o e x iste la ú ltim a u bicación


Capítulo 6. Localización Geográfica 311

La actividad principal está implementada en la clase ObteniendoDireccion, cuyo


funcionamiento general es el siguiente:
• Creación del objeto GoogleApiClient.
• Establecimiento de la conexión con Google Play Services. Esto se lleva a cabo
en los métodos onStart() y onConnectionSuspended(), este último en caso de
que la conexión se haya perdido. Recuerda que la desconexión se realiza en el
método onStop().
• Cuando la conexión con Google Play Services se haya establecido, se ejecuta el
método onConnectedQ, dentro del cual se obtiene la última ubicación, se
comprueba si hay un Geocoder disponible y se habilita el botón de la UI para
averiguar la dirección.
• En el método que responde al evento onClick del mencionado botón es donde
el usuario debe pulsar para iniciar al proceso de obtener la dirección.
• Se lanza el Intent.
• Se crea una Inner Class (clase anidada o embebida) que herede de
ResultReceiver. Dentro de dicha clase se implementa el método
onReceiveResult(), y será invocado una vez que el IntentService haya
terminado su trabajo. Dentro de onReceiveResult() se muestra la dirección o el
mensaje de error.

Vamos a ver cómo quedaría el código, en el cual se han omitido todas las
importaciones y se han añadido los comentarios necesarios para que puedas seguirlo con
facilidad.

public class ObteniendoDireccion extends ActionBarActivity


implements ConnectionCallbacks, OnConnectionFailedListener {

/* Constantes usadas para:


* - manejar errores
* - enviar y recibir información
* al IntentService (por eso las constantes se nombran acabando en IS)
*/
public static final int RESULTADO_EXITO_IS = 0;
public static final int RESULTADO_FALLO_IS = 1;
public static final String PACKAGE_NAME = "com.m m c .obteniendodireccion";
public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
public static final String DIRECCION_OBTENIDA_IS =
PACKAGE_NAME + ".DIRECCION_OBTENIDA";
public static final String ULTIMA_UBICACION_IS = PACKAGE_NAME + " ,ULTIMA_UBICACION";

private GoogleApiClient googleApiClient;


private Location ultimaUbicacion;
private Textview direccionTextView;
312 Programación en Android

private Button averiguarDireccionButton;

// Receiver registrado en esta actividad


// Su misión es obtener la respuesta de AveriguarDireccionlntentService
private ResultadoDireccionReceiver receiver;

@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R. layout.activity_obteniendo_direccion)

receiver = new ResultadoDireccionReceiver(new Handler ()),-

direccionTextView = (TextView) findViewByld(R.id.direccionTextView);


averiguarDireccionButton = (Button) findViewByld(R.id.direccionButton);
averiguarDireccionButton.setEnabled(false),-

crearGoogleApiClient() ;
}

// Se crea un objeto de la clase GoogleApiClient


private void crearGoogleApiClient() {
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}

/* Método asociado el evento onClick del botón direccionButton del UI


* Objetivo: comprobar si googleApiClient está conectado y si existe la última
* ubicación, en caso afirmativo llama al método para iniciar el IntentService
*/
public void averiguarDireccionButton(View view){
if (googleApiClient.isConnected() && ultimaUbicacion != null) {
iniciarlntentService();

@Override
public void onConnected(Bundle connectionHint) {
ultimaUbicacion =
LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
if (ultimaUbicacion != null) {
// Se comprueba si hay un Geocoder disponible
if (¡Geocoder.isPresent0) {
Toast.makeText(this, "No hay un Geocoder disponible".
Toast,LENGTH_LONG).show();
return;
Capítulo 6. Localización Geográfica 313

averiguarDireccionButton.setEnabled(true);
}
else Toast.makeText(this, "NO existe la última ubicación".
Toast.LENGTH_LONG).show();
}
@Override
public void onConnectionFailed(ConnectionResult result) {
}

@Override
public void onConnectionSuspended(int cause) {
googleApiClient.connect();
}
/*
* Método: iniciarIntentService()
* Objetivo: crear el intent que se pasará al IntentService añadiendo dos extras:
* (1) el receiver para recibir la respuesta, (2) la última ubicación
*/
private void iniciarlntentService0 {
// Intent explícito que se pasa al IntentService
Intent intent = new Intent(this, AveriguarDireccionlntentService.class);

// Se añade como dato extra el receiver


intent.putExtra(RECEIVER, receiver);

// También se añade como dato extra la última ubicación


intent.putExtra(ULTIMA_UBICACION_IS, ultimaUbicacion);
startService(intent);
}

// Para mostrar un Toast (usado desde el receiver)


protected void mostrarToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}

/*
* RECEIVER
* Clase ResultadoDireccionReceiver extends ResultReceiver
* Objetivo: recibir resultados desde el IntentService
* (AveriguarDireccionlntentService)
*/
class ResultadoDireccionReceiver extends ResultReceiver {

public ResultadoDireccionReceiver(Handler handler) {


super(handler);
}
// Recibe los resultados enviados desde AveriguarDireccionlntentService
@Override
314 Programación en Android

protected void onReceiveResult(int resultCode, Bundle resultData) {


// Se extrae la información enviada desde el Intent Service (puede ser
// la dirección o un mensaje de error) y se muestra en el TextView
String direccionObtenida = resultData.getString(DIRECCION_OBTENIDA_IS);
direccionTextView.setText(direccionObtenida);

// Se muestra un mensaje por pantalla si se ha encontrado la dirección


if (resultCode == RESULTADO_EXITO_IS) {
mostrarToast("Dirección encontrada");

@Override
protected void onStart() {
//
super.onStart() ;
googleApiClient.connect ()
}

©Override
protected void onStopO {
super.onStop();
II
if (googleApiClient.isConnected0) {
googleApiClient.disconnect() ;

ACTIVIDAD 6.11.
Desarrolla una aplicación que m uestre la dirección partiendo de la últim a posición conocida
(definida por una longitud y latitud).
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
ObteniendoDireccion.rar, del m aterial complementario.

6.4. SENSORES
Si te paras a pensarlo unos minutos, estarás de acuerdo conmigo en que es increíble la
maravilla tecnológica que llevamos hoy en día en nuestros bolsillos; muchas veces no
siendo conscientes de ello.
Capítulo 6. Localización Geográfica 315

En un dispositivo móvil se pueden instalar todo tipo de aplicaciones, permitiendo un


uso muy cercano al conseguido con un ordenador común. Además llevan incorporada
una cámara de fotos que en muchos casos supera las prestaciones de las cámaras de
gama media y alta. A lo que se suma la grabación de vídeos de alta calidad. Poder
acceder a Internet desde cualquier lugar y a cualquier hora, con velocidades de
subida/bajada con las que soñábamos hace 10 años. Desde un punto de vista comercial,
las empresas de videojuegos se dieron cuenta hace tiempo del tremendo filón que suponía
el desarrollo de videojuegos para móviles.
A todo esto hay que sumar la tecnología de los sensores, los cuales permiten calcular:
fuerzas de aceleración (acelerómetro), tem peratura ambiente, la rotación del dispositivo
(giroscopio), luz ambiente, campos magnéticos, presión atmosférica, etc.
El mundo de los sensores abre la puerta, entre otras cosas, a:
• Una nueva forma de interacción con el dispositivo. Hasta ahora teníamos el
teclado, la pantalla táctil, o la voz. A partir de ahora, es posible crear
aplicaciones basadas en los cambios detectados por dichos sensores.
• Nuevos negocios, ideas y oportunidades. Por ejemplo, como el acelerómetro
detecta cambios en fuerzas de aceleración, combinado con el GPS, se podrían
detectar accidentes en el mismo instante en que están ocurriendo, avisando al
112 y a la Guardia Civil de la posición exacta (latitud y longitud) del
accidente.

6.4.1. SISTEM A DE C O O R D E N A D A S
Como veremos en el siguiente apartado (“Tipos de sensores. Clasificación”), hay
sensores que devuelven un solo valor mientras que otros devuelven valores basados en
tres ejes. Así que por ahora, vamos a centrarnos en entender cómo funciona el sistema
de coordenadas.
Observando la imagen, podemos apreciar que si tenemos el dispositivo en posición
vertical, con la pantalla perpendicular al suelo, tenemos los siguientes ejes:
• El eje Y (vertical), con valores positivos hacia arriba y valores negativos hacia
abajo.
• El eje X (horizontal), con valores positivos hacia la derecha y valores negativos
hacia la izquierda.
• El eje Z (profundidad), con valores positivos saliendo de la pantalla hacia el
frente, y valores negativos por detrás de la pantalla).
316 Programación en Android

Sistema de coordenadas (imagen extraída de Android Developer)

Algo muy importante que debes entender, es que el sistema de coordenadas no


cambia cuando cambia la orientación del dispositivo. Es decir, independientemente de la
posición del dispositivo, el sistema de coordenadas (con sus tres ejes) es siempre el
mismo.
Además, el sistema de coordenadas se basa en la orientación natural del dispositivo.
Y no se debe asumir que la orientación por defecto es la vertical (portrait, como la de la
imagen), pues para muchos dispositivos (por ejemplo, tablets) la orientación por defecto
es la horizontal (landspace).

6.4.2. TIPO S DE SEN SO RES. CLASIFICACIÓN


Los sensores implementados por Android, se pueden clasificar en tres categorías:
• Sensores medioambientales: estos sensores miden valores de medio ambiente,
como la presión atmosférica, la temperatura, o la iluminación. Esta categoría
incluye, entre otros, el barómetro y el termómetro.
• Sensores de movimiento, que miden fuerzas de aceleración o rotación a lo largo
de tres ejes (X, Y, Z). Esta categoría incluye, entre otros, el giroscopio y el
acelerómetro.
• Sensores de posición, los cuales miden la posición física del dispositivo. Esta
categoría incluye, entre otros, los sensores de orientación y de campos
magnéticos.
La siguiente tabla muestra los sensores soportados por Android hasta el momento. En
la primera columna se indica el tipo de sensor, en la segunda columna se expone una
breve descripción, y en la tercera y última columna se muestra la cantidad de valores
necesarios para representar un valor de dicho sensor.
Capítulo 6. Localización Geográfica 317

Cantidad
Tipo de sensor Descripción de valores
necesarios
Mide la fuerza de aceleración a lo largo
T Y PE _A C C E L E R O M E T E R de tres ejes (X, Y, Z) en m /s2, 3
incluyendo la fuerza de la gravedad

Mide la tem peratura am biente en grados


T Y P E _A M B IE N T _T E M PE R A T U R E 1
Celsius (°C)

Mide la fuerza de la gravedad a lo largo


T Y P E „G R A V IT Y 3
de tres ejes (X, Y, Z) en m /s2

Mide el índice de rotación alrededor de


T Y PE _G Y R O SC O PE tres ejes (X, Y, Z) en radianes/segundo 3
(r/s)

Mide el nivel de iluminación en lux (lx).


T Y P E _L IG H T 1
Un lux equivale a un lum en/m 2

Mide la fuerza de aceleración a lo largo


T Y PE _L IN E A R _A C C E L E R A T IO N de tres ejes (X, Y, Z) en m /s2, 3
excluyendo la fuerza de la gravedad

Mide la inducción m agnética (campo


T Y PE _M A G N E T IC _FIE L D magnético) para los tres ejes (X, Y, Z). 3
La unidad usada es pT (microTeslas)

Mide la presión atmosférica en milibares


T Y P E JP R E S S U R E 1
(rnbar) o hectopascales (hPa)

Mide la distancia (en centím etros, cm)


T Y P E _P R O X IM IT Y 1
de un objeto a la pantalla del dispositivo

T Y PE _R E L A T IV E _H U M ID IT Y Mide la hum edad am biente en % 1

Mide la orientación del dispositivo


T Y P E _R O T A T IO N _V E C T O R descrita como el ángulo de rotación 3
alrededor de un eje (°)

Mide la tem peratura de un dispositivo


en grados Celsius (°C). Este sensor fue
T Y P E _T E M P E R A T U R E reemplazado por 1
T Y P E „A M B IE N T _T E M P E R A T U R E
desde la A PI Level 14
318 Programación en Android

Cuando se indica que un sensor necesita tres valores para representar la información,
quiere decir que toma un valor en cada uno de los tres ejes (X, Y, Z), explicados
anteriormente. Esto se hace mediante el siguiente array:
public final float [] values

Donde:
• values[0] representa el valor para el eje X
• valuesfl] representa el valor para el eje Y
• values[2] representa el valor para el eje Z

6.4.3. LAS CLASES Sensor Y SensorM anager


Mientras que la clase Sensor representa un sensor y proporciona un conjunto de
métodos y constantes para trabajar con los objetos de dicha clase, la clase
SensorManager es la que nos abre la puerta para poder trabajar con los sensores.
La clase SensorManager nos permite crear una referencia al servicio de sensores
(,SEN SO R_SERVIC E). Proporciona métodos para acceder y listar sensores, para
registrar listeners y para actualizar los valores proporcionados por los sensores.
También aporta multitud de constantes para informar sobre la precisión de un sensor,
calibrar sensores, etc.
Para obtener la referencia a SENSO R_SERVICE, debemos hacer una llamada al
método getSystemService() de la siguiente manera:

SensorManager SensorManager;
SensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

6 .4 .3 .1 . C óm o o b te n e r un lista d o d e to d o s los se n so r es
d isp o n ib le s
Obviamente, no todos los dispositivos móviles dispondrán de los mismos sensores. Esa
es una de las razones por las que unos son más caros que otros. Así que no te asustes si
cuando listes todos los sensores de tu dispositivo (móvil o tablet), no aparecen todos los
comentados en los apartados anteriores. Para el siguiente código partimos de una
interfaz de usuario que solo contiene un LinearLayout. Dentro del propio código se irán
generando TextViews para mostrar el nombre de cada sensor.
A través del objeto de la clase SensorManager se invoca al método getSensorList(
Sensor. T Y P E _A LL ) que devuelve un listado de todos los sensores disponibles en el
dispositivo. A partir de ahí, se recorre el array obteniendo el nombre de cada sensor,
siendo cada uno de ellos mostrado en el UI ( User Interface) con un TextView distinto.
public class MainActivity extends Activity {
Capítulo 6. Localización Geográfica 319

@Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);

SensorManager sensorManager;
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

List<Sensor> listadoSensores = sensorManager.getSensorList(Sensor.TYPE_ALL);

generarUI(listadoSensores);
}

public void generarUI(List<Sensor> listadoSensores){


LinearLayout linearLayout = (LinearLayout) findViewByld(R.id.linearLayout);
Textview textview;

// Mensaje de LOG para mostrar el tamaño del array de sensores


// es decir, cuantos sensores están disponibles
Lo g .w("listadoSensores.size():", "" + listadoSensores.size());

for (int i=0; i<listadoSensores.size(); i++) {


String nombreSensor = listadoSensores.get(i).getName0;
textview = new Textview(this);
textview.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
textview.setText("Sensor " + i + " || Nombre: " + nombreSensor);
textview.setld(View.generateViewId() ) ;
linearLayout.addview(textview);
}

}
Si en lugar de obtener un listado de todos los sensores, se quiere conseguir un listado
de los sensores disponibles de un tipo determinado, ya sea acelerómetro, giroscopio, etc.,
se le pasará al método getSensorList() un parámetro que indique el tipo de sensor
deseado (T YP E _ ACCELERO METER, TYPE_PROXIM ITY, TYPE_PRESSURE,
etc.).
Por ejemplo:
List<Sensor> acelerometro = sensorManager.getSensorList (Sensor.TYPE_ACCELEROMETER)

En caso de que haya varias implementaciones del sensor deseado, se le puede solicitar
a Android que nos devuelva el sensor por defecto de una determinada categoría; el cual,
en la mayoría de los casos, será la mejor alternativa. El siguiente código muestra cómo
obtener el giroscopio por defecto:
Sensor giroscopioPorDefecto;
giroscopioPorDefecto = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
320 Programación en Android

ACTIVIDAD 6.12.
Im plem ents una aplicación que m uestre todos los sensores disponibles en un dispositivo móvil. Se
debe m ostrar el nom bre de cada sensor.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
SensoresOl.rar, del m aterial complementario.

6.4.4. M O N ITO R IZA N D O SENSORES


Para monitorizar un sensor hay que implementar el interfaz S e n so rE v e n tL iste n e r,
del cual habrá que programar dos métodos:
• onSensorChanged(SensorEvent event)
Este método es invocado cada vez que un sensor detecta un cambio. El objeto
SensorEvent contiene los siguientes campos o propiedades:
accuracy: precisión del sensor cuando ocurrió el evento.
sensor, el objeto Sensor que provocó el evento.
timestamp: el tiempo (en nanosegundos) cuando ocurrió el evento.
values: un array de float que contiene los nuevos datos capturados por el
sensor.
• o n A c c u ra c y C h a n g e d (S e n so r sen so r, in t a ccu ra cy)
Este método es invocado cuando un sensor cambia su precisión. El primer
parámetro es una referencia al objeto (de la clase Sensor) que ha cambiado de
precisión. El segundo parámetro indica la nueva precisión del sensor.

La clase SensorManager dispone de cuatro constantes que representan distintos


valores de precisión {accuracy):
• SENSOR_STATUS_ACCURACY__HIGH. Indica que el sensor está
capturando datos con la máxima precisión.
• SENSOR_STATUS_ACCURACY_MEDIUM. Indica que el sensor está
capturando datos con una precisión media. Una calibración puede mejorar las
lecturas de datos.
• SENSOR_STATUS_ACCURACY_LOW. Indica que el sensor está
capturando datos con una precisión baja y necesita ser calibrado.
• SENSOR_STATUS_UNRELIABLE. Indica que los valores devueltos por el
sensor no son fiables.

Para registrar el S e n so r E v e n tL is te n e r disponemos del método:


• registerListener(SensorEventListener listener, Sensor sensor, int
samplingPeriod Us)
listener es el objeto de la clase SensorEventListener.
Capítulo 6. Localización Geográfica 321

sensor es el objeto a registrar


samplingPeriodUs es el ritmo al que se envían los datos. Este valor está
definido por cuatro constantes:
■ SENSOR_DELAY_NORMAL. Velocidad o ritmo por defecto.
■ SENSOR_DELAY_UI. Para una velocidad apropiada para
actualizar el interfaz de usuario.
■ SENSOR_DELAY_GAME. Velocidad apropiada para los juegos.
■ SENSOR_DELAY_FASTEST. Velocidad de actualización más
rápida.
Y para eliminar el registro, tenemos el método:
• unregisterListener (SensorEventListener listener)
Habrás caído en la cuenta de que tener los sensores activos detectando cambios en el
entorno es muy costoso desde el punto de vista energético. De modo que, si no quieres
que tu aplicación agote la batería del dispositivo en cuestión de horas, será de vital
importancia decidir cuándo se debe registrar el S e n so rE v e n tL iste n e r, y cuándo se
debe eliminar dicho registro. ¿Adivinas cuándo se deben llevar a cabo estas acciones?
Piensa unos segundos... Pista... ciclo de vida de una actividad... ¿Qué método se ejecuta
cuando nuestra actividad pasa a estar activa? ¡Eso es! El método onResumef) es el lugar
idóneo para registrar el Listener. ¿Y para eliminar el registro? Pues cuando nuestra
aplicación deje de estar activa, en el método onPause().
El siguiente ejemplo monitoriza el acelerómetro. El UI ( User Interface) es muy
sencillo, tan solo se compone de varios TextView donde se mostrarán el nombre del
sensor y los valores actuales (la monitorización). El código es el siguiente:
public class Sensores02 extends Activity implements SensorEventListener {
SensorManager sensorManager;
Sensor acelerometro;

©Override
protected void onCreate(Bundle savedlnstanceState) {
s u p e r .onCreate(savedlnstanceState);
setContentView(R.layout.activity_sensores02);

sensorManager =
(SensorManager)getSystemService(Context.SENSOR_SERVICE);

acelerometro =
sensorManager.getDefaultSensor(Sensor,TYPE_ACCELEROMETER);

TextView nombreSensorTextView = (TextView)


findViewByld(R.id.nombreSensorTextView);
nombreSensorTextView.setText(acelerometro.getName());

/* Mediante este método se monitorizan los valores de un sensor,


322 Programación en Android

* en este caso el acelerómetro */


@Override
public void onSensorChanged(SensorEvent event) {
TextView valoresTextView = (TextView)
findViewByld(R.id.valoresTextView);
float x, y, z;
// Tal como se ha explicado, el acelerómetro es un sensor que se
// representa mediante 3 valores
x = event. v a l u e s [0];
y = event. v a l u e s [1];
z = e v e n t . v a l u e s [2];
String valores = "X: " + x + " | Y: " + y + " | Z : " + z;
valoresTextView.setText(valores);
}

/*
* El método onAccuracyChanged() es invocado cuando un sensor cambia su
* precisión.
* Parámetros:
* - Sensor sensor: Objeto de tipo Sensor que ha cambiado de precisión
* - int accuracy: la nueva precisión del sensor
*/

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { }

@0verride
protected void o n R e s u m e (){
s u p e r .o n R e s u m e ();
sensorManager.registerListener(this, acelerómetro,
SensorManager.SENSOR_DELAY_NORMAL);
}

©Override
protected void o n P a u s e (){
s u p e r .o n P a u s e ();
sensorManager.unregisterListener(this);
}
}

ACTIVIDAD 6.13.
Im plem enta una aplicación que m onitorice el acelerómetro.
P a ra ver el código completo del ejemplo con la solución de esta actividad, consulta el fichero
SensoresO2.rar, del m aterial complementario.
Capítulo 6. Localización Geográfica 323

PRÁCTICA 6. GEOLOCALIZACIÓN
Desarrolla todas las aplicaciones de esta tarea para el siguiente SDK mínimo: API 17
(Android 4.2 Jelly Bean):

1. Implementa una aplicación que permita introducir al usuario una localización definida
por una latitud y una longitud. Se debe mostrar:
a) Por un lado, la distancia que hay entre la localización actual del usuario y la
localización establecida.
b) Por otro lado, cómo se actualiza esa distancia cuando el usuario se mueva, por
ejemplo, caminando. Dicha actualización debe ser en tiempo real, es decir, si la
distancia inicial es de 1500 metros y el usuario camina en dirección hacia el punto
(localización) marcado, se deberá mostrar cada cierto tiempo cuánto le queda para
llegar a ese punto. Decide tú mismo cada cuanto tiempo se debe actualizar la
distancia.
2. Desarrolla una aplicación que muestre todos los sensores disponibles en el dispositivo.
También se deben mostrar los valores actuales de cada sensor disponible, así como
actualizarse en tiempo real dependiendo de las acciones del usuario. Por ejemplo, para el
acelerómetro, se deberá mostrar cómo cambian sus valores en los tres ejes conforme el
usuario mueva el dispositivo.

C R ITER IO S D E C A LIFIC A C IÓ N

Ejercicio 1: 5 puntos

• Distancia que hay entre la localización actual del usuario y la localización


establecida: 2 puntos
• Actualización en tiempo real de la distancia dependiendo del movimiento del
usuario: 2 puntos
• Diseño de la aplicación: 1 punto

Ejercicio 2: 5 puntos

• Se muestran todos los sensores disponibles en el dispositivo: 2 puntos


• Se actualizan los valores de cada sensor dependiendo de las acciones del
usuario: 2 puntos
• Diseño de la aplicación: 1 punto
Android
Programación multimedia y de
dispositivos móviles

ste libro e stá dirigido a to d a perso n a que

E d e s e e su m erg irse en el maravilloso m u n ­


do de la program ación multimedia de dis­
positivos móviles con Android.
Para a b o rd a r e s te tex to , no se requiere nin­
gún conocim iento previo de Android. Sin e m ­
bargo, sí son n ecesarios conocim ientos de
program ación en Java o, al m e n o s, en algún
lenguaje de program ación o rientado a o b je ­
tos.
Sigue un enfoque to ta lm e n te práctico y di­
dáctico. Se s a b e que el conocim iento que real­
m e n te se a p re n d e , se asimila y se llega a d o ­
minar, e s aquel que perm ite al e stu d ia n te j u ­
g a r con él, q u e le da la oportunidad de equivo­
c arse y a p re n d e r de su s propios errores. Esto
solo se consigue si se prioriza la p arte práctica
sobre la teórica; contiene teoría, pero la ju s ta
y necesaria para po d er a b o rd a r las actividades
y las prácticas p ro p u estas.
D esde el prim er capítulo se p u e d e c re a r un
proyecto de Android. El libro e stá e stru c tu ra d o
para q u e alguien q u e no te n g a ningún cono­
cimiento d e Android a ca b e d iseñ an d o y pro­
g ra m a n d o su s propias aplicaciones. Por ello,
se recom ienda una lectura o rd e n a d a h a sta el
capítulo 3 (C om unicaciones). A partir de ahí,
se p u e d e disfrutar ju g a n d o con la geolocaliza-
ción, con el stream in g de vídeo/audio o con el
maravilloso capítulo de videojuegos, d o n d e se
a p re n d e rá a p ro g ra m a r partiendo d e cero.

978-84-1622-831-7

www.garceta.es

9 788416 228317

También podría gustarte