Android PDF
Android PDF
Android PDF
Android
Programación multimedia y de dispositivos móviles
CAPÍTULO 1. INTRODUCCIÓN................................................................................................................ 1
CAPÍTULO 3. COMUNICACIONES.......................................................................................................113
3.1 Ejecución de proyectos en dispositivos reales................................................................................... 114
índice vii
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
W
l ■
APPS WIDGETS
0#
metáforas del mundo real para utilizar los objetos API D em os Browser Calculator C alendar I
&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
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
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.
Por tanto, tienes que tener en cuenta todos estos aspectos cuando desarrolles en
Android.
<- e S i? r
Cour<»yv i#m « . v I ímbm ? . v S e a rc h Q.
Java SE Downloads
*■»»»£*«
WBean t JtíSüaüíili
i ¿iiU&üír. í.g,iw
• fcr»ws»F*s
(si cuando leas esto, el enlace ya no es válido, busca en google Jav a JD K y descárgalo).
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
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
Android
Studio
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
T77Í L -c’o r
Android S tudio S etu p I *—11
Configuration Settings
Install Locations
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.
Show details
■------------------------------------------------------------------
Android Studio Setup
Android
Studio
J
Unidad 1. Introducción 11
_Ej - •;
Android Studio Setup
Update Info
A n e w version o f Android S tu d io is available?
Show Details
Cancel Bnisr:
r-1 C o m p le te In stallation
You can import your settings from a previous version o f Android Studio.
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
Desde esta pantalla podemos crear un nuevo proyecto, importarlo, abrir uno que
existe, personalizar la gestión de configuración...
*
£2 Android SDK Manager
Packages Tools
SDK Path: D: Android Studio Tools'-sdk
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:
Packages Tools
SDK Path: DAAndroid Studio Tools'\sdfc
Packages
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.
Packages
©
Downloading Android Wear ARM EABIv7a System Image, Android API 20, revision 1 (61%, 462 KiB/s, 88 seconds left)
------------------------------------------------------------------------ ----------
* j Android SDK Manager 1o . a M 'i .
Packages Tools
; SDK Path: D:\Android Studio TooLv.sdk
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”:
New Project
Previous Cancel
S e le c t th e fo rm facto rs y o u r a p p w ill ru n on
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
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 No Activity
WL
t
Fullscreen Activity Google Maps Activity Google Play Services Activity
£revious Cancel
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.
Comenzará el proceso de creación del proyecto y configuración del entorno para que
comiences a programar:
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
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.
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:
.i
a- e &
.ilwe*ri*í'<Sí*
J t*n*wU^tesa!Plrtivt)
JtjfrltUyevl
Tílsteftsw
1f§KtU$v«Uy«uc
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.
c miPnmeraActividad.java * £ artrv¿ty_mi_primera_actrvidadjtml *
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
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>
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 "
/>
package ccm.misprcyectcs.ilm.miprimeraapp;
[j|import ,.,
@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( ) •
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 ;
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 ) ;
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.
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
@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...
Cbi+F9
Make M odule 'app'
Rebuild Project
Clean Project
wdrcidStudioPrcjec
Generate Signed APK...
Unidad 1. Introducción 27
* Choose Device -
1 . 1 = ^
Device il • -_ ^ - tu -_
U ! Cancel
..
28 Programación en Android
O Groovy Console..,
p u l #» Navigation Editor
super.onCreate(savedln
q SDK Manager
* AVD Manager
E ] $
Virtual devices allow you to test your application without having to
own the physical devices.
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
S y s te m Im a g e
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
iilPlIiff
Android Virtual Device (AVD)
'-i ti ■ si
VerifyConfiguration fl - '*
Startup size
and
orientation
Scale 1 Nothing Selected
.Previous Cancel
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
De, cr ‘ Ser
di t i t ‘
'U U u rg ic -;h «
( • ) Launch emulator
a S 3 B Cincel
Unidad 1. Introducción 31
Pulsa aquí
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:
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 ) ;
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.
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 " );
}
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
<1 O □
Ejemplos de ejecución:
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:
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
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.
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:
on Destroy!)
Activity
shut down
Imagen de developer.android.com
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
Applications
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
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
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
Button
Text field
te . -i- i» ,» J
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.
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:
$ a
'Ir Developers - Oewgn :a a
Raining APIftádes ■' - too»® 6óo#e Services
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
*~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
<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
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
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 ! " ) ;
}
<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
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/
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
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
<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:
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
P aso 3. C odificarlo
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
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» __________________
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é
0 Me gusto el fútbol
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 ) ;
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
Off On
ON
Toggle buttons Switches (in Android 4 0+)
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
D Color
▼ t i Drawable
■—“— 0 icjauncher
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í:
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.
r1 Asset Studio . __
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 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
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);
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
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.
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 )
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 _______________________/
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
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:
<> AndroidManifest.xml
ó</resources>
jitignore
»¡ 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
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
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.
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
public class
RelativeLayout
extends ViewGroup
java, lartg.Object
Uandroid view. View
Landroid view. ViewGroup
Uandroid widget.RelativeLayout
Imágenes de android.developer.com
Summary
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.
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.
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:
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
Fila
ACTIVIDAD 2.13.
Observa con atención la demo del LinearLayout: http://www.voutube.com/watcli7v—nTAXcQ7KMi8
ACTIVIDAD 2.14.
Demo de la propiedad peso: http://w w w . youtube.com Avatcli?v=2WcJOVpQR.4g
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
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.
<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' |/'>
< 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
V § A 57 I
\ ’W * GndLayout i 1
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
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
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
}
g.addView(b,i);
}
}
b = new B u t t o n ( t h i s ) ;
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 ) ;
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:
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 ) ;
}
}
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
}
í j f I GridLayout
btn4 btn5
tm
btn? btn8
btn9 btnIO
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.
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
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.
@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 ();
}
©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
RespuestaDialogoSexo respuesta;
}
82 Programación en Android
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
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
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
• 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:
@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
í $ 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.
}”
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ó.
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
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 ());
ACTIVIDAD 2.19.
Puedes ver el código de esta unidad en el fichero Selecciones.rar del material
complementario.
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:
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 ();
ACTIVIDAD 2.20.
Puedes ver el código completo en el fichero SeleccionMultiple.rar del material
complementario.
i f I Spinner
Elige: Elige:
Toledo Toledo
Toledo
Ciudad Real
Cuenca
Guadalajara
Albacete
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
/ / 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 .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
<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
@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);
}
}
}
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
$ S i» & 7 o 7 ^ T o T , O 20:30
ACTIVIDAD 2.22.
Puedes ver este código en SpinnerPersonalizado.rar del material complementario.
11 sep 2013 08 54
12 oct 2014 09 55
13 nov 2015 10 56
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
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);
©Override
public void onDateSet(DatePicker datePicker, int i, int i2, int i3) {
GregorianCalendar g=new GregorianCalendar(i,i2,i3);
f .onResultadoFecha (g) ,-
}
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);
©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);
}
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.
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 ■
Facturas
Ajustes
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.
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
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>
<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
@ 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 );
Robb Stark
Robb Stark
Ned Stark
Ned Stark filfE
Brandon Stark
Brandon Stark
Sanar
©
Enviar Mensaje Jaime Lannister
C u’ ■
-■■■* '
Cersev Lannister 5?
Cersey Lannister
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
starks.xml
C ancel
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 ( ) ;
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:
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>
}
// 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;
}
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:
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.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
* 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
Se ha desenganchado S Pen
% Encuentra hippiene»
I Instrucciones
Ok
[
Se ha desenganchado S Pen
1 1 1
2 1 2
■
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.
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 /
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'
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
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).
; « 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
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
^ r .Ciudad Real
Selecciona tu provincia! 'Cuenca
Guadalajara
f i n i s h () Albacete
.^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
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" />
</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í):
<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.
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
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
4
Al indicar la clase Selecciona, se construye de forma explícita un Un*e trm a r por LAN
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);
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
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?") ;____________________________
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);
IMltud:
ENVIAR
URL ilopezmon@gmail.com
httD.//wwwaooa!e.es irawfr
A b
ivanfopez@nberadeltaio.es
¿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
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 />
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
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
}
}
}
&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
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
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
/*>
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 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
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
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 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
»
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
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
/ * * 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 !" );
}
t&sisSC
SSlV
M tí?"r.Prissxpal*>
•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*
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" />
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.
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
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-----
D e te n w ,S « v tc io
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
ACTIVIDAD 3.3.
Puedes ver y analizar este caso práctico en el siguiente fichero DemoServicio.rar del material
complementario.
• 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
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
descargar
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
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 " ) ;
}
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
■■
vie, 2.1 notdemóre &
O
▼ #) i
Wt-Fi Ubicación Son»do Modo manos Bluetooth
libres
-5 V " Ají
Notificaciones
'loti B orrar
■’ running
Lista Negra
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
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.+"
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 ! ! " ) ;
/ / 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
w •
!'>
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:
/ / 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:
« 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.
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:
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();
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
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
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
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
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 ()+" % "};
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
@ 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 ;
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
# MisDiscos
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
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
© 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
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 []).
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 ;
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 ) ;
}
}
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 ;
/ / 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 ( ) ;
}
}
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!
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
//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... »
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
se escribió este texto) podrás publicar tantas Subir APK 1 Preparar ficha de Pisy Store
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
M
LLAMANDO A WALTER WHITE
W
=- ; APK APK
□ 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
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
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
File Edit V ie* N avigate C ode Analyze Refactor Run J o o ls VCS Wind
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.
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
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.
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
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
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
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.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.
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
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
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.
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
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 >
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:
}
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:
< 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' />
é -« 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 >
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:
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"
MisPreíerencias
Nickname
Tan solo estribe tu apodo
¡Hasta aquí los cuatro pasos! Ha sido sencillo, ¿verdad? Añadimos un quinto paso
para hacer el debug antes mencionado.
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 );
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.
¿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:
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>
}
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
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.
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
©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?
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));
ACTIVIDAD 4.8.
Modifica la aplicación de la actividad anterior para que sea compatible con versiones anteriores a
Honeycomb.
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
- 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
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
▼ 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
▼ 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)
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
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
Dentro del método que controla el STOP tenemos que tener cuidado con las
excepciones que puede provocar el método prepare().
} 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.
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 t a r t () { }
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
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
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.
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 " / >
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 " />
K /ia a n i f e s t >
AndroidManifest.xml
206 Programación en Android
Por:
videoView.setVideoURI(Uri.parse("http://mim.zz.mu/ut4_multimedia/magia.webm"));
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
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" />
¡</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 /~):
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 *
• 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.
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
• 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
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 ;
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
// 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
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.
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.
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
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:
• Los Dibujables (Drawables): Android tiene una librería 2D para dibujar formas
e imágenes.
5.2. LA A R Q U IT E C T U R A DE U N V ID EO JU EG O
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
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
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:
//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);
• Jpg: Aceptable
Capítulo 5. Programación de videojuegos 223
• Gif: Desaconsejado
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
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:
/ / 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.
Animadores: Realiza cambios en una vista para producir una animación. Algunas de
las animaciones más útiles que puedes encontrar son las siguientes.
/>
« 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>
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 );
@ 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 ;
}
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
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.
« 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.
; D
0
R
UPDATE RENDER UPDATE DORMIR UPDATE R EN D ER
M
1______ R
@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);
}
}_____________________________ _____
//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
}
©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
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.
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
/ / 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 ;
} ___________________ ___________________________________________________________
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+ + ;
}________ _______
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:
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
b o o lea n h ay T o q u e= fa lse;
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);
}
@ 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
index = MotionEventCompat.getActionlndex(event);
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 );
}
}
}
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.
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.
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.
// 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:
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.
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 • * • * « • *
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
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
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
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 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
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:
//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 ) ;
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
/* 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
/ / 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 );
/ / 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:
enemigo enemigo
listo tonto
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 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;
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) {
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
i f (coordenada_x>juego.AnchoPantalla-juego.enemigo_tonto.getWidth()
&& direccion_horizontal==l)
direccion_horizontal=-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
//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 ;
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 ;
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 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--;
// 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.
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 ;
/* 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 --;
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);
}
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:
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:
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:
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
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++;
if (Nivel!=Puntos/PUNTOS_CAMBIO_NIVEL) {
Nivel = Puntos / PUNTOS_CAMBIO_NIVEL;
eneraigos_minuto += (20 * Nivel);
}
/♦Puntos*/
i f (e .t ipo_enemigo==e .ENEMIGO_INTELIGENTE)
Puntos+=50;
else
Puntos+=10 ;
}
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;
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();
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:
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
/ / 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;
}
/ / 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():
/ /VELOCIDAD_HORIZONTAL a d a p t a d a
V ELOCIDAD_HO RIZO NTAL=AnchoPantalla*l0 / 1 0 8 0 ;
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 ;
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;
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 ( 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;
}
}
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) {}
}
}
Videojuego en 2D Videojuego en 3D
Capítulo 5. Programación de videojuegos 275
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
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
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
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);
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
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 .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-).
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);
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 ? " ) ;
}
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
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
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
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
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.
« 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
@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
// 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 );
@ 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
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.
@ 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
// 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 );
}
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.
• 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).
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
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.
File Edit View Navigate Code Analyze Refactor Build Run Tools VCS Window Help
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 ?
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:
- 12:27
APPS WIDGETS
0 & “ & t
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
OK Cancel
OK I
i \ J
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
USER CONTROLS
Content filtering
Set the content filtering level to restrict apps that
can be downloaded
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.
# com,google.android.gms,common.api,GoogleApiClient.OnConnectionFailedUstener
jm^^^22!^32^SS!22í^^2!^SS!^S!ii2!221
I__I CopyJavaDoc
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
@ 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();
}
}
UltimaUb¡cacion_v2
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
// 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.
@ 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
I__! CopyJavaDoc
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 );
}
}
@ 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
¡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ó.
« 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
}
}
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());
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 ;
}
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
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);
Averiguar dirección
ObteniendoDireccion
Hello world!
dirección
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.
@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R. layout.activity_obteniendo_direccion)
crearGoogleApiClient() ;
}
@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);
/*
* RECEIVER
* Clase ResultadoDireccionReceiver extends ResultReceiver
* Objetivo: recibir resultados desde el IntentService
* (AveriguarDireccionlntentService)
*/
class ResultadoDireccionReceiver extends ResultReceiver {
@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
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
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
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
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);
generarUI(listadoSensores);
}
}
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.
©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);
/*
* 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
Ejercicio 2: 5 puntos
978-84-1622-831-7
www.garceta.es
9 788416 228317