Documentos de Académico
Documentos de Profesional
Documentos de Cultura
de Educacin, Cultura
y Deporte
aplicaciones para
Android II
COLECCIN AULA MENTOR SERIE PROGRAMACIN
CamSp SGALV
Desarrollo de Aplicaciones
para Android II
Programacin
Catlogo de publicaciones del Ministerio: www.educacion.gob.es
Catlogo general de publicaciones oficiales: www.publicacionesoficiales.boe.es
Autor
David Robledo Fernndez
Coordinacin pedaggica
Hugo Alvarez
NIPO: 030-14-019-1
ISBN: 978-84-369-5541-5
NDICE
Pg.
Unidad 0. Introduccin 11
1. Por qu un curso avanzado de Android?................................................................11
2. Cambios en las ltimas versiones de Android .......................................................11
3. La simbiosis de Android y Linux..............................................................................13
4. Instalacin del Entorno de Desarrollo ....................................................................16
4.1 Qu es Eclipse? 16
4.2 Instalacin de Java Development Kit ( JDK) 16
4.3 Instalacin de Eclipse ADT 18
5. Aadir versiones y componentes de Android...........................................................23
6. Definicin del dispositivo virtual de Android..........................................................26
Unidad 0. Introduccin
1.5 Cupcake 1.6 Donut 2.0/2.1 Eclair 2.2 Froyo 2.3 Gingerbread 3.0/3.1 Honeycomb ...IceCream
Sandwich
Aula Mentor
Quien est familiarizado con el sistema operativo Android ya sabr que los nombres de sus
diferentes versiones tienen el apodo de un postre.
A continuacin, vamos a comentar la evolucin de las diferentes versiones indicando las
mejoras y funcionalidades disponibles en cada una. Partiremos de la versin 3.0 ya que las ver-
siones anteriores a sta se tratan en el curso de Iniciacin de Android de Mentor.
Esta versin de Android se dise exclusivamente para ser utilizada en tabletas. En otros
dispositivos, como los telfonos, era necesario seguir utilizando la versin 2.3.7 disponible
en ese momento.
Definicin de perfiles de usuario limitados que, desde el punto de vista del desarrolla-
dor, implican una gestin de las Intenciones implcitas (Implicit Intent) para compro-
bar si el usuario tiene permisos para acceder a ese tipo de Intencin.
Mejoras en la gestin multimedia y de codecs de archivos de vdeo. Adems, permite
crear un vdeo de una Superficie dinmica.
Nuevos tipos de sensores relacionados con juegos.
Nueva Vista ViewOverlay que permite aadir elementos visuales encima de otros ya
existentes sin necesidad de incluir en un Layout. til para crear animaciones sobre la
interfaz de usuario.
Nuevas opciones de desarrollo como revocar el acceso a la depuracin USB de todos
los ordenadores o mostrar informacin del uso de la GPU del dispositivo.
Notification Listener es un nuevo servicio que permite que las aplicaciones reciban
notificaciones del sistema operativo y sustituye al servicio Accessibility APIs.
Importante
13
Los contenidos de este curso estn diseados para alumnos que estn familiariza-
dos con el entorno de desarrollo Eclipse / Android / Emulador de Android. Por ello,
los alumnos deben conocer y manejar con soltura Vistas bsicas, Actividades, Me-
ns, Dilogos, Adaptadores, sistema de ficheros, Intenciones, Notificaciones, Con-
tent Providers y utilizacin de SQLite. Todos estos conceptos bsicos de desarrollo
en este sistema operativo se tratan en el curso de Iniciacin a Android de Mentor.
Como sabes, Android est basado en Linux para los servicios base del sistema, como seguridad,
gestin de memoria, procesos y controladores. El diagrama de la arquitectura de Android tiene
este aspecto:
14
Antes del ao 2005, Linux estaba disponible en servidores web, aplicaciones de escritorio de
algunas empresas y administraciones, as como en ordenadores de programadores y entusiastas.
Sin embargo, con el despegue de Android, Linux empieza a estar instalado en nuestros mviles
y tabletas de forma masiva. En este apartado vamos a ver por qu es tan importante la simbiosis
Android y Linux.
El desarrollo de Linux empez el ao 1991 de la mano del famoso estudiante finlands
Linus Torvalds que crea la primera versin de este sistema operativo con el fin de implementar
una versin libre de licencias (Open Source) de Unix que cualquier programador pudiera mo-
dificar o mejorar a su antojo.
Al poco tiempo, grandes compaas como Intel e IBM advirtieron su potencial frente a
Windows e invirtieron grandes cantidades de dinero. Su objetivo principal era no depender de
Microsoft y, de paso, obtener un sistema operativo sin tener que empezar de cero.
En la actualidad, los sistemas operativos basados en Linux son sinnimo de estabilidad,
seguridad, eficiencia y rendimiento.
Sin embargo, hasta la aparicin de Android, a Linux le faltaba el xito entre el gran p-
U0 Introduccin
Es muy importante tener en cuenta que, para poder ejecutar el entorno de desarrollo Eclipse
ADT, es necesario tener instaladas en el ordenador las libreras de desarrollo de Java. La ltima
versin 1.7 ya es compatible con Eclipse ADT.
Podemos descargar la versin correcta del JDK de Java en:
http://www.oracle.com/technetwork/es/java/javase/downloads/index.html
U0 Introduccin
17
Si haces clic en el enlace anterior indicado, puedes encontrar un listado con todos los JDK de
Java:
Aula Mentor
Nota: en el caso de Linux o Mac, es posible tambin instalar Java usando los programas habituales
del sistema operativo que permiten la actualizacin de paquetes.
Nota: si vas a instalar Eclipse ADT en Linux, lee las notas que se encuentran en Preguntas y
Respuestas de esta Introduccin en la mesa del curso.
Si vamos a instalar Eclipse ADT en Windows, podemos hacer clic directamente en el enlace
Download the SDK. En caso contrario debemos hacer clic en el enlace DOWNLOAD FOR
OTHER PLATFORMS y seleccionar el sistema operativo correspondiente.
18
Hay que tener en cuenta que debemos descargar la versin 32 bits o 64 bits en funcin del
sistema operativo de que dispongamos.
En el caso de Windows podemos ver el tipo de sistema operativo haciendo clic con el
botn derecho del ratn en el icono Equipo o Mi PC del Escritorio y haciendo clic de nuevo
en Propiedades:
U0 Introduccin
En el caso de Linux, desde la lnea de comandos podemos ejecutar el siguiente comando para
saber si el sistema operativo es de 64bits:
$ uname -m
x86_64
En el caso de Apple Mac, desgraciadamente, slo est disponible Eclipse ADT si ests utilizando
un kernel de 64 bits. Para saber si tu Mac ejecuta el sistema operativo de 64 bits sigue estas
instrucciones: 19
- En el men Apple ( ), selecciona Acerca de este Mac y a continuacin, haz clic en Ms
informacin:
20
Si arrancamos Eclipse ADT haciendo doble clic sobre el acceso directo que hemos creado
anteriormente, a continuacin, Eclipse pedir que seleccionemos el workspace, es decir, el
directorio donde queremos guardar los proyectos.
U0 Introduccin
Importante:
Recomendamos usar el directorio C:\cursosMentor\proyectos como carpeta per- 21
sonal.
Si cerramos la pestaa abierta, podemos ver ya el entorno de desarrollo que deberas conocer si
has hecho del curso de inciacin:
22 Ahora vamos a comprobar en las preferencias que la versin de Java en Eclipse ADT es correcta
para compilar proyectos de Android. Para ello, hacemos clic en la opcin del men Window->
Preferences..., hacemos clic en el panel izquierdo sobre Java->Installed JREs y seleccionamos
jre7 en el campo Installed JREs:
U0 Introduccin
Para finalizar, en esta ventana hay que seleccionar la versin de Java utilizada para compilar los
proyectos de Android. Para ello hacemos clic en Java->Compiler y elegimos 1.6 en el campo
Compiler compliance settings:
23
24
Para instalar la versin 4.3 (si no lo est ya), seleccionamos los paquetes que se muestran en la
siguiente ventana:
25
Nota: la revisin de las versiones de Android puede ser superior cuando al alumno o alumna
instale el SDK.
Una vez hemos pulsado el botn Install 4 packages, aparece esta ventana y seleccionamos la
opcin Accept All y, despus, hacemos clic en Install:
Aula Mentor
El instalador tarda un rato (10-20 minutos) en descargar e instalar los paquetes. Una vez acabado
26 se indicar que la instalacin ha finalizado correctamente.
27
28
Atencin:
Este acelerador de hardware slo est disponible en algunos procesadores de
Intel que disponen de tecnologa de virtualizacin (VT=Virtualization Technology).
Adems, slo est disponible para el sistema operativo Windows.
U0 Introduccin
29
Aula Mentor
30
U0 Introduccin
Si no has podido instalar el acelerador del emulador, debes seleccionar el campo CPU/ABI
siguiente:
31
La opcin Snapshot-> Enabled permite guardar el estado del dispositivo de forma que todos
los cambios que hagamos, como cambiar la configuracin de Android o instalar aplicaciones,
queden guardados. As, la prxima vez que accedamos al emulador, se recupera automticamente
el ltimo estado.
Importante:
En el curso hemos creado un dispositivo virtual que no guarda el estado porque
puede producir problemas de ejecucin con Eclipse ADT. En todo caso, el alumno
o alumna puede usar la opcin Edit del AVD cuando crea necesario que los lti-
mos cambios sean almacenados para la siguiente sesin de trabajo.
Aula Mentor
32
Puedes encontrar el vdeo Cmo instalar Eclipse ADT, que muestra de manera
visual los pasos seguidos en las explicaciones anteriores.
U1 Multimedia y Grficos en Android
1. Introduccin
En esta Unidad vamos a explicar cmo disear aplicaciones multimedia Android para or
msica, grabar con el micrfono y cargar vdeos desde una tarjeta SD.
Algunas aplicaciones Android deben mostrar un aspecto dinmico o representar algn
dato en forma grfica para que el usuario visualice mejor la informacin que se le est ofrecien-
do. Como hemos comentado anteriormente en el curso, una aplicacin Android tiene xito si
est bien programada internamente y, adems, si tiene una apariencia atractiva exteriormente.
Para poder desarrollar aplicaciones que incluyan estas funcionalidades es necesario ad-
quirir previamente los Conceptos bsicos de grficos en Android.
Los grficos 2D/3D y las animaciones suelen ser muy tiles para presentar visualmente
informacin al usuario.
Para adquirir estas destrezas como programador Android, aprenderemos a animar imge-
nes de forma sencilla utilizando la API de animaciones de Android.
Despus, veremos qu es una Vista de tipo Superficie (ViewSurface) y sus aplicacio-
nes ms interesantes. 33
Finalmente, estudiaremos cmo aplicar a proyectos Android la conocidsima librera
OpenGL para crear grficos en 2D y 3D, aplicarles colores, animarlos y permitir que el usua-
rio interaccione con ellos.
2. Android Multimedia
Hoy en da, los dispositivos mviles han sustituido a muchos antiguos aparatos que utilizbamos
para escuchar msica, grabar conversaciones, ver vdeos, etctera.
En este apartado vamos a ver cmo disear aplicaciones multimedia Android y reprodu-
cir este tipo de archivos de audio y vdeo.
Mediante ejemplos prcticos expondremos una explicacin detallada de las funciones
propias del SDK que permitirn implementar una aplicacin multimedia.
La integracin de contenido multimedia en aplicaciones Android resulta muy sencilla e
intuitiva gracias a la gran variedad de clases que proporciona su SDK.
Tambin es posible grabar audio y vdeo, siempre y cuando el hardware del dispositivo lo
permita.
Aula Mentor
A continuacin, se muestra un listado de las clases de Android que nos permiten acceder
a estos servicios Multimedia:
- MediaPlayer: reproduce audio y vdeo desde ficheros ode streamings.
- MediaController: representa los controles estndar para MediaPlayer (botones de repro-
ducir, pausa, stop, etctera).
- VideoView: Vista que permite la reproduccin de vdeo.
- MediaRecorder: clase que permite grabar audio y vdeo.
- AsyncPlayer: reproduce una lista de archivos de tipo audio desde un hilo secundario.
- AudioManager: gestor del sonido del sistema operativo de varias propiedades como son el
volumen, los tonos de llamada/notificacin, etctera.
- AudioTrack: reproduce un archivo de audio PCM escribiendo un bfer directamente en
elhardware. PCM son las siglas de Pulse Code Modulation, que es un procedimiento de mo-
dulacin utilizado para transformar una seal analgica en una secuencia de bits.
- SoundPool: gestiona y reproduce una coleccin de recursos de audio de corta duracin.
- JetPlayer: reproduce audio y video interactivo creado con SONiVOX JetCreator.
- Camera:clase para tomar fotos y video con la cmara.
- FaceDetector: clase para identificar la cara de las personas en una imagen de tipo bitmap.
El sistema operativo Android soporta una gran cantidad de tipos de formatos multimedia,
la mayora de los cuales pueden ser tanto decodificados como codificados. A continuacin,
mostramos una tabla con los formatos nativos multimedia soportados por Android. Hay
que tener en cuenta que algunos modelos de dispositivos pueden incluir formatos adicionales
que no se incluyen en esta tabla, como DivX.
34 Extensin
Tipo Formato Codifica Decodifica Informacin
fichero
3GPP (.3gp)
H.263 S S MPEG-4
(.mp4)
3GPP (.3gp)
a partir Baseline Profile
H.264 AVC S MPEG-4
Android 3.0 (BP)
Video (.mp4)
MPEG-4 SP S 3GPP (.3gp)
WebM
a partir
Streaming a partir (.webm)
WP8 Android
de Android 4.0 Matroska
2.3.3
(.mkv)
Extensin
Tipo Formato Codifica Decodifica Informacin
fichero
JPEG S S Base + progresivo JPEG (.jpg)
GIF S GIF (.gif)
PNG S S PNG (.png)
Imagen BMP S BMP (.bmp)
a partir a partir WebP
WEBP
Android 4.0 Android 4.0 (.webp)
U1 Multimedia y Grficos en Android
Extensin
Tipo Formato Codifica Decodifica Informacin
fichero
AAC LC/LTP S S Mono/estreo
a partir con cualquier
HE-AACv1 S combinacin estndar 3GPP (.3gp)
Android 4.1
de frecuencia > MPEG-4(.
160 Kbps y ratios mp4)
HE-AACv2 S de muestreo de 8 a No soporta
48kHz raw AAC
(.aac) ni
a partir MPEG-TS (.ts)
a partir Mono/estreo,
AAC ELD Android
Android 4.1 16-8kHz
4.1
Tipo 0 y 1
MIDI tipo 0 y 1. DLS (.mid, .xmf,
v1 y v2. XMF y XMF .mxmf).
mvil. Soporte para RTTTL / RTX
MIDI S
tonos de llamada (.rtttl, .rtx),
RTTTL / RTX, OTA y OTA (.ota)
iMelody. iMelody
(.imy)
Ogg (.ogg)
Matroska
Ogg Vorbis S
(.mkv
a partir 4.0)
a partir
mono/estereo
FLAC Android
(no multicanal) FLAC (.flac)
3.1
8 y 16 bits PCM lineal
a partir
PCM/WAVE S (frecuencias limitadas WAVE (.wav)
Android 4.1
por el hardware)
Aunque el listado anterior pueda parecer muy complicado y amplio, te recomendamos que le
eches un vistazo a la Wikipedia donde se explica los distintos Formatos de archivo de audio.
Aula Mentor
El SDK de Android dispone de dos APIs principales que permiten reproducir ficheros de tipo
audio: SoundPool y MediaPlayer.
memoria desde un recurso (dentro de la APK) o desde el sistema de archivos. SoundPool utiliza
el servicio de la clase MediaPlayer, que estudiaremos a continuacin, para descodificar el au-
dio en un formato crudo (PCM de 16 bits) y mantenerlo cargado en memoria; as, el hardware
lo reproduce rpidamente sin tener que decodificarlas cada vez.
La clase SoundPool realiza esta carga en memoria de los archivos multimedia de forma
asncrona, es decir, el sistema operativo lanzar el sonido con el listener OnLoadComplete-
Listener cuando se haya completado la carga de cada uno de los archivos.
Es posible repetir los sonidos en un bucle tantas veces como sea necesario, definiendo
un valor de repeticin al reproducirlo, o mantenerlo reproduciendo en un bucle infinito con el
valor -1. En este ltimo caso, es necesario detenerlo con el mtodo stop().
Tambin podemos establecer la velocidad de reproduccin del sonido, cuyo rango pue-
de estar entre 0.5 y 2.0. Una velocidad de reproduccin de 1.0 indica que el sonido se reproduce
en su frecuencia original. Si definimos una velocidad de 2.0, el sonido se reproduce al doble de
su frecuencia original y, por el contrario, si fijamos una velocidad de 0.5, lo har lentamente a la
mitad de la frecuencia original.
Cuando se crea un objeto del tipo SoundPool hay que establecer mediante un parmetro
el nmero mximo de sonidos que se pueden reproducir simultneamente. Este parmetro no
tiene por qu coincidir con el nmero de sonidos cargados. Adems, cuando se reproduce un
sonido con su mtodo play(), hay que indicar su prioridad. As, cuando el nmero de repro-
ducciones activas supere el valor mximo establecido en el constructor, esta prioridad permite
que el sistema detenga el flujo con la prioridad ms baja y, si todos tienen la misma prioridad, se
parar el ms antiguo. Sin embargo, en el caso de que el nuevo flujo sea el de menor prioridad,
ste no se reproducir.
En el ejemplo prctico vamos a estudiar los mtodos ms importantes de esta clase.
37
38
U1 Multimedia y Grficos en Android
Mediante un ejemplo prctico vamos a estudiar los mtodos ms importantes de esta clase.
Adicionalmente, la clase MediaRecorder dispone de mtodos que puedes utilizar para configurar
la grabacin de video.
Tal y como ocurre con la clase MediaPlayer, para poder invocar los diferentes mtodos
de la clase MediaRecorder debemos estar en un estado determinado. El siguiente esquema
permite conocer los mtodos que podemos invocar desde cada uno de sus estados y cul es el
nuevo estado al que cambiar el objeto tras invocarlo:
40
pulsa en un botn.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<LinearLayout
android:id=@+id/linearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=vertical
android:gravity=top
android:layout_marginTop=6dp
android:layout_marginBottom=1dp>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignParentLeft=true
android:layout_alignParentTop=true
android:text=Haz clic en un botn
android:textAppearance=?android:attr/textAppearanceMedium/>
<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=65dp 41
android:layout_width=fill_parent
android:orientation=horizontal>
<Button
android:id=@+id/soundpool1
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Tono SoundPool 1
android:tag=1 />
<Button
android:id=@+id/soundpool2
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Tono SoundPool 2
android:tag=2 />
</LinearLayout>
<Button
android:id=@+id/mediaplayer
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp
android:text=Reproducir Cancin con MediaPlayer />
<Button
android:id=@+id/mediaplayer_record
Aula Mentor
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp
android:text=Grabar conversacin />
<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Log:/>
</ScrollView>
</LinearLayout>
</RelativeLayout>
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
42 MainActivity.java:
// Botones de la Actividad
private Button boton_spool1, boton_spool2;
private Button boton_mplayer;
private Button boton_mrecorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
// Localizamos las Vistas del layout
logTextView = (TextView) findViewById(R.id.Log);
U1 Multimedia y Grficos en Android
scrollview = ((ScrollView)findViewById(R.id.ScrollView));
boton_spool2.setOnClickListener(click);
recorder.release();
addRecordingToMediaLibrary();
// Refrescamos interfaz usuario
boton_mrecorder.setText(Grabar conversacin);
boton_spool.setEnabled(true);
boton_mplayer.setEnabled(true);
// Log de la accin
log(Parada grabacin MediaRecorder);
} else
{
// Cambiamos los botones y hacemos log
boton_mrecorder.setText(Parar grabacin);
boton_spool.setEnabled(false);
boton_mplayer.setEnabled(false);
log(Grabando conversacin);
log();
}
} // end clase
La siguiente sentencia establece el tipo de flujo a msica, lo que permite que el usuario utilice
los botones de subida y bajada de volumen del dispositivo:
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
Por ltimo, debemos precargar con el objeto SoundPool los archivos de audio con el mto-
do siguiente: SoundPool.load(Context context, int resId, int priority). Donde
resId es la Id de nuestro archivo de msica. El parmetro priority permite seleccionar la
prioridad de este sonido frente a otro en caso de que se llegue al mximo nmero de sonidos
simultneos establecidos en el constructor de la clase.
Mediante el listener OnLoadCompleteListener el sistema operativo avisar cada
vez que complete la carga de un archivo de sonido.
Para reproducir un sonido debemos usar el mtodo play (int soundID, float left-
Volume, float rightVolume, int priority, int loop, float rate) cuyos parmetros
indican:
- soundID: ID del sonido que ha indicado el mtodo load() al cargarlo.
- leftVolume: volumen del altavoz izquierdo (rango de 0.0 a 1.0)
- rightVolume: volumen del altavoz derecho (rango de 0.0 a 1.0)
- priority: prioridad del sonido (0 es la ms baja)
- loop: modo en bucle si establecemos el valor -1.
- rate: velocidad de reproduccin (1.0 = normal, rango de 0.5 a 2.0)
47
En el siguiente bloque de cdigo hemos utilizado la clase Mediaplayer para reproducir una
pista de audio mediante su mtodo start() y pararla con el mtodo stop().
Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en la car-
peta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp3 se empaqueta
con la aplicacin y hace que sta ocupe mucho espacio. Si queremos reproducir una cancin
desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
- MediaPlayer mPlayer = new MediaPlayer();
- mPlayer.setDataSource(RUTA+NOMBRE_FICHERO);
- mPlayer.prepare();
- mPlayer.start();
Observa que, en este caso, hay que invocar previamente el mtodo prepare() para cargar el
archivo de audio. En el ejemplo del curso no es necesario hacerlo ya que esta llamada se hace
desde el constructor create().
El ltimo bloque de cdigo realiza una grabacin empleando la clase MediaRecor-
der. Hemos definido la variable audiofile para guardar la grabacin. Para iniciar la grabacin
utilizamos los mtodos setAudioSource() que establece el micrfono de entrada; setOut-
putFormat() selecciona el formato de salida; setAudioEncoder() indica la codificacin del
audio; setOutputFile() establece el fichero de salida y start() inicia la grabacin.
A la hora de parar la grabacin, simplemente debemos invocar los mtodos stop() y
release() que libera los recursos del sistema.
Para finalizar con el cdigo Java, hemos desarrollado el mtodo local addRecording-
ToMediaLibrary() que aade la nueva grabacin a la librera multimedia del dispositivo.
Para ello, vamos a utilizar un Intent del tipo ACTION_MEDIA_SCANNER_SCAN_FILE y enviar
un mensaje Broadcast al sistema operativo para buscar el nuevo contenido multimedia de tipo
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente apli-
cacin:
48
Para poder or en tu AVD los sonidos, debes encender los altavoces de tu ordena-
dor. Prueba a ejecutar sonidos mediante SoundPool simultneamente, incluso si
se est reproduciendo msica con el MediaPlayer.
Sin embargo, la funcionalidad de grabacin de audio no est integrada en el AVD y, para poder
U1 Multimedia y Grficos en Android
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar Ajustes del dispositivo en
las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
3.4 Cmo habilitar USB Debugging en Android 4.2 y superior Jelly Bean
A partir de la versin de Android Jelly Bean 4.2, Google esconde la opcin de Desarrollo
(Developer) en los Ajustes (Settings) del dispositivo. Para que aparezca esta opcin debes
dar los pasos siguientes:
- Abre Opciones->Informacin del telfono/Tablet.
- Haz clic repetidamente en la opcin Nmero de compilacin (Build Number) hasta 7
veces seguidas.
Eso es todo, aparecer un mensaje de que Ya eres un developer y vers que aparece la
nueva opcin Opciones de desarrollo (Developer) y dentro encontrars USB Debugging.
Fjate en las siguientes capturas de pantalla:
49
Aula Mentor
La clase VideoView permite al programador incluir vdeos en las aplicaciones y abarca una gran
cantidad de mtodos para hacerlo. Los mtodos ms utilizados son los siguientes:
- setVideoPath(String path): especifica el directorio y archivo de vdeo que se repro-
duce. Podemos indicar tanto una URL para vdeos en Internet como un archive local en el
dispositivo.
- setVideoUri(Uri uri): de igual forma que el mtodo anterior, establece la fuente del
vdeo en formato URI.
- start(): inicia la reproduccin del vdeo.
- stopPlayback(): para la reproduccin del vdeo.
- pause(): pausa la reproduccin del vdeo.
- isPlaying(): devuelve true o false indicando as si se est reproduciendo un vdeo o no.
- setOnPreparedListener(MediaPlayer.OnPreparedListener): define un mtodo
callback que se invoca cuando el vdeo est preparado para reproducirse.
- setOnErrorListener(MediaPlayer.OnErrorListener): establece el mtodo callback
que el sistema invocar en caso de un error en reproduccin del vdeo. Este mtodo es muy
til cuando el vdeo est mal codificado u ocurre un error de conexin a Internet al repro-
ducir un vdeo remoto.
- setOnCompletionListener(MediaPlayer.OnCompletionListener): permite definir un
mtodo callback para detectar que la reproduccin del vdeo ha terminado.
- getDuration(): indica la duracin del vdeo. Devuelve siempre -1 salvo que lo ejecutemos
dentro del evento OnPreparedListener().
- getCurrentPosition(): devuelve la posicin actual de reproduccin del vdeo.
50 - setMediaController(MediaController): establece el objeto MediaController que ve-
remos a continuacin.
La interfaz de la aplicacin muestra una barra de herramientas en la parte superior con botones
que permiten al usuario controlar la reproduccin del vdeo. En la parte central hemos incluido
un objeto heredado de la clase VideoView. En la parte de abajo de la Actividad hemos incluido
una vista de tipo TextView desplazable que muestra las acciones del usuario.
<RelativeLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_height=fill_parent
android:layout_width=fill_parent>
<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=wrap_content
android:layout_width=fill_parent
android:orientation=horizontal
android:layout_alignParentTop=true
android:gravity=center_horizontal>
<ImageButton
android:id=@+id/play
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/play/>
<ImageButton 51
android:id=@+id/pause
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/pause/>
<ImageButton
android:id=@+id/stop
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/stop/>
<ImageButton
android:id=@+id/controls
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/plus/>
<ImageButton
android:id=@+id/logButton
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/log/>
</LinearLayout>
<es.mentor.unidad3.eje2.video.CustomVideoView
android:id=@+id/videoView
android:layout_height=fill_parent
android:layout_width=fill_parent
Aula Mentor
android:layout_below=@+id/botonesLayout/>
<ScrollView
android:id=@+id/ScrollView
android:layout_height=150dp
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:text=Log:/>
</ScrollView>
</RelativeLayout>
Puedes observar que en lugar de utilizar directamente la clase VideoView hemos indicado la
clase heredada de sta es.mentor.unidad3.eje2.video.CustomVideoView. Esto se hace
as porque hemos redefinido los eventos onPlay(), onPause() y onTimeBarSeekChanged()
y, para hacerlo, es necesario extender la clase VideoView.
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fi-
chero MainActivity.java:
mediaC.setMediaPlayer(visorVideo);
visorVideo.setMediaController(mediaC);
// Definimos el listener cuando el usuario reproduce, para o
// cambia la posicin del vdeo
visorVideo.setPlayPauseListener(new
CustomVideoView.PlayPauseListener() {
// Reproduce vdeo
@Override
public void onPlay() {
// Hacemos log y deshabilitamos botones
log(REPRODUCIENDO VIDEO);
bPause.setEnabled(true);
bStop.setEnabled(true);
bPlay.setEnabled(false);
}
@Override
public void onPause() {
// Hacemos log y deshabilitamos botones
log(VIDEO EN PAUSA);
bPause.setEnabled(false);
bPlay.setEnabled(true);
}
@Override
public void onTimeBarSeekChanged(int currentTime) {
// Hacemos log
log(CAMBIO POSICION VIDEO: +currentTime);
// Si se ha parado el vdeo lo indicamos 53
if (currentTime==0 && !visorVideo.isPlaying()) {
bStop.setEnabled(false);
log(PARADA VIDEO);
}
}
}); // end setPlayPauseListener
logTextView = (TextView)findViewById(R.id.Log);
54 // Buscamos el botn Reproducir y definimos su evento onClick
bPlay = (ImageButton)findViewById(R.id.play);
bPlay.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pedimos el foco del vdeo y lo reproducimos
visorVideo.requestFocus();
visorVideo.start();
}
});
// Buscamos el botn Pausa y definimos su evento onClick
bPause = (ImageButton)findViewById(R.id.pause);
bPause.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pausamos la reproduccin
visorVideo.pause();
}
});
// Buscamos el botn Parada y definimos su evento onClick
bStop = (ImageButton)findViewById(R.id.stop);
bStop.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pausamos la reproduccin y vamos al principio del
// vdeo
visorVideo.pause();
visorVideo.seekTo(0);
}
});
// Buscamos el botn Controles y definimos su evento onClick
bControls = (ImageButton)findViewById(R.id.controls);
U1 Multimedia y Grficos en Android
bControls.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
// Mostramos los controles de usuario del vdeo
mediaC.show();
}
});
// Buscamos el botn Log y definimos su evento onClick
bLog = (ImageButton)findViewById(R.id.logButton);
bLog.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (scrollview.getVisibility()==TextView.VISIBLE) {
scrollview.setVisibility(TextView.INVISIBLE);
} else {
scrollview.setVisibility(TextView.VISIBLE);
}
}
});
// Inicializamos Vistas
log();
bPause.setEnabled(true);
bStop.setEnabled(true);
} // end onCreate
En el cdigo fuente anterior puedes ver que la aplicacin extiende la clase Activity. Adems,
implementa varias interfaces que corresponden a varios eventos. Despus, se sigue con la
declaracin de los diferentes elementos de la aplicacin. La variable posActual almacena la
posicin de reproduccin.
En lugar de utilizar directamente la clase VideoView, la hemos extendido en la nueva
clase CustomVideoView para poder definir los eventos onPlay(), onPause() y onTime-
BarSeekChanged() ya que la clase base no los incluye y es necesario redefinir sus mtodos
start(), pause() y seekTo() respectivamente para poder detectar cundo el usuario realiza
una de estas acciones.
Con el mtodo setVideoURI() hemos indicando el fichero local del paquete de la
aplicacin. Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en
la carpeta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp4 se em-
paqueta con la aplicacin y hace que sta ocupe muchsimo espacio. Si queremos reproducir
vdeo desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
videoView.setVideoURI(Uri.parse(file:// +
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES) + /video.mp4));
@Override
public void start() {
// Primero llamamos al mtodo de la clase original
super.start();
if (mListener != null) {
// Si el listener est asignado, ejecutamos el mtodo
mListener.onPlay();
}
}
@Override
public void seekTo(int msec)
{
// Primero llamamos al mtodo de la clase original
super.seekTo(msec);
if (mListener != null) {
// Si el listener est asignado, ejecutamos el mtodo
mListener.onTimeBarSeekChanged(msec);
}
}
// Definimos las interfaces de la nueva clase que deben
Aula Mentor
// implementarse
interface PlayPauseListener {
void onPlay();
void onPause();
void onTimeBarSeekChanged(int currentTime);
}
} // end clase
Como puedes ver, la clase anterior es muy sencilla: hemos redefinimos los mtodo internos
start(), pause() y seekTo() de la clase VideoView para poder invocar dentro de sta los
mtodos que se definen en la interfaz PlayPauseListener: onPlay(), onPause() y on-
TimeBarSeekChanged() respectivamente. Mediante su mtodo setPlayPauseListener()
podemos establecer el listener correspondiente.
Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente apli-
cacin:
58
U1 Multimedia y Grficos en Android
Android representa un color utilizando un nmero entero de 32 bits. Estos bits se dividen en 4
campos de 8 bits: alfa, rojo, verde y azul (ARGB, si usamos las iniciales en ingls). Dado que
cada componente de color consta de 8 bits, podr tomar 256 valores diferentes.
Las componentes rojo, verde y azul son utilizadas para definir un color y la componente
alfa define su grado de transparencia con respecto al fondo (capa inferior). Un valor de 255
significa un color opaco y, a medida que reduzcamos este valor, el color se ir haciendo ms
transparente.
Para conseguir una ptima separacin entre la programacin Java y el diseo de la interfaz de
usuario, se recomienda utilizar la ltima opcin, es decir, no definir directamente los colores
en el cdigo fuente, sino utilizar el fichero de recursos res/values/colors.xml del proyecto:
As, si deseamos cambiar los colores de la aplicacin, nicamente debemos modificar este
archivo de recursos.
Aula Mentor
A continuacin, expondremos las clases ms importantes que se utilizan para dibujar en Android:
La clase Paint se emplea para definir el pincel que utilizaremos para pintar. Podemos definir
su color, su tipo de trazo, transparencia, etctera. Veamos los mtodos de esta clase ms utili-
zados por el programador:
- setColor(int color): indica el color del pincel utilizando una de las definiciones ante-
riores de colores.
- setAlpha(int alfa): modifica el grado de transparencia del pincel.
- setStrokeWidth(float grosor): define el grosor del trazado.
- setStyle(Paint.Style estilo): marca el estilo de relleno con las constante FILL (relle-
no), FILL_AND_STROKE (relleno y borde), STROKE (slo dibuja borde).
- setShadowLayer(float radio, float x, float y, int color): realiza un segundo tra-
zado a modo de sombra.
- setTextAlign(Paint.Align justif): justifica el texto segn las constantes CENTER, LEFT
y RIGHT.
- setTextSize(float size): establece el tamao de la fuente del texto.
- setTypeface (Typeface typeface): indica el tipo de fuente MONOSPACE, SERIF y SANS_
SERIF. Adems, podemos definir negrita e itlica.
- setTextScaleX(float escala): seala el factor de escalado horizontal. Un valor de 1.0
indica sin escalado.
60 - setTextSkewX(float inclinacion): indica el factor de inclinacin del texto. 0 denota sin
inclinacin.
- setUnderlineText(boolean subrayado): determina si un texto aparece subrayado o no.
La clase Rect permite dibujar un rectngulo que se representa mediante sus cuatro lados: iz-
quierdo, derecho, alto y bajo. Su constructor tiene este aspecto:
Rect(int left, int top, int right, int bottom)
Podemos obtener su ancho y largo mediante los mtodos height() y width() respectiva-
mente.
La clase Path (del ingls, camino) permite definir un trazado mediante segmentos de lnea y
curvas. Un Path tambin se puede utilizar para dibujar un texto sobre el trazado marcado y
para ocultar (tramas) o difuminar.
- addRect(float left, float top, float right, float bottom, Path.Direction dir):
aade un rectngulo del tamao indicado siguiendo el sentido dir que puede tomar los va-
lores CW (giro de la agujas del reloj) y CCW (sentido contrario a las agujas del reloj).
- addCircle(float x, float y, float radio, Path.Direction dir): aade un crculo de
radio siguiendo el sentido dir que puede tomar los valores CW (giro de la agujas del reloj)
y CCW (sentido contrario a las agujas del reloj).
- offset (float dx, float dy): desplaza el trazado en dx y dy.
- reset(): limpia el trazado actual.
- close(): cierra el trazado actual, es decir, ya no se pueden aadir nuevos elementos al
mismo.
La clase Canvas (del ingls, lienzo) representa la superficie bsica donde podemos dibujar
grficos. Dispone de varios mtodos que permiten representar lneas, crculos, texto, etctera.
Para dibujar en un lienzo debemos utiliza un pincel (clase Paint que hemos visto)
donde indicamos el color, grosor de trazo, transparencia, etctera.
Tambin es posible definir una matriz de 3x3 (Matrix) que permite transformar coor-
denadas aplicando una translacin, escala o rotacin del lienzo. 61
Adems, podemos seleccionar un rea conocida como Clip para que los mtodos de
dibujo afecten solo a esta rea.
A continuacin, veamos los mtodos ms importantes de esta clase Canvas segn su
funcin. Como vers, no hemos incluido todos sus mtodos, por lo que recomendamos con-
sultar la documentacin oficial para obtener informacin ms detallada.
Puedes notar que el tamao del lienzo es un nmero entero aunque, para dibujar en l, debes
indicar posiciones con nmeros decimales (float) contenidos en ste.
- drawLine(float iniX, float iniY, float finX, float finY, Paint paint): pinta un
lnea empezando en la posicin (iniX, iniY) y finalizando en (finX, finY) empleando el
pincel paint.
- drawLines(float[] puntos, Paint paint): pinta una lnea continua siguiendo los pun-
tos de la matriz bidimensional puntos empleando el pincel paint.
- drawArc(RectF rect, float iniAngulo, float finAngulo, boolean usarCentro,
Paint paint): dibuja un arco en el rectngulo rect, de ngulo inicial iniAngulo y final
finAngulo, muestra el centro del valo si lo indicamos en el parmetro usarCentro y em-
plea el pincel paint.
- drawPath(Path trazo, Paint paint): dibuja un camino utilizando el pincel paint.
- drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint): dibuja la ima-
gen bitmap en el rectngulo dst del lienzo recortando la imagen original con el rectngulo
src y utilizando el pincel paint.
- drawBitmap(Bitmapbitmap,Matrixmatriz,Paintpincel): dibuja la imagen bit-
map recortando la imagen original utilizando la matriz y el pincel paint.
A continuacin, se muestra el Ejemplo 3 donde se crea una Vista que se dibuja mediante
cdigo Java empleando la clase Canvas, es decir, no se define un fichero layout xml en el
proyecto. Si abres el archivo MainActivity.java vers que contiene:
El cdigo anterior comienza con la creacin de una Activity en la que asociamos un objeto
CanvasView extendido de tipo View mediante el mtodo setContentView() que no est
definido mediante un layout XML.
Sin embargo, si accedes al fichero CanvasView.java vers que define:
@Override
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 3, vers que se muestra la siguiente aplicacin en
el Emulador:
65
Si cambias la orientacin del AVD con el atajo de teclado [CTRL+F12] vers que la imagen se
amolda al nuevo tamao de pantalla y muestra un mensaje indicndolo.
Aula Mentor
Un Dibujable (del ingls, Drawable) es un mecanismo para dibujar la interfaz de una aplica-
cin Android. Existen muchos tipos de recursos dibujables, tales como, archivos de imgenes,
colores, gradientes, formas geomtricas, etctera. A continuacin, vamos a estudiar los ms
importantes.
Podemos entender la clase Drawable como una abstraccin que representa algo que
se puede dibujar. Muchos de estos dibujables pueden ser definidos como recursos mediante
ficheros XML.
Notamos que es posible utilizar como base la clase Drawable o uno de sus descendientes para
crear tus propias clases grficas.
Al inicio de la Unidad 2 estudiaremos algunos tipos de Drawables ms.
Adems, esta clase Drawable proporciona una serie de mecanismos para indicar cmo
debe ser pintado el grfico (cada tipo de Drawable implementa algunos de ellos). Veamos los
ms importantes:
- setBounds(x1, y1, x2, y2): indica el rectngulo donde se debe dibujar el Drawable.
ste debe respetar el tamao indicando por el programador, es decir, el dibujo se escala. Po-
demos consultar el tamao de un Drawable mediante los mtodos getIntrinsicHeight()
y getIntrinsicWidth().
- getPadding(Rect): proporciona informacin sobre los mrgenes recomendados para re-
presentar contenidos. Por ejemplo, un Drawable destinado a ser el marco de un botn, debe
devolver los mrgenes correctos para localizar las etiquetas u otros contenidos en el interior
del botn.
U1 Multimedia y Grficos en Android
Como has visto, la forma ms sencilla de aadir imgenes a una aplicacin Android es incluir-
las en la carpeta res/drawable del proyecto. El SDK de Android soporta los formatos PNG,
JPG y GIF. El formato recomendado es PNG, aunque tambin se puede utilizar JPG. Android
desaconseja el uso del formato GIF.
Cada imagen de esta carpeta se asocia automticamente a un ID de recurso. Por ejemplo, para
el archivo mentor.png crear el ID mentor que permite hacer referencia a la imagen desde el
cdigo o desde un archivo de recursos XML:
<bitmap xmlns:android=http://schemas.android.com/apk/res/android
android:src=@drawable/mentor/>
Este tipo de objetos grficos se utiliza con frecuencia como fondo de botones o de pantalla.
El parmetro angle establece la direccin del degradado. nicamente se pueden definir los
ngulos 0, 90, 180 y 270.
Si guardamos el archivo anterior en res/drawable/degradado.xml entonces
podemos utilizarlo para establecer el fondo de una vista en su Layout en XML introduciendo
el siguiente atributo en el Layout main.xml de la aplicacin:
android:background=@drawable/degradado
Adems, es posible introducir la siguiente sentencia en el constructor de una Actividad
para que este drawable sea utilizado como degradado de fondo:
setBackgroundResource(R.drawable.degradado);
Aula Mentor
Este drawable permite dibujar formas dinmicamente mediante primitivas vectoriales dispo-
nibles en Android: Veamos un ejemplo sencillo que pinta una forma ovalada y la rellena de
color rojo:
ShapeDrawable imagen = new ShapeDrawable(new OvalShape());
imagen.getPaint().setColor(0xffff0000);
imagen.setBounds(10, 10, 250, 75);
imagen.draw(canvas);
Con la orden setBounds() hemos establecido los lmites de la forma, es decir, su tamao.
Android proporciona varias formas de crear animaciones, tambin mediante Drawables, que
tienen la ventaja de que se pueden crear desde un fichero XML.
Estas animaciones se crean a partir de un grupo de fotogramas. Para ello, emplearemos
la clase AnimationDrawable.
Veamos en el Ejemplo 4 de esta Unidad una aplicacin sencilla que utiliza este tipo
de animacin.
Si abres el archivo res/drawable/animacion.xml advertirs que hemos definido los
fotogramas de la animacin utilizando la etiqueta animation-list y las imgenes contenidas
en esta carpeta:
68 <animation-list
xmlns:android= http://schemas.android.com/apk/res/android
android:oneshot= false>
<item android:drawable=@drawable/android1
android:duration=200 />
<item android:drawable=@drawable/android2
android:duration=200 />
<item android:drawable=@drawable/android3
android:duration=200 />
</animation-list>
imagen.setBackgroundColor(Color.WHITE);
// Establecemos la animacin en la imagen
imagen.setImageDrawable(animacion);
// Definimos el evento onClick de la imagen que inicia o para la
// animacin
imagen.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (animacion.isRunning()) animacion.stop();
else animacion.start();
}
});
// El contenido de la actividad es la imagen
setContentView(imagen);
} // end onCreate
} // end clase
Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra la siguiente aplicacin en
el Emulador:
Aula Mentor
5. Animaciones de Android
Android dispone de tres mecanismos para crear animaciones en las aplicaciones:
- AnimationDrawable: mediante esta clase, ya vista en el apartado anterior, podemos crear
drawables que reproducen una animacin fotograma a fotograma (en ingls se denomina
Frame Animation).
- Animaciones Tween: crean efectos de translacin, rotacin, zoom y alfa a cualquier vista
de una aplicacin Android, cambiando su representacin en la pantalla.
- API de animacin de Android: anima cualquier propiedad de un objeto Java sea del tipo
Vista o no, modificando el objeto en s mismo.
A continuacin, mediante ejemplos aprenderemos a usar estos dos ltimos mecanismos para
crear animaciones en Android.
Una animacin tween (del ingls between, que significa en medio o entre) consiste en
realizar una serie de transformaciones simples en las Vistas de una Actividad, como su posicin,
70 tamao, rotacin y transparencia.
Por ejemplo, es posible mover, rotar, modificar el tamao o cambiar la transparencia a un
objeto del tipo TextView.
La clase Animation de Android es la que permite crear animaciones en las Vistas de una
Actividad.
Las instrucciones que definen esta animacin son transformaciones donde indicamos
cundo ocurrirn y cunto tiempo tardarn en completarse. Estas transformaciones pueden eje-
cutarse de forma secuencial o simultnea. Cada tipo de transformacin posee unos parmetros
especficos, si bien existen unos parmetros comunes a todas ellas, como son el tiempo de du-
racin y de inicio.
Los ficheros XML que definen animaciones deben almacenarse en el directorio res/
anim/ del proyecto Android y deben contener un nico elemento raz que indique las transfor-
maciones que deseamos ejecutar. Esta etiqueta raz debe ser una de las siguientes:
- <translate>: mueve la Vista.
- <rotate>: rota la Vista.
- <scale>: escala la Vista.
- <alpha>: modifica la opacidad de la Vista.
- <set>: conjunto de varias transformaciones anteriores.
U1 Multimedia y Grficos en Android
Por defecto, todas las instrucciones de una animacin ocurren a partir del instante inicial. Si es
necesario que una animacin comience ms tarde, hay que especificar su atributo startOffset.
Nombre
Atributo Descripcin
transformacin
fromXDelta Valores inicial y final del desplazamiento en el
toXDelta eje X.
<translate>
fromYDelta Valores inicial y final del desplazamiento en el
toYDelta eje Y.
Grado inicial y final de la rotacin. Para
realizar un giro completo en sentido
fromDegrees antihorario debemos establecer 0 y 360
toDegrees respectivamente. Para sentido horario, de 360
<rotate>
a 0 o de 0 a -360. Para dos giros consecutivos
escribe 0 y 720.
pivotX Punto sobre el que se realiza el giro que
pivotY queda fijo en la pantalla.
fromXScale Valor inicial y final para la escala del eje X
toXScale (0.5=50%, 1=100%)
fromYScale
<scale> toYScale
Valor inicial y final para la escala del eje Y
pivotX Punto sobre el que se realiza el giro que
pivotY queda fijo en la pantalla.
fromAlpha,
<alpha> toAlpha
Valor inicial y final de la opacidad.
Aula Mentor
En el Ejemplo 5 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza este
tipo de animacin Tween.
Si abres el archivo res/anim/animacion.xml advertirs que hemos definido un con-
junto de transformaciones de la animacin utilizando la etiqueta set:
<set xmlns:android=http://schemas.android.com/apk/res/android>
<alpha
android:startOffset=0
android:duration=3000
android:fromAlpha=0
android:toAlpha=1 />
<rotate
android:startOffset=4000
android:duration=2000
android:fromDegrees=0
android:toDegrees=360
android:pivotX=50%
android:pivotY=50%/>
<translate
android:startOffset=6000
android:duration=500
android:fromXDelta=0
android:fromYDelta=0
android:toXDelta=500
android:toYDelta=0 />
72 </set>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las imgenes de la animacin
setContentView(R.layout.main);
final TextView texto = (TextView)findViewById(R.id.textoAnimado);
// Definimos la animacin
final Animation animacion = AnimationUtils.loadAnimation(this,
R.anim.animacion);
// Evento que lanza Android cuando le ocurre algo a la animacin
animacion.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// Cuando la animacin acaba, volvemos a iniciarla
U1 Multimedia y Grficos en Android
El cdigo fuente anterior comienza declarando el objeto animacion de la clase Animation que
se inicializa mediante el mtodo loadAnimation() a partir del fichero XML anterior incluido en
la carpeta anim.
Despus, se inicia la animacin sobre una etiqueta TextView mediante el mtodo
startAnimation().
Finalmente, se crea un listener del tipo AnimationListener para detectar cundo se inicia
la animacin, se para o se repite. En este caso usamos este listener para que la animacin
comience de nuevo.
73
Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente apli-
cacin:
Aula Mentor
En este apartado vamos a estudiar la API de Animacin de Android que permite cambiar las
propiedades de objetos en un intervalo de tiempo, dando as al usuario una sensacin de
movimiento lineal en la interfaz.
Esta API est disponible a partir de la versin 3.0 de Android (nivel de API 11). Sin em-
bargo, existe una librera en Internet que permite utilizar animaciones en versiones anteriores.
En este curso slo utilizaremos la librera nativa de Android.
Mediante esta API podemos indicar el movimiento de una Vista o cualquier objeto Java
de una aplicacin indicando su nueva posicin y el tiempo en el que debe acabar la animacin.
A diferencia de las animaciones Tween, que nicamente son aplicables a Vistas, la API
de animacin puede aplicarse a cualquier tipo de objetos Java. Adems, es ms flexible poder
animar cualquier propiedad del objeto, es decir, no est restringida a las cuatro transformaciones
que hemos estudiado anteriormente. Por ejemplo, podemos crear una animacin que cambie
progresivamente el color de fondo de una Vista.
Las animaciones Tween slo modifican la forma en que Android representa una
Vista, pero sus propiedades internas no cambian. Por ejemplo, si aplicamos una animacin
Tween a una etiqueta para desplazarla por la pantalla, la animacin se visualizar correctamente
pero, al finalizar, la etiqueta seguir en la posicin inicial. Si hubiramos empleado la API de
Animacin, las propiedades de la etiqueta hubieran sido modificadas efectivamente.
Es posible definir animaciones en archivos XML dentro del directorio /res/anim del proyecto
empleando esta API.
Adems, cuando una aplicacin consta de varias Actividades, podemos tambin animar
los cambios que ocurren cuando el usuario cambia la Actividad activa.
A continuacin, vamos a estudiar en detalle esta API.
5.2.1.1 Animator
Superclase de la API de la que se extienden el resto de clases. Veamos los mtodos ms im-
portantes de esta clase:
- start(): comienza la animacin.
- end(): termina la animacin. El objeto se queda en el ltimo estado marcado por esta ani-
macin.
- cancel(): cancela la animacin y vuelve al estado inicial.
- setDuration(long duration): indica la duracin en milisegundos de la animacin.
- setTarget(): establece el objeto que va a animar.
- addListener(): aade el listener correspondiente a los cambios de estado de la anima-
cin. 75
- removeAllListeners(): quita todos los listeners de la animacin.
- removeListener(listener): quita el listener indicado como parmetro de la anima-
cin.
- isRunning(): seala si la animacin se est ejecutando.
- setInterpolator(): establece el interpolador que usaremos en la animacin.
- setStartDelay(long retraso): retrasa el inicio de la animacin despus de invocar el
mtodo start(). Como es habitual, el tiempo retraso se indica en milisegundos.
Hay que tener en cuenta que todas las clases siguientes, al extenderse de sta, heredan los
mtodos anteriores.
5.2.1.2 ValueAnimator
Una forma de entender mejor por qu se llama tambin a esta API Animacin de Propie-
dades es mediante el siguiente ejemplo que define una animacin mediante la clase Va-
lueAnimator. En sta se modifica el valor de una variable de tipo entero durante un intervalo
de tiempo de 5000 milisegundos. Veamos cdigo fuente del ejemplo:
La clase ValueAnimator es un mecanismo para hacer algo cada 10 milisegundos (valor por
defecto).
5.2.1.3 ObjectAnimator
5.2.1.4 AnimatorSet
5.2.1.5 AnimatorBuilder
5.2.1.6 AnimationListener
Como puedes ver, los listeners son bastante intuitivos y sencillos de implementar.
Aula Mentor
5.2.1.7 PropertyValuesHolder
Esta clase permite animar mltiples valores durante el ciclo de animacin. Puede entenderse
esta clase como un objeto que contiene la dupla propiedad/valor. Podemos utilizar esta clase
para crear animaciones con ValueAnimator y ObjectAnimator y, as, ejecutar varios cam-
bios de propiedades en paralelo.
5.2.1.8 Keyframe
Esta clase base permite crear animaciones con fotogramas. Esta clase alberga la dupla tiempo/
valor que se debe aplicar a una animacin. Se puede utilizar con las clases ValueAnimator
y ObjectAnimator.
78 Para entender cmo funciona, podemos hacer una analoga con el cine, es decir, cada
fotograma Keyframe es un estado del objeto y el movimiento de la animacin se produce al
cambiar el fotograma en el tiempo.
Incluso es posible definir para cada Keyframe un objeto del tipo TimeInterpolator
que define cmo se hace la interpolacin en el cambio de fotograma.
5.2.1.9 TypeEvaluator
Esta interface permite interactuar con un objeto cuando ste es la propiedad que queremos
animar. Hay que tener en cuenta que los objetos tienen mucha complejidad y debemos indicar
cmo se animan.
Slo dispone del mtodo evaluate(float tiempo, T valorInicial, T valorFinal)
que devuelve el resultado de evaluar el cambio desde el valorInicial al valorFinal del
objeto durante el tiempo.
U1 Multimedia y Grficos en Android
Vemos ahora un sencillo ejemplo que evala una animacin que utiliza puntos (clase PointF)
de Android:
79
5.2.1.10 ViewPropertyAnimator
Esta clase permite animar de forma automtica y optimizada varias propiedades de una Vista
exclusivamente al mismo tiempo.
La sintaxis de esta clase es muy intuitiva y fcil de aplicar ya que nicamente debemos
decirle a la Vista que deseamos animarla indicando el nuevo valor de la propiedad. Es decir,
no tenemos que utilizar todo el mecanismo de Animator.
Esta clase no dispone de constructor, por lo que para crear un objeto de este tipo
debemos invocar la orden animate() en la Vista que deseamos animar y que devolver una
instancia a la clase ViewPropertyAnimator.
Veamos los mtodos ms importantes de esta clase que permiten modificar propiedades de
una Vista:
- alpha(float valor): cambia la opacidad de la Vista segn el valor.
- rotationX(float valor): rota la Vista en el eje X segn el valor.
- rotationY(float valor): rota la Vista en el eje Y segn el valor.
- scaleX(float valor): escala la Vista en el eje X segn el valor.
- scaleY(float valor): escala la Vista en el eje Y segn el valor.
- translationX(float valor): traslada la Vista en el eje X segn el valor.
- translationY(float valor): traslada la Vista en el eje Y segn el valor.
- x(float valor): cambia la posicin X de la Vista segn el valor.
- y(float valor): cambia la posicin Y de la Vista segn el valor.
Aula Mentor
5.2.1.11 LayoutTransition
Esta clase permite animar los Layout mediante transiciones cada vez que se aade o se quita
una Vista de este contenedor. Para ello, debemos invocar el mtodo setLayoutTransition
(LayoutTransition) en el Layout que deseemos animar.
Tambin es posible aplicar animaciones a los cambios entre Actividades de una aplicacin. Para
hacerlo, debemos definir el mtodo overridePendingTransition() en la Actividad activa.
Este mtodo tiene dos parmetros: la animacin de salida de la Actividad actual y la animacin
de entrada de la Actividad nueva.
En el Ejemplo 6 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza
la API de animacin.
Si abres el archivo res/layout/main_layout.xml advertirs que hemos definido una
etiqueta que animaremos y un conjunto de botones que permitirn al usuario elegir cmo desea
animarla:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:orientation=vertical
android:layout_marginTop=10dp >
80 <TextView
android:id=@+id/etiqueta
android:layout_width=fill_parent
android:layout_height=wrap_content
android:background=@color/azul
android:textColor=@color/blanco
android:text=@string/texto
android:padding=4dp/>
<TableLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:stretchColumns=0,1
android:layout_marginTop=10dp>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Fundido
android:layout_column=0 />
<Button
android:id=@+id/boton2
U1 Multimedia y Grficos en Android
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Movimiento
android:layout_column=1 >
</Button>
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton3
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Anim. secuencial
android:layout_column=0 />
<Button
android:id=@+id/boton4
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Animac. XML
android:layout_column=1 /> 81
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton5
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=PropertiesHolder
android:layout_column=0 />
<Button
android:id=@+id/boton6
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=ViewAnimator
android:layout_column=1 />
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
Aula Mentor
<Button
android:id=@+id/boton7
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=TypeEvaluator
android:layout_column=0 />
<Button
android:id=@+id/boton8
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=KeyFrames
android:layout_column=1 />
</TableRow>
</TableLayout>
</LinearLayout>
<set xmlns:android=http://schemas.android.com/apk/res/android
android:ordering=sequentially>
<objectAnimator
82 android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=1
android:valueTo=0
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
<objectAnimator
android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=0
android:valueTo=1
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
</set>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
etiqueta = (TextView)this.findViewById(R.id.etiqueta);
U1 Multimedia y Grficos en Android
fuenteEtiqueta=etiqueta.getTextSize();
}
// Evento que se lanza cuando el usuario pulsa un botn
public void animar(View boton) {
// Variables que se usan ms abajo
float dest = 0;
float h, w , x , y;
PropertyValuesHolder pvhX;
// Animacin de tipo objeto
ObjectAnimator animacion=null;
// Animacin secuencial
AnimatorSet as = null;
case R.id.boton5:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();
// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);
// Creamos un PropertiesHolder en el eje X e Y para
// que vuelva a la posicin inicial
pvhX = PropertyValuesHolder.ofFloat(x, x);
PropertyValuesHolder pvhY =
PropertyValuesHolder.ofFloat(y, y);
// Definimos la animacin con estos PropertiesHolder
animacion =ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhX, pvhY);
// La animacin dura 5 segundos
animacion.setDuration(5000);
// Definimos un acelerador de la animacin
animacion.setInterpolator(new
AccelerateDecelerateInterpolator());
break;
// Botn ViewAnimator
case R.id.boton6: 85
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();
// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);
// Definimos una animacin del tipo ViewAnimator
ViewPropertyAnimator vpa = etiqueta.animate();
// Definimos la posicin final de la etiqueta en la
// animacin
vpa.x(x);
vpa.y(y);
// La animacin dura 5 segundos
vpa.setDuration(5000);
// Definimos el listener de la animacin
vpa.setListener(this);
// Definimos un acelerador de la animacin
vpa.setInterpolator(new
AccelerateDecelerateInterpolator());
break;
// Botn TypeEvaluator
case R.id.boton7:
// Ponemos la etiqueta en su sitio y sin fundido
Aula Mentor
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Definimos el color inicial y final de la animacin
Integer colorIni =
getResources().getColor(R.color.rojo);
Integer colorFin =
getResources().getColor(R.color.azul);
// Definimos la animacin utilizando el TypeEvaluator
// ArgbEvaluator e indicando los colores inicial y
// final
ValueAnimator colorAnimation = ValueAnimator.ofObject(
new ArgbEvaluator(), colorIni, colorFin);
// Definimos el listener que se lanza cada vez que es
// necesario actualizar la animacin
colorAnimation.addUpdateListener(
new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator
animator) {
// Cambiamos el color de la etiqueta
etiqueta.setBackgroundColor(
(Integer)animator.getAnimatedValue());
}
});
// La animacin dura 5 segundos
colorAnimation.setDuration(5000);
// Quitamos todos los listeners de la animacin
86 colorAnimation.removeAllListeners();
// Aadimos el listener local
colorAnimation.addListener(this);
// Iniciamos la animacin
colorAnimation.start();
break;
PropertyValuesHolder.ofKeyframe(alpha, kf0,
kf1, kf2);
// Definimos tambin el movimiento horizontal
pvhX = PropertyValuesHolder.ofFloat(x, w, x);
// Definimos la animacin a partir de fotogramas
animacion =
ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhAlpha,pvhX);
// La animacin dura 5 segundos
animacion.setDuration(5000);
break;
default:
break;
}
if (animacion!=null) {
// Quitamos todos los listeners de la animacin
animacion.removeAllListeners();
// Aadimos el listener local
animacion.addListener(this);
// Iniciamos la animacin
animacion.start();
} else
if (as!=null) {
// Quitamos todos los listeners de la animacin
as.removeAllListeners();
// Aadimos el listener local
as.addListener(this);
as.start(); 87
}
} // end animar
@Override
public void onAnimationEnd(Animator animation) {
Toast.makeText(this, Finaliza la animacin,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationRepeat(Animator animation) {}
@Override
public void onAnimationStart(Animator animation) {
Toast.makeText(this, Empieza la animacin,
Toast.LENGTH_SHORT).show();
}
} // end clase
El cdigo fuente anterior utiliza la clase ObjectAnimator aprovechando sus mtodos ofFloat
y ofInt para cambiar la opacidad (propiedad alpha) y mover la etiqueta (propiedad x)
Aula Mentor
respectivamente.
Mediante la clase AnimatorSet y su mtodo playSequentially() hemos ejecutado
dos animaciones de forma secuencial que llevan a cabo un fundido a negro y a blanco.
Tambin hemos desarrollado una animacin desde un fichero XML mediante la orden
AnimatorInflater.loadAnimator(this, R.anim.fundidos). El fichero fundidos.xml se
ha descrito anteriormente.
La clase PropertyValuesHolder mueve la etiqueta en el eje X e Y con el mtodo
ofFloat().
De forma similar, la clase ViewPropertyAnimator desplaza la etiqueta con los mtodos
x() e y().
En el botn sptimo hemos utilizado la subclase ArgbEvaluator de TypeEvaluator
para cambiar los colores de fondo de la etiqueta.
La clase KeyFrames desarrolla una animacin de tipo fotograma mediante sus mtodos
ofFloat() de la propiedad alpha de la etiqueta.
Finalmente, se crea un listener del tipo AnimationListener para detectar cundo la
animacin se inicia, se para o se repite, y mostrar al usuario un mensaje de tipo Toast.
El resto de mtodos son intuitivos y se ha explicado en la parte terica de este apartado,
te sugerimos que le eches un vistazo detallado al cdigo fuente completo.
Si ejecutas en Eclipse DT este Ejemplo 6 en el AVD, vers que se muestra la siguiente aplicacin:
U1 Multimedia y Grficos en Android
Recomendamos al alumno que pruebe en Eclipse ADT el efecto de animacin que se produce
sobre la etiqueta superior cuando pulsa sobre los diferentes botones.
89
En esta Unidad puedes encontrar el vdeo API de Animacin - Interpoladores,
que muestra de manera visual cmo cambia una animacin en funcin del
interpolador aplicado.
En el Ejemplo 7 de esta Unidad hemos desarrollado una sencilla aplicacin que utiliza
interpoladores.
Si abres el archivo res/layout/main_layout.xml advertirs que hemos definido una
imagen a la izquierda a la que animaremos mediante un conjunto de botones que llaman a un
interpolador diferente.
Si abres los archivos de la carpeta res/anim/ advertirs que hemos definido un conjunto
de animaciones que trasladan la imagen de arriba abajo con los mimos parmetros mediante el
atributo translate. Sin embargo, mediante el atributo android:interpolator hemos indi-
cando que se aplique un interpolador distinto en cada una de ellas.
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin
observars que contiene las siguientes sentencias:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
//cambian el interpolador.
final Animation animAccelerateDecelerate =
AnimationUtils.loadAnimation(this,
R.anim.accelerate_decelerate);
final Animation animAccelerate =
AnimationUtils.loadAnimation(this, R.anim.accelerate);
final Animation animAnticipate =
AnimationUtils.loadAnimation(this, R.anim.anticipate);
final Animation animAnticipateOvershoot =
AnimationUtils.loadAnimation(this,
R.anim.anticipate_overshoot);
final Animation animBounce = AnimationUtils.loadAnimation(this,
R.anim.bounce);
final Animation animCycle = AnimationUtils.loadAnimation(this,
R.anim.cycle);
final Animation animDecelerate =
AnimationUtils.loadAnimation(this, R.anim.decelerate);
final Animation animLinear = AnimationUtils.loadAnimation(this,
R.anim.linear);
final Animation animOvershoot = AnimationUtils.loadAnimation(this,
R.anim.overshoot);
botonAccelerate.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animAccelerate);
}
});
botonAnticipate.setOnClickListener(new Button.OnClickListener(){
@Override
U1 Multimedia y Grficos en Android
botonAnticipateOvershoot.setOnClickListener(new
Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animAnticipateOvershoot);
}
});
botonBounce.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animBounce);
}
});
botonCycle.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animCycle);
}
});
botonDecelerate.setOnClickListener(new Button.OnClickListener(){ 91
@Override
public void onClick(View arg0) {
imagen.startAnimation(animDecelerate);
}
});
botonLinear.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animLinear);
}
});
botonOvershoot.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animOvershoot);
}
});
} // end onCreate
} // end clase
Como puedes observar, el cdigo anterior carga las animaciones a partir de archivos XML y asocia
a cada botn la animacin de la imagen que corresponde teniendo en cuenta el interpolador.
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente
aplicacin:
92
93
- El usuario ejecuta una nueva aplicacin que, a su vez, crea la Actividad principal.
- El sistema operativo crea una nueva ventana para esa Actividad y la registra en el gestor de
ventanas WindowManagerService.
- Android crea una nueva superficie para la ventana y la devuelve a la Actividad. Para ello, usa
el Compositor de pantalla (Screen Compositor) denominado SurfaceFlinger.
- La aplicacin dibuja jerrquicamente las Vistas de la Actividad (TextView, Button, Image-
View, etctera) en la superficie.
- Finalmente, se componen todas las superficies visibles en la pantalla del dispositivo, es decir,
la de la Actividad que aparece en la pantalla y la de la barra de notificacin.
ViewSurface (Vista de tipo Superficie) proporciona una superficie de dibujo dedicado que se
incrusta dentro de una jerarqua de vistas.
Es posible dibujar el contenido de esta superficie, su posicin y su tamao en la pantalla
del dispositivo.
Esencialmente, la clase ViewSurface es una Vista que contiene una Superficie de An-
droid (Surface) que, a su vez, es un buffer en crudo de datos y que utiliza el Compositor de
pantalla de Android, que es el encargado de dibujar toda la interfaz de usuario en Android.
Aula Mentor
En el Ejemplo 8 de este Unidad vamos a mostrar cmo desarrollar una sencilla aplicacin en
Android que utiliza la clase SurfaceView. En ella se presenta un juego sencillo de una pelota
que rebota en toda la pantalla del dispositivo y, si el usuario pulsa sobre ella, cambia el sentido
de su movimiento.
Es recomendable abrir el Ejemplo 8 en Eclipse ADT para entender la explicacin si-
guiente.
Fjate que en el cdigo fuente de esta aplicacin no hemos incluido ningn archivo
Layout que defina la interfaz de usuario.
94
La interfaz de usuario de la aplicacin se compone de una Actividad que crea una superficie y
la asigna a la interfaz de usuario. Veamos su contenido en el fichero MainActivity.java:
A continuacin, se muestra cmo se implementa la interfaz visual del usuario mediante esta
superficie definida en el fichero SuperficieView.java:
// Constructor de la Superficie
public SuperficieView(Context context) {
super(context);
// Creamos el hilo
bucleThread = new BucleThread(this);
// Obtenemos el Holder de la superficie y le asignamos los
// eventos definidos en esta clase
getHolder().addCallback(this);
// Delegamos el evento onTouch al Hilo
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(bucleThread!=null) {
return bucleThread.onTouch(event);
}
else return false;
}
}); // end onTouch
} // end onCreate 95
En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
getHolder().addCallback(this) para gestionar los eventos del SurfaceView y poder
acceder a la superficie.
En este caso hemos implementado algunos de los eventos anteriores. Veamos las senten-
96 cias ms novedosas de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos
creado en el constructor de esta clase y al que hemos pasado como parmetro la referencia del
surfaceholder. Despus, establecemos el estado de ejecucin del hilo y lo arrancamos con la
orden start(). La funcin de este hilo es actualizar la interfaz de usuario dibujndola cuando
lo consideremos necesario.
El mtodo SurfaceDestroyed() se lanza cada vez que la aplicacin pasa a segundo
plano y el SurfaceView se va a destruir junto con todo su contenido. Por lo tanto, es necesario
parar el hilo. Para ello, unimos el Hilo de actualizacin con el Hilo principal de la aplicacin
invocando la orden join hasta que lo conseguimos. Es necesario unir todos los hilos para des-
truirlos correctamente.
Es imprescindible que la interaccin del usuario con la aplicacin sea suave y
los grficos se muestren sin parpadeos. Para conseguirlo, tenemos que ejecutar dos hilos
en la aplicacin: el hilo principal, que se encarga de gestionar la Actividad de la aplicacin, y el
segundo hilo, que dibuja la superficie y gestiona la interaccin del usuario con sta.
La razn principal de esta divisin de tareas est en que, como estudiamos en el curso
de Iniciacin a Android, no debemos bloquear nunca el hilo principal de una aplicacin. Por
ejemplo, si el usuario presiona la pantalla tctil puede crear paradas en la ejecucin del cdigo.
Hemos implementado el Hilo BucleThread para actualizar el dibujo del objeto SurfaceView
y gestionar los toques sobre ste. En el constructor de este hilo le pasamos como parmetro la
referencia (this) al objeto SuperficieView para poder modificar el contenido de sta.
Por esto, en el mtodo surfaceChanged() del SuperficieView obtenemos el tamao
de la pantalla del dispositivo Android y se lo pasamos al hilo BucleThread.
Adems, en el constructor de esta clase, hemos delegado el mtodo onTouchEvent() en
este Hilo mediante la sentencia setOnTouchListener().
Finalmente, no hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo
quien se encargue de dibujarla.
A continuacin, se muestra cmo se implementa este hilo en el archivo BucleThread.
java:
} // end while
} // end run()
touched=false;
xVelocidad = xVelocidad*-1;
yVelocidad = yVelocidad*-1;
}
switch (action) {
// Cuando se toca la pantalla
case MotionEvent.ACTION_DOWN:
Log.e(TouchEven ACTION_DOWN, Usuario toca la pantalla );
touched = true;
break;
// Cuando se desplaza el dedo por la pantalla
case MotionEvent.ACTION_MOVE:
touched = true;
Log.e(TouchEven ACTION_MOVE, Usuario desplaza dedo por la
pantalla );
break;
// Cuando levantamos el dedo de la pantalla que estbamos
// tocando
case MotionEvent.ACTION_UP:
touched = false;
Log.e(TouchEven ACTION_UP, Ya no tocamos la pantalla);
break;
// Cuando se cancela el toque. Es similar a ACTION_UP
Aula Mentor
case MotionEvent.ACTION_CANCEL:
touched = false;
Log.e(TouchEven ACTION_CANCEL, );
break;
// El usuario ha tocado fuera del rea de la interfaz del
// usuario
case MotionEvent.ACTION_OUTSIDE:
Log.e(TouchEven ACTION_OUTSIDE, );
touched = false;
break;
default:
}
return true;
} // end onTouch
Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:
101
Puedes probar a hacer clic sobre la bola para comprobar que su movimiento cambia de sentido.
Si instalas este sencillo juego en un dispositivo real vers que es ms sencillo probar la aplicacin.
7. Grficos en 3D en Android
Android dispone de dos bibliotecas para pintar grficos en 3D:
- OpenGL: biblioteca estndar de diseo 3D.
- RenderScript: nueva biblioteca de bajo nivel de dibujo grfico en 3D disponible desde la
versin 3.0 de Android que aprovecha el procesador GPU de la tarjeta grfica para pintar
dejando as al procesador principal del dispositivo capacidad disponible para otras tareas.
Estas grficas se desarrollan con un lenguaje especfico llamado C99.
Aula Mentor
7.1 OpenGL
Para entender de forma grfica estas dos clases anteriores, podemos hacer una
analoga con un pintor y su lienzo. En este caso, el pintor es la interfaz GLSurfa-
ceView.Renderer y el lienzo es GLSurfaceView.
Por compatibilidad entre las distintas versiones de Android vamos a utilizar la ver-
sin 1.0 de la biblioteca OpenGL para desarrollar los ejemplos del curso.
Antes de empezar a dibujar grficos es importante recordar los siguientes conceptos bsicos
U1 Multimedia y Grficos en Android
de geometra:
- Lnea o Recta: conjunto de puntos que se extienden con determinada longitud.
- Vrtice: punto donde dos o ms rectas se encuentran. Veamos unos ejemplos sobre cmo
utilizar vrtices para definir un punto o polgonos en Android:
Punto
// Punto ubicado en la coordenada (1,1,0)
float vertice[] = { 1f ,1f ,0f };
Cuadrado
float vertices[] = {
-1f, 1f, 0f,// vrtice ubicado en (-1,1,0)
-1f, -1f, 0f, // vrtice ubicado en (-1,-1,0)
1f, -1f, 0f, // vrtice ubicado en (1,-1,0)
1f, 1f, 0f// vrtice ubicado en (1,1,0)
};
Como puede observar, para definir vrtices, simplemente debemos indicar las coordenadas
(x,y,z) en una matriz de tipo float.
- Cara: cada lado que forma un polgono. La modificacin de una cara afecta a sus vrtices y 103
a sus aristas.
- Arista o Borde: unin de dos vrtices. En un modelo 3D, la arista puede estar compartida
por dos caras o polgonos.
- Polgono: figura cerrada que est formada por tres o ms caras que se unen en sus aristas.
- Eje de coordenadas: tipo de coordenadas ortogonales usadas en espacios euclidianos ca-
racterizadas por que usa como referencia ejes ortogonales entre s que se cortan en un punto
origen. En el espacio 3D estos ejes tienen el aspecto que se refleja en el grfico inferior. En
el dispositivo Android, X e Y se encuentran en los ejes planos horizontal y vertical. El eje
Z es un eje abstracto perpendicular a su pantalla, que en grficos 3D se usa para crear la
sensacin de lejana o cercana del grfico respecto al observador. Cuanto ms lejos est un
objeto respecto al observador, menor ser su valor en el eje Z.
Aula Mentor
Transformaciones: funciones que cambian la posicin del objeto relativa al centro de la figura
y pueden trasladarla, escalarla y rotarla. Matemticamente, estas transformaciones multiplican la
matriz anterior de coordenadas por otra matriz que es la que genera la transformacin. Veamos
unos ejemplos grficos:
Esquema Descripcin
Esquema Descripcin
La orden glRotatef(-45, 0, 0, 1)
rota el objeto. Los ngulos positivos
rotan al contrario que el movimiento
de las agujas del reloj.
La orden glScalef(2, 2, 1)
aumenta el tamao del objeto.
Por ejemplo, al trasladar un objeto dos unidades en el eje X, se genera la siguiente matriz de
transformacin:
Si aplicamos esta transformacin a la matriz anterior, nos quedar que la nueva matriz de trans- 105
formacin es:
Si ahora dibujamos el punto (1, 0, 0) teniendo en cuenta que la matriz de transformacin indica
un desplazamiento de dos unidades en eje X, el punto deber dibujarse en la posicin (3,0,0).
Para esto, se multiplica el punto por la matriz de transformacin:
cambiar al modo de matriz correspondiente, para que las operaciones afecten a esa matriz
especficamente.
- Modo de dibujo: OpenGL dispone de distintas formas de pintar:
GL_POINTS: dibuja nicamente los vrtices.
GL_LINES: dibuja las lneas entre dos vrtices independientemente de otros vrtices.
GL_LINE_STRIP: dibuja las lneas conectadas entre s por un vrtice.
GL_LINE_LOOP: pinta de la misma forma que el anterior mtodo, pero une el punto
final con el punto inicial.
GL_POLYGON: dibuja una superficie de tipo polgono.
GL_TRIANGLES: dibuja superficies mediante tringulos usando tres puntos de referen-
cia.
GL_TRIANGLE_STRIP: pinta superficies mediante tringulos continuos usando tres
puntos de referencia
GL_TRIANGULE_FAN: pinta superficies mediante tringulos usando un punto en co-
mn, a modo de abanico.
GL_SQUADS: dibuja cuadrilteros de cuatro vrtices.
GL_QUAD_STRIP: dibuja cuadrilteros continuos de cuatro vrtices.
Como una imagen vale ms que mil palabras, veamos esquemticamente estos mecanismos:
106
U1 Multimedia y Grficos en Android
Una vez explicados los conceptos bsicos de OpenGL, vamos a desarrollarlos mediante ejem-
plos prcticos, pues es ms sencillo y pedaggico entender en la prctica cmo se utiliza esta
biblioteca.
7.2 Grficos en 2D
En el Ejemplo 9 de esta Unidad hemos dibujado los polgonos tringulo y cuadrado en 2D. Es
recomendable abrir el Ejemplo 9 en Eclipse ADT para entender la explicacin siguiente.
Si abres el archivo res/layout/main_layout.xml, advertirs que hemos definido una
barra de botones que permitirn al usuario elegir el polgono que desea visualizar:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<LinearLayout 107
android:id=@+id/linearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=horizontal
android:gravity=top
android:layout_marginTop=1dp
android:layout_marginBottom=1dp>
<Button
android:id=@+id/cuadrado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Ver cuadrado />
<Button
android:id=@+id/triangulo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Ver tringulo />
<CheckBox
android:id=@+id/cbColorear
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Colorear />
</LinearLayout>
Aula Mentor
<FrameLayout
android:id=@+id/frame
android:layout_width=fill_parent
android:layout_height=fill_parent
android:layout_below=@+id/linearLayout>
</FrameLayout>
</RelativeLayout>
// Superficie de OpenGL
private GLSurfaceView glSurface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
findViewById(R.id.cuadrado);
boton_cuadrado.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Recreamos la superficie con el polgono cuadrado e
// indicamos si es necesario aadir el color
glSurface = new GLSurfaceView(MainActivity.this);
glSurface.setRenderer(new
MiRenderer(MiRenderer.MOSTRAR_CUADRADO,
conColor.isChecked()));
// Quitamos las Vistas del Frame y aadimos la nueva
// superficie
frame.removeAllViews();
frame.addView(glSurface, 0);
}
});
} // end onCreate
} // end clase
Puedes observar en el cdigo anterior que, para crear los grficos, hemos utilizado la clase
superficie GLSurfaceView de OpenGL a la que asociamos con el mtodo setRenderer() el
Renderer que se encargar de dibujar la imagen en funcin de los parmetros pasados en el
constructor de este mtodo.
Adems, hemos utilizado la Layout del tipo FrameLayout para mostrar en la interfaz
del usuario la superficie creada anteriormente.
Hemos detallado el Renderer anterior en la clase MiRenderer del proyecto, que contie-
ne el siguiente cdigo fuente:
// Constructor de la clase
public MiRenderer(int mostrar_poligono, boolean ver_colores) {
// Guardamos el tipo de polgono que queremos mostrar y
// creamos la geometra correspondiente
this.mostrar_poligono=mostrar_poligono;
if (mostrar_poligono == MOSTRAR_TRIANGULO)
triangulo = new Triangulo(ver_colores);
else
cuadrado = new Cuadrado(ver_colores);
}
En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos:
- Sombreado suave en el dibujo (renderizado) mediante el mtodo glShadeModel().
- Color del fondo gris mediante glClearColor().
- Configurar el tamao del buffer de profundidad mediante glClearDepth() cuando se vaca.
El buffer de profundidad o z-buffer se utiliza para gestionar la visibilidad de varios grficos
3D superpuestos segn las coordenadas de sus pixeles. 111
- Indicamos el modo de renderizado mediante glEnable(GL10.GL_DEPTH_TEST), que tam-
bin se conoce como algoritmo de z-Buffer porque gestiona qu elementos de una escena
son visibles y cules permanecern ocultos segn sus posiciones en el eje Z (distancia a c-
mara). En definitiva, lo que se hace es comparar las profundidades de todos los grficos para
representar el objeto ms cercano o lejano al observador segn el parmetro que indique-
mos. Es decir, con el modo GL_DEPTH_TEST, los objetos cercanos ocultan a los ms lejanos.
- Especificamos la funcin que se usar para comparar la profundidad de los objetos mediante
el mtodo glDepthFunc(). El parmetro GL_LEQUAL indica que el valor de profundidad se
almacena si la cercana al observador es menor o igual al valor existente.
- Con el mtodo glHint() establecemos, con la propiedad GL_PERSPECTIVE_CORRECTION_
HINT, la calidad del color y la interpolacin de las coordenadas de la textura al valor GL_NI-
CEST, que es la mxima calidad posible.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de pro-
fundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). Como
hemos visto, la matriz de proyeccin se utiliza para poder realizar cambios de perspectiva
(giros, translaciones, etctera). Realmente, lo que estamos haciendo es borrar la matriz de
proyeccin dejndola a 0, es decir, no hay transformacin ninguna.
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, ale-
jamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado ante-
riormente.
Aula Mentor
Veamos ahora cmo hemos definido las clases que describen la geometra de los polgonos.
Empezamos por el tringulo desarrollado en el archivo Triangulo.java:
Podemos ver en el cdigo anterior que hemos definido los vrtices del tringulo en el constructor
utilizando un buffer de tipo float, ya que la biblioteca OpenGL necesita disponer, en este tipo 113
de variable, de los vrtices del polgono.
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el tringu-
lo, realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW). Este sentido de dibujo es importante ya que determina la
direccin del vector normal de iluminacin; si ste apunta a la direccin equivocada, no ve-
remos nada si utilizamos iluminacin. El orden contrario a las agujas del reloj indica que la
superficie del polgono est dirigida hacia el observador (regla de la mano derecha). Fjate
en este esquema:
- Si hemos indicado que debemos colorear el polgono, establecemos el color del tringulo en
modo RGBA mediante glColor4f().
- Finalmente, se desactiva el buffer de los vrtices con glDisableClientState().
En ambos mtodos debemos indicar el modo de dibujo empleado por OpenGL para pintar el
grfico.
bufferVertices.put(vertices);
// Movemos la posicin del buffer al inicio
bufferVertices.position(0);
// Guardamos si es necesario colorear el polgono
this.conColor=conColor;
// Si es necesario colorear el cuadrado...
if (conColor) {
// Definimos el buffer de la matriz de colores de igual
// forma que hemos hecho con la matriz de vrtices
byteBuf = ByteBuffer.allocateDirect(colores.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
bufferColores = byteBuf.asFloatBuffer();
bufferColores.put(colores);
bufferColores.position(0);
}
} // end constructor
// Buffer de colores
if (conColor) {
// Indicamos el n de campos que definen el color (4), el
// tipo de datos de la matriz (float), la separacin en la
// matriz de los colores (0) y el buffer con los colores.
gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);
// Indicamos al motor OpenGL que le hemos pasado una matriz
// de colores
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
}
Podemos ver en el cdigo anterior que hemos definido los vrtices y colores del cuadrado en el
constructor utilizando dos buffer de tipo float ya que la biblioteca OpenGL necesita disponer,
Aula Mentor
116
Finalmente, para que la aplicacin aparezca sin ttulo y en el modo de pantalla completa hemos
definido en el fichero AndroidManifest.xml el tipo de estilo siguiente:
android:theme=@android:style/Theme.NoTitleBar.Fullscreen
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 9 (OpenGL 2D) de la Uni-
dad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la biblioteca OpenGL de Android.
Si ejecutas en Eclipse ADT este Ejemplo 9 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja dos polgonos:
U1 Multimedia y Grficos en Android
117
En el Ejemplo 10 de esta Unidad hemos dibujado los polgonos tringulo y cuadrado en 3D,
es decir, una pirmide y un cubo a los que hemos animado con movimientos. Es recomendable
abrir el Ejemplo 10 en Eclipse ADT para seguir la explicacin posterior.
Slo se describirn en detalle aquellos ficheros de cdigo fuente que son distintos del
Ejemplo 9 anterior.
Los archivos res/layout/main_layout.xml, MainActivity.java, Cuadrado.java y
Triangulo.java de este proyecto son muy parecidos a los estudiados en el ejemplo anterior.
Te invitamos a que los abras en Eclipse ADT.
Hemos desarrollado el Renderer en la clase MiRenderer del proyecto, que contiene el
siguiente cdigo fuente:
// Constructor de la clase
public MiRenderer(int mostrar_poligono) {
// Guardamos el tipo de polgono que queremos mostrar y
// creamos la geometra correspondiente
this.mostrar_poligono=mostrar_poligono;
if (mostrar_poligono == MOSTRAR_TRIANGULO)
triangulo = new Triangulo();
else
if (mostrar_poligono == MOSTRAR_CUADRADO)
cuadrado = new Cuadrado();
else
if (mostrar_poligono == MOSTRAR_CUBO)
cubo = new Cubo();
else
if (mostrar_poligono == MOSTRAR_PIRAMIDE)
piramide = new Piramide();
}
En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos los mismos parmetros
que en el Ejemplo 9.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de pro-
fundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). La matriz
de proyeccin se utiliza para poder realizar cambios de perspectiva (giros, translaciones,
etctera). Realmente, lo que estamos haciendo es mover de nuevo el puntero de dibujo al
centro del eje de coordenadas (0,0,0).
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, ale-
jamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Rotamos el polgono mediante el mtodo glRotatef(). As, se logra la sensacin de mo-
120 vimiento del polgono.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado ante-
riormente.
// Constructor de la pirmide
public Piramide() {
// Definimos el buffer con los vrtices del polgono.
// Un nmero float tiene 4 bytes de longitud, as que 121
// multiplicaremos x 4 el nmero de vrtices.
ByteBuffer byteBuf=ByteBuffer.allocateDirect(vertices.length*4);
byteBuf.order(ByteOrder.nativeOrder());
bufferVertices = byteBuf.asFloatBuffer();
bufferVertices.put(vertices);
bufferVertices.position(0);
Podemos ver en el cdigo anterior que hemos definido los vrtices de los cuatro tringulos
que forman las superficies de la pirmide en el constructor utilizando un buffer de tipo float.
Adems, definimos tambin un buffer adicional para los colores de sta.
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de los
122 vrtices y colores a la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores median-
te el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays()para dibujar la superficie del polgono mediante la ma-
triz en el modo tringulo GL_TRIANGLES.
- Finalmente, se desactiva los buffer de vrtices y colores con glDisableClientState().
Podemos ver en el cdigo anterior que hemos definido los vrtices y colores del cubo en el
constructor utilizando dos buffer de tipo float ya que la biblioteca OpenGL necesita disponer,
en este tipo de variable, de los vrtices y colores del polgono.
Adems, hemos definido el buffer adicional bufferIndices que usamos para pasar a la
biblioteca OpenGL los ndices del cubo. Las caras del cubo se componen de dos tringulos
pegados, por lo tanto, debemos definir las caras del cubo mediante esta matriz. Veamos el
aspecto geomtrico del cubo:
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el cubo, realizamos
los siguientes trabajos:
U1 Multimedia y Grficos en Android
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con el mtodo glVertexPointer() y glColorPointer() asociamos los vrtices y colores
al grfico.
- Indicamos al motor OpenGL que le hemos pasado una matriz de vrtices y colores mediante
el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie del polgono mediante la
matriz en el modo tringulo GL_TRIANGLE utilizando el buffer de ndices bufferIndices
para unirlos y formar las caras.
- Finalmente, se desactivan los buffers de vrtices y colores con glDisableClientState().
Si ejecutas en Eclipse ADT este Ejemplo 10 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja cuatro polgonos (en 2D y 3D):
125
En el Ejemplo 11 de esta Unidad hemos dibujado una pirmide en 3D con textura y que
responde con movimientos a los toques del usuario girando sobre sus ejes. Es recomendable
Aula Mentor
// Constructor de la clase
public Superficie(Context context) {
super(context);
126 // Indicamos que el Renderer se define en esta misma superficie
this.setRenderer(this);
// Solicitamos el foco de la aplicacin para que el usuario
// pueda hacer clic en la imagen. Nota: esto slo es necesario
// si la aplicacin tuviera otras Vistas, como botones
this.requestFocus();
// Indicamos que la superficie responde a toques del usuario,
// es decir, implementa el evento onTouchEvent
this.setFocusableInTouchMode(true);
}
} // end onDrawFrame
Puedes observar en el cdigo anterior que hemos extendido esta clase de GLSurfaceView e
implementado Renderer; as, en un mismo fichero Java desarrollamos la superficie de dibujo
(lienzo) y el dibujante.
En el constructor indicamos que el Renderer se define en esta misma clase y que la
superficie responde a toques del usuario, es decir, implementa el evento onTouchEvent().
U1 Multimedia y Grficos en Android
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de pro-
fundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity().
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, ale-
jamos el dibujo en el eje Z dando al observador la sensacin de profundidad.
- Rotamos la pirmide mediante el mtodo glRotatef(). As, se logra la sensacin de movi-
miento cuando el usuario toca la pantalla.
- Invocamos el mtodo draw del objeto que debemos dibujar, que hemos instanciado ante-
riormente.
// Vrtices de la pirmide
private float vertices[] = {
0.0f, 1.0f, 0.0f, // Punto superior del tringulo frontal
-1.0f, -1.0f, 1.0f, // Punto izq. del tringulo frontal
1.0f, -1.0f, 1.0f, // Punto dcho. del tringulo frontal
0.0f, 1.0f, 0.0f, // Punto superior del tringulo dcho.
1.0f, -1.0f, 1.0f, // Punto izq. del tringulo dcho.
1.0f, -1.0f, -1.0f, // Punto dcho. del tringulo dcho.
Aula Mentor
bufferVertices = makeFloatBuffer(vertices);
bufferTexturas = makeFloatBuffer(texturas);
bufferVerticesBase = makeFloatBuffer(vertices_base);
bufferColores = makeFloatBuffer(colores);
return fb;
}
} // end clase
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar la pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Activamos la texturas en 2D (tipo superficie) con glEnable() y seleccionamos la textura
activa invocando glBindTexture().
- Con los mtodos glVertexPointer() y glTexCoordPointer() asociamos los buffer de
vrtices y texturas de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y texturas medi-
ante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la pirmide mediante la
matriz en el modo tringulo GL_TRIANGLES.
- Deshabilitamos la textura para seguir pintando sin sta con el mtodo glDisable().
132 - Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de vrti-
ces y colores de la base de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores medi-
ante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la base de la pirmide
mediante la matriz en el modo tringulo GL_TRIANGLES_STRIP.
- Finalmente, se desactiva los buffer de vrtices, colores y texturas con glDisableClient-
State().
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 11 (3D con texturas e inte-
raccin) de la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el
resultado del programa anterior, en el que hemos utilizado la API de OpenGL para
crear una pirmide en 3D con textura y que interacciona con el usuario.
U1 Multimedia y Grficos en Android
Si ejecutas en Eclipse ADT este Ejemplo 11 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja cuatro una pirmide con textura en 3D:
133
Recomendamos al alumno que desplace el ratn sobre la pirmide para ver cmo gira sobre sus
ejes y cambia la perpectiva de sta.
8. Resumen
1. Introduccin
En esta segunda Unidad vamos a explicar cmo se aplican los temas y estilos de Android para
cambiar el aspecto visual de una aplicacin. Adems, implementaremos Widgets en el Home y
Lock Screen y crearemos un Live WallPaper (fondo de escritorio animado).
Asimismo, utilizaremos los Fragmentos en Android y las Barras de Accin (Action Bars)
en un proyecto Android.
Finalmente, usaremos las clases GridView, Interruptor(Switch) y Navigation
Drawer para mejorar la interfaz de usuario con varios ejemplos de aplicacin en Android.
Los temas en Android se incluyen dentro de la carpeta res/values/ mediantes un archivo xml
(por defecto, se llama styles.xml) donde se definen todos los atributos que aplican a este
tema.
137
Si abres el archivo res/values/temas.xml, vers que hemos creado el tema Tema a partir
del tema padre por defecto de Android android:Theme y aadido un par de atributos:
En el cdigo anterior podemos ver que hemos personalizado los atributos margenActividad
(margen de la pgina) y android:windowTitleSize (tamao de la barra del ttulo de la
aplicacin). Es recomendable establecer el tema padre (parent=android:Theme) dentro del
tema por defecto del sistema operativo Android para heredar todos los atributos del sistema.
Aula Mentor
Los temas visuales de Android son un medio rico y complejo que permiten definir todos los
atributos del aspecto de Vistas de Android. Dada la cantidad de atributos que define Android, es
recomendable definir nuestro nuevo tema a partir de uno ya existente en el sistema operativo,
es decir, debemos extenderlo de uno predefinido.
Veamos los cuatro temas bsicos por defecto que define el sistema operativo Android:
- Theme: es el Tema ms bsico que se incluy en la primera versin de Android y que hemos
utilizado en el ejemplo del curso. Se trata de un tema con fondo oscuro y los textos en color
claro que est disponible en todas las versiones de Android, si bien puede variar ligeramente
dependiendo del fabricante del dispositivo.
- Theme.Light: variacin de tema Theme, muestra texto oscuro con fondo claro.
- Theme.Holo: este tema se introdujo en la versin 3.0 de Android y presenta un aspecto ms
moderno que los dos anteriores. Tiene una particularidad: los fabricantes de dispositivos no
deben modificarlo.
- Theme.Holo.Light: variacin en color claro del tema anterior.
Para indicar el tema visual por defecto de la aplicacin podemos establecer el atributo android_
theme en la aplicacin o en cada una de las actividades en su archivo Manifest.xml. Por
ejemplo, as:
<application android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/Tema>
Tambin se puede establecer el tema de una actividad mediante sentencias de Java. Para ello,
debemos utilizar el mtodo setTheme() en el evento onCreate() de la actividad justo antes de
138
la ejecucin de la sentencia setContentView(). Hay que tener en cuenta que, por lo general,
se debe evitar esta forma de indicar el tema visual, salvo que la aplicacin que desarrollemos
deba cambiar de forma dinmica sus temas.
Como hemos visto anteriormente, para modificar los mrgenes de todas las actividades, podemos
definir el nuevo tamao en un nico lugar: archivo temas.xml de la aplicacin estableciendo
el atributo margenActividad:
<item name=margenActividad>2sp</item>
Sin embargo, si copiaras el texto anterior en el archive tema.xml veras que Eclipse mostrara el
error: No resource found that matches the given name: attr margenActividad.
Esto ocurre porque el atributo margenActividad no existe por defecto en el sistema Android.
No obstante, el atributo android:windowTitleSize es correcto ya que s est definido en el
SDK de Android.
Para definir este atributo nuevo, debemos crear el archivo atributos.xml dentro de la
carpeta res/values/. Podemos ver cmo hemos definido el atributo margenActividad en
este fichero:
<?xmlversion=1.0encoding=utf-8?>
<resources>
<attrname=margenActividadformat=reference|dimension/>
</resources>
format indica el tipo de valores que se pueden definir para este atributo; en este caso, hemos
indicado que debe indicarse el nombre de otro atributo (reference) o (|) una dimensin
(dimension) como, por ejemplo 2sp o 4px. Adems, podemos incluir tantos formatos como
queramos del siguiente listado:
- reference: el nombre de otro atributo
- string: de tipo cadena
- color
- dimension: dimensin
- boolean: lgico
- integer: nmero entero
- float: nmero con decimales
- fraction: fraccin
- enum: campo enumerado
- flag: de tipo marcas
Una vez definidos los mrgenes de las Vistas, podemos establecerlo haciendo referencia al
mismo en la definicin del layout correspondiente: por ejemplo, as:
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Texto de prueba
android:layout_margin=?margenActividad
android:textSize=?tamanioTexto/>
En este caso, hemos indicado que el estilo, que se define como un atributo ms, debe hacer
referencia a otro atributo.
En el ejemplo del curso, en el archivo res/layout/main.xml podemos ver cmo se
asigna el estilo textoTitulo a un TextView:
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=@string/texto_titulo
style=?textoTitulo/>
<item name=paginaBackground>@style/pagina_background_blanco
</item>
...
<item name=textoTitulo>@style/texto_titulo_blanco</item>
Aqu ya s se establecen las propiedades visuales de las distintas Vistas teniendo en cuenta las
propiedades que posean.
A modo de resumen, para utilizar los temas conjuntamente con estilos debemos dar los siguientes
pasos:
- Definir el nombre del tema y sus atributos (caractersticas de aspecto) disponibles en el ar-
140 chivo res/values/temas.xml del proyecto.
- En el fichero res/values/atributos.xml especificar los mismos atributos anteriores me-
diante referencia a otro atributo (format=reference).
- Disear el aspecto de cada atributo con las propiedades visuales de una Vista Android en el
fichero res/values/estilos.xml.
- Aplicar el estilo correspondiente a las vistas que define la interfaz del usuario (en el archivo
layout xml) mediante su propiedad style.
Como hemos estudiado en la Unidad 1, un recurso dibujable (del ingls, Drawable) es una
forma de definir cmo se dibuja la interfaz de una aplicacin Android. Hemos visto que existen
muchos tipos de recursos dibujables, tales como archivos de imgenes, colores, gradientes,
formas geomtricas, etctera. A continuacin, vamos a estudiar algunos recursos, los que estn
directamente relacionados con los estilos:
Como su nombre indica, con este recurso podemos definir un color comn a toda la aplicacin
creando el fichero res/values/colores.xml y escribiendo lo siguiente:
<?xml version=1.0 encoding=utf-8?>
<resources>
<color name=azul>#FFF</color>
</resources>
U2 Multimedia y Grficos en Android
Como vers, su utilizacin es muy sencilla: en el cdigo anterior hemos declarado la etiqueta
color con su nombre e indicado el color en formato hexadecimal. Despus, para poder aplicar
este color a un estilo o Vista, basta con abrir el archivo estilos.xml y usar este color con la
nomenclatura @color:
<style name=texto_titulo_azul>
<item name=android:textColor>@color/azul</item>
Como su nombre indica, un recurso de tipo Dimensin permite definir una longitud comn
a toda la aplicacin. Por ejemplo, en el fichero res/values/estilos.xml hemos incluido lo si-
guiente:
<dimen name=pagina_margen_azul>2sp</dimen>
Como vers, su utilizacin es tambin muy sencilla; en el cdigo anterior hemos declarado la
etiqueta dimen con su nombre e indicado el tamao en formato sp como unidad de medida.
Despus, para poder aplicar esta dimensin a un estilo o Vista, basta con abrir el archivo es-
tilos.xml y usarla con la nomenclatura @dimen:
<style name=pagina_background_azul>
<item name=android:background>#000</item>
<item name=android:padding>@dimen/pagina_margen_azul</item>
</style>
En este caso hemos establecido un margen interno (padding) del fondo del tema azul.
Las magnitudes de medida en interfaces grficas de Android son las siguientes: 141
- px: Pxeles - unidad en pxeles independientemente de la resolucin de la pantalla.
- in: Inches (Pulgadas) - unidad en pulgadas independientemente del tamao de la pantalla.
- mm: Milmetros - unidad en milmetros que depende del tamao de la pantalla.
- pt: Points (Puntos) - unidad en puntos por pulgada que depende del tamao de la pantalla
del dispositivo.
- dp: Density-independent Pixels (Densidad independiente en pxeles) - unidad abstracta
basada en la densidad fsica de la pantalla. Es recomendable intentar utilizar siempre esta
unidad ya que los elementos grficos y Vistas se representarn con el mismo tamao inde-
pendientemente del tamao y resolucin del dispositivo Android.
- sp: Scale-independent Pixels (Independiente de la escala en pxeles) unidad parecida a la
anterior (dp) pero utilizada para definir los tamaos de la fuente de las textos que aparecen
en la interfaz del usuario.
En este caso hemos definido un degradado que comienza (startColor) en el color gris y ter-
mina (endColor) en el color negro. Como puedes ver, estos colores tambin estn definidos
como un recurso de color. Adems, hemos indicado que aplique un giro (angle) al degradado
de 270 grados.
Una vez hemos definido el gradiente, es sencillo aplicarlo al tema en el archivo estilos.xml
as:
<style name=pagina_background_blanco>
<item name=android:background>@drawable/gradiente_gris_background
</item>
<item name=android:padding>@dimen/pagina_margen_blanco</item>
</style>
Vers que hemos definido un selector que cambia el aspecto de una Vista cuando ocurre algu-
no de estos eventos:
- state_pressed: si el usuario presiona (hace clic) sobre la Vista que tiene asociado este
estilo, entonces se debe cambiar su aspecto utilizando el elemento dibujable boton_blan-
co_presionado. Definido tambin dentro de la carpeta drawable con el nombre boton_
blanco_presionado.xml. Puedes abrir este archivo para ver su contenido.
- state_focused: si el usuario selecciona (centra el foco con el tabulador) la Vista que tiene
asociado este estilo, entonces se debe cambiar su aspecto utilizando el elemento dibuja-
ble boton_blanco_seleccionado. Definido tambin dentro de la carpeta drawable con
el nombre boton_blanco_seleccionado.xml. Puedes abrir este archivo para ver su con-
tenido.
- Sin evento: en este caso, no se incluye ningn evento y se define el aspecto normal de la
Vista utilizando el elemento dibujable boton_blanco_normal. Definido tambin dentro
de la carpeta drawable con el nombre boton_blanco_normal.xml. Puedes abrir este ar-
chivo para ver su contenido.
U2 Multimedia y Grficos en Android
Si haces clic en los botones del ejemplo del curso, vers que cambia el aspecto de botn:
143
2.3.5 Nine-patch drawable con botones
Un Nine-patch Drawable es un tipo especial de imagen que escala o crece tanto a lo largo
como a lo ancho y que mantiene su relacin de aspecto visual. Es decir, Android va a aumentar
el tamao de este elemento teniendo en cuenta las dimensiones de las vistas.
Aunque se pueden utilizar en cualquier tipo de Vista, comnmente se utilizan en Bo-
tones. En el Ejemplo 1 hemos implementado este tipo de elemento en el Tema Azul. Veamos
cmo se hace siguiendo estos sencillos pasos:
- Disponemos de un archivo de tipo imagen que representa el botn en tamao pequeo
res/drawable/boton.png:
- windowBackground
Drawable utilizado como el fondo de la ventana. A diferencia del atributo anterior color-
BackgroundCacheHint, podemos establecer tanto texturas, colores slidos y traslcidos
como fondos de pantalla.
- windowFrame
Drawable utilizado para definir el marco de una ventana.
- windowActionBar
Marca que indica que la ventana debe mostrar una Barra de Accin (Action Bar) en lugar
del usual ttulo de la ventana. Ms adelante, en esta misma Unidad 2, veremos en qu con-
siste un Action Bar.
- alertDialogTheme
Tema que se debe aplicar a una ventana de dilogo de alerta.
- progressBarStyle
Estilo por defecto de la vista ProgressBar. Normalmente, es un crculo que gira.ac-
tionBarStyle
Atributo que permite establecer el estilo de un Action Bar.
Recuerda que para indicar un atributo por defecto del sistema operativo de An-
droid es necesario escribir el texto android: antes del nombre del mismo, por
ejemplo, as: android:windowBackground.
En el manual oficial sobre ATRIBUTOS de Android puedes encontrar un listado completo con 145
la descripcin de todos los atributos disponibles. En este apartado hemos incluido los ms
interesantes o utilizados por el programador.
Es posible definir en tiempo de ejecucin, el tema visual que debe cargar una aplicacin Android.
Si abres el archivo temas.xml, vers que hemos definido varios temas visuales:
<style name=Tema.Blanco>
<item name=paginaBackground>@style/pagina_background_blanco</item>
<item name=paginaPaddingLayout>@style/pagina_padding_layout_blanco
</item>
<item name=textoTitulo>@style/texto_titulo_blanco</item>
...
<style name=Tema.Azul>
<item name=paginaBackground>@style/pagina_background_azul</item>
<item name=paginaPaddingLayout>@style/pagina_padding_layout_azul
</item>
<item name=textoTitulo>@style/texto_titulo_azul</item>
Para intercambiar los diferentes temas en tiempo de ejecucin hemos reiniciado la Actividad
principal y utilizado el mtodo setTheme() en el evento onActivityCreateSetTheme() antes de
que la aplicacin invoque el mtodo setContentView():
// Reinicia y establece el tema de una Actividad.
public static void changeToTheme(Activity activity, int tema)
{
Aula Mentor
nTema = tema;
// Finalizamos la actividad
activity.finish();
// Volvemos a ejecutar de nuevo la Actividad
activity.startActivity(new Intent(activity,
activity.getClass()));
}
// Establece el tema de la Actividad de acuerdo con la variable nTema
public static void onActivityCreateSetTheme(Activity activity)
{
switch (nTema)
{
default:
case TEMA_DEFECTO:
break;
case TEMA_BLANCO:
activity.setTheme(R.style.Tema_Blanco);
break;
case TEMA_AZUL:
activity.setTheme(R.style.Tema_Azul);
break;
}
}
Si ejecutas en Eclipse este Ejemplo 1, vers que se muestra la siguiente aplicacin en el Emulador:
U2 Multimedia y Grficos en Android
Al pulsar en los distintos botones, vers que la carga de un tema hace que el aspecto de la
aplicacin cambie radicalmente. As de potente es esta tcnica si se emplea correctamente.
Aunque a priori pueda parecer complicado gestionar temas visuales, basta con recorrer
el cdigo fuente del Ejemplo 1 del curso para ir entendiendo las relaciones entre Estilos, Temas
y Vistas. Te animamos a que estudies con atencin su cdigo fuente.
- Si hemos definido una Actividad de configuracin en el fichero anterior, cada vez que se cree
el Widget se ejecutar esta Actividad para indicar sus parmetros iniciales. Si deseamos que
el usuario pueda reconfigurar el Widget debemos crear un Actividad extra que aparezca en
el escritorio y permita realizar esta tarea.
Los Widgets de Android parecen muy simples cuando los utilizamos, sin embargo existen ciertas
limitaciones a la hora de programarlos que debes conocer.
Si desarrollas un Widget sencillo que no requiere la gestin de su estado y se actualiza un par
de veces al da, vers que es muy fcil de implementar.
Si implementas un Widget con gestin del estado que se actualiza con poca frecuencia
con cambios elementales, podemos definir su comportamiento dentro del propio Widget.
Sin embargo, para un Widget complejo que se actualiza cada poco tiempo (segundos o
milisegundos), debemos utilizar el gestor de alarmas (AlarmManager) o, probablemente, sea ne-
cesario desarrollar un servicio que gestione el estado del Widget. En el ejemplo del curso hemos
utilizado los servicios, aunque no es estrictamente necesario, para mostrar cmo se implementa
este tipo de Widget complejo.
La nica interaccin posible del usuario con las Vistas incluidas en un Widget es median-
te el evento OnClickListener que se lanza cuando el usuario hace clic sobre ste.
En relacin con los gestos, nicamente se permite un toque (Touch) y arrastrar vertical-
mente (Vertical Swipe) En el Ejemplo 3 veremos cmo funciona este ltimo gesto al cambiar
las imgenes.
148 Otro factor a considerar es que los objetos RemoteViews que especifican la interfaz del
usuario tienen restricciones sobre los tipos Vistas y layouts que podemos incluir en ellos. Es de-
cir, no podemos incluir en un Widget todas las Vistas disponibles en el SDK de Android. Adems,
el programador no puede gestionar directamente las Vistas internas del RemoteView, sino debe
hacerlo mediante los mtodos proporcionados por esta clase.
Seguidamente, mostramos las Vistas y layouts que podemos incluir en el diseo de un Widget:
FrameLayout
LinearLayout
RelativeLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
Aunque esta lista parezca reducida, aumenta con cada nueva versin de Android. La razn
principal para restringir lo que se puede incluir en una RemoteView es que estas Vistas deben
estar desconectadas de los procesos que las controlan. En realidad, estas Vistas estn alojadas
en otra aplicacin como es Home Application (aplicacin de lanzadera de aplicaciones que
funciona como escritorio en Android que, adems, el usuario puede cambiar a su antojo). Los
gestores de estas Vistas son procesos en segundo plano que se invocan por temporizadores, por
U2 Multimedia y Grficos en Android
esta razn, a esas vistas se las denomina vistas remotas (remote views) y pueden ser modifica-
das por otros procesos con los mismos permisos.
Antes de empezar a implementar un Widget, vamos a analizar el ciclo de vida de ste dentro
del sistema operativo Android estudiando la secuencia de creacin y actualizacin.
149
Adems, el sistema operativo puede desactivar o activar un Widget como una Actividad normal
y corriente mediante los mtodos ya conocidos por el alumno onDisable() onEnabled()
respectivamente y onDestroy() en caso de eliminacin.
Aula Mentor
Para iniciar un proyecto que implemente un Widget debemos crear en Eclipse ADT un proyecto
normal de tipo Android.
En esta parte, vamos a ver como se implementa un Widget estudiando los ficheros que hemos
nombrado anteriormente.
Interfaz de usuario del Widget: /res/layout/widget_layout_rojo.xml y /res/layout/
widget_layout_verde.xml
150
Estos dos primeros archivos definen los layout que dibujan la interfaz del Widget que est
compuesta nicamente por un LinearLayout, una imagen (ImageView) y una etiqueta de
texto (TextView) donde aparece el mensaje al usuario. Veamos, por ejemplo, cmo es el layout
widget_layout_verde.xml:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/layout_widget
android:layout_width=match_parent
android:layout_height=match_parent
android:layout_margin=1dip
android:orientation=horizontal
android:background=@drawable/forma_verde >
<ImageView
android:src=@drawable/out
android:id=@+id/icono
android:layout_gravity=center
android:gravity=center_horizontal|center_vertical
android:layout_width=wrap_content
android:layout_margin=5dip
android:layout_height=wrap_content/>
<TextView
android:id=@+id/texto
style=@android:style/TextAppearance.Medium
android:layout_width=match_parent
android:layout_height=match_parent
android:layout_gravity=center
android:gravity=center_horizontal|center_vertical
android:text=Sin datos >
</TextView>
U2 Multimedia y Grficos en Android
</LinearLayout>
151
3.4.1 Fichero de configuracin del widget:
/res/xml/widget_info.xml
En este fichero XML se definen las propiedades del Widget como, por ejemplo, el tamao en
pantalla o la frecuencia de actualizacin. Este XML se debe crear en la carpeta \res\xml del
proyecto. En el ejemplo del curso tiene la siguiente estructura:
<appwidget-provider
xmlns:android=http://schemas.android.com/apk/res/android
android:initialLayout=@layout/widget_layout_verde
android:minHeight=40dp
android:minWidth=180dp
android:resizeMode=horizontal|vertical
android:updatePeriodMillis=180000 android:configure=es.mentor.
unidad2.eje2.widgethomescreen.PreferenciasWidget>
</appwidget-provider>
Existen otras propiedades que se pueden definir como, por ejemplo, el icono de la vista previa
del widget (android:previewImage), aunque slo para Android >3.1. Puedes consultarlas
todas en AppWidgetProviderInfo.
La pantalla inicial de Android se divide en un mnimo de 44 celdas (un dispositivo con
mayor pantalla pueden contener ms) donde se pueden colocar aplicaciones, accesos directos
y Widgets. Teniendo en cuenta las diferentes dimensiones de estas celdas segn el dispositivo y
la orientacin de la pantalla, existe una frmula sencilla para ajustar las dimensiones de nuestro
widget para que ocupe un nmero determinado de celdas sea cual sea la orientacin:
- ancho_mnimo = (num_celdas * 70) 30
- alto_mnimo = (num_celdas * 70) 30
Empleando esta frmula, el widget del Ejemplo del curso ocupa un tamao mnimo de 3 celdas
de ancho por 1 celda de alto; por lo tanto, debemos indicar unas dimensiones de 146dp x 40dp.
HorarioTrabajoWidgetProvider.java
152 Esta clase implementa la funcionalidad del widget y deber extenderse de la clase AppWidget-
Provider que, a su vez, es una clase derivada de BroadcastReceiver, ya que los widgets de
Android son un caso particular de este tipo de componentes.
En esta clase se implementan los mensajes a los que vamos a responder desde el Wid-
get, entre los que destacan:
- onEnabled(): lanzado cuando se crea la primera instancia de un Widget.
- onUpdate(): ejecutado peridicamente cuando hay que actualizar un Widget cada vez que
se cumple el periodo de tiempo definido por el parmetro updatePeriodMillis descrito
anteriormente o al aadir el Widget al escritorio.
- onDeleted(): lanzado cuando se elimina del escritorio una instancia de un widget.
- onDisabled(): ejecutado cuando se elimina del escritorio la ltima instancia de un Widget.
- onReceive(): se implementa para enviar llamadas a los distintos mtodos de otros AppWid-
getProvider o para actualizar un Widget individualmente.
En la mayora de los casos, tendremos que implementar, como mnimo, el evento onUpdate().
El resto de mtodos dependern de la funcionalidad de nuestro Widget. En el Ejemplo del cur-
so implementamos este mtodo onUpdate() y el mtodo onReceive():
// Define la Clase que llama Android cada vez que se crea y actualiza // el
Widget. Debe extenderse de la clase AppWidgetProvider
public class HorarioTrabajoWidgetProvider extends AppWidgetProvider {
private static final String LOG =
es.mentor.unidad2.eje2.widgethomescreen;
// Evento que inicia Android cada vez que tiene que actualizar el
// widget
@Override
public void onUpdate(Context context, AppWidgetManager
U2 Multimedia y Grficos en Android
ServicioActualizacionWidget.java
Como hemos visto, un Widget puede actualizar su aspecto/informacin cada cierto tiempo,
definido en su archivo xml de informacin con la propiedad updatePeriodMillis.
Cuando la actualizacin implica operaciones de larga duracin (ms de 10 segundos), pode-
mos aplicar dos mtodos en el mtodo onUpdate() de la clase WidgetProvider para imple-
mentarla:
- Mediante un servicio que veremos en el ejemplo de este curso avanzado
- Utilizando el Gestor de alarmas (AlarmManager) estudiado en el curso de iniciacin de
Mentor (Unidad 7 Ejemplo 3).
La diferencia entre un mtodo y otro radica en que el mnimo intervalo que podemos definir
en un servicio es de 1.800.000 milisegundos (30 minutos), mientras que si usamos el Gestor de
alarmas (AlarmManager) podemos aumentar la frecuencia de actualizacin y, adems, es ms
eficiente desde el punto de vista energtico para el dispositivo.
Para utilizar este segundo mtodo debemos definir un servicio similar al primer mtodo
pero, en lugar de llamarlo, hay que programarlo en este Gestor de alarmas (AlarmManager).
En la Unidad 7 del curso de Iniciacin se estudia cmo hacerlo.
}
// Guardamos las preferencias
PreferenciasWidget.guardarTrabajando(
ServicioActualizacionWidget.this, trabajando);
PreferenciasWidget.guardarHora(
ServicioActualizacionWidget.this, hora);
// Registramos el evento onClickListener mediante un
// PendingIntent
Intent clickIntent = new
Intent(this.getApplicationContext(),
HorarioTrabajoWidgetProvider.class);
// Indicamos el tipo de accin de Intent
clickIntent.setAction(
AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// Indicamos todos los IDs de los Widgets porque onUpdate
// del WidgetProvider lo necesita para actualizar todos los
// Widgets
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
losWidgetIds);
// Indicamos tambin el ID de este Widget porque onReceive del
// WidgetProvider lo necesita para actualizarlo (si estuviera
// desarrollado este evento) clickIntent.
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
widgetId);
// Definimos una Intencin Pendiente para este widgetId
PendingIntent pendingIntent = PendingIntent.getBroadcast(
getApplicationContext(), widgetId, clickIntent,
156 PendingIntent.FLAG_UPDATE_CURRENT);
// Establecemos el evento onClick sobre el layout completo del
// Widget para detectar el clic en cualquier sitio de ste.
remoteViews.setOnClickPendingIntent(R.id.layout_widget,
pendingIntent);
// Indicamos al appWidgetManager que actualice la vista con el
// nuevo remoteViews
appWidgetManager.updateAppWidget(widgetId, remoteViews);
} // end for
// Paramos el servicio al acabar para que no se actualice
// continuamente
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
// No es posible que un Cliente se conecte (Bind) al servicio
return null;
}
}
Es importante destacar que, como hemos comentado repetidamente, las Vistas internas de
un Widget se basan en un tipo especial llamadoRemoteViews. En el cdigo anterior pue-
des observar que para acceder a estas Vistas remotas que constituyen la interfaz del Widget
construimos un nuevo objeto RemoteViews inflando su layout correspondiente.
Una vez creado este objeto, podemos modificar el contenido de las Vistas internas
mediante una serie de mtodosset (uno para cada tipo de datos bsicos) que permiten esta-
blecer las propiedades de cada Vista individual del Widget. Por ejemplo, remoteViews.set-
U2 Multimedia y Grficos en Android
/res/layout/preferencias_widget.xml
En el fichero definimos la interfaz de configuracin inicial del widget de forma similar a como
haramos con cualquier otra actividad Android diseando su layout xml. En este caso, el diseo
es muy sencillo con un cuadro de texto para introducir el nombre personalizado y dos botones,
uno para aceptar la configuracin y otro para cancelar, en cuyo caso el widget no se crea en
el escritorio. Veamos su contenido:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical>
<TextView
android:id=@+id/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/MensajeConfiguracion
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
Aula Mentor
android:id=@+id/txtNombre
android:layout_width=match_parent
android:layout_height=wrap_content
android:hint=Sin nombre
android:text=Sin texto >
<requestFocus />
</EditText>
<TableRow android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center>
<Button
android:id=@+id/btnAceptar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=@string/Aceptar />
<Button
android:id=@+id/btnCancelar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=@string/Cancelar />
</TableRow>
</LinearLayout>
PreferenciasWidget.java
En el cdigo anterior podemos ver cmo establecemos el resultado por defecto que devuelve
la Actividad de configuracin mediante el mtodo setResult().
Una actividad de configuracin de un Widget debe siempre devolver un resultado
RESULT_OK si la configuracin es correcta o RESULT_CANCELED si el usuario sale de la con-
figuracin sin aceptar los cambios. En las sentencias anteriores establecemos al principio un
resultado RESULT_CANCELED por defecto para asegurarnos de que, si el usuario abandona la
configuracin pulsando el botn Volver del dispositivo o pulsando el botn Cancelar de esta
Actividad de configuracin, no se aade el Widget al escritorio por error.
AndroidManifest.xml
Finalmente, para acabar, debemos declarar el Widget dentro del manifest de la aplicacin. As,
incluimos los elementos siguientes dentro de la etiqueta <application>:
<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>
Aula Mentor
<receiver
android:name=HorarioTrabajoWidgetProvider
android:icon=@drawable/maletin
android:label=Control de horario >
<intent-filter>
<action android:name=android.appwidget.action.
APPWIDGET_UPDATE />
</intent-filter>
<meta-data
android:name=android.appwidget.provider
android:resource=@xml/widget_info />
</receiver>
<service
android:name=es.mentor.unidad2.eje2.widgethomescreen.
ServicioActualizacionWidget >
</service>
<activity
android:name=es.mentor.unidad2.eje2.widgethomescreen.
PreferenciasWidget>
<intent-filter>
<action android:name=android.appwidget.action.
APPWIDGET_CONFIGURE/>
</intent-filter>
</activity>
</application>
162
El Widget se declarar como un elemento <receiver> indicando la siguiente informacin:
- Atributo name: referencia a la clase java del WidgetProvider.
- Atributo icon: imagen que se muestra al usuario cuando desea aadir el Widget.
- Atributo label: texto que se muestra al usuario cuando desea aadir el Widget.
- Elemento <intent-filter>: indica los eventos a los que responder el widget, por ejem-
plo, aqu indicamos el evento APPWIDGET_UPDATE para detectar la accin de actualizacin.
Nota: en este apartado podramos incluir ms elementos <intent-filter> para capturar
ms eventos, incluso eventos personalizados por la propia aplicacin.
- Elemento <meta-data>: establecemos la referencia el XML que define la informacin del
Widget.
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Control horario de traba-
jo) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el
resultado del programa anterior, en el que hemos utilizado Widgets.
Para probarlo, podemos ejecutar el proyecto desde Eclipse ADT en el emulador de Android
y esperar a que se ejecute la aplicacin principal (sin desarrollar, ya que no hemos incluido
U2 Multimedia y Grficos en Android
ninguna funcionalidad) e ir a la pantalla principal del emulador y aadir nuestro Widget al es-
critorio tal cmo lo haramos en un dispositivo fsico:
- Hasta Android 2.x: pulsacin larga sobre el escritorio o pulsacin en la tecla Men y selec-
cionar la opcin Widgets seleccionando el Widget del curso.
- Desde Android 3.0 en adelante: accedemos al men principal, pulsamos la pestaa Widgets y
buscamos el Widget en la lista y realizamos sobre l una pulsacin larga hasta que el sistema
nos permite arrastrarlo y soltarlo sobre el escritorio.
163
Prueba a aadir varias instancias al escritorio, actualizarlos haciendo click sobre ellos, despla-
zarlos por la pantalla, cambiar su tamao y eliminarlos.
Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin en el
Emulador:
Aula Mentor
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (El tiempo) de la Unidad
2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado Widgets con colecciones de Vistas.
U2 Multimedia y Grficos en Android
Para probarlo, podemos ejecutar el proyecto desde Eclipse ADT en el emulador de Android
y esperar a que se ejecute la aplicacin principal (sin desarrollar, ya que no hemos incluido
ninguna funcionalidad) e ir a la pantalla principal del emulador y aadir nuestro Widget al
escritorio tal cmo lo haramos en un dispositivo fsico:
165
Si arrastramos la imagen superior hacia arriba veremos cmo van rotando las imgenes del
tiempo en el Widget.
Desde la versin 4.2 de Android en adelante es posible incluir Widgets en la pantalla de bloqueo
(Lock Screen) del dispositivo.
Para ello, basta con incluir en el archivo XML que define el Widget el atributo
android:widgetCategory de esta forma:
<appwidget-provider xmlns:android=http://schemas.android.com/apk/res/
android
android:widgetCategory=keyguard|home_screen
...
>
...
</appwidget-provider>
En el ejemplo anterior indicamos que el Widget se puede incluir tanto en la pantalla de bloqueo
como en el escritorio normal.
Adems, es posible detectar en tiempo de ejecucin en qu pantalla se est ejecutando el
Widget, basta con aadir el mtodo onUpdate() del WidgetProvider estas sentencias:
Aula Mentor
As, es posible que un mismo Widget presente diferente aspecto si se ejecuta en la pantalla de
bloqueo o en el escritorio normal.
De forma anloga, es posible definir un atributo similar a android:initialLayout que
define el layout que se infla por primera vez al crear el Widget; pero asociado nicamente a la
pantalla de bloqueo mediante el nuevo atributo android:initialKeyguardLayout cuando se
aade a sta.
Para iniciar un proyecto que implemente un fondo de pantalla animado debemos crear en
Eclipse ADT un proyecto normal de tipo Android.
En esta parte, vamos a ver cmo se implementa un Widget estudiando los ficheros que hemos
nombrado anteriormente.
/res/xml/miwallpaper.xml
Para crear un fondo de pantalla animado es necesario componer un archivo XML que lo des-
criba.
<wallpaper
android:settingsActivity=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
xmlns:android=http://schemas.android.com/apk/res/android
android:description=@string/wallpaper_descripcion
android:thumbnail=@drawable/sphere />
Este archivo debe incluir la descripcin del fondo animado (atributo android:description)
y puede contener una vista previa o icono del fondo (atributo android:thumbnail) y la re-
ferencia a la Actividad de configuracin (atributo android:settingsActivity) que permite
personalizar el fondo de pantalla animado. Como puedes ver, en Android se pueden establecer 167
preferencias hasta en los fondos animados.
MiServicioWallpaper.java
Este objeto Engine controla el ciclo de vida de los eventos, animaciones y de dibujo del fondo
animado a travs de,los mtodos siguientes, entre otros:
- onCreate(): se lanza al crearlo.
- onVisibilityChanged(): aparece cuando el fondo animado cambia su visibilidad. Por
ejemplo, cuando el usuario ejecuta una aplicacin y el fondo de pantalla deja de estar visible
y viceversa.
- onTouchEvent(): se ejecuta cuando el usuario toca el fondo de pantalla.
- onSurfaceDestroyed(): ejecutado cuando se destruye el fondo de tipo SurfaceHolder.
Aula Mentor
// Constructor de la clase
public MiWallpaperEngine() {
// Accedemos a las preferencias del fondo animado
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(MiServicioWallpaper.this);
// Recuperamos preferencias
numeroMax = Integer
.valueOf(prefs.getString(numeroMaxCirculos, 10));
touchActivado = prefs.getBoolean(touch, false);
// Definimos el listado de crculos mediante la clase
// Punto (coordenada en la pantalla)
circulos = new ArrayList<MiCirculo>();
// Indicamos a paint que suavice los bordes de todo
// lo que dibuje
paint.setAntiAlias(true);
// Forma de unir las lneas (redondeada)
paint.setStrokeJoin(Paint.Join.ROUND);
// Indicamos al handler que ejecute el Runnable
handler.post(drawRunnable);
// Creamos un listener que notifica si hay un cambio
// en las preferencias del liveWallpaper
prefs.registerOnSharedPreferenceChangeListener(this);
}
}
// Evento que se ejecuta cuando el usuario toca el fondo de
// pantalla
@Override
public void onTouchEvent(MotionEvent event) {
// Si est activado el toque en preferencias
if (touchActivado) {
// Obtenemos las coordenadas del toque
float x = event.getX();
float y = event.getY();
// Obtenemos la referencia al SurfaceHolder donde se
// ejecuta el fondo animado
SurfaceHolder holder = getSurfaceHolder();
// Definimos un canvas donde vamos a dibujar los crculos
Canvas canvas = null;
try {
// Obtenemos el canvas bloqueando el fondo SurfaceHolder
// para que el usuario no pueda manipularlo
canvas = holder.lockCanvas();
// Si lo hemos obtenido correctamente entonces...
if (canvas != null) {
// Definimos el fondo de color negro
canvas.drawColor(Color.BLACK);
// Si no podemos aadir 5 crculos borramos los 5
// primeros
while (circulos.size()+5 > numeroMax) {
circulos.remove(0);
170 }
// Aadimos nuevos crculos alrededor del punto de
// toque
circulos.add(new MiCirculo(x, y, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x, y-30, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x+30, y, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x, y+30, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x-30, y, colorAleatorio(),
radioAleatorio()));
// Dibujamos los crculos
dibujaCirculos(canvas, circulos);
}
} finally {
// Muy importante! Hay que desbloquear el fondo SurfaceHolder
if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}
super.onTouchEvent(event);
}
}
// Mtodo local que dibuja los crculos en el fondo
private void dibuja() {
// Obtenemos el fondo SurfaceHolder
SurfaceHolder holder = getSurfaceHolder();
// Definimos un canvas donde vamos a dibujar los crculos
U2 Multimedia y Grficos en Android
}
private int radioAleatorio() {
return (int) (30 * Math.random()) + 5;
}
// Ocurre cuando hay un cambio en las preferencias del
// liveWallpaper
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
// Actualizamos las preferencias
if (key.equals(numeroMaxCirculos))
numeroMax =
Integer.valueOf(sharedPreferences.getString(
numeroMaxCirculos, 10));
else if (key.equals(touch))
touchActivado =sharedPreferences.getBoolean(touch,false);
}
}
}
Muy importante! Hay que desbloquear el fondo SurfaceHolder una vez haya-
mos acabado de dibujarlo. Si no lo hacemos, se quedar congelado y ya no ser
animado.
/res/xml/preferencias.xml
En este fichero establecemos las propiedades de la ventana de preferencias del fondo animado.
En este ejemplo vamos a usar la gestin de preferencias de Android mediante la clase Prefe-
renceScreen:
<PreferenceScreen xmlns:android=http://schemas.android.com/apk/res/
android>
<CheckBoxPreference android:key=touch
U2 Multimedia y Grficos en Android
android:title=Habilitar toque></CheckBoxPreference>
<EditTextPreference android:key=numeroMaxCirculos
android:title=Nmero mximo de crculos
android:negativeButtonText=@string/cancelar
android:positiveButtonText=@string/aceptar>
</EditTextPreference>
</PreferenceScreen>
Con el cdigo anterior definimos una ventana con un CheckBox y un campo de tipo texto que
indica el nmero mximo de crculo que dibujar el fondo animado.
PreferenciasActivity.java
// valor es > 5
if (newValue != null && newValue.toString().length() > 0
&& newValue.toString().matches(\\d*) &&
Integer.valueOf(newValue.toString()) > 4) {
return true;
}
AndroidManifest.xml
<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>
<service
android:name=MiServicioWallpaper
android:enabled=true
android:label=Ejemplo de live Wallpaper
android:permission=android.permission.BIND_WALLPAPER >
<intent-filter>
<action
android:name=android.service.wallpaper.WallpaperService >
</action>
</intent-filter>
<meta-data android:name=android.service.wallpaper
android:resource=@xml/miwallpaper >
</meta-data>
</service>
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
android:exported=true
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
</activity>
175
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
EstablecerWallpaperActivity
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
</application>
Esto evita que el fondo animado se instale en dispositivos que no tienen hardware compatible.
Adems, hemos definido la Actividad de Preferencias y la principal de la aplicacin.
Aula Mentor
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Live Wallpaper de curso
Mentor) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el
AVD el resultado del programa anterior, en el que hemos diseado un Fondo de
pantalla animado.
Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra el siguiente fondo animado
en el Emulador:
176
Prueba a arrastrar el dedo sobre el mismo y vers cmo se actualiza el fondo de pantalla.
En Internet, existen mltiples listados para fondos animados de pantalla: Veamos un
conjunto de ejemplos segn su funcionalidad:
- Launchers: este tipo de fondos de pantalla permite lanzar aplicaciones y realizar mltiples
acciones de forma rpida. Un ejemplo es Launcher Wall:
U2 Multimedia y Grficos en Android
Acceso a Contactos: fondo que muestra los contactos de una forma atractiva y rpida con los
que podremos interactuar. Un ejemplo es Contact Pro Live Wallpaper:
- Relojes: existen infinidad de fondos animados de este tipo. Vemos algunos: 177
Analogy TextClock
Prediccin del tiempo: existen mltiples fondos de este tipo como GoWeather
que, adems de ser un Widget estupendo, dispone tambin de un excelente
178
Fondo Animado que cambia segn el tiempo que hace.
U2 Multimedia y Grficos en Android
Aunque este listado pueda parecer muy completo, Google Play est lleno de fondos animados
cada da que proporcionan las funcionalidades ms inverosmiles. Animamos al alumno a que 179
busque o, mejor, que desarrolle su propio fondo animado: Sirva la lista anterior de ejemplo
para comenzar.
5. Fragmentos
Cuando surgieron los dispositivos con pantallas de gran tamao de tipo Tableta u ordenadores,
Google tuvo que desarrollar la versin 3.0 de Android especficamente diseada para solucionar
el problema de la adaptacin de la interfaz grfica de las aplicaciones a ese nuevo tamao de
pantallas. Una interfaz de usuario diseada para un telfono mvil no se adaptaba fcilmente
a una pantalla 5 7 pulgadas. La solucin fue implementar un nuevo tipo de componente
denominado Fragment.
Un Fragment no puede considerarse ni una Actividad, ni una Vista, es algo parecido a
un contenedor. Un Fragment es un pedazo de la interfaz de usuario que se puede aadir
o eliminar de la interfaz global de usuario de forma independiente al resto de elementos de
la Actividad. Esto permite que pueda reutilizarse en otras Actividades.
As, podemos dividir la interfaz de usuario en varios segmentos o fragmentos; de ah su
nombre, de forma que podamos disear diversas configuraciones de pantalla, dependiendo del
tamao y orientacin de sta sin tener que duplicar cdigo reutilizando los distintos fragmentos
para cada una de las posibles configuraciones de pantalla.
Por ejemplo, supongamos que estamos diseando una aplicacin de correo electrnico
en la que queremos mostrar la lista de mensajes recibidos con los campos tpicos De y Asun-
to y, por otro lado, mostrar el contenido completo del mensaje seleccionado. En un telfono
mvil, lo habitual es tener una primera Actividad que muestra el listado completo de los men-
sajes y, cuando el usuario selecciona uno de ellos, ejecutar una nueva Actividad que muestre el
Aula Mentor
contenido de dicho mensaje. Sin embargo, en una tableta puede existir espacio ms que sufi-
ciente para tener ambas partes de la interfaz en la misma pantalla. Por ejemplo, en una tableta
en posicin apaisada podramos tener una columna a la izquierda con el listado de mensaje y
destinar la zona derecha a presentar el detalle del mensaje seleccionado, todo esto sin necesidad
de cambiar de Actividad.
En las versiones de Android anteriores a la 3.0 (sin Fragments) podramos haber imple-
mentado diferentes Actividades con sus respectivos layouts para cada configuracin de pantalla,
pero esto nos hubiera obligado a duplicar gran parte del cdigo fuente en cada Actividad. Si,
en su lugar, utilizamos fragmentos, podramos desarrollar un fragmento que muestra el listado
de mensajes y otro fragmento que muestra el contenido del mensaje seleccionado, cada uno de
ellos acompaado de su lgica de aplicacin y definir nicamente varios layouts en funcin del
tamao de la pantalla que incluya los fragmentos necesarios en cada momento.
En el Ejemplo 5 de este Unidad vamos a simular la aplicacin de correo que hemos descrito,
adaptndola a tres configuraciones distintas de pantalla: normal, grande horizontal y grande
vertical. En el primer caso, ubicaremos el listado de mensajes en una Actividad y, en otra
Actividad, el detalle. Sin embargo, en el segundo y tercer caso estos dos elementos visuales
estarn en la misma Actividad, ocupando la parte derecha/izquierda para la pantalla en el modo
apaisado y la parte arriba/abajo en el modo vertical.
Por lo tanto, en estos ds ltimos casos hemos desarrollado dos fragmentos: uno para el
listado de mensajes y el otro para mostrar su contenido.
180 De la misma forma que una Actividad, un Fragmento se infla a partir de un fichero de
layout XML para la interfaz visual (en la carpeta /res/layout) y una clase Java con las senten-
cias que incluyen sus funcionalidades.
El primero de los Fragmentos creados contiene una Vista ListView con su Adaptador
personalizado que muestra dos campos por fila: De y Asunto. Durante el curso de Iniciacin
ya estudiamos este tipo de elemento, por lo que aqu no daremos indicaciones sobre su uso.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:padding=5dp >
<ListView
android:id=@+id/listado
android:layout_width=match_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>
Como hemos comentado, si un fragmento incluye lgica, debe tener asociadasu propia clase
java que indique las sentencias que debe ejecutar y que se extiende de la clase Fragment.
U2 Multimedia y Grficos en Android
Aunque los fragmentos estn disponibles slo a partir de la versin 3.0 de Android, sin embargo
este sistema operativo permite utilizar esta caracterstica en versiones anteriores
incluyendo la librera de compatibilidad android-support en el proyecto:
Nota: esta librera se puede incluir manualmente en el proyecto mediante la opcin Add Support
Library del men contextual del proyecto:
181
Veamos dnde se comprueba que la librera est disponible en el entorno de desarrollo Eclipse
ADT:
182
Usando este truco, podemos utilizar Fragmentos en la mayora de versiones de Android, incluso
desde la versin 1.6.
return(item);
}
}
El fichero Mensaje.java contiene una sencilla clase que almacena los campos De, Asunto y
Texto de un mensaje. Puedes abrirla en Eclipse ADT y ver su contenido.
Si analizamos con detenimiento la clase FragmentListado anterior es fcil advertir que
existen muy pocas diferencias de cuando desarrollamos un listado de opciones con su adapta-
dor personalizado en un Actividad normal.
En este caso, las diferencias radican en los mtodos que sobrescribimos. En el caso de
los fragmentos normalmente son los siguientes: onCreateView() y onActivityCreated().
Veamos para qu se usan.
- onCreateView(): es el mtodo equivalente al onCreate() de una Actividad. Es decir, aqu
inflamos el layout asociado al fragmento.
- onActivityCreated(): mtodo que se ejecuta cuando la Actividad contenedora del frag-
mento est creada por completo. En el ejemplo del curso, aprovechamos este mtodo para
obtener la referencia a la Vista ListView y asociarle su adaptador correspondiente.
Ahora vamos a explicar el segundo fragmento que, como comentamos, se encarga de mostrar
el detalle del mensaje seleccionado. La definicin de este fragmento es ms simple que la del
anterior. Si accedemos a su layout XML fragmento_detalle.xml vemos el siguiente contenido:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
U2 Multimedia y Grficos en Android
android:background=#FFBBBBBB
android:orientation=vertical
android:padding=10dp >
<TextView
android:id=@+id/cuerpoMensaj
android:layout_width=wrap_content
android:layout_height=wrap_content />
</LinearLayout>
// Inflamos la Vista
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
Notamos que todos estos layouts se denominan con el mismo nombre activity_main.xml; sin
embargo, su uso lo marca la carpeta donde colocamos cada uno. Por lo tanto, Android elegir
automticamente el layout en funcin del tamao y orientacin de la pantalla.
Para el caso de pantalla normal, la Actividad principal mostrar slo el listado de mensajes; por
lo tanto, el layout incluir nicamente el fragmento FragmentListado:
<fragment xmlns:android=http://schemas.android.com/apk/res/android
class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_width=match_parent
android:layout_height=match_parent />
<fragment xmlns:android=http://schemas.android.com/apk/res/android
class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_width=match_parent
android:layout_height=match_parent />
<LinearLayout
186 xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=horizontal
android:layout_width=match_parent
android:layout_height=match_parent>
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_weight=30
android:layout_width=0px
android:layout_height=match_parent />
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_weight=70
android:layout_width=0px
android:layout_height=match_parent />
</LinearLayout>
En este caso, al haber mucha pantalla disponible, incluimos los dos fragmentos dentro de un
LinearLayout horizontal, asignando al primer fragmento un peso (propiedad layout_weight)
de 30 y al segundo de 70 para que la columna izquierda con el listado de mensajes ocupe un
30% de la pantalla y la de detalle ocupe el resto.
Por ltimo, para el caso de la pantalla grande vertical (directorio layout-large-port)
utilizamos un LinearLayout vertical.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
U2 Multimedia y Grficos en Android
android:layout_width=match_parent
android:layout_height=match_parent>
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_weight=40
android:layout_width=match_parent
android:layout_height=0px />
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_weight=60
android:layout_width=match_parent
android:layout_height=0px />
</LinearLayout>
187
Para poder ver las diferencias debes crear dos AVDs, uno con pantalla normal y otro grande.
Para cambiar la orientacin de la pantalla de un dispositivo virtual debes usar el atajo de teclado
[Ctrl+F12]. Si ejecutas en Eclipse ADT este ejemplo, vers que se muestra el siguiente fondo
animado en el Emulador:
Pantalla normal (dispositivo del curso):
Aula Mentor
188
//...
//..
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
// Buscamos el listado del layout
listado = (ListView)getView().findViewById(R.id.listado);
// Asociamos el adaptador
listado.setAdapter(new AdaptadorMensajes(this));
// Definimos el evento onClic del listado
listado.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> list, View view, int
pos, long id) {
if (listener!=null) {
// Lanzamos el evento onMensajeSeleccionado y le pasamos
// como parmetro la posicin del elemento
// seleccionado
Aula Mentor
listener.onMensajeSeleccionado(
(Mensaje)listado.getAdapter().getItem(pos));
}
}
}); // end onItemClick
}
Como vemos, una vez definido este truco, debemos en el evento onItemClick() de la lista
lanzar nuestro evento personalizado onMensajeSeleccionado() pasndole como parmetro el
contenido del mensaje, que hemos obtenido mediante el adaptador con el mtodo getAdapter()
y recuperamos el elemento con getItem().
Ahora ya tenemos todas las piezas del puzle que debemos encajar en la clase java Main-
Activity que define la Actividad principal. Para ello, en su mtodo onCreate(), obtendremos
una referencia al fragmento de listado mediante el mtodo getFragmentById() del gestor de
190 fragmentos (Fragment Manager es el componente del sistema operativo encargado de
gestionar los fragmentos de una aplicacin) y le asignaremos el evento mediante su mtodo
setMensajesListener() que acabamos de definir anteriormente:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Definimos el fragmento del listado mediante
// referencia a su id
FragmentListado frgListado
=(FragmentListado)getSupportFragmentManager()
.findFragmentById(R.id.FrgListado);
boolean hayDetalle =
(getSupportFragmentManager().findFragmentById
(R.id.FrgDetalle) != null);
// Si se ven los dos fragmentos entonces cuando el usuario haga
// clic en un mensaje se muestra su detalle
if(hayDetalle) {
((FragmentDetalle)getSupportFragmentManager()
.findFragmentById(R.id.FrgDetalle)).mostrarDetalle(
mensaje.getTexto());
}
// Si no existe el fragmento de detalle lanzamos la actividad de
// detalle con un Intent pasando el texto como una propiedad en
// ste
else {
Intent i = new Intent(this, DetalleActivity.class);
i.putExtra(DetalleActivity.EXTRA_TEXTO,
mensaje.getTexto());
startActivity(i);
}
}
}
Se puede observar en el cdigo anterior que esta Actividad principal implementa la interfaz
MensajesListener, por lo que nos basta indicar this al mtodo setMensajesListener().
Adems, esta Actividad no se hereda de la clase Activity como suele ser habitual, sino de
FragmentActivity ya que se utiliza la librera de compatibilidad android-support para
utilizar fragmentos conservando la compatibilidad con versiones de Android anteriores a la
191
3.0. En caso de no necesitar esta compatibilidad se puede seguir heredando de Activity sin
inconvenientes.
Finalmente, si prestamos atencin al mtodo onMensajeSeleccionado(), vemos que se
ejecutar cada vez que el fragmento del listado indique que se ha seleccionado un determinado
mensaje de la lista y aplicar la lgica ya mencionada, es decir, si en la pantalla existe el frag-
mento de detalle simplemente lo actualizaremos mediante su mtodo mostrarDetalle() y, en
caso contrario, abriremos la nueva Actividad DetalleActivity utilizando un nuevo Intent
con la referencia a dicha clase y aadiendo como propiedad un campo extra de texto con el
contenido del mensaje seleccionado. Acabamos iniciando la Actividad con el mtodo startAc-
tivity().
El cdigo de la segunda Actividad DetalleActivity se limita a recuperar esta pro-
piedad extra pasada en el Intent y mostrarla en el fragmento de detalle mediante su mtodo
mostrarDetalle():
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalle);
// Buscamos el fragmento de detalle que es un fragmento
FragmentDetalle detalle =
Aula Mentor
(FragmentDetalle)getSupportFragmentManager()
.findFragmentById(R.id.FrgDetalle);
// Cargamos el texto del detalle en este fragmento
detalle.mostrarDetalle(getIntent().getStringExtra(EXTRA_TEXTO));
}
}
Animamos al alumno o alumna a que vuelva a probar la aplicacin en Eclipse ADT para
comprobar el correcto funcionamiento de la seleccin de mensajes en las distintas configuraciones
de pantalla.
Una vez hemos visto cmo crear y utilizar un fragmento, vamos a estudiar algunas de sus
funcionalidades avanzadas que pueden interesar al programador.
De igual forma que ocurre con una Actividad en Android, un Fragmento dispone de su propio
ciclo de Vida. A continuacin, se muestra el ciclo de vida de un Fragmento en funcin del estado
de la Actividad que lo contiene:
192
U2 Multimedia y Grficos en Android
Como puedes ver, el ciclo de vida de un Fragmento est muy relacionado con el ciclo
de vida de la Actividad que lo contiene. Cada invocacin que hace el sistema operativo de
un evento de una Actividad provoca que se invoquen a su vez eventos de los Fragmentos que
contiene.
Por ejemplo, cuando la Actividad recibe el evento onPause(), todos sus fragmentos re-
ciben tambin el mismo evento onPause().
Atencin: para sobreescribir cualquier mtodo del ciclo de vida (con excepcin del
mtodo onCreateView()) debemos siempre llamar al mtodo de la clase superior
(mediante super.nombreMetodo()). Si no lo hacemos, al ejecutar la aplicacin
veremos un error de ejecucin.
193
5.2.1 Cmo guardar el estado de un Fragmento
Como sabes, por defecto, cuando ocurre un cambio dinmico y automtico de la configuracin
del dispositivo (por ejemplo, un cambio de orientacin de la pantalla de ste), la Actividad de
una aplicacin Android se recrea (se destruye y se vuelve a crear). Esto le sucede tambin a
los Fragmentos que contenga dicha Actividad, que se destruyen y se vuelven a recrear auto-
Aula Mentor
mticamente.
Sin embargo, la clase Fragment dispone del mtodo setRetainInstance(boolean)
que permite al programador indicar con el parmetro true que se desea no destruir la instan-
cia del Fragmento cuando esto ocurra. Podemos invocar este mtodo en el evento onCreate()
del Fragmento. Examinemos sus caractersticas y cmo se utiliza. Si indicamos con la sentencia
setRetainInstance(true) que deseamos que Android no destruya el Fragmento en los
cambios de configuracin automticos, ocurre lo siguiente:
- Los mtodos onDestroy() y onCreate() no se invocarn de nuevo cuando se produzca
la recreacin de la Actividad.
- El resto de eventos del ciclo de vida de un Fragmento se siguen invocando igualmente y en
la misma secuencia.
- El parmetro Bundle de los mtodos onCreateView() y onActivityCreated() en nulo
(null) porque el Fragmento no se recrea.
Ambos mtodos devuelven una referencia al fragmento o null si no existe dentro de la Activi-
dad.
Es posible llevar a cabo otras operaciones dinmicas con Fragmentos en tiempo de ejecucin
que modifican la interfaz del usuario, tales como, mostrar u ocultar un fragmento. Android de-
nomina transaccin (transaction) a un cambio de este tipo.
Slo es posible utilizar el mtodo commit() mientras la Actividad (y, por lo tanto, el
Fragmento) no se encuentre guardando su estado con el mtodo onSaveInstan-
ceState. La sentencia commit mostrar una excepcin si la ejecutamos despus
de haber guardado el estado de la Actividad.
Como puedes observar en el cdigo anterior, hemos encadenado operaciones en una nica
sentencia. Es muy importante no olvidarse nunca de la orden commit(), ya que si no la usa-
mos, no se realizan los cambios.
Adems, debes saber que el orden en que realices las operaciones determina el orden
en que Android las realizar en la interfaz del usuario.
En el curso de iniciacin de Android vimos que el sistema operativo organiza las Actividades
en una pila de ejecucin (en ingls stack) donde se van apilando las actividades que el usua-
rio va invocando. De esta forma, el usuario puede moverse en esta pila utilizando la tecla re-
troceso del dispositivo .
De forma similar, Android tambin dispone de una pila de ejecucin llamada back
stack para almacenar los Fragmentos.
Si se aade mediante una transaccin un fragmento a la pila, entonces el usuario puede
ir al fragmento anterior mediante la tecla retroceso del dispositivo. Cuando el usuario llega al
Aula Mentor
Como hemos indicado, no es obligatorio que un Fragmento tenga asociado una interfaz de
usuario, es decir, un layout. Por ejemplo, un Fragmento se puede utilizar para mantener infor-
macin de estado o gestionar hilos (threads). Por lo tanto, no necesita disponer de interfaz
de usuario y no es necesario sobreescribir el mtodo onCreateView(). Es decir, mediante un
fragmento podemos ejecutar sentencias en segundo plano.
Para aadir un fragmento a la Actividad debemos usar el mtodo FragmentTransac-
tion.add(Fragment, String), donde el segundo parmetro indica una etiqueta (tag) para
identificar el fragmento.
Como sabes, un fragmento est directamente ligado a la Actividad que lo contiene. Por lo tanto, 197
es posible una comunicacin entre ambos:
- La Actividad puede llamar a todos los mtodos pblicos del fragmento mediante una refe-
rencia al objeto del fragmento. Si no se guarda la referencia del fragmento una vez creado,
es posible utilizar los mtodos findFragmentById() o findFragmentByTag() de la clase
FragmentManager como hemos visto anteriormente.
- El fragmento puede accede a la Actividad que lo contiene a travs del mtodo Fragment.
getActivity(). Por ejemplo, es posible obtener una referencia a una Vista de la Actividad
principal as:
View listView = getActivity().findViewById(R.id.lista);
Desde el punto de vista de programacin es importante evitar que las Actividades y Frag-
mentos sean muy interdependientes entre s ya que esto reduce las posibilidades de poder
reusar el fragmento en otra Actividad o aplicacin. Lo recomendado es que sea la Actividad la
que interaccione con el Fragmento.
Para conseguir este objetivo es recomendable definir interfaces y listerners en el
fragmento que luego implementar la clase que contiene la Actividad. En el Ejemplo 5 de esta
Unidad hemos utilizado este mtodo que recordamos:
Aula Mentor
- Disea la aplicacin para que la Actividad sea el componente intermediario entre los frag-
mentos, ya que a stos ltimos no es posible asociarles intenciones, es decir, un Intent no
puede llamar directamente a un fragmento.
- Todos los fragmentos deben tener definido un constructor por defecto.
- Los fragmentoss generalmente no deben implementar constructores adicionales o sobreescri-
bir el ya existente de la clase heredada.
- El primer mtodo de un fragmento donde podemos escribir una sentencia para que la eje-
cute Android es el mtodo onAttach().
- Una vez se ha creado un fragmento, es posible inicializarlo desde la Actividad mediante par-
metros que se pasan en su mtodo setArguments(Bundle). Fjate en el siguiente ejemplo:
En el fragmento podemos usar ahora estos parmetros como deseemos a travs del mtodo
getArguments(); por ejemplo, en el mtodo onActivityCreated:
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getArguments().containsKey(ID)) {
...
}
}
...
199
5.2.8 Implementar dilogos con Fragmentos
A partir de la version 3.0 de Android, las clsicas ventanas de dilogo (clase Dialog) estn en
desuso (en ingls, se denomina deprecated) en favor de los fragmentos. Este cambio tiene
mucho sentido si pensamos que las ventanas de dialogo son componentes que se reutilizan
mucho.
La clase DialogFragment de Android define la clase bsica que implementa un frag-
mento que muestra una ventana de dilogo flotante en una Actividad. Este fragmento incluye
un objeto Dialog que contiene el diseo de la ventana de dilogo. La aplicacin debe gestio-
nar la ventana de dilogo de tipo fragmento mediante los mtodos disponibles en en la clase
DialogFragment.
Si slo vas a utilizar el fragmento como una ventana de dilogo, debemos sobreescribir el
mtodo onCreateDialog() devolviendo una instancia de la clase Dialog o de sus subclases.
Si vas a emplear el nuevo fragmento como dilogo y como fragmento propiamente
dicho dentro de una Activiad, debes sobreescribir el mtodo onCreateView() y devolver la
Vista creada.
La clase DialogFragment dispone del mtodo sobrecargado (overload) show() que
Aula Mentor
muestra la ventana de dilogo como una transaccin, es decir, crea una transaccin en el objeto
FragmentManager, aade el fragmento y ejecuta commit(). Cuando se cierra la ventana de
dilogo, se crea otra transaccin para quitar el fragmento de la ACtividad.
Como no poda ser de otra forma, la clase DialogFragment dispone del mtodo dis-
miss() para cerrar el fragmento explcitamente. De igual forma, este mtodo se ejecuta me-
diante transaciones.
A continuacin, vamos a ver un sencillo ejemplo de ventana de dilogo, utilizando fragmentos,
que pide confirmacin para realizar cualquier operacin.
@Override
public void onNegativeClick() {
Aula Mentor
Toast.makeText(this, android.R.string.cancel,
Toast.LENGTH_LONG).show();
}
}
Existen en Android subclases adicionales extendidas de Fragment desarrolladas para usos muy
comunes y que facilitan el trabajo al programador. Veamos las ms importantes:
- ListFragment: fragmento que proporciona una lista del tipo ListView. Es anlogo a la
clase ListActivity.
- WebViewFragment: fragmento que crea y gestiona automticamente la clase WebView para
presentar un mini navegador de Internet.
La Barra de Accin de Android o, del ingls, Action Bar es, como su propio nombre indica, una
barra que aparece en la parte superior de una aplicacin. Puede incluir un icono, el ttulo de la
Actividad y varios botones de accin o un men extendido desplegable (en ingls, se denomina
overflow). Este men extendido, a su vez, incluye ms acciones cuando stas no caben como
botones en el espacio disponible de la pantalla del dispositivo o el programador decide por
motivos de diseo que se deben ocultar de la barra.
202 Vemos un ejemplo visual de Barra de accin:
Adems de poder definir mediante un archivo XML el men, es posible definir el tipo de men
dinmicamente mediante sentencias Java de la clase MenuItem con su mtodo menuItem.
setShowAsAction(int actionEnum) e indicando una de las siguientes acciones:
- SHOW_AS_ACTION_ALWAYS
- SHOW_AS_ACTION_IF_ROOM
- SHOW_AS_ACTION_NEVER
- SHOW_AS_ACTION_WITH_TEXT
Por ejemplo, si creas con Eclipse ADT un proyecto nuevo, vers que el men definido por
defecto (llamado normalmente /res/menu/activity_main.xml) tiene este contenido:
<menu xmlns:android=http://schemas.android.com/apk/res/android >
<item
android:id=@+id/menu_settings
android:showAsAction=never
android:title=@string/menu_settings/>
</menu>
En el cdigo anterior puedes ver que aparece un men con una nica opcin denominada
Settings y con el atributo showAsAction=never, es decir, la opcin aparece en el men
extendido.
En el Ejemplo 6 del curso hemos definido la tpica barra de accin con las opciones: Nuevo,
Guardar y Opciones:
<menu xmlns:android=http://schemas.android.com/apk/res/android >
204
<item
android:id=@+id/menu_guardar
android:showAsAction=ifRoom
android:icon=@android:drawable/ic_menu_save
android:title=@string/menu_guardar/>
<item
android:id=@+id/menu_nuevo
android:showAsAction=ifRoom|withText
android:icon=@android:drawable/ic_menu_add
android:title=@string/menu_nuevo/>
<item
android:id=@+id/menu_opciones
android:showAsAction=never
android:title=@string/menu_opciones/>
</menu>
En el cdigo anterior se puede observar que la segunda opcin combina varios valores de
showAsAction utilizando el carcter |.
Como puedes ver, la opcin Guardar se muestra como botn si hay espacio, la segunda
es igual que la primera pero aade el texto y la tercera opcin no aparece nunca, es decir, es
un men.
U2 Multimedia y Grficos en Android
En el cdigo anterior hemos empleado las imgenes del propio sistema operativo
@android:drawable/ic_menu_save y @android:drawable/ic_menu_add para
establecer el icono de los botones. Puedes encontrar todas las imgenes disponi-
bles por defecto en Android en tu SDK de Android en el directorio (calidad media):
path_sdk_Android\platforms\android-17\data\res\drawable-mdpi
Una vez definido el men mediante su correspondiente fichero XML, hay que asociarlo a la
Actividad principal utilizando el tpico mtodo OnCreateOptionsMenu(). Para ello, de igual
forma que se hace con un men, vamos a inflarlo llamando al mtodo inflate() e indicando el
ID del fichero XML donde se ha definido dicho men:
public class MainActivity extends Activity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflamos el men -> Infla la barra de accin.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
Si ejecutas la aplicacin, puedes ver que, dependiendo del tamao de pantalla del AVD, aparecen
diferentes opciones en la barra de accin, por ejemplo, en el modo vertical slo se visualiza:
En este caso hemos ejecutado el proyecto en un AVD de 800 dpi de resolucin de pantalla
y vemos que aparecen como botones de accin las dos opciones que hemos marcado como
showAsAction=ifRoom, pero no aparece el texto de la segunda opcin ni el men extendido
(overflow) ya que no hay espacio disponible con esta pantalla en vertical. Tampoco se visualiza
el ttulo completo de la aplicacin. Sin embargo, si rotamos la pantalla del emulador al modo
horizontal (pulsando la combinacin de teclado [Ctrl + F12]) vemos lo siguiente:
@Override
// En funcin de la opcin seleccionada mostramos un Toast en la
// aplicacin
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_nuevo:
Toast.makeText(getApplicationContext(),
Has pulsado en accin Nuevo,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_guardar:
Toast.makeText(getApplicationContext(),
Has pulsado en accin Guardar,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_opciones:
Toast.makeText(getApplicationContext(),
Has pulsado en accin Opciones,
Toast.LENGTH_SHORT).show();
return true;
U2 Multimedia y Grficos en Android
default:
return super.onOptionsItemSelected(item);
}
}
Hasta ahora hemos estudiado cmo introducir una Barra de accin en las
aplicaciones de Android. Pero, adems, Android permite que el programador
pueda aadir pestaas a la citada barra de forma sencilla. Es ms, el
interior de estas pestaas pueden ser fragmentos. El hecho de integrar
pestaas en la misma barra presenta una ventaja adicional ya que Android
va a adaptar automticamente la interfaz del usuario a los distintos
tamaos y configuraciones de pantalla mejorando la visualizacin de la
aplicacin, sobre todo en los dispositivos de pantalla grande.
Por ejemplo, si Android detecta que hay suficiente espacio disponible en la Barra de
accin, integrar las pestaas dentro de la propia barra de forma que no ocupan espacio extra
en una fila inferior. Por el contrario, si no hubiera espacio suficiente situara las pestaas debajo
de la barra.
Lo primero que vamos a hacer es crear un nuevo fragmento por cada pestaa que tenga la apli-
cacin. Lo usual en las aplicaciones con pestaas es que cada una de ellas contenga diferente
funcionalidad. En este ejemplo hemos definido dos pestaas que cargan dos fragmentos. En
207
este apartado ya hemos visto cmo se implementan los fragmentos: con un fichero layout xml
y su clase Java asociada:
Pestaa 1:
- Tab1Fragmento.java
- fragmento1.xml
Pestaa 2:
- Tab2Fragmento.java
- fragmento2.xml
Por motivos pedaggicos, la interfaz de los fragmentos ser minimalista y contendrn nica-
mente la etiqueta de texto Pestaa 1 o Pestaa 2 para poder detectar el cambio de pestaa.
Por ejemplo, para la pestaa 1 el fichero fragmento1.xml contiene:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TextView
android:id=@+id/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/tab_1 />
</LinearLayout>
Aula Mentor
Hemos definido la pestaa 2 de forma similar. Desde Eclipse ADT puedes abrir el proyecto y
observar su contenido.
As, ya disponemos del contenido de las pestaas y vamos a aadirlas al Ejemplo del
curso y enlazarlas en la barra de accin asignndoles un listener desde el que se responde
a los eventos que se produzcan.
Veamos el contenido del listener que incluye el cdigo que gestiona los eventos cl-
sicos de pestaascomo la seleccin, reseleccin y deseleccin.
Para esto, hemos definido la nueva clase MiTabListener que se extiende de Ac-
tionBar.TabListener en la que reescribimos los mtodos de sus eventos onTabSelec-
ted(), onTabUnselected() y onTabReselected(). Veamos su cdigo fuente:
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// Mostramos un mensaje
Toast.makeText(actividad.getApplicationContext(),
Pestaa + tab.getText() + sin
seleccionar,Toast.LENGTH_SHORT).show();
// Quitamos el fragmento del contenedor.
ft.remove(fragment);
}
}
Estos mtodos lo nico que hacen es mostrar u ocultar los fragmentos en funcin de la pestaa
seleccionada por el usuario. As, en el evento onTabSelected() se reemplaza el fragmento
que est visible en la Actividad principal por el de la pestaa seleccionada. Por el contrario,
en el mtodo onTabUnselected() se oculta el fragmento asociado a la pestaa que pierde el
foco.
Ambos eventos utilizan el parmetro del tipo FragmentTransaction que permite ges-
tionar los fragmentos de la actividad. En el primer caso, invoca su mtodo replace() y, en el
segundo, su mtodo remove(). Hemos aadido adems un mensaje para mostrar al usuario
que se ha realizado un cambio sobre las pestaas.
Una vez implementado este listener, en el evento onCreate() de la Actividad principal
de la aplicacin, tenemos que crear las pestaas, asociar sus respectivos fragmentos y engan- 209
charlas a la barra de accin.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar.Tab tab2 =
barra.newTab().setText(getString(R.string.tab_2));
Aula Mentor
En el cdigo anterior podemos ver que hemos obtenido una referencia a la barra de accin
mediante el mtodo getActionBar() y establecemos el mtodo de navegacin a NAVIGA-
TION_MODE_TABS para que se muestren las pestaas.
El mtodo setNavigationMode() puede tomar los siguientes valores que establecen el tipo
de navegacin en la barra de accin:
- NAVIGATION_MODE_STANDARD: muestra la barra con un ttulo y un icono clsicos.
- NAVIGATION_MODE_LIST: las pestaas se integran en el ttulo de la barra mostrando un lis-
tado desplegable en ste.
- NAVIGATION_MODE_TABS: las pestaas se integran en la barra.
A continuacin, creamos las pestaas con el mtodo newTab() de la barra y, en la misma sen-
210 tencia, determinamos su texto con setText(). Despus, instanciamos los dos fragmentos y los
asociamos a cada pestaa utilizando el listener setTabListener(). Para acabar, aadimos las
pestaas a la barra de accin mediante el mtodo addTab().
Como podemos observar, Android ha colocado las pestaas debajo de la barra de accin por-
que no hay suficiente espacio disponible. Si cambiamos el emulador a la orientacin horizontal
[Ctrl+F12], vemos que las pestaas ya aparecen integradas en la barra:
Fjate en el espacio que queda libre para definir el resto de la interfaz optimizando al usuario
la comodidad y usabilidad de la aplicacin final.
211
Si ejecutas el proyecto en Eclipse ADT y cambias varias veces de pestaa vers que se
cambia de fragmento y se muestran los mensajes correspondientes.
Como puedes ver, el conjunto de opciones seleccionables estn distribuidas de forma tabular
o, dicho de otra forma, divididas en filas y columnas como una matriz. Dada la naturaleza de la
Vista, sus propiedades ms importantes son las tpicas de cualquier listado:
- android:numColumns: indica el nmero de columnas de la tabla. Tambin podemos indi-
car auto_fit si deseamos que el propio sistema operativo las establezca a partir de ciertas
propiedades.
212
- android:columnWidth: marca el ancho de las columnas de la tabla.
- android:horizontalSpacing: establece el espacio horizontal entre las celdas.
- android:verticalSpacing: indica el espacio vertical entre las celdas.
- android:stretchMode: marca qu hacer con el espacio horizontal sobrante. Si se establece
el valor columnWidth, este espacio ser dividido a partes iguales por las columnas de la
tabla. Por el contrario, si se establece a spacingWidth ser dividido a partes iguales por los
espacios entre las celdas.
Veamos cmo definiramos un GridView en la aplicacin del Ejemplo 7 del curso en el archivo
activity_main.xml:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=match_parent
android:layout_height=match_parent >
<TextView
android:id=@+id/labelMensaje
android:layout_width=match_parent
android:layout_height=wrap_content
android:text=@string/selecciona_una_opcion />
<GridView
android:id=@+id/gridOpciones
android:layout_width=match_parent
android:layout_height=match_parent
android:columnWidth=150dp
android:horizontalSpacing=1dp
U2 Multimedia y Grficos en Android
android:numColumns=auto_fit
android:stretchMode=columnWidth
android:verticalSpacing=5dp />
</LinearLayout>
Una vez est definida la interfaz de usuario, la forma de asignarle sus opciones es exactamente
la misma que en otro tipo de listados.
Por motivos pedaggicos, vamos a utilizar una matriz simple como adaptador utilizando
la clase ArrayAdaptery un layout genrico (simple_list_item_1, compuesto por un simple
TextView). Asociamos el adaptador al control GridView mediante su mtodo ya conocido
setAdapter(). Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 7
veremos:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Por defecto, las opciones de la matriz se aaden al GridView ordenados por filas y, si no caben
todas en la pantalla, se activa el desplazamiento (scroll) sobre la lista.
En cuanto a los eventos disponibles, el ms frecuente es el lanzado cuando un usuario
selecciona una opcin determinada de la lista: onItemClick. Este evento se captura de igual
forma que con las lista Spinner y ListView.
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 7, vers que se muestra la siguiente lista con los
meses del ao:
214
Como has visto, hemos mostrado el uso bsico del listado GridView. El alumno o alumna pue-
de aplicar por su cuenta, de forma prcticamente directa, todo lo comentado sobre listas en el
curso de Iniciacin de Android de Mentor.
Entre los conocimientos que ya debe tener podemos citar la personalizacin de las op-
ciones para presentar datos complejos, la creacin de un adaptador personalizado y las distintas
optimizaciones para mejorar el rendimiento de la aplicacin, como la reutilizacin de las Vistas
de las opciones.
Animamos al alumno a que pruebe con todas estas opciones realizando una aplicacin
propia.
U2 Multimedia y Grficos en Android
Switch es una Vista del tipo botn compuesto que muestra un interruptor que permite al
usuario indicar si una opcin est activa o inactiva.
Como una imagen vale ms que mil palabras, veamos el aspecto de este tipo de Vista
activado y desactivado:
Como puedes ver, se trata del clsico interruptor donde el usuario puede activar o desactivar
una opcin con un desplazamiento sobre ste.
Veamos cmo utilizamos varios Switch en la aplicacin del Ejemplo 8 del curso en el archivo
215
activity_main.xml:
<ScrollView xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent>
<LinearLayout
android:orientation=vertical
android:layout_width=match_parent
android:layout_height=wrap_content>
<Switch android:text=@string/interruptor_por_defecto
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:text=@string/interruptor_encendido
android:checked=true
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:id=@+id/switch_gestionado
android:text=@string/interruptor_gestionado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch
android:text=@string/interruptor_con_texto_personalizado
Aula Mentor
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textOn=Encendido
android:textOff=Apagado
android:layout_marginBottom=32dip />
</LinearLayout>
</ScrollView>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el switch gestionado para definir el evento
// onCheckedChange
Switch s = (Switch) findViewById(R.id.switch_gestionado);
if (s != null) {
s.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
@Override
// Evento que ocurre cuando el usuario enciende o
// apaga un interruptor
public void onCheckedChanged(CompoundButton
buttonView, boolean isChecked) {
216 // Mostramos un sencillo mensaje
Toast.makeText(MainActivity.this, El
interruptor est + (isChecked ?
ENCENDIDO! : APAGADO!),
Toast.LENGTH_SHORT).show();
}
});
} //end if not null
} // end onCreate
}
En el cdigo fuente anterior puedes observar que es fcil definir el evento onCheckedChanged
que se lanza cuando el usuario activa o desactiva un interruptor.
Es sencillo utilizar el mtodo setCheck() para indicar si el interruptor est o no activa-
do. Adems, entre otros, tambin dispone del mtodo isCheck() para conocer el estado actual
del interruptor. En la ayuda oficial de Android puedes encontrar otros mtodos interesantes.
Si ejecutas en Eclipse ADT este Ejemplo 8, vers que se muestra la siguiente aplicacin con
interruptores:
U2 Multimedia y Grficos en Android
Vamos a partir del Ejemplo 5 de esta Unidad, para desarrollar este ejemplo. Para
mejorarlo lo hemos modificado utilizando la biblioteca de compatibilidad y, as,
poder ejecutarlo desde la versin 2.2 de Android.
Veamos cmo empleamos este elemento visual en la aplicacin del Ejemplo 9 del curso en el
218 archivo activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/drawer_layout
android:layout_width=match_parent
android:layout_height=match_parent >
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la Interfaz usuario
drawerLayout = (DrawerLayout)
findViewById(R.id.drawer_layout);
// Listado con las opciones del men lateral
drawerList = (ListView) findViewById(R.id.menu_lateral);
219
// Definimos las opciones del men lateral
final String[] opcionesMenu = new String[] {Opcin 1,
Opcin 2};
// Hacemos los cambios de fragmento en la interfaz
// del usuario
FragmentManager fragmentManager =
getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_contenido, fragmento)
.commit();
drawerList.setItemChecked(position, true);
// Obtenemos el ttulo de la opcin seleccionada y
// lo ponemos como ttulo de la aplicacin
tituloMenuLateral = opcionesMenu[position];
getSupportActionBar().setTitle(tituloMenuLateral);
// Cerramos el men lateral
drawerLayout.closeDrawer(drawerList);
}
}); // end onClick
// Inicializamos las variables de ttulos con el ttulo
// inicial de la aplicacin
tituloMenuLateral = getTitle();
tituloAplicacion = getTitle();
// Definimos el icono de apertura del men lateral
drawerToggle = new ActionBarDrawerToggle(this,
drawerLayout,
220 R.drawable.ic_navigation_drawer,
R.string.drawer_open,
R.string.drawer_close)
{
// Evento que ocurre cuando se cierra el men
// lateral
public void onDrawerClosed(View view) {
// Cambiamos el ttulo de la cabecera a
// la opcin elegida del men
getSupportActionBar().setTitle(
tituloMenuLateral);
// Ejecutamos el evento onPrepareOptionsMenu()
//para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(
MainActivity.this);
}
// Evento que ocurre cuando se abre el men
// lateral
public void onDrawerOpened(View drawerView) {
// Cambiamos el ttulo de la cabecera al
// de la aplicacin
getSupportActionBar().setTitle(
tituloAplicacion);
// Ejecutamos el evento onPrepareOptionsMenu()
// para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(
MainActivity.this);
}
};
U2 Multimedia y Grficos en Android
// Definimos el listener del men lateral
drawerLayout.setDrawerListener(drawerToggle);
// Indicamos que el icono de la aplicacin es activo
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} // end onCreate()
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflamos el men -> Infla la barra de accin.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
// En funcin de la opcin seleccionada mostramos un Toast en la
// aplicacin
public boolean onOptionsItemSelected(MenuItem item) {
// Si el usuario hace clic en el icono de la barra
// mostramos el men lateral
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
else // Si no, mostramos el onClick de la opcin del men
// correspondiente
switch (item.getItemId()) {
case R.id.menu_nuevo:
Toast.makeText(this, 221
Has pulsado en accin Nuevo,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_guardar:
Toast.makeText(this,
Has pulsado en accin Guardar,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_buscar:
Toast.makeText(this,
Has pulsado en accin Buscar,
Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
} // end onOptionsItemSelected
// Evento que se lanza antes de mostrar el men
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Vemos si el men lateral est abierto
boolean menuAbierto = drawerLayout.isDrawerOpen(
drawerList);
// Si est abierto, ocultamos la opcin del men buscar
if(menuAbierto)
menu.findItem(R.id.menu_buscar).setVisible(false);
else // Si no, la mostramos
Aula Mentor
menu.findItem(R.id.menu_buscar).setVisible(true);
return super.onPrepareOptionsMenu(menu);
}
En el cdigo anterior, dentro del mtodo onCreate() de la actividad, buscamos las Vistas de
la Interfaz de usuario y definimos las opciones del men lateral en un matriz de tipo String.
Adems, creamos el adaptador del listado con las opciones del men lateral pasando
como parmetro a este adaptador el contexto obtenido invocando el mtodo getThemedCon-
222
text() de la action bar. Hemos utilizando getSupportActionBar() para acceder a la barra
de accin en lugar del habitual getActionBar() ya que estamos utilizando la biblioteca de
compatibilidad.
Como el layout de los elementos de la lista hemos establecido el estndar
android.R.layout.simple_list_item_1. De esta forma, el listado ser compatible con la
mayora de versiones de Android. Sin embargo, este estilo hace que la opcin seleccionada no
est resaltada en el men cada vez que se cierre y se vuelva a abrir. Para hacer esto, en Android
4, podemos aplicar el layout android.R.layout.simple_list_item_activated_1, si bien
aparecer un error si se ejecuta la aplicacin en versiones anteriores de Android.
Si deseamos evitar este problema de compatibilidad tenemos varias opciones:
Aplicar un layout diferente dependiendo de la versin de Android en la que se ejecuta la
aplicacin:
drawerList.setAdapter(
new ArrayAdapter<String>(getSupportActionBar().getThemedContext(),
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?
android.R.layout.simple_list_item_activated_1 :
android.R.layout.simple_list_item_1, opcionesMenu));
Para poder utilizar la biblioteca de compatibilidad es necesario importarla. Para ello, hacemos
clic en la opcin File->Import de Eclipse ADT y buscamos el directorio adt-bundle-
windows-x86\sdk\extras\android\support\v7\appcompat del SDK de Android:
224
225
Para que el tema visual de la aplicacin sea compatible en todas las versiones de Android,
debemos sustituirlo o extenderlo aplicando un tema definido especficamente en la biblioteca
de compatibilidad:
Theme.AppCompat
Theme.AppCompat.Light
Theme.AppCompat.Light.DarkActionBar
<application
android:allowBackup=true
android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/Theme.AppCompat.Light.DarkActionBar >
226
Hay que tener tambin en cuenta que, en versiones anteriores a Android 3.0, no existen los
atributos de los mens utilizados por la funcionalidad de la barra de accin como, por ejemplo,
el atributo android:showAsAction y, por lo tanto, no podemos utilizarlos directamente.
Para solucionar este problema, vamos a utilizar dichos atributos indicando un espacio
de nombres personalizado que definimos en el elemento <menu> y usamos en los elementos
<item>. Veamos el aspecto del archivo unidad2.eje9.navigation_drawer\res\menu\ac-
tivity_main.xml:
<menu xmlns:android=http://schemas.android.com/apk/res/android
xmlns:aplicacion=http://schemas.android.com/apk/res-auto>
<item
android:id=@+id/menu_guardar
aplicacion:showAsAction=ifRoom
android:icon=@android:drawable/ic_menu_save
android:title=@string/menu_guardar/>
<item
android:id=@+id/menu_nuevo
aplicacion:showAsAction=always
android:icon=@android:drawable/ic_menu_add
android:title=@string/menu_nuevo/>
<item
android:id=@+id/menu_buscar
U2 Multimedia y Grficos en Android
aplicacion:showAsAction=always
android:icon=@drawable/ic_action_search
android:title=@string/menu_buscar/>
</menu>
Si ejecutas en Eclipse ADT este Ejemplo 9, vers que se muestra la siguiente aplicacin:
227
Si ejecutas en un AVD de Android 2.x puedes ver que la aplicacin funciona correctamente y el
aspecto es muy similar:
Aula Mentor
8. Resumen
- Para crear la interfaz visual de usuario de los Widgets debemos utilizar la clase
RemoteViews.
Aula Mentor
1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan los sensores y dispositivos de Android en una
aplicacin. Primero, haremos una introduccin a los sensores; despus, veremos cmo se usan
conjuntamente con el Simulador de sensores.
Adems, mostraremos mediante ejemplos la integracin de los dispositivos WIFI, Blue-
tooth, GPS y Cmara de fotos en una aplicacin Android.
Finalmente, veremos cmo aplicar sensores a un juego sencillo.
En los dispositivos Android existe otro tipo de elementos que, si bien no pueden considerarse
estrictamente sensores en realidad, son componentes de hardware que contienen sensores
que se usan con un propsito muy definido. Por ejemplo, un dispositivo WIFI puede usarse
para conectase a una red WIFI (o para medir la potencia de sta). Entre ellos, encontramos:
dispositivos GPS, WIFI, Bluetooth y Cmara de fotos. Este tipo de componentes los estudiaremos
de forma separada un poco ms adelante en esta Unidad.
Aula Mentor
Para acceder a los sensores disponibles en un dispositivo y obtener sus datos, Android
proporciona varias clases e interfaces que realizan una amplia variedad de tareas relacionados
con los sensores. Por ejemplo, se pueden utilizar para:
- Obtener los sensores disponibles en el dispositivo.
- Conseguir las capacidades e informacin de un sensor en particular, como el rango de me-
dida, el fabricante, la potencia y resolucin de la medida.
- Recibir los datos de las magnitudes medidas de los sensores definiendo un intervalo de ac-
tualizacin de las medidas.
- Registrar y quitar los listeners asociados a la monitorizacin y medida que realizan los
sensores.
En este apartado vamos a estudiar cmo se usan los sensores de Android. Todos los sensores se
manipulan de forma homognea y con ellos podremos implementar mejorar la interaccin del
dispositivo con el usuario.
No todos los dispositivos disponen de los mismos sensores. Cada modelo y fabri-
cante incluye los que considera apropiados. Adems, para gestionar estos senso-
res el dispositivo emplea drivers que el fabricante no suele hacer pblicos.
Desde el punto de vista del desarrollador, las clases Java que debemos usar son pocas y
sencillas. Esto es as porque, para que el dispositivo Android gestione sensores, debe utilizar
drivers cuyo cdigo fuente no suele hacer pblico su fabricante. Por esto, Android tiene
tanto xito entre los fabricantes de dispositivos, ya que stos no tienen que publicar
el cdigo fuente de los drivers que, en realidad, muestra cmo funciona su hardware.
La clase Sensor contiene la informacin y propiedades completas de un sensor. Para
obtener el tipo de sensor que contiene un objeto de esta clase, debemos usar su mtodo getTy-
pe() que devuelve 11 tipos de sensores mediante alguna de las siguientes constantes:
U3 Sensores y dispositivos de Android
Desde
Tipo / CONSTANTE Descripcin Dimensiones
API
presin atmosfrica
Altmetro y barmetro 1 3
TYPE_PRESSURE
Evita sobrecalentamientos del
temperatura interna
dispositivo. 1 3 233
TYPE_TEMPERATURE
(Obsoleto desde API 14)
gravedad Mide la aceleracin debida a la
3 9
TYPE_GRAVITY gravedad.
acelermetro lineal Mide aceleraciones sin tener en
3 9
TYPE_LINEAR_ACCELERATION cuenta la gravedad.
vector de rotacin
Detecta giros 3 9
TYPE_ROTATION_VECTOR
temperatura ambiental
Mide la temperatura del aire. 1 14
TYPE_AMBIENT_TEMPERATURE
humedad relativa Mide la humedad absoluta y
1 14
TYPE_RELATIVE_HUMIDITY relativa.
Esta lista anterior se va ampliando con las nuevas versiones de Android, si bien los sensores
disponibles varan mucho en funcin del dispositivo utilizado.
Adems, podemos dividir los sensores en dos categoras ms: real y virtual. Real se
refiere a que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de la combinacin de las medidas de otros sensores o son medidas
relativas.
Aula Mentor
Puedes observar que los sensores TEMPERATURE y ORIENTATION son obsoletos. El primero se ha
sustituido por el sensor AMBIENT_TEMP. El segundo se quitar de Android porque normalmente
no hay un sensor de orientacin en los dispositivos. Para conocer esta orientacin, el dispositivo
combina los datos de los sensores acelermetro y del campo magntico.
Para obtener la orientacin del dispositivo puedes utilizar el mtodo android.view.Display.
getRotation() de Android.
Para acceder a los sensores de un dispositivo Android proporciona el gestor Sensor-
Manager. Para obtener el listado completo de los sensores del dispositivo, debemos usar su
mtodo getSensorList(tipo) que devuelve una lista de tipo Sensor. En el parmetro tipo
234
hay que marcar el tipo de sensores que queremos obtener mediante uno de los tipos del listado
anterior o Sensor.TYPE_ALL para indicar todos los sensores.
Sin embargo, en el apartado siguiente veremos cmo instalar un software de emulacin que
permitir realizar pruebas sobre algunos sensores simulados del emulador.
Como los sensores estn muy relacionados con el mundo real, la mejor forma de entender su
funcionamiento es mediante un ejemplo prctico.
Vamos a comenzar con el Ejemplo 1 de este Unidad donde vamos a obtener todos los
sensores de un dispositivo y mostraremos los datos medidos por stos.
Si abrimos su layout XML activity_main.xml vemos el siguiente contenido tpico del layout
de una Actividad con etiquetas:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=LISTADO DE SENSORES
android:textAppearance=?android:attr/textAppearanceLarge
android:textColor=@color/color_fuente
android:typeface=monospace />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<TextView
android:id=@+id/sensores
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=
android:textColor=@color/color_fuente />
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content 235
android:text=DATOS
android:textStyle=bold
android:layout_gravity=center_horizontal
android:typeface=monospace
android:textColor=@color/color_fuente />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<ScrollView
android:id=@+id/scrollView1
android:layout_width=match_parent
android:layout_height=wrap_content
android:fadeScrollbars=false >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TextView
android:id=@+id/datos
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Datos />
</LinearLayout>
Aula Mentor
</ScrollView>
</LinearLayout>
En el fichero anterior puedes ver que hemos definido la Vista ScrollView con el atributo
android:fadeScrollbars=false para que no se oculte la barra de desplazamiento ver-
tical.
Si ahora abres el archivo Java que define la Actividad, vers el siguiente contenido:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensores = (TextView) findViewById(R.id.sensores);
datos = (TextView) findViewById(R.id.datos);
// Conectamos con el gestor de sensores
SensorManager sensorManager =
(SensorManager)getSystemService(SENSOR_SERVICE);
// Obtenemos el listado con todos los sensores
List<Sensor> listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ALL);
236 // Mostramos en pantalla el listado de sensores
for(Sensor sensor: listaSensores) {
log(sensor.getName(), sensores);
} // end for
// A continuacin, vamos a buscar los sensores de varios tipos en
// concreto y le asignamos al primero que encontremos a un
// listener para leer sus medidas
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ROTATION_VECTOR);
if (!listaSensores.isEmpty()) {
Sensor orientationSensor = listaSensores.get(0);
// Registramos el lister para este sensor e indicamos que
// se realice una medida cada SENSOR_DELAY_UI=1000 miliseg.
sensorManager.registerListener(this, orientationSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (!listaSensores.isEmpty()) {
Sensor acelerometerSensor = listaSensores.get(0);
sensorManager.registerListener(this, acelerometerSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
if (!listaSensores.isEmpty()) {
Sensor magneticSensor = listaSensores.get(0);
sensorManager.registerListener(this, magneticSensor,
U3 Sensores y dispositivos de Android
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_AMBIENT_TEMPERATURE);
if (!listaSensores.isEmpty()) {
Sensor temperatureSensor = listaSensores.get(0);
sensorManager.registerListener(this, temperatureSensor,
SensorManager.SENSOR_DELAY_UI);
}
} // end onCreate
// Mtodo que aade a la vista un texto
private void log(String string, TextView vista) {
vista.append(string + \n);
}
// Invocada cuando cambia se exactitud del sensor.
// Por ejemplo, cuando la deteccin del GPS pasa de hacerse
// de la red de telefona mvil al sensor GPS del dispositivo (ms
// preciso)
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
// Evento que se lanza cada vez que se modifican los datos del
// sensor. Es decir, es una nueva medida.
@Override
public void onSensorChanged(SensorEvent evento) { 237
String sensorStr=Desconocido;
// Como estamos monitorizando todos los sensores, es necesario
// controlar el acceso a las Vistas internas de la Actividad.
// Es decir, cada sensor puede provocar que un thread principal
// invoque a la vez este evento. Si sincronizamos esas
// sentencias, entonces indicamos mediante Java que se deben
// ejecutar todas ellas antes de que se pueda volver a ejecutar
// de nuevo este bloque de cdigo.
synchronized (this) {
switch(evento.sensor.getType()) {
case Sensor.TYPE_ROTATION_VECTOR:
sensorStr=Rotacin;
break;
case Sensor.TYPE_ACCELEROMETER:
sensorStr=Acelermetro;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
sensorStr=Campo magntico;
break;
case Sensor.TYPE_AMBIENT_TEMPERATURE:
sensorStr=Temperatura;
break;
default:
} // end case
for (int i=0 ; i<evento.values.length ; i++) {
log(sensorStr + + i + : + evento.values[i], datos);
} // end for
} // end synchronized
Aula Mentor
} // end onSensorChanged
}
Como hemos visto, la clase Sensor permite manipular sensores. A continuacin, se listan los
mtodos pblicos de esta clase Sensor:
Para que nuestra clase implemente la interface que hemos comentado, la clase principal
incluye el cdigo:
implements SensorEventListener
U3 Sensores y dispositivos de Android
Todo programador en Java sabe que cuando usamos una interface estamos obligados a
implementar todos sus mtodos. En este caso, la interfaz SensorEventListener que se usa
para recibir las medidas de los sensores tiene dos mtodos que debemos implementar:
- onAccuracyChanged: invocada cuando cambia la exactitud de la medida del sensor. Por
ejemplo, cuando el modo la deteccin de localizacin geogrfica cambia desde la red de
telefona mvil al sensor GPS del dispositivo (ms preciso).
- onSensorChanged: se lanza cuando un sensor indica su medida. Android indica en su pa-
rmetro de la clase SensorEvent la informacin del sensor que lo ha causado y podemos
leer los datos medidos por ste.
Si ejecutas en Eclipse ADT este Ejemplo 1, vers que se muestra la siguiente aplicacin:
240
Descargamos la ltima versin del simulador desde este enlace. La ltima versin a fecha de
redaccin de este documento es la SensorSimulator 2.0-rc1.
- Descomprimimos el archivo descargado en una carpeta. Recomendamos utilizar el directorio
donde se encuentre el android-sdk.
- Iniciamos el Simulador haciendo clic sobre el archivo sensorsimulator-2.0-rc1.jar del
subdirectorio bin o ejecutando la orden en la ventana de comandos:
java -jar sensorsimulator-2.0-rc1.jar/bin/sensorsimulator-2.0-rc1.jar
241
Quick Settings: podemos establecer las propiedades iniciales de los sensores bsicos.
Sensors Parameters: podemos establecer las propiedades iniciales del resto de senso-
242
res:
U3 Sensores y dispositivos de Android
El botn derecho que aparece en la ventana principal nos permite acceder a las siguientes
opciones adicionales:
Generalmente, no es necesario modificar ninguno de estos parmetros ya que los valores por
defecto funcionan correctamente.
Lo primero que debemos hacer antes de poder simular datos de sensores en un dispositivo
virtual de Android es instalar la aplicacin del SensorSimulator en l.
Para instalar la aplicacin en el AVD. primero debemos ejecutar desde Eclipse ADT el
dispositivo virtual del curso.
Una vez haya arrancado correctamente, usamos la utilidad adb que se encuentra en el
directorio android-sdk/platform-tools/adb. Debemos abrir una ventana de comandos en
este directorio y obtener los dispositivos conectados ejecutando el comando:
C:\cursosMentor\adt\sdk \platform-tools>adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached
emulator-5554 device
C:\cursosMentor\adt\sdk\platform-tools>
244
U3 Sensores y dispositivos de Android
Debemos dejar los campos IP y Socket como estn. Esta IP debe coincidir con la IP que aparece
en el lado izquierdo (parte de abajo) en el SensorSimulator:
Cambiamos en la aplicacin del AVD a la pestaa Testing y hacemos clic en el botn Connect:
245
Aula Mentor
En la ventana anterior podemos ver los datos de los sensores que tenemos habilitados (color
azul ms oscuro) en el Simulador de sensores:
246
Podemos balancear (yaw), girar (roll) y mover (move) el dispositivo arrastrando el ratn sobre
el esquema del dispositivo virtual.
La aplicacin Sensor Simulator Settings se usa de puente entre el AVD y el SensorSi-
mulator. De esta forma, se comunica la informacin del cambio de los valores de los sensores
que luego usar nuestra aplicacin Android.
Una vez hemos instalado y comprobado que funciona el simulador de sensores, veamos cmo
podemos utilizarlo en el desarrollo de una aplicacin.
Vamos a partir del Ejemplo 1 de esta Unidad y modificaremos su cdigo fuente para
que sea compatible con este simulador. Lo primero que debemos hacer es crear el directo-
rio libs dentro del directorio raz del proyecto y copiar dentro el archivo sensorsimula-
tor-2.0-rc1/lib/sensorsimulator-lib-x.x.x.jar. Despus, desde Eclipse ADT hace-
mos clic con el botn derecho del ratn sobre este archivo y seleccionamos la opcin Build
Path y Add to Build Path:
247
Una vez hecho esto, debemos abrir las propiedades del proyecto y en la opciones Java Build
Path en la pestaa Order and Export hay que subir la librera a la primera posicin (para
que las clases de esta librera tengan prioridad frente a las de Android) y marcarla como expor-
table, tal y como vemos en la siguiente pantalla:
Aula Mentor
A continuacin, vamos a modificar el cdigo fuente del Ejemplo 1 de esta Unidad copindolo
previamente como Ejemplo 2. A continuacin, abrimos el Ejemplo 2 para ver cmo queda el
248 cdigo fuente una vez modificado. Empezamos comentando los imports originales y agregan-
do los de las clases del paquete org.openintents.sensorsimulator:
/*import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;*/
import org.openintents.sensorsimulator.hardware.Sensor;
import org.openintents.sensorsimulator.hardware.SensorEvent;
import org.openintents.sensorsimulator.hardware.SensorEventListener;
import org.openintents.sensorsimulator.hardware.SensorManagerSimulator;
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
por la sentencia:
sensorManager.connectSimulator();
Cuando ejecutemos la aplicacin en el AVD, veremos en la Vista Logcat de Eclipse ADT los siguientes
mensajes debidos a la ejecucin de la sentencia anterior:
U3 Sensores y dispositivos de Android
Normalmente, debemos hacer el registro y desregistro de los listeners en los eventos onRe-
sume() y onStop() respectivamente. Para hacerlo, hemos escrito:
@Override
protected void onResume() {
super.onResume();
// Registramos todos los listener de los sensores
sensorManager.registerListener(eventListenerAcelerometro,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerAcceleracionLineal,
sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerGravedad,
sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY), 249
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerCampoMagnetico,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerOrientacion,
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerTemperatura,
sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerLuz,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerPresion,
sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerBarcode,
sensorManager.getDefaultSensor(Sensor.TYPE_BARCODE_READER),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerRotacion,
sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(eventListenerGiroscopio,
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManagerSimulator.SENSOR_DELAY_FASTEST);
}
Aula Mentor
@Override
protected void onStop() {
// Desregistramos todos los listeners de los sensores
sensorManager.unregisterListener(eventListenerAcelerometro);
sensorManager.unregisterListener(eventListenerAcceleracionLineal);
sensorManager.unregisterListener(eventListenerGravedad);
sensorManager.unregisterListener(eventListenerCampoMagnetico);
sensorManager.unregisterListener(eventListenerOrientacion);
sensorManager.unregisterListener(eventListenerTemperatura);
sensorManager.unregisterListener(eventListenerLuz);
sensorManager.unregisterListener(eventListenerPresion);
sensorManager.unregisterListener(eventListenerRotacion);
sensorManager.unregisterListener(eventListenerGiroscopio);
sensorManager.unregisterListener(eventListenerBarcode);
super.onStop();
}
Adems, para que esta aplicacin pueda comunicarse con el Simulador de sensores, hay que
agregar al archivo Manifest el siguiente permiso:
<uses-permission android:name=android.permission.INTERNET/>
Una vez hayamos terminado y depurado la aplicacin hay que volver a modificar
los imports y usar la clases de sensores nativas de Android, ya que, si no lo hace-
mos, la aplicacin fallar en un dispositivo real.
U3 Sensores y dispositivos de Android
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Listado Sensores- Sen-
sorSimulator) de la Unidad 3. Estudia el cdigo fuente y ejectalo para mostrar en
el AVD el resultado del programa anterior, en el que hemos utilizado un Simulador
de Sensores.
Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin:
251
En esta Unidad puedes encontrar el vdeo Cmo utilizar Sensor-
Simulator, que muestra de manera visual los pasos seguidos en
las explicaciones anteriores. Te recomendamos que lo muestres y
leas las explicaciones que aparecen.
Como hemos comentado anteriormente, es posible utilizar un dispositivo real para guardar los
datos de sus sensores y, despus, poder hacer una simulacin con ellos. As, el programador
puede automatizar esta tarea y repetirla tantas veces como sea necesaria cuando depuremos
una aplicacin de Android.
Debes asegurarte de que el dispositivo Android se encuentra en la misma red que la aplica-
252 cin Java de tu ordenador (SensorSimulator). Lo ms sencillo es utilizar una red wifi. La IP
que escribas debe corresponder a la IP de tu ordenador. A veces, es conveniente tambin
deshabilitar el firewall del ordenador. Cuando hayamos, acabado debemos pulsar el botn
Stop.
- En la aplicacin externa del SensorSimulator seleccionar la pestaa Scenario Simulator.
En esta ventana veremos cmo se van cargando los valores de los sensores seleccionados:
U3 Sensores y dispositivos de Android
Animamos al alumno a que pruebe a utilizar su propio dispositivo para grabar escenarios de
prueba de simulaciones.
4. Dispositivos de Android
En este apartado vamos a mostrar los fundamentos de utilizacin de los dispositivos o conectores
ms importantes de Android: el mdulo WIFI, el mdulo Bluetooth, la Cmara de fotos y el
mdulo GPS.
Mediante un ejemplo prctico expondremos, para cada tipo de mdulo, una explicacin
detallada de las funciones propias del SDK. As, podrs realizar cualquier aplicacin que haga
uso de ellos.
El SDK de Android dispone de clases que permiten hacer uso del mdulo WIFI, siempre y
cuando el dispositivo disponga de este mdulo.
Wi-Fi es una abreviatura de Wireless Fidelity (Fidelidad inalmbrica) y es un mecanismo
de conexin de dispositivos electrnicos de forma inalmbrica.
La clase ms importante de la API WIFI de Android se denomina WifiManager y permite al
desarrollador realizar todo tipo de operaciones sobre este mdulo: buscar redes WIFI, obtener
informacin de dichas redes y conectarse a ellas.
Podemos realizar una bsqueda de redes con la clase WifiManager a travs de su mtodo
startScan(), que devuelve una lista de objetos de la clase ScanResult que representan cada 253
una de las redes WIFI encontradas. La informacin que podemos obtener de estas redes WIFI
encontradas es la que se muestra a continuacin:
- SSID: el SSID (Service Set IDentifier) denomina el nombre de una red inalmbrica que apa-
rece en tu dispositivo cuando quieres conectarte a una red WIFI. Mediante la constante SSID
de la clase ScanResult podemos acceder a este nombre.
- Seguridad: la seguridad que implementa una red inalmbrica WIFI puede ser: WEP (Wired
Equivalent Privacy), WPA (WIFI Protected Access) o WPA2. Podemos acceder a ella a travs
del segundo parmetro del resultado de la bsqueda de redes, tal y como veremos en el
ejemplo que se explica a continuacin.
- Potencia de seal: la potencia de la seal que emite la red WIFI se mide en dB (decibelios).
Para obtener este valor debemos acceder el atributo pblico level de la clase ScanResult.
- BSSID: el BSSID (Basic Service Set Identifier) de una red WIFI es un nombre de identifica-
cin nico de todos los paquetes que se trasmiten por dicha red. Este identificador BSSID es
la direccin fsica MAC (Media Access Control) del router WIFI de la red. Para obtener este
valor debemos acceder el atributo pblico BSSID de la clase ScanResult.
- Canal de frecuencia: el canal de frecuencia indica en qu banda emite la red WIFI. Para
obtener este valor debemos acceder el atributo pblico frecuency de la clase ScanResult.
En el Ejemplo 3 de este Unidad vamos a mostrar cmo desarrollar una sencilla aplicacin en
Android que utiliza la clase WifiManager para buscar las redes WIFI y mostrar su nombre en un
listado. Adems, si el usuario pulsa sobre una red de la lista, aparece la descripcin detallada de
esta red ofrecindole sus datos: BSSID de la red, el canal de frecuencia, la potencia de la seal
emitida que le llega al dispositivo Android y el tipo de seguridad que implementa.
Es recomendable abrir el Ejemplo 3 de esta Unidad para entender la explicacin si-
guiente.
Aula Mentor
Para empezar hemos definido la clase Red que sirve de modelo de datos de una red WIFI. En
un objeto de este tipo guardamos los datos de una red WIFI: el SSID, la seguridad, la potencia
de seal, el BSSID y el canal de frecuencia de cada una. Veamos el sencillo aspecto de la clase:
Como puedes observar en el cdigo anterior, esta clase contiene todos los atributos y mtodos
necesarios para obtener la informacin mencionada de una red inalmbrica.
La interfaz de usuario de la aplicacin se compone de dos Actividades: la Actividad principal
contiene un botn que, al pulsarlo, realiza una bsqueda de redes WIFI y muestra el resultado
en un listado ListView en la parte inferior de esta Actividad. Si el usuario hace clic en una red
del listado, puede visualizar la informacin detallada de sta al abrir una segunda Actividad.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
U3 Sensores y dispositivos de Android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height= fill_parent
android:background=#FFFFFF>
<Button android:id=@+id/boton
android:text=Buscar redes WIFI...
android:layout_width=260dip
android:layout_height=60dip
android:textSize=20dp
android:layout_marginTop=6dip
android:layout_gravity=center_horizontal/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView
android:id=@+id/listView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:background=#FFFFFF/>
</LinearLayout>
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
MainActivity.java:
@SuppressWarnings(unused)
AsyncTask<?, ?, ?> task = new ProgressTask(this).execute();
break;
}
}
Como se puede observar en el cdigo anterior, el evento onClick del botn invoca a la clase
AsyncTask. Como sabes, esta clase se encarga de realizar tareas asncronas en Android Ya se
estudi en el curso de Iniciacin de Mentor.
Hemos empleado una tarea asncrona porque el buscador de redes WIFI puede tardar
en responder un tiempo y no es recomendable bloquear el hilo principal de ejecucin de la
aplicacin. En esta Actividad hemos definido el atributo redes con visibilidad static de forma
que pueda ser accedido por cualquier Actividad de la aplicacin. Este atributo es un ArrayList
U3 Sensores y dispositivos de Android
de objetos de clase Red que permite almacenar todas las redes inalmbricas encontradas en el
escner de redes WIFI.
// Clase que inicia una tarea asncrona para buscar las redes WIFI
public class ProgressTask extends AsyncTask<String, Void, Boolean> {
// Dilogo de progreso
private ProgressDialog dialog;
private Context context;
// Gestor de Android de redes WIFI
private WifiManager manWifi;
// Lista donde guardamos las redes encontradas
private List<ScanResult> wifiList;
// Constructor de la clase
public ProgressTask(Context c){
this.context = c;
dialog = new ProgressDialog(context);
}
En el cdigo anterior se pueden observar los tres estados por los que pasa la clase ProgressTask:
onPreExecute, doInBackground y onPostExecute. La bsqueda de redes se realiza en
258 segundo plano mediante el mtodo dolnBackground y, mientras dura la ejecucin de dicho
escner, se muestra al usuario una alerta del tipo Dialog que le informa de que se est
realizando una bsqueda de redes WIFI.
La bsqueda de redes se realiza mediante la clase WifiManager usando su mtodo
startScan(). Posteriormente, utilizamos el mtodo getScanResult() para obtener una lis-
ta de objetos del tipo ScanResult que contiene la informacin de cada red WIFI. Por ltimo,
recorremos dicha lista procesando cada objeto ScanResult y obteniendo los datos necesarios
para el presente ejemplo.
Una vez terminada la lgica del mtodo doInBackground, se finaliza la ejecucin de la
clase ProgressTask mediante el mtodo onPostExecute() liberando el Dialog y refrescando
los datos del adaptador de la Actividad principal.
Esta clase principal implementa la interface OnItemClickListener que indica las
sentencias que se deben ejecutar cuando el usuario hace clic en una opcin de la lista.
En este bloque de cdigo obtenemos la informacin de la red WIFI pulsada y la enviamos a otra
Actividad que se encarga de mostrar este detalle. Como sabes, el envo de datos o parmetros
entre Actividades se realiza a travs del mtodo putExtra() de la clase Intent.
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:background=#FFFFFF
android:gravity=left
android:orientation=vertical
android:padding=10dp >
U3 Sensores y dispositivos de Android
<TextView
android:id=@+id/SSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=25sp
android:textColor=#000000/>
<TextView
android:id=@+id/SeguridadTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Seguridad:
android:textColor=#000000/>
<TextView
android:id=@+id/BSSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=BSSID:
android:textColor=#000000/>
<TextView
android:id=@+id/FrecuenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Frecuencia:
android:textColor=#000000/>
<TextView 259
android:id=@+id/PotenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Potencia:
android:textColor=#000000/>
</LinearLayout>
El diseo de esta Actividad consta de cinco Vistas del tipo TextView que se encargan de
mostrar la informacin enviada por la Actividad principal de la aplicacin. Veamos la lgica de
esta Actividad secundaria llamada InfoRed:
// OnCreate de la Actividad
public void onCreate(Bundle savedlnstanceState){
super.onCreate(savedlnstanceState);
this.tv_frecuencia = (TextView)findViewById(R.id.FrecuenciaTV) ;
this.tv_potencia = (TextView)findViewById(R.id.PotenciaTV) ;
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (WIFI) de la Unidad 3. Es-
tudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado
del programa anterior, en el que hemos utilizado su interfaz WIFI.
Desgraciadamente, el AVD del SDK de Android no incluye la interfaz WIFI, ni siquiera mediante
el Simulador de Sensores. Por lo tanto, es necesario probar esta aplicacin en un dispositivo
real de Android.
U3 Sensores y dispositivos de Android
Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
261
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
El SDK de Android tambin ofrece soporte para la tecnologa Bluetooth que permite a un
dispositivo intercambiar datos de forma inalmbrica con otros dispositivos cercanos. Esta
funcionalidad permite realizar lo siguiente:
- Bsqueda de otros dispositivos Bluetooth.
- Consultar los dispositivos Bluetooth emparejados con el nuestro.
- Establecer canales de comunicacin RFCOMM (Radio Frecuency Communication) utilizando,
para simular, comunicaciones propias de puertos serie como los modem de datos.
- Conectar con otros dispositivos a travs del descubrimiento de servicios y transferir datos
entre ellos.
- Administrar conexiones mltiples.
Aula Mentor
Las clases ms importantes (no incluimos todas) de la API Bluetooth de Android son:
- BluetoothAdapter: permite realizar todo tipo de operaciones sobre este mdulo, como
buscar dispositivos bluetooth con el mtodo startDiscovery() y obtener informacin
de stos.
- BluetoothDevice: representa un dispositivo bluetooth remoto con sus atributos y propie-
dades correspondientes.
- BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin de datos.
La diferencia principal con el mdulo anterior WIFI est en que en este mdulo Bluetooth
es necesario definir un receptor de mensajes del tipo BroadcastReceiver para recibir
asncronamente los dispositivos encontrados mediante mensajes del BluetoothAdapter.
En este apartado, se pretende mostrar al alumno de forma prctica cmo buscar dispo-
sitivos Bluetooth en el sistema operativo Android. Es recomendable abrir el Ejemplo 4 de esta
Unidad para seguir la explicacin siguiente.
Hemos desarrollado este Ejemplo 4 con la misma estructura que el anterior Ejemplo 3. Por
esto, no mostraremos el cdigo fuente completo del mismo ya que se repiten algunas partes del
cdigo. Slo estudiaremos la parte nueva.
getMajorDeviceClass()), device.getBondState() !=
BluetoothDevice.BOND_BONDED ? DISPONIBLE :
VINCULADO));
}
} else
// Si hemos acabado la bsqueda ocultamos el dilogo y
// refrescamos el adaptador del ListView
if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
{
if (dialog.isShowing()) {
dialog.dismiss();
}
adaptador.notifyDataSetChanged();
}
} // end onReceive
@Override
public void onClick(View v) {
switch (v.getId()) {
case (R.id.boton):
// Buscamos el adaptador Bluetooth
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Si no existe mostramos un mensaje de error
if (mBtAdapter == null) {
Toast.makeText(this, ERROR: dispositivo Bluetooth no
est disponible., Toast.LENGTH_LONG).show();
return;
}
// Si existe el dispositivo pero no est activo solicitamos
// al sistema mediante un Intent que lo active
264 if (!mBtAdapter.isEnabled()) {
Intent enableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) ;
this.startActivity(enableIntent);
// Nota: podemos ejecutar el Intent anterior mediante
// la orden startActivityForResult y esperar a la
// vuelta de ste para seguir con el descubrimiento
// de los dispositivos.
return;
}
// Mostramos una ventana de progreso
this.dialog.setMessage(Buscando dispositivos
Bluetooth...);
this.dialog.show();
// Limpiamos la matriz de resultados
dispositivos.clear();
// Registramos 2 Intent con un filtro para recepcin de
// eventos cuando se encuentra Intent un dispositivo
// Bluetooth o se acaba la bsqueda
IntentFilter filter = new
IntentFilter(BluetoothDevice.ACTION_FOUND);
// Indicamos el mtodo callback bluereceiver que se
// ejecutar cada vez que se encuentre un dispositivo
this.registerReceiver(blueReceiver, filter);
filter = new
IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
// Indicamos el mtodo callback bluereceiver que se
// ejecutar una vez acabe la bsqueda de dispositivos
this.registerReceiver(blueReceiver, filter);
U3 Sensores y dispositivos de Android
mBtAdapter.startDiscovery();
break;
}
}
Como podemos ver en el cdigo anterior, para realizar una bsqueda de dispositivos Bluetooth
debemos comprobar que el mdulo Bluetooth est presente en el dispositivo en cuestin.
El mdulo Bluetooth es instanciado a travs del mtodo getDefaultAdapter() de la clase
BluetoothAdapter.
Si dicha instancia devuelve un valor null, significa que el mdulo Bluetooth no est
disponible en el dispositivo Android. Sin embargo, si la instancia devuelve un valor distinto de
Aula Mentor
null, pero su mtodo isEnabled() devuelve un valor falso, revela que el mdulo Bluetooth
est apagado.
En el caso de que el mdulo Bluetooth se encuentre apagado, es posible encender-
lo automticamente creando un Intent del sistema operativo mediante la constante Blue-
toothAdapter.ACTION_REQUESTENABLE.
Una vez inicializado el mdulo Bluetooth, la aplicacin debe registrar en el sistema que
desea recibir mensajes de los eventos ACTIONFOUND y ACTION_DISCOVERY_FINISHED del siste-
ma operativo. Estos mensajes indican que se ha encontrado un dispositivo Bluetooth durante la
bsqueda y que ha finalizado la bsqueda respectivamente.
Para poder recibir dichos mensajes hay que definir un receptor de mensaje del sistema
de la clase BroadcastReceiver que se encarga de esta tarea.
Adems, debemos llamar al mtodo startDiscovery() de la clase BluetoothAdapter
para iniciar la bsqueda de dispositivos.
Todas estas operaciones anteriores se han implementado en el evento onClick del botn
de la aplicacin.
Mediante un objeto de tipo BroadcastReceiver llamado blueReceiver se filtran los
mensajes recibidos a travs del parmetro arg1 que es un Intent.
Mediante el mtodo getAction() de este Intent podemos obtener el tipo de mensa-
je (ACTION) recogido y ejecutar el bloque de sentencias que corresponda. Por ejemplo, en el
caso de recibir el mensaje de que se ha encontrado un dispositivo Bluetooth usamos el mtodo
getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) de este Intent para obtener sus
caractersticas.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acce-
so a la interfaz WIFI del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.
266 xml las siguientes etiquetas:
<uses-permission android:name=android.permission.BLUETOOTH/>
<uses-permission android:name=android.permission.BLUETOOTH_ADMIN/>
Es sencillo mejorar la aplicacin para incluir funcionalidades nuevas, como la vinculacin entre
dispositivos y la trasmisin de datos mediante la clase BluetoothSocket. En la Gua oficial
del desarrollador Bluetooth de Android puedes encontrar ms informacin, as como ejemplos
descriptivos.
Si ejecutas en Eclipse ADT este Ejemplo 4 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
267
Si ejecutas esta aplicacin en tu dispositivo fsico Android, vers que en el listado nicamente
aparecen los dispositivos que no estn vinculados a l.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Casi todos los dispositivos Android integran una cmara de fotos, incluso, algunos disponen
de una cmara frontal y otra en la parte posterior. El SDK de Android incluye las clases Java
necesarias para gestionar una o varias cmaras de fotos.
Como hemos comentado, es muy sencillo utilizar un Intent de Android para lanzar la aplica-
cin de cmara de fotos de un dispositivo y, posteriormente, obtener la foto tomada. Fjate en
el cdigo fuente del siguiente ejemplo:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Recuperamos el ImageView
imageView = (ImageView) findViewById(R.id.resultado);
}
// Mtodo
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQUEST_CODE && resultCode ==
Activity.RESULT_OK)
try {
// Si el bitmap ya existe tenemos que vaciarlo de datos
if (bitmap != null) {
bitmap.recycle();
}
// Leemos los datos devuelto por el Intent en un stream
InputStream stream =
getContentResolver().openInputStream(data.getData());
// Pasamos los datos al bitmap
bitmap = BitmapFactory.decodeStream(stream);
// Liberamos el stream
stream.close();
// Asignamos la imagen al ImageView
imageView.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
U3 Sensores y dispositivos de Android
} catch (IOException e) {
e.printStackTrace();
}
super.onActivityResult(requestCode, resultCode, data);
}
}
Como puedes ver en el cdigo anterior, basta con utilizar una Intencin con estas caractersti-
cas:
Tipo: image/*
Accin: ACTION_GET_CONTENT
Categora: CATEGORY_OPENABLE
De esta forma podemos ejecutar la aplicacin interna del dispositivo Android y, posteriormente,
esperar el evento onActivityResult(), para capturar el resultado, que es una imagen.
Puedes incluir este cdigo en tu propia aplicacin cuando desees integrar la captura de
fotos en tus aplicaciones.
El SDK de Android incluye el soporte nativo, mediante una librera, que permite utilizar las
cmaras de los dispositivos para sacar fotos y grabar vdeos.
Existe gran cantidad de aplicaciones que hacen uso de esta cmara de fotos mejorando
la experiencia del usuario y abriendo un nuevo campo de integracin entre el mundo real y
el virtual. 269
Veamos las clases de la API de la cmara de Android:
- Camera: sta es la clase bsica de la API para controlar la cmara, tomar fotos y grabar v-
deos. Como un dispositivo Android puede tener varias cmaras, para distinguirlas, esta clase
utiliza la variable de tipo entero cameraId.
- CameraInfo: clase que contiene la informacin de la cmara con el identificativo cameraId.
- SurfaceView: esta clase se usa para mostrar al usuario la previsualizacin de la cmara de
fotos.
- MediaRecorder: esta clase se emplea para grabar vdeos.
En este apartado se pretende mostrar al alumno, de forma prctica, cmo utilizar la cmara de
fotos. Es recomendable abrir el Ejemplo 5 de esta Unidad para seguir la explicacin que se
ofrece.
La aplicacin que desarrollamos permitir capturar una foto con la cmara o seleccionar
una imagen de la galera del dispositivo y mostrarla en la interfaz de usuario.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent android:id=@+id/layout>
<TextView android:layout_width=fill_parent
android:layout_height=wrap_content android:text=Cmara
de fotos
android:textSize=24sp />
Aula Mentor
<FrameLayout android:id=@+id/preview
android:layout_weight=1
android:layout_width=fill_parent
android:layout_height=fill_parent>
</FrameLayout>
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center_horizontal >
<Button
android:id=@+id/boton_disparador
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginTop=6dip
android:layout_weight=1
android:text=Disparador />
<Button
android:id=@+id/boton_galeria
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginTop=6dip
android:layout_weight=1
android:text=Ver Galera />
</LinearLayout>
</LinearLayout>
270
En el diseo anterior hemos incluido el elemento FrameLayout, que sirve como contenedor
de la previsualizacin de la cmara de fotos o de la imagen seleccionada por el usuario en la
galera.
Si ahora abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente conte-
nido:
@Override
public void onClick(View v) {
// Si la aplicacin est visualizando una imagen entonces...
if (boton_disparador.getText().equals(Ver previsualizacin))
{
// Volvemos a modo previsualizacin de la cmara
boton_disparador.setText(Disparador);
// Intercambiamos en el frame el objeto de previsualizacin
((FrameLayout)findViewById(R.id.preview)).removeAllViews();
((FrameLayout)findViewById(R.id.preview)).addView(preview);
return;
}
// Si no se encuentra la cmara entonces no podemos sacar
// fotos
if (cameraId < 0) {
Toast.makeText(getBaseContext(), ERROR: no se encuentra
la cmara de fotos en este dispositivo.,
Toast.LENGTH_LONG).show();
} else {
// Si encontramos la cmara entonces paramos la
// previsualizacin, tomamos la foto y volvemos a
// previsualizar
camara.stopPreview();
// Tomamos la foto indicando el mtodo callback
// PictureCallback en la clase FotoHandler para que
// trate la foto una vez se ha sacado.
camara.takePicture(null, null, new
FotoHandler(getApplicationContext())); 271
camara.startPreview();
}
}
}); // end onClick
// Botn galera
Button boton_galeria = (Button)findViewById(R. id.boton_galeria);
// Evento onClick del botn galera
boton_galeria.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Definimos un Intent del tipo siguiente
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.
INTERNAL_CONTENT_URI);
// Iniciamos la Actividad con este Intent indicando que
// queremos seleccionar una imagen
startActivityForResult(intent, SELECT_PICTURE);
}
});
En el cdigo anterior puedes ver que fijamos una constante para identificar la accin realizada
(seleccionar imagen de la galera). Adems, definimos las variables de la clase Camera de An-
droid para gestionar la cmara y Preview de previsualizacin de la cmara definida por esta
aplicacin y que estudiaremos ms adelante.
En el evento onCreate() de esta Actividad hemos incluido las sentencias necesarias
para asignar a los dos botones las operaciones que deben realizar cuando un usuario haga clic
sobre ellos.
En el caso del botn Disparador se aplica la siguiente lgica: si el FrameLayout no est
mostrando la previsualizacin, la incluimos para que el usuario pueda sacar una foto. Si no
ocurre esto anterior y existe una cmara, entonces tomamos una fotografa mediante el mtodo
Aula Mentor
takePicture() clase Camera donde pasamos como parmetro un mtodo callback del tipo
PictureCallback que hemos implementado en la clase FotoHandler de esta aplicacin, para que
almacene la foto una vez se ha tomado.
Para la funcionalidad del botn Ver Galera hemos utilizado un Intent del tipo MediaSto-
re.ACTION_IMAGE_CAPTURE y lo hemos ejecutado mediante la orden startActivityFo-
rResult() para recibir la imagen seleccionada por el usuario en el mtodo onActivityRe-
sult(). En este ltimo mtodo recibimos el URI de la imagen y construimos un Bitmap a
partir de un stream de Bytes para cargarlo en una Vista de tipo imagen y la asignamos al frame
para mostrarla al usuario parando la previsualizacin de la cmara de fotos con la sentencia
camara.stopPreview().
Adems de definir las rdenes que deben ejecutar los botones, hemos insertado la sentencia
siguiente que valida si el dispositivo tiene cmara de fotos:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
Si existe una cmara, entonces buscamos identificarla mediante el cameraId para su uso
posterior mediante el mtodo local encuentraCamaraPrincipal(). Este mtodo obtiene el
nmero total de cmaras del dispositivo con el mtodo Camera.getNumberOfCameras() y, a
travs de un bucle, la clase CameraInfo extrae las caractersticas de cada cmara buscando la
cmara principal trasera que se define con la constante CAMERA_FACING_BACK.
Al final del cdigo de esta clase MainActivity hemos definido los mtodos siguientes del
ciclo de vida de la actividad:
- onPause(): cuando la Actividad pasa al segundo plano, debemos parar la previsualizacin
de la cmara con la orden stopPreview(), ya que consume recursos de sistema operativo.
274
- onResume(): en el caso de que sea necesario, se debe volver a iniciar la previsualizacin de
la cmara con startPreview() cuando la Actividad vuelva al primer plano.
- onDestroy(): si la Actividad se destruye, debemos eliminar el mtodo callback de la previ-
sualizacin con camara.setPreviewCallback(null) y, a continuacin, liberar la cmara
con camara.release().
Preview(Context context) {
super(context);
// Obtenemos el contenedor del SurfaceView y le asignamos
// un mtodo para detectar cundo se crea y se destruye esta
// superficie
mHolder = getHolder();
U3 Sensores y dispositivos de Android
mHolder.addCallback(this);
}
Lo primero que hemos hecho es obtener el contenedor del SurfaceView y le asignamos los
mtodos callback para detectar cundo se crea, cambia o se destruye esta superficie. A con-
tinuacin, definimos los mtodos de esta interfaz SurfaceHolder.Callback:
- surfaceCreated(): este evento ocurre cuando el sistema crea la superficie. En l, indica-
mos a la cmara que la Vista de previsualizacin es esta superficie y, para poder depurarla,
definimos el mtodo PreviewCallback(), que ocurre cuando el sistema operativo actualiza
la previsualizacin de la cmara. Realmente no es necesario definirlo.
- surfaceDestroyed(): este evento ocurre cuando se destruye la superficie. En este caso
hemos delegado en la Actividad principal la destruccin de este objeto Preview. Tambin,
podramos haber destruido las variables creadas.
- surfaceChanged(): evento que sucede cuando el sistema operativo detecta que se ha pro-
ducido un cambio en la superficie. En este caso, establecemos los parmetros del tamao de
previsualizacin de la cmara.
- draw(): evento que se lanza la primera vez que se dibuja la superficie. Mostramos un texto
en color rojo.
// Guardamos la foto
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
Toast.makeText(contexto, Se ha guardado la imagen: +
fotoFile, Toast.LENGTH_LONG).show();
} catch (Exception error) {
Log.d(MainActivity.DEBUG_TAG, File + filename + no
guardada: + error.getMessage());
Toast.makeText(contexto, ERROR: no se puede guardar la
imagen., Toast.LENGTH_LONG).show();
}
// Iniciamos la previsualizacin de la cmara
camera.startPreview();
}
} // end clase
Cuando tomamos una fotografa, hay que invocar el mtodo takePicture() de la clase Ca-
mera, donde debemos indicar como parmetro un mtodo callback del tipo PictureCallback 277
que hemos implementado en la clase anterior FotoHandler. Este mtodo se encarga de guar-
dar la foto una vez se ha tomado.
Puedes observar que la clase implementa el mtodo onPictureTaken, que es el que se
encarga de almacenar la imagen en la tarjeta SD del dispositivo Android. Como parmetro de
este mtodo usamos la matriz de bytes data que contiene la informacin de la foto tomada. En
este bloque de cdigo seguimos los pasos habituales para almacenar un fichero de tipo binario
en la tarjeta SD del dispositivo; en concreto, lo guardamos en el directorio de fotos Environ-
ment.DIRECTORY_PICTURES del sistema operativo.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a la cmara y tarjeta SD del dispositivo. Para ello, hay que incluir en el fichero Androi-
dManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.CAMERA/>
<uses-permission
android:name=android.permission.WRITE_EXTERNAL_STORAGE/>
Adems, recordamos que para poder depurar la aplicacin en un dispositivo real de Android,
es necesario indicarlo en el archivo manifest en la etiqueta <application> mediante el
atributo android:debuggable=true.
En este ejemplo, s podemos utilizar el AVD del SDK de Android configurndolo correctamente.
Para ello, debemos editar el dispositivo virtual y elegir en la cmara trasera (Back Camera) la
opcin Emulated para que simule una cmara (como hemos hecho en las capturas que apa-
recen despus) o Webcam0 para utilizar la Webcam de tu ordenador como cmara de fotos:
278
U3 Sensores y dispositivos de Android
Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente apli-
cacin:
Si tomas una foto y, a continuacin, pulsas el botn Ver Galera, vers que no aparece nin-
guna foto en sta:
279
Esto se debe a que el AVD, por motivos de rendimiento, no busca automticamente nuevos
contenidos multimedia en la tarjeta SD. Para forzar esta bsqueda debemos hacer clic en la
aplicacin Dev Tools que aparece en el escritorio de aplicaciones de Android del AVD:
Aula Mentor
Despus, debemos seleccionar la opcin Media Provider y hacer clic en el botn Scan SD
card para que el sistema busque nuevos contenidos multimedia en la tarjeta SD:
Si ahora haces clic en el botn Galera, vers que ya puedes seleccionar esa imagen correc-
tamente:
280
En la actualidad, existen muchas aplicaciones que hacen uso de la localizacin del usuario para
ofrecer servicios avanzados. Google Maps es, posiblemente, la aplicacin que ms aprovecha
la funcionalidad del GPS para ofrecer al usuario servicios teniendo en cuenta su localizacin,
como, por ejemplo, una gua de rutas o la localizacin basada en mapas.
Sin embargo, los desarrolladores reinventan continuamente el uso de la geolocalizacin
y crean proyectos y aplicaciones que ofrecen al usuario servicios en funcin de su localizacin,
como, por ejemplo, la posibilidad de obtener recomendaciones de puntos de inters cercanos o
compartir informacin y conversaciones con personas de la misma zona.
Existen varios mtodos de obtener la localizacin de un dispositivo mvil, si bien
la ms conocida es la localizacin mediante el mdulo GPS. Tambin es posible conseguir la
posicin geogrfica de un dispositivo utilizando las antenas de telefona mvil o mediante los
puntos de acceso WI-FI cercanos. Cada uno de estos mtodos tiene una precisin, velocidad
y consumo de recursos del sistema distintos. Por otra parte, la aplicacin al cdigo fuente del
modo de funcionamiento de cada uno de estos mtodos no es directa ni intuitiva.
En este apartado, se pretende mostrar el alumno, de forma prctica, cmo utilizar la fun-
cionalidad de geolocalizacin que ofrece el SDK de Android a travs del mdulo GPS que, hoy
en da, tiene la mayora de los dispositivos. Es recomendable abrir el Ejemplo 6 de esta Unidad
para seguir la explicacin que se ofrece a continuacin.
La aplicacin que desarrollamos muestra los mdulos disponibles de localizacin del dispositivo
Android, selecciona el mejor mdulo en funcin de los parmetros indicados por el programador
y muestra la posicin geogrfica del dispositivo y el estado del mdulo escogido.
281
En el cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent
android:layout_margin=3dp>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content >
<ImageView
android:id=@+id/imageView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/redpin />
<Button android:id=@+id/botonActivar
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Activar localizacin />
<Button android:id=@+id/botonDesactivar
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Desactivar localizacin />
Aula Mentor
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:gravity=center_vertical
android:text=Mejor provider de localizacin GPS: />
<TextView android:id=@+id/lblMejorProvider
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosicion
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Posicin Actual: />
<TextView android:id=@+id/lblPosLatitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
282 android:textColor=#000000 />
<TextView android:id=@+id/lblPosLongitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosPrecision
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Estado del GPS: />
<TextView android:id=@+id/lblEstado
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
U3 Sensores y dispositivos de Android
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Listado de providers: />
<ListView
android:id=@+id/listView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:background=#FFFFFF/>
</LinearLayout>
Como puedes ver, la interfaz del usuario es sencilla. Se incluyen dos botones que permiten
Activar/Actualizar y Desactivar la localizacin, etiquetas para mostrar los datos al usuario y un
listado de tipo ListView para mostrar los mdulos de localizacin disponibles.
lblEstado = (TextView)findViewById(R.id.lblEstado);
btnDesactivar.setEnabled(false);
// Definimos el adaptador del listado de LocationProviders
adaptador = new AdapterElements(this);
listaProviders = (ListView)findViewById(R.id.listView) ;
listaProviders.setAdapter(adaptador);
// Mostramos los datos en blanco
muestraPosicion(null);
if(loc != null)
{
lblLatitud.setText(Latitud: +
String.valueOf(loc.getLatitude()));
lblLongitud.setText(Longitud: +
String.valueOf(loc.getLongitude()));
lblPrecision.setText(Precision: +
String.valueOf(loc.getAccuracy()));
Log.i(Localizacin Android,
String.valueOf(loc.getLatitude() + - +
String.valueOf(loc.getLongitude())));
}
else
{
lblLatitud.setText(Latitud: (sin_datos));
lblLongitud.setText(Longitud: (sin_datos));
lblPrecision.setText(Precision: (sin_datos));
lblMejorProvider.setText();
lblEstado.setText();
}
}
En el mtodo obtenerGPS() podemos ver que, para obtener la posicin GPS del dispositivo,
lo primero que debemos hacer es conseguir, mediante el mtodo getSystemService(), una
instancia del gestor GPS utilizando la clase LocationManager, en la que se basa la API de
localizacin de Android, del mvil utilizando.
Una vez tenemos una instancia del gestor de GPS, vamos a obtener qu mtodos de localizacin
(Proveedor de localizacin o, en ingls, LocationProvider) posee el dispositivo; as conocemos
sus mdulos GPS disponibles. Como ya hemos comentado, los ms comunes son el GPS y la
localizacin mediante la red de telefona.
Para conocer los proveedores de localizacin disponibles en el dispositivo podemos usar
el mtodo getAllProviders() de la clase LocationManager.
Una vez obtenida la lista completa de proveedores disponibles, accedemos a las propie-
dades de ellos (precisin, consumo de batera, si puede obtener la altitud, la velocidad, etcte-
ra). Podemos obtener una referencia al provider mediante su nombre empleando el mtodo
getProvider(nombre) y, posteriormente, los mtodos disponibles para conocer sus propieda-
des.
Por ejemplo, para conocer la precisin podemos utilizar getAccuracy(), que devuelve
las constantes Criteria.ACCURACY_FINE para precisin alta y Criteria.ACCURACY_COARSE
para precisin media. El mtodo supportsAltitude() indica si el mdulo proporciona la alti-
tud y el mtodo getPowerRequirement() indica el nivel de consumo de recursos del provee-
dor (batera). Puedes ver la lista completa de los mtodos con las caractersticas de un proveedor
en el enlace siguiente LocationProvider.
Dicho Criterio est configurado para obtener la precisin de la medida mediante el mtodo
setAccuracy() y una de las constantes siguientes:
ACCURACY_FINE: mejor precisin del dispositivo.
ACCURACY_HIGH: precisin de localizacin alta (menor de 100 metros).
ACCURACY_MEDIUM: precisin entre 100 y 500 metros.
ACCURACYLOW: precisin mayor de 500 metros.
Lgicamente, cuanto menor sea la precisin, menos tiempo tardar el dispositivo en obtener la
localizacin.
Una vez hemos definido el criterio de precisin de la localizacin, dejamos que el sistema
operativo elija automticamente el proveedor de localizacin a travs del mtodo getBestPro-
vider() de la clase LocationManager. Debemos indicar como primer parmetro el criterio de
precisin creado y, como segundo parmetro, si slo deseamos proveedores que estn activos
en ese momento.
Aula Mentor
Hemos visto que podemos buscar dinmicamente los proveedores de localizacin segn un
determinado criterio de precisin. Sin embargo, normalmente una aplicacin est diseada para
utilizar un proveedor en concreto, como, por ejemplo, el mdulo GPS y, por lo tanto necesitamos
conocer si ste est activado o no en el dispositivo.
Para esto, la clase LocationManager dispone del mtodo isProviderEnabled(). De-
bemos pasarle como parmetro el tipo de provider que queremos consultar. En el cdigo
fuente anterior comprobamos si el mtodo seleccionado est activo, mostrando al usuario un
mensaje.
288 Una vez conocemos el proveedor de localizacin, vamos a definir un listener que monitoriza
el estado del GPS del dispositivo. Este listener debe implementarse a partir de la clase
LocationListener aadiendo los siguientes mtodos:
- onLocationChanged: este mtodo se lanza cada vez el sistema operativo refresca la posi-
cin y la velocidad del dispositivo.
- onProviderEnabled: este mtodo se ejecuta al activarse el proveedor de localizacin.
- onProviderDisabled: este mtodo se lanza al desactivarse el proveedor.
- onStatusChanged: este mtodo se ejecuta cuando cambia el estado del proveedor. Puede
devolver las constantes siguientes: OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE, AVAI-
LABLE.
Este mtodo NO devuelve la posicin actual ni solicita una nueva posicin al pro-
veedor de localizacin, sino que se limita a indicar la ltima posicin obtenida. Por
lo tanto, es recomendable esperar a recibir la primera actualizacin a travs del
LocationListener y no leer los datos mediante este mtodo.
En el mtodo muestraPosicion() vamos mostrar los distintos datos de la posicin en las Vistas
de la interfaz de usuario, utilizando para ello los mtodos siguientes proporcionados por la
clase Location: getLatitude(), getAltitude() y getAccuracy() para obtener la latitud,
longitud y precisin respectivamente. Adems, esta clase dispone de otros mtodos para obtener
la altura, orientacin, velocidad, etctera.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso al
mdulo GPS del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las
siguientes etiquetas:
<uses-permission android:name=android.permission.INTERNET />
<uses-permission android:name=android.permission.ACCESS_FINE_LOCATION />
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (GPS) de la Unidad 3. Es-
tudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado su Mdulo GPS.
289
Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente aplicacin:
Aula Mentor
Para que funcione la deteccin por GPS en el AVD debemos configurar las opciones de
Localizacin abriendo sus Ajustes y configurando lo siguiente:
291
Una vez cargado el archivo, debajo aparecen cuatro botones (de izquierda a derecha) que
permiten las operaciones siguientes:
- Avanzar automticamente por el listado enviando la coordenada al AVD.
- Ir a la posicin anterior del listado de forma manual.
- Ir a la posicin siguiente del listado de forma manual.
- Establecer la velocidad de cambio automtico.
Aula Mentor
A continuacin, puedes probar este listado en la aplicacin del Ejemplo 6 de esta Unidad
ejecutndolo en el AVD y pulsando el botn Activar localizacin para comenzar a detectar
cambios de posicin. Despus, pulsa el botn de avance automtico (botn verde).
Vers que la interfaz de la aplicacin se actualiza varias veces con nuevos valores de lati-
tud y longitud cada poco tiempo. Sin embargo, con el mtodo requestLocationUpdates()hemos
definido que se muestren actualizaciones de posicin cada 15 segundos.
Si abres la Vista de LogCat de Eclipse ADT, advertirs que se han generado los mensajes de
depuracin siguientes (en tu caso deben ser muy parecidos):
Si observamos la fecha y hora vemos que el tiempo entre nueva posicin no coincide con los
15 segundos que ha definido el programador. Sin embargo, Android agrupada varias medidas
292 dentro de los 15 segundos indicados.
Adems, si investigas en el log de mensajes de Eclipse ADT, observars que cambia el estado
del proveedor a 1 (TEMPORARILY_UNAVAILABLE) despus de recibir un grupo de posiciones y,
antes de recibir de nuevo posiciones, el proveedor pasa a estado 2 (AVAILABLE).
Estos cambios en el estado de los proveedores de localizacin pueden ayudarnos a rea-
lizar diversas acciones. Por ejemplo, se puede utilizar el cambio de estado a 1 (se termina de
recibir un grupo de lecturas) para seleccionar la lectura ms precisa del grupo recibido. Esto es
muy importante cuando se estn utilizando varios proveedores de localizacin simultneamente
(GPS o WIFI) que, como sabes, tienen una precisin diferente.
U3 Sensores y dispositivos de Android
La popularizacin del mvil inteligente (del ingls, Smartphone) ha provocado tambin que
sus dueos y dueas jueguen con sus dispositivos en cualquier sitio. Android no es ajeno a esta
moda y dispone de una amplia y variada galera de juegos, como, por ejemplo, los ya clsicos
Trivial (Atrviate en su versin mvil), Apalabrados y Angry Birds.
Existen en Internet mltiples libreras y herramientas que ayudan al programador a desa-
rrollar, de forma sencilla, juegos en Android. Veamos algunos de ellos.
Libreras de juegos en 3D
- Unity: evolucin del popular motor Unity 3D para dispositivos Android.
- Ogre 3D: evolucin a Android del motor 3D abierto y gratuito. Muy utilizado.
Libreras de juegos en 2D
- Marmalade SDK: potente motor 2D multiplataforma con el que se puede empezar a desarro-
llar juegos de forma totalmente gratuita.
- Corona SDK: otra opcin para realizar juegos y aplicaciones 2D multiplataforma.
- Cocos 2D x: librera capaz de abarcar una amplia gama de dispositivos, basada en exten-
siones. Totalmente gratuita.
- LibGDX: librera abierta y multiplataforma que combina grficos y audio. Muy interesante. 293
- AndEngine: joven motor de juegos 2D gratuito, rpido y divertido.
Estas libreras son muy tiles para desarrollar juegos de forma rpida; sin embargo, no ensean
realmente cmo hacerlo en Android de forma nativa, ya que sirven de pegamento entre el SDK
de Android y el programador.
Adems, hay que tener en cuenta que las libreras se pasan de moda o se quedan obsole-
tas. Por lo tanto, en este apartado vamos a estudiar el desarrollo de un juego sencillo utilizando
los mtodos disponibles en el SDK de Android.
Para empezar, el cdigo del layout main.xml incluye el diseo de la Actividad principal:
<FrameLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<es.mentor.unidad3.eje7.juego.JuegoView
android:id=@+id/areajuego
android:layout_width=fill_parent
android:layout_height=fill_parent/>
<RelativeLayout
android:id=@+id/gameLayout
android:layout_width=fill_parent
android:layout_height=fill_parent >
<LinearLayout
android:id=@+id/fuegoLayout
android:layout_width=fill_parent
android:layout_height=33px
android:orientation=vertical
android:gravity=bottom
android:background=@drawable/muromuellearriba
android:layout_alignParentTop=true/>
<ImageView
android:id=@+id/imageHome
294 android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignTop=@+id/puntuacion
android:layout_centerHorizontal=true
android:layout_alignParentTop=true
android:src=@drawable/home />
<TextView
android:id=@+id/texto
android:text=@string/modo_preparado
android:visibility=visible
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:gravity=center_horizontal
android:textColor=#ff0000ff
android:textSize=40sp/>
<TextView
android:id=@+id/puntuacion
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:layout_marginTop=3dp
android:gravity=top
android:text=@string/puntuacion_texto
android:textColor=#ff0000ff
android:textSize=24sp
android:visibility=visible
U3 Sensores y dispositivos de Android
android:layout_below=@+id/texto/>
<LinearLayout
android:id=@+id/IzqLayout
android:layout_width=33px
android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=left
android:background=@drawable/muromuelleizq
android:layout_alignParentLeft=true
android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/DchaLayout
android:layout_width=33px
android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=right
android:background=@drawable/muromuelledcha
android:layout_alignParentRight=true
android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/fuegoLayout
android:layout_width=fill_parent
android:layout_height=45dp
android:orientation=vertical
android:gravity=bottom 295
android:background=@drawable/murofuego
android:layout_alignParentBottom=true/>
</RelativeLayout>
</FrameLayout>
Adems, para definir los laterales de la pantalla hemos usado un LinearLayout cuyo fondo
background es un drawable que, a su vez, es una imagen que se apila repetidamente
(tileMode=repeat):
<bitmap xmlns:android=http://schemas.android.com/apk/res/android
android:src=@drawable/fire
android:tileMode=repeat />
mJuegoThread.restoreState(savedInstanceState);
// Si el juego estaba en el modo EJECUCION, lo pasamos
// a PAUSA para que el usuario pueda jugar
if(mJuegoThread.getEstado() ==JuegoThread.ESTADO_EJECUCION)
{
mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);
}
}
else {
// Si no existe el hilo pero ya hemos ejecutado la
// aplicacin, creamos un hilo nuevo.
JuegoThread jThread = new JuegoThread(mJuegoView);
mJuegoView.setHilo(jThread);
mJuegoThread = mJuegoView.getHilo();
// Recuperamos los datos anteriores
mJuegoThread.restoreState(savedInstanceState);
// El estado actual es preparado
mJuegoThread.setEstado(JuegoThread.ESTADO_PREPARADO);
}
}
// Accedemos al gestor de Sensores y se lo pasamos a la vista de
// Juego para que conecte el sensor que corresponda con su
// listener
mJuegoView.conectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
}
@Override
protected void onDestroy() {
super.onDestroy();
// Si se destruye la Actividad liberamos memoria de la Vista
// juego
mJuegoView.liberarTodo();
// Desconectamos del gestor de sensores
mJuegoView.desconectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
// Liberamos variables locales
mJuegoThread = null;
mJuegoView = null;
// Liberamos los sonidos guardados y la variable soundPool
soundPool.release();
soundPool = null;
}
@Override
// Mtodo que se lanza cuando es necesario guardar el estado de la
Aula Mentor
// Actividad
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Guardamos el estado del Hilo si ste existe
if(mJuegoThread != null) {
mJuegoThread.saveState(outState);
}
}
} // end clase
A continuacin, se muestra cmo se implementa la interfaz visual del rea de juego del usuario
mediante esta superficie definida en el fichero JuegoView.java:
// Constructor de la clase
public JuegoView(Context context, AttributeSet attrs) {
super(context, attrs);
@Override
// Definimos los mensajes
public void handleMessage(Message m) {
// Si el hilo indica el tipo de mensaje puntuacion
// actualizamos la Vista correspondiente
if(m.getData().getBoolean(puntuacion)) {
mPuntuacionView.setText(m.getData().getString(texto));
}
else // Si es otro tipo de mensaje entonces es un cambio de
// estado de la aplicacin
{
// Mostramos el TextView si el mensaje lo indica as
mEstadoView.setVisibility(m.getData().getInt(visible));
mEstadoView.setText(m.getData().getString(texto));
}
// Reproducimos un sonido si lo pide el mensaje
if (m.getData().getInt(sonido)>-1) {
if (m.getData().getInt(sonido)==R.raw.rebote)
JuegoActivity.soundPool.play(JuegoActivity.idRebote,
1, 1, 0, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.gameover)
JuegoActivity.soundPool.play(JuegoActivity.idGameOver,
1, 1, 1, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.win)
JuegoActivity.soundPool.play(JuegoActivity.idWin,
1, 1, 1, 0, 1);
}
300 }; // end Handler
} // end constructor
}
else return false;
}
}); // end onTouch
/*
* FUNCIONES DE PANTALLA
*/
} // end class
Veamos el cdigo anterior que implementa la superficie que define el rea del juego.
Tal y como hemos estudiado en la Unidad 1, para utilizar una superficie debemos crear
una subclase que se extienda de la clase SurfaceView e implemente la interfaz SurfaceHol-
der.Callback para poder gestionar los mtodos relacionados con esta superficie:
En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
addCallback(this) para gestionar los eventos del SurfaceView y poder acceder a la citada
superficie.
Para actualizar las Vistas de esta superficie (hilo principal) desde el Hilo del juego es
recomendable utilizar un Handler de Java. Este mecanismo permite trasmitir informacin entre
hilos de una aplicacin.
Para ello, en el constructor de esta superficie definimos un Handler que comunica in-
formacin desde el Hilo hacia la Vista del juego que recibe mensajes de ste, como actualizar la
puntuacin o cambiar el estado del juego. Para ello, implementamos el mtodo handleMessa-
ge() que gestiona los mensajes recibidos del hilo y actualiza las Vistas de la superficie.
En esta aplicacin hemos implementado algunos de los eventos que lanza una superficie.
Estudiemos las sentencias ms importantes de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos crea-
do en el constructor de la clase JuegoActivity y que se ha recibido en la superficie mediante
el mtodo setHilo() desde la Actividad principal. Despus, establecemos el estado del hilo al
modo ejecucin y lo arrancamos con la orden start(). La funcin de este hilo es actualizar la
Aula Mentor
304 Hemos implementado el Hilo JuegoThread para actualizar el dibujo del objeto SurfaceView,
gestionar los toques sobre ste y obtener datos de los sensores. En el constructor de este hilo
pasamos como parmetro la referencia al objeto JuegoView, para poder modificar el contenido
de sta.
Por esto, en el mtodo surfaceChanged() del JuegoView obtenemos el tamao de la
pantalla del dispositivo Android y se lo pasamos al hilo JuegoThread.
No hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo el encar-
gado de dibujarla.
En el mtodo setHilo() de esta clase hemos delegado el mtodo onTouchEvent() al
Hilo mediante la sentencia setOnTouchListener(). Tambin definimos, en este mtodo, el
listener del sensor y lo delegamos nuevamente al Hilo.
Este listener lo utilizamos en el mtodo conectaSensor(), donde mediante la orden
registerListener() del ServiceManager lo registramos indicando con SENSOR_DELAY_
GAME que el dispositivo debe comunicar con el menor tiempo posible los cambios de orientacin
(TYPE_ORIENTATION), para que el jugador note fluidez en las respuestas a sus movimientos.
Aunque el tipo de sensor TYPE_ORIENTATION es obsoleto (deprecated), lo hemos utiliza-
do por sencillez en este Ejemplo. Si no queremos usar un sensor obsoleto, debemos utilizar una
combinacin de los sensores TYPE_ACCELEROMETER y TYPE_MAGNETIC_FIELD.
// Puntuacin de la partida
private long puntuacion = 50;
// Imagen de la pelota
private Bitmap pelota;
// Posicin actual de la pelota
private float pelotaX = -1;
private float pelotaY = -1;
// Direccin de la pelota
private float pelotaVelocidadX = 0;
private float pelotaVelocidadY = 0;
(eljuegoView.getContext().getResources(),
R.drawable.pelota);
}
// Constructor para cuando se desea jugar otra vez
public JuegoThread(JuegoView gameView, JuegoThread oldThread) {
// Recuperamos las variables anteriores
juegoView = gameView;
mSurfaceHolder = gameView.getHolder();
mHandler = gameView.getmHandler();
mContexto = gameView.getContext();
pelota = oldThread.pelota;
pelotaX = oldThread.pelotaX;
pelotaY = oldThread.pelotaY;
pelotaVelocidadX = oldThread.pelotaVelocidadX;
pelotaVelocidadY = oldThread.pelotaVelocidadY;
}
306
// Liberamos todas las variables
public void liberarTodo() {
this.mContexto = null;
this.juegoView = null;
this.mHandler = null;
this.mSurfaceHolder = null;
}
bundle.putFloat(mPelotaX, pelotaX);
bundle.putFloat(mPelotaY, pelotaY);
bundle.putFloat(mPelotaDX, pelotaVelocidadX);
bundle.putFloat(mPelotaDY, pelotaVelocidadY);
}
}
return bundle;
} // end saveState
}
setEstado(ESTADO_EJECUCION);
}
// Mtodo que establece el estado del juego tanto desde este Hilo
// como desde la Actividad juego
public void setEstado(int estado) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {
// Guardamos el nuevo estado
mEstado = estado;
// Si el estado cambia a ejecucin creamos un nuevo
// mensaje y lo enviamos al JuegoView para que actualice
// las Vistas en funcin del estado del juego
if (mEstado == ESTADO_EJECUCION) {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
// Ocultamos la etiqueta del estado del juego
b.putString(texto, );
b.putInt(visible, View.INVISIBLE);
// Enviamos el mensaje al JuegoView
msg.setData(b);
mHandler.sendMessage(msg);
}
// Si no estamos jugando debemos mostrar el mensaje
// correspondiente
else { 311
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
msg.setData(b);
mHandler.sendMessage(msg);
}
}
} // end detestado
// Mtodo que enva un mensaje al hilo principal
// para que reproduzca sonido de rebote
public void sonidoRebote() {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
b.putInt(sonido, R.raw.rebote);
msg.setData(b);
// Enviamos el mensaje del sonido
mHandler.sendMessage(msg);
} // end sonidoRebote
Empecemos por comentar que, en el cdigo anterior, hemos definido las constantes que
empiezan por ESTADO_ para controlar el estado del juego y actuar en consecuencia.
Puedes ver tambin que esta clase es un hilo tpico de Java con su correspondiente m-
todo run(). En ste puedes ver que bloqueamos el canvas de la superficie para dibujarlo con
la orden mSurfaceHolder.lockCanvas() y, cuando es necesario, sincronizamos los mtodos
internos que mueven y dibujan la pelota. Si el usuario est jugando, entonces llamamos al
mtodo muevePelota(), que desplaza la pelota en la zona de juego. Habitualmente los desarro-
lladores de juegos a este mtodo lo denominan physics() ya que simula la fsica (movimiento)
del juego. Adems, en este bloque run() dibujamos la superficie con la orden doDraw(). Una
vez hemos ejecutado este bloque de cdigo, liberamos el canvas de la superficie desbloquen-
dolo con la orden unlockCanvasAndPost(canvas).
Si te fijas en el mtodo doDraw() vers que nicamente pinta la superficie de juego con
la pelota en la posicin que corresponda.
Sin embargo, muevePelota() calcula la posicin siguiente de la pelota en funcin del
tiempo pasado (as simulamos el movimiento) y la direccin de la pelota. Tambin cambia el
estado del juego si la pelota acaba en ciertas posiciones de la pantalla.
El mtodo onTouch() sirve para interactuar con la pulsacin del usuario en la pantalla.
Este mtodo tiene como parmetro la clase MotionEvent que permite conocer la accin del
usuario mediante la orden getAction(). As, slo utilizamos la accin MotionEvent.ACTION_
DOWN cuando toca la pantalla para continuar o empezar el juego.
Para que el jugador controle el movimiento de la pelota, hemos utilizando el sensor de
orientacin y desarrollado el mtodo onSensorChanged() que nos proporciona en su parme-
tro SensorEvent la inclinacin alrededor del eje x (-90 a 90) y la rotacin sobre el eje Y (-180
a 180), que empleamos para obtener la nueva velocidad y sentido de la pelota.
Es importante notar que esta clase tiene dos constructores. El tradicional 313
JuegoThread(JuegoView eljuegoView) que obtiene la superficie del juego, su Handler
y el Contexto de la aplicacin. Y un segundo mtodo JuegoThread(JuegoView gameView,
JuegoThread oldThread) que utilizamos en el mtodo surfaceCreated() de JuegoView
para comprobar con la constante Thread.State.NEW si el hilo se ha ejecutado alguna vez. En
la documentacin de Android se indica que es obsoleto parar y reiniciar hilos. Ten en cuenta
que un usuario puede decidir abrir otra aplicacin mientras est jugando y esto pausa el juego
destruyendo la superficie, por lo que volver a recrearla.
Por lo tanto, una forma de recrear un hilo nuevo es definir un segundo mtodo Jue-
goThread() donde copiamos todos los atributos del hilo existente y, as, el nuevo hilo comienza
en el estado en el que se qued anteriormente garantizando la experiencia del usuario al conti-
nuar el juego en el estado donde se qued.
Te recomendamos que le eches un vistazo a los mtodos setPuntuacion() y se-
tEstado() que establecen en la superficie JuegoView la puntuacin y el estado del juego,
respectivamente, desde este hilo utilizando un objeto de la clase Bundle. En esta clase vamos a
introducir la informacin que deseamos pasar al hilo principal, a aadirla al objeto Message con
setData(bundle) y a enviar este mensaje con la orden sendMessage(mensaje) de Handler.
Como no podemos bloquear el hilo del juego, hemos aprovechado estos mensajes para
notificar al hilo principal que debe reproducir sonidos con la clase asncrona SoundPool en
funcin del estado del juego o cuando la pelota rebota en los muelles.
Finalmente, hemos definido algunos mtodos auxiliares, como empezarJuego() que inicia el
juego, saveState() y restoreState() que guardan y recuperan el estado de ejecucin del
Hilo, respectivamente, y varios mtodos ms del tipo set() y get() necesarios en el hilo.
Aula Mentor
Aunque es posible utilizar el SensorSimulator para depurar la aplicacin, es lento y poco prctico;
por lo tanto, recomendamos probar esta aplicacin en un dispositivo real de Android.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente aplicacin:
Puedes probar a mover el dispositivo para constatar cmo cambia el sentido de la pelota y or
los efectos de sonido. Esperamos que el juego te resulte entretenido!
U3 Sensores y dispositivos de Android
6. Resumen
- Se pueden dividir los sensores en dos categoras ms: real y virtual. Real seala
que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de otros sensores o son medidas relativas.
- Para desarrollar aplicaciones que utilizan sensores, es necesario disponer de un
dispositivo fsico ya que el emulador de Android no permite depurarlas correc-
tamente.
- Por defecto, en el emulador de dispositivos virtuales (AVD) del SDK de Android 315
no se pueden utilizar sensores para depurar aplicaciones.
- SensorSimulator es un simulador de sensores que permite depurar una aplica-
cin Android que los use.
- El SDK de Android dispone de la clase WifiManager que permite hacer uso del
mdulo WIFI, siempre y cuando el dispositivo disponga de l.
- El SDK de Android tambin ofrece soporte para la tecnologa Bluetooth que per-
mite a un dispositivo intercambiar datos de forma inalmbrica con otros disposi-
tivos cercanos.
- Las clases ms importantes del SDK Bluetooth de Android son:
BluetoothAdapter: permite realizar todo tipo de operaciones sobre este
mdulo bluetooth.
BluetoothDevice: representa un dispositivo bluetooth remoto con sus atri-
butos y propiedades correspondientes.
BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin
de datos.
- Es muy importante gestionar los mtodos del ciclo de vida de la Actividad que
contiene la cmara de fotos, ya que sta consume muchos recursos del sistema,
como CPU y batera, por lo que slo se deben utilizar cuando sea necesario.
- Muchos dispositivos Android integran el mdulo GPS de localizacin geogrfica
mundial. El SDK de Android incluye las clases Java necesarias para gestionarlo.
- La clase LocationManager de Android permite gestionar el mdulo GPS.
- Existen varios mtodos de obtener la localizacin de un dispositivo mvil: me-
diante el mdulo GPS, utilizando las antenas de telefona mvil o mediante los
puntos de acceso WI-FI cercanos.
316
U4 Bibliotecas, APIs y Servicios de Android
1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan bibliotecas de una aplicacin para ampliar su
funcionalidad.
Adems, usaremos la API de Telefona de Android para gestionar llamadas de telfono y
mensajes cortos (SMS) desde un dispositivo.
Despus, utilizaremos el Calendario de Android para administrar las citas y eventos de
ste.
Tambin veremos cmo aprovechar la funcionalidad del gestor de descargas (Download
manager) de Android.
Finalmente, estudiaremos cmo enviar un correo electrnico e implementaremos Servi-
cios avanzados en Android.
- A excepcin de los archivos de Java, el resto de los archivos que pertenecen a una biblioteca,
por ejemplo, recursos de tipo imagen, se mantiene en el proyecto de tipo biblioteca.
- Si utilizamos una biblioteca en un proyecto Android, es necesario incluirla en ste como
dependencia del proyecto.
- A partir del SDK 15 de Android los IDs de recursos de la biblioteca no son finales (final).
- Tanto el proyecto de tipo biblioteca como el proyecto principal pueden acceder a los recur-
sos internos de la biblioteca a travs de sus respectivas referencias a R.java.
- Es posible definir IDs de recursos repetidos en el proyecto principal y en la biblioteca. Los
IDs del proyecto principal tendrn prioridad sobre los de la biblioteca. Por esto, es reco-
mendable distinguir los IDs de recursos entre los dos proyectos utilizando diferentes prefijos
de recursos, por ejemplo, anteponiendo el prefijo lib_ para los recursos de la biblioteca.
- Un proyecto principal puede hacer referencia a varias bibliotecas al mismo tiempo. Es posi-
ble establecer la prioridad de las bibliotecas para marcar qu recursos son ms importantes.
- Si queremos utilizar una Actividad (Activity) o un Servicio (Service) desarrollados en una
biblioteca, deben incluirse en el archivo manifest.xml del proyecto principal. Adems, es
necesario indicar el nombre del paquete completo de la biblioteca al incluir la Actividad o
el Servicio.
- Un proyecto de tipo biblioteca no puede hacer referencia a otra biblioteca, si bien en futuras
versiones del SDK de Android se podr hacer.
Para crear un proyecto de tipo biblioteca, debemos crear un proyecto normal de Android y
luego marcar la opcin biblioteca (library). Veamos de forma prctica cmo definir y utilizar una
biblioteca en Android.
A continuacin, vamos a estudiar cmo crear e integrar una biblioteca en una aplicacin Android.
En el Ejemplo 1 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza una
biblioteca.
Lo primero que vamos a hacer es crear la biblioteca. Para ello, pulsamos en el men
principal de Eclipse ADT en la opcin siguiente:
U4 Bibliotecas, APIs y Servicios de Android
319
En la ventana anterior es muy importante marcar la opcin Mark this Project as a library.
Despus, hacemos clic de nuevo en el botn Next donde podramos cambiar el icono de la
biblioteca:
320
Volvemos a pulsar el botn Next para pasar a la siguiente pantalla:
U4 Bibliotecas, APIs y Servicios de Android
321
En la ventana anterior rellenamos los campos tal y como aparecen en la captura y para acabar
la creacin de la biblioteca hacemos clic en el botn Finish.
Si abres el explorador de archivos en la carpeta bin del proyecto anterior (unidad4.
eje1.biblioteca) vers que Eclipse ADT ya ha compilado el proyecto en el formato .jar de
biblioteca Java:
Esta biblioteca consta de una Actividad secundaria que invocaremos desde la aplicacin principal
y de una imagen (robot.png) que usaremos en sta:
Aula Mentor
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:paddingBottom=@dimen/activity_vertical_margin
322
android:paddingLeft=@dimen/activity_horizontal_margin
android:paddingRight=@dimen/activity_horizontal_margin
android:paddingTop=@dimen/activity_vertical_margin
tools:context=.ActividadSecundaria >
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/etiqueta />
<LinearLayout
android:id=@+id/linearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
android:gravity=top
android:layout_marginTop=10dp>
<Button
android:id=@+id/botonOK
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado OK
android:layout_weight=1
android:onClick=botonClick />
<Button
android:id=@+id/botonCANCEL
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado CANCEL
android:layout_weight=1
android:onClick=botonClick/>
U4 Bibliotecas, APIs y Servicios de Android
</LinearLayout>
</LinearLayout>
Una vez importada la librera biblioteca, vers que aparecen dos archivos de recursos en la
carpeta gen: uno de la aplicacin que contiene la biblioteca (es.mentor.unidad4.eje1.
biblioteca_aplicacion) y el otro de la biblioteca (es.mentor.unidad4.eje1.biblioteca)
propiamente dicho:
Estudiemos ahora cmo utilizar los recursos (imagen robot.png) y clase (ActividadSecundaria)
que define esta biblioteca.
<TextView
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_marginBottom=5dp
android:text=@string/etiqueta />
<Button
android:id=@+id/boton
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Lanza actividad secundaria
android:layout_gravity=center
android:onClick=lanzaActSec />
<TextView
android:layout_width=match_parent
android:layout_height=wrap_content
android:text=@string/etiqueta2 />
<ImageView
android:layout_width=wrap_content
android:layout_height=wrap_content
U4 Bibliotecas, APIs y Servicios de Android
android:layout_gravity=center
android:layout_margin=10dp
android:src=@drawable/robot />
</LinearLayout>
Lo primero que observamos en el fichero anterior es que la imagen de la biblioteca est disponible
como recurso del proyecto y basta escribir @drawable/robot para acceder a ella.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// Mtodo onClick del botn que lanza la Actividad secundaria
public void lanzaActSec(View v) {
// Aadimos un parmetro a la invocacin
Intent intent = new Intent(this,ActividadSecundaria.class);
intent.putExtra(parametro, Texto del parmetro);
// Lanzamos el Intent esperando su respuesta
startActivityForResult(intent, 1);
}
325
// Evento que se lanza cuando la Actividad secundaria acaba
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
// Si respondemos a la actividad ejecutada
if (requestCode == 1) {
// Mostramos un mensaje en funcin del resultado de la
// Actividad
if (resultCode == RESULT_OK) {
Toast.makeText(this, Actividad secundaria OK,
Toast.LENGTH_LONG).show();
} else
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, Actividad secundaria
CANCELADA, Toast.LENGTH_LONG).show();
}
}
} // end onActivityResult
} // end clase
<application
Aula Mentor
android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/AppTheme android:allowBackup=true>
<activity
android:name=
es.mentor.unidad4.eje1.biblioteca_aplicacion.MainActivity>
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category
android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
<activity android:name=
es.mentor.unidad4.eje1.biblioteca.ActividadSecundaria
android:label=Actividad secundaria/>
</application>
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente aplicacin:
U4 Bibliotecas, APIs y Servicios de Android
3.1 TelephonyManager
3.2 SMSManager
SMSManager, este Gestor permite enviar mensajes cortos (SMS) y leer su formato interno PDU
que significa Protocol Description Unit, que es el formato estndar de los mensajes cortos SMS.
Estudiemos ahora, de forma prctica, cmo utilizar la API de telefona. Es recomendable abrir el
Ejemplo 2 de esta Unidad para seguir la explicacin que se expone a continuacin.
La aplicacin que desarrollamos muestra dos pestaas: Telfono y Mensajes cortos
(SMS). La primera pestaa permite realizar llamadas de telfono y recibir cambios en el estado
del telfono. La segunda, enviar y recibir mensajes SMS.
<android.support.v4.app.FragmentTabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@android:id/tabhost
android:layout_width=match_parent
android:layout_height=match_parent >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
328 android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en
// el archivo activity_main.xml
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la
// pestaa
mTabHost.setup(this, getSupportFragmentManager(),
R.id.tabFrameLayout);
// Evento que se lanza cada vez que se selecciona una pestaa nueva
@Override
public void onTabChanged(String tabId) {
// Mostramos un mensaje al usuario
Toast.makeText(this, Has cambiado a la pestaa + tabId,
Toast.LENGTH_SHORT).show();
}
} // end clase
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical >
<LinearLayout
android:orientation=horizontal
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=5dp>
<Button
android:id=@+id/llamarBtn
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Llamar por telfono/>
<EditText
android:id=@+id/nTelefono
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_weight=1
android:ems=10
android:text=091334455
android:inputType=phone >
</EditText>
</LinearLayout>
330
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Datos Telfono:/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
U4 Bibliotecas, APIs y Servicios de Android
<TextView
android:id=@+id/Log
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceSmall
android:text=Log:/>
</ScrollView>
</LinearLayout>
tv.append(\nTipo telfono: );
// Obtenemos el gestor de la API del telfono
tm = (TelephonyManager)getActivity().getSystemService(
Context.TELEPHONY_SERVICE);
// Definimos el listener de llamadas
miListener = new MiPhoneStateListener();
// Obtenemos el tipo de telfono del dispositivo
int phoneType = tm.getPhoneType();
switch(phoneType){
case TelephonyManager.PHONE_TYPE_NONE:
tv.append(No hay telfono);
break;
case TelephonyManager.PHONE_TYPE_GSM:
tv.append(GSM);
break;
case TelephonyManager.PHONE_TYPE_CDMA:
tv.append(CDMS);
break;
332
case TelephonyManager.PHONE_TYPE_SIP:
tv.append(SIP);
break;
default:
tv.append(Desconocido);
}
@Override
public void onResume() {
super.onResume();
Log.d(Telfono, onResume);
// Indicamos al gestor de la API del telfono que escuche
// los eventos de ste con el listener que hemos creado
// anteriormente
tm.listen(miListener, PhoneStateListener.LISTEN_CALL_STATE);
U4 Bibliotecas, APIs y Servicios de Android
}
// Si deseamos dejar de controlar la llamadas cuando la Actividad
// pase a segundo plano deberamos descomentar el siguiente mtodo
/*@Override
public void onPause() {
super.onPause();
Log.d(Telfono, onPause);
tm.listen(miListener, PhoneStateListener.LISTEN_NONE);
}*/
} // end clase
En el cdigo anterior puedes observar que, al tratar con un Fragmento, hemos empleado el
mtodo onCreateView() para buscar las Vistas.
En este mismo mtodo hemos definido el evento onClick() del botn Llamar por telfono
que crea una Intencin de tipo Intent.ACTION_CALL e inicia la Actividad correspondiente.
Tambin podramos utilizar los tipos ACTION_DIAL o ACTION_VIEW para ver el marcador de
telfono pero sin iniciar la llamada.
334
Despus, hemos obtenido el gestor TelephonyManager de la API del telfono mediante la orden
getSystemService(Context.TELEPHONY_SERVICE) y utilizado sus mtodos extraemos los
datos siguientes:
- getPhoneType(): tipo de telfono del dispositivo.
- getDeviceId(): IMEI del dispositivo.
- getSubscriberId(): n de telfono.
- getSimCountryIso(): pas de la tarjeta SIM.
- getNetworkOperatorName(): nombre del operador de telefona.
- getVoiceMailNumber(): n de buzn de voz.
En este mismo mtodo creamos el listener que se extiende de PhoneStateListener, que recibir
los eventos del telfono y que se lanza cuando cambia el estado de llamada del telfono con
onCallStateChanged(). Cuando esto ocurre, simplemente aadimos un texto al Log de la
interfaz del usuario.
En los eventos onResume() y onDestroy() del fragmento indicamos al gestor de la API
del telfono que escuche o deje de escuchar respectivamente los eventos del telfono con el
listener que hemos creado anteriormente.
Para ello, utilizamos el mtodo listen(PhoneStateListener listener, int
events) del gestor TelephonyManager e indicamos en el segundo parmetro el tipo de evento
que queremos escuchar: LISTEN_CALL_STATE (cambios en el estado de llamada). Tambin es
posible escuchas otro tipo de eventos, como envo de datos, cambios en la potencia de la seal,
informacin de la celda, etctera.
Sin embargo, para detectar llamadas salientes es necesario definir en el mtodo onCrea-
te() del fragmento un receptor de mensajes para el Intent del tipo android.intent.action.
NEW_OUTGOING_CALL mediante la orden registerReceiver(). Hemos incluido el receptor de
mensajes llamado OutGoingCallReceiver del tipo BroadcastReceiver para recibir el evento del
U4 Bibliotecas, APIs y Servicios de Android
sistema operativo cuando se produzca una nueva llamada y se lance el evento onReceive(). En
este caso, aadimos un nuevo mensaje de Log al usuario.
Veamos ahora cmo se implementa el fragmento de la segunda pestaa que gestiona
mensajes cortos (SMS).
Si abres en Eclipse ADT el archivo sms_fragment_layout.xml, vers que contiene
un diseo muy sencillo que consiste en dos cajas de texto (telfono y mensaje corto) con su
respectivo botn para enviar el mensaje y un listado ListView para mostrar los mensajes cortos
recibidos:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical
android:layout_margin=2dp >
<LinearLayout
android:orientation=horizontal
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button android:id=@+id/enviarBoton
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Enviar SMS/>
<TextView android:layout_width=wrap_content
android:textAppearance= 335
?android:attr/textAppearanceMedium
android:layout_margin=4dp
android:layout_height=wrap_content
android:text=Telfono: />
<EditText
android:id=@+id/nTelefono
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_weight=1
android:ems=10
android:text=091334455
android:inputType=phone >
</EditText>
</LinearLayout>
<LinearLayout
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=wrap_content>
<TextView android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Mensaje: />
<EditText android:id=@+id/mensajeET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=prueba de mensaje />
</LinearLayout>
<TextView
android:layout_width=fill_parent
Aula Mentor
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=
?android:attr/textAppearanceMedium
android:text=Mensajes recibidos:/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView
android:id=@+id/listado
android:layout_width=fill_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Definimos un Intent del tipo nueva llamada saliente
String outgoing = android.provider.Telephony.SMS_RECEIVED ;
IntentFilter intentFilter = new IntentFilter(outgoing);
// Registramos un receptor de mensajes para el Intent anterior
getActivity().registerReceiver(IncomingSMSReceiver,
intentFilter);
// Indicamos que el fragmento no debe destruirse
setRetainInstance(true);
// Definimos los campos de la BD de SMS que vamos a
// utilizar y las Vistas donde aparecer el texto
String[] columnas = new String[] { address, body };
int[] nombres = new int[] { R.id.telefono, R.id.sms };
// Indicamos el loader que va a gestionar las actualizaciones
// del listado mediante un Cursor
getActivity().getLoaderManager().initLoader(0, null, this);
U4 Bibliotecas, APIs y Servicios de Android
String str = ;
// Si el mensaje contiene algo
if (bundle != null)
{
// Obtenemos la informacin del SMS recibido.
// El campo PDU significa Protocol Description Unit
// y es el formato estndar de los mensajes cortos
// SMS
Object[] pdus = (Object[]) bundle.get(pdus);
// Pasamos todos los mensajes recibidos en formato
// pdu a una matriz del tipo SmsMessage que contendr
// los mensajes en un formato interno y accesible por
// Android.
// Definimos el tamao de la matriz con el n de
// mensaje recibidos
mensjs = new SmsMessage[pdus.length];
// Recorremos todos los mensajes recibidos
for (int i=0; i<mensjs.length; i++){
// Pasamos el elemento i a la matriz de mensajes
mensjs[i] =
SmsMessage.createFromPdu((byte[])pdus[i]);
// Guardamos en texto plano el mensaje
str += SMS de +
mensjs[i].getOriginatingAddress();
str += . Mensaje: ;
str += mensjs[i].getMessageBody().toString();
str += \n;
338 } // end for
// Mostramos un mensaje indicando que se ha recibido
// el mensaje
Toast.makeText(context, str,
Toast.LENGTH_SHORT).show();
} // end if bundle != null
} // end onReceive()
};
adapter.swapCursor(result);
}
// Evento que se lanza cuando el Loader se destruye o se
// reinicia, es decir, ya no dispone de datos
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
// Indicamos al adaptador que ya no debe utilizar ningn
// cursor
adapter.swapCursor(null);
}
} // end clase
Hay que tener en cuenta que es posible recibir simultneamente varios mensajes
SMS; por lo tanto, el campo pdus de la variable Bundle es de tipo matriz.
Para mostrar todos los mensajes SMS disponibles en el dispositivo, vamos a utilizar un listado de
tipo ListView con su respectivo adaptador del tipo SimpleCursorAdapter. Sin embargo, en lugar
de utilizar un Cursor para cargar los datos tal y como se estudia en el curso de Iniciacin de
Android de Mentor, vamos a emplear la clase Loader. Dada la importancia de esta clase vamos
a estudiarla en detalle.
Esta clase Loader permite cargar datos asncronamente. Monitoriza los datos de la fuente y
muestra nuevos resultados cuando stos cambian. Adems, no es necesario conectar de nuevo
Aula Mentor
con los datos cuando hay cambios de configuracin en el dispositivo (cambio de orientacin).
Esta clase est disponible desde Android 3.0, aunque puede usarse mediante el paquete
de compatibilidad desde la versin 1.6.
Se puede utilizar tanto en un Actividad como en un Fragmento.
Para crear un objeto de tipo Loader debemos invocar el mtodo getLoaderMana-
ger().initLoader(0, null, this).
Cuando el Cursor deja de ser vlido, entonces Android invoca el mtodo onLoaderReset()
para realizar las acciones pertinentes.
Uno de los retos del programador de Android en la gestin de bases de datos es que su
acceso es lento y que hay que tener en cuenta el ciclo de vida de la Actividad correctamente,
por ejemplo, para abrir y cerrar el Cursor si ocurre un cambio de configuracin.
En versiones anteriores a Android 3.0, para gestionar el ciclo de vida se puede utilizar
el mtodo managedQuery(). A partir de esta versin, este mtodo queda obsoleto y debemos
emplear la clase Loader para acceder a un ContentProvider.
La clase SimpleCursorAdapter, que se puede usar con ListView, dispone del m-
todo swapCursor(), que el cargador puede utilizar para actualizar el Cursor en su mtodo
onLoadFinished() del Loader.
U4 Bibliotecas, APIs y Servicios de Android
Es importante crear el objeto del tipo Loader en onCreate() del Fragmento para
que est disponible en el mtodo OnCreateView() posteriormente.
Si te fijas en el cdigo fuente de este fragmento, vers que hemos creado un Loader de tipo
Cursor que se encargar de conectar a la BD y obtener los registros del ContentProvider.
Al crear el Loader se lanza el evento onCreateLoader() que, a su vez, crea un Cur-
sorLoader que accede al contenido content://sms/inbox. Es muy importante indicar en
su constructor los campos que deseamos obtener de la base de datos de mensajes SMS. Es
imprescindible obtener siempre el campo _id de la base de datos ya que Android lo asocia
automticamente al ListView.
Cuando el Loader finaliza la carga los datos, Android invoca el evento onLoadFinis-
hed() y es aqu donde actualizamos la interfaz del usuario indicando el nuevo Cursor al adap-
tador del listado mediante el mtodo swapCursor() de este ltimo.
En el caso de que el Loader se destruya o se reinicie, es decir, de que ya no disponga
de datos, Android invoca el evento onLoaderReset() y es necesario eliminar el Cursor del
adaptador.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga los permisos
necesarios para leer el estado del telfono, realizar llamadas, procesar las llamadas entrantes y
leer, recibir y enviar los mensajes cortos. Para ello, hay que incluir en el fichero AndroidMa-
nifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_PHONE_STATE/>
<uses-permission android:name=android.permission.CALL_PHONE/> 341
<!-- Necesario para detectar la llamadas salientes -->
<uses-permission
android:name=android.permission.PROCESS_OUTGOING_CALLS/>
<uses-permission android:name=android.permission.READ_SMS/>
<uses-permission android:name=android.permission.RECEIVE_SMS/>
<uses-permission android:name=android.permission.SEND_SMS/>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (API Telfono) de la Unidad
4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del progra-
ma anterior, en el que hemos utilizado la API de telefona de Android.
Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente aplica-
cin:
Aula Mentor
342
Para simular una llamada entrante de telfono o la recepcin de un mensaje corto SMS en el
AVD y comprobar que la aplicacin responde correctamente, abrimos la perspectiva de DDMS
de Eclipse ADT y, en la pestaa Emulator Control, en el apartado Telephony Status:
U4 Bibliotecas, APIs y Servicios de Android
Podemos crear una llamada entrante (botn Call) o un mensaje SMS recibido
(botn Send) de forma manual al AVD en ejecucin.
Si despliegas las distintas opciones, vers que es posible configurar el tipo de llamada
recibida y casi cualquier caracterstica de sta.
4. Calendario de Android
El sistema operativo Android dispone de Calendarios donde el usuario puede administrar citas
y recordatorios. Existen dos formas de gestionarlos:
- Mediante Intenciones (Intents), accediendo indirectamente al Calendario mediante Ac-
tividades.
- Utilizando la API oficial, que permite gestionar de forma directa el Calendario del sistema
operativo que Google public en la versin 4.0 y que permite insertar, actualizar y eliminar
calendarios, eventos, recordatorios, etctera. En versiones anteriores a la 4.0 era difcil de-
sarrollar una aplicacin que usase directamente el Calendario, ya que hay que acceder a la
base de datos y sta puede cambiar en funcin del fabricante del dispositivo.
344
Esta tabla contiene el tiempo de inicio y final para cada
evento, donde cada fila representa una nica ocurrencia
del evento. Para los eventos nicos existe una relacin
CalendarContract.Instances
1:1 entre esta tabla y la tabla de eventos. Sin embargo,
para eventos recurrentes se generan varias filas que
corresponden a las repeticiones de ese evento.
En los cuadros principales aparecen los campos que los vinculan entre s.
345
Para poder utilizar la API del Calendario es necesario que la aplicacin tenga los permisos
de lectura y escritura al Calendario del dispositivo. Para ello, hay que incluir en el fichero
AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_CALENDAR/>
<uses-permission android:name=android.permission.WRITE_CALENDAR/>
Veamos ahora las tablas que forman la estructura del modelo de datos del calendario:
Nombre Descripcin
Por razones de simplicidad, en los ejemplos siguientes las consultas se realizan en el proceso
principal de la aplicacin. En la prctica, estas consultas deben hacerse en un hilo asncrono
mediante Loaders o mediante la clase AsyncQueryHandler para modificar datos.
Por ejemplo, para consultar todos los calendarios dados de alta en el dispositivo podemos
escribir las sentencias:
346 Hay que tener en cuenta que el nombre de la cuenta del usuario Calendars.
ACCOUNT_NAME (por ejemplo, usuario@gmail.com) se puede repetir en
diferentes tipos de cuentas ACCOUNT_TYPE.
Existe tambin el tipo de cuenta especial ACCOUNT_TYPE_LOCAL que se usa para crear
calendarios no asociados con la cuenta de Google del dispositivo. Y que no se sincroniza.
Para actualizar un calendario hay que buscarlo primero indicando un criterio de selec-
cin como, por ejemplo, su campo _ID. Fjate en el siguiente ejemplo:
Los Calendarios genricos estn pensados para ser manipulados principalmente mediante un
adaptador de sincronizacin (Sync Adapter); por lo tanto, otras aplicaciones slo pueden hacer
cambios superficiales, como cambiar el nombre de visualizacin (ver ejemplo anterior).
Para crear un calendario local desde una aplicacin en el dispositivo debemos indicar
el tipo de cuenta account_type de ACCOUNT_TYPE_LOCAL. Como hemos comentado, los ca-
U4 Bibliotecas, APIs y Servicios de Android
lendarios de este tipo no se sincronizan con un servidor externo. Fjate en el siguiente ejemplo
de creacin de un calendario local:
Para borrar un calendario permanentemente as como todos sus eventos asociados podemos
ejecutar las siguientes sentencias:
Nombre Descripcin
Nombre Descripcin
Si tenemos en cuenta que para aadir un evento al calendario hay que rellenar muchos campos,
vemos que es recomendable utilizar un Intent para hacerlo. Estudiaremos cmo hacerlo un poco
ms adelante en este apartado.
cv.put(Events.CALENDAR_ID, CAL_ID);
cv.put(Events.TITLE, Ttulo del evento);
cv.put(Events.DTSTART, fecha_inicio_en_milisegundos);
cv.put(Events.DTEND, fecha_fin_en_milisegundos);
cv.put(Events.EVENT_LOCATION, Lugar: mi casa);
U4 Bibliotecas, APIs y Servicios de Android
// Insertamos el evento
getContentResolver().insert(CalendarContract.Events.CONTENT_URI, cv);
Para actualizar un evento hay que buscarlo primero indicando un criterio de seleccin, como,
por ejemplo, su campo Events._ID. Fjate en el siguiente ejemplo:
getContentResolver().update(CalendarContract.Events.CONTENT_URI, cv,
seleccion, seleccionArgs); 349
Nombre Descripcin
Tipo de invitado:
350 ATTENDEE_TYPE TYPE_REQUIRED: requerido
TYPE_OPTIONAL: opcional
De igual forma que hemos hecho en los casos anteriores, es muy sencillo aadir un invitado al
evento con el _ID eventoID:
Campo Descripcin
MINUTES Minutos anteriores a la hora del evento en los que se debe iniciar el recordatorio.
Mtodo del recordatorio:
METHOD_ALERT: alerta
METHOD METHOD_DEFAULT: por defecto
METHOD_EMAIL: correo electrnico
METHOD_SMS: mensaje corto
Es muy sencillo aadir un recordatorio 15 minutos antes al evento con el _ID eventoID:
Campo Descripcin
START_MINUTE Minuto medido desde la media noche de inicio de la instancia del evento.
Aula Mentor
Como hemos comentado, es posible utilizar Intenciones del sistema operativo para gestionar el
calendario. Aunque el uso de estas Intenciones es limitado en comparacin con un Proveedor
de contenido (ContentProvider), tiene las ventajas de que el usuario ya conoce la aplicacin
Calendario de su dispositivo y de que, desde el punto de vista del programador, ya estn
desarrolladas y automatizadas las alarmas, la configuracin de los asistentes, etctera. Adems.
Para utilizar las Intenciones, la aplicacin no necesita permisos adicionales.
Con las Intenciones podemos realizar las siguientes operaciones con el calendario:
- Abrir la aplicacin Calendario en una fecha especfica.
- Ver eventos.
- Crear nuevos eventos.
- Editar eventos existentes.
content://com.android.
calendar/time/ Abre el calendario
VIEW
Tambin podemos utilizar la especificado en una Ninguno.
352 constante: CalendarContract. fecha.
CONTENT_URI.
content://com.android.
calendar/events/
Visualiza un evento en CalendarContract._
VIEW
Tambin podemos utilizar la concreto. ID
constante: Events.CONTENT_
URI.
content://com.android.
calendar/events/
Edita un evento en CalendarContract._
EDIT
Tambin podemos utilizar la concreto. ID
constante: Events.CONTENT_
URI.
content://com.android.
calendar/events
E D I T Cualquiera de los
Tambin podemos utilizar la Crea o edita un evento. campos del siguiente
INSERT listado.
constante:
Events.CONTENT_URI.
U4 Bibliotecas, APIs y Servicios de Android
Si deseamos abrir el calendario por una fecha especfica (por ejemplo, dos meses desde la fecha
actual), podemos escribir las siguientes sentencias Java:
Por ejemplo, para lanzar un Intent que cree un evento en el calendario podemos escribir las
sentencias:
En funcin de las necesidades de la aplicacin, es mejor utilizar Intents o la API del calendario.
Veamos los pros y los contras de cada forma de enfocar el desarrollo:
354
Tipo Ventajas Desventajas
Estudiemos ahora, de forma prctica, cmo utilizar la API del Calendario y sus Intents. Es
recomendable abrir el Ejemplo 3 de esta Unidad para seguir la explicacin que damos.
La aplicacin que desarrollamos permite buscar y borrar eventos de un calendario en
una fecha concreta utilizando la API del calendario. Adems, mediante Intents se pueden aadir
y Editar estos eventos.
U4 Bibliotecas, APIs y Servicios de Android
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:orientation=vertical >
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Selecciona la fecha y busca o gestiona un
evento en el Calendario
android:gravity=center
android:padding=10dip/>
<Spinner
android:id=@+id/spinner1
android:layout_width=match_parent
android:layout_height=wrap_content />
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
android:gravity=center >
<TextView android:id=@+id/fecha
android:layout_width=wrap_content 355
android:layout_height=wrap_content
android:gravity=center
android:text=Sin fecha
android:padding=10dip/>
<Button
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Seleccionar fecha
android:onClick=showDatePickerDialog />
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView android:id=@+id/evento
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center
android:padding=10dip/>
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
Aula Mentor
android:gravity=center >
<Button android:id=@+id/anterior
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Anterior
android:padding=10dip/>
<Button android:id=@+id/siguiente
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Siguiente
android:padding=10dip/>
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
android:gravity=center >
<Button
356 android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Alta evento
android:onClick=altaEvento />
<Button android:id=@+id/borrar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Borrar evento
android:onClick=borrarEvento />
<Button android:id=@+id/editar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Editar evento
android:onClick=editarEvento />
</LinearLayout>
</LinearLayout>
Como puedes observar, la interfaz de usuario es muy sencilla y consta de un selector donde el
usuario elije un calendario. Hay, adems, una serie de botones para ver el siguiente/anterior
evento, dar de alta uno nuevo, editarlo o borrarlo.
Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el
siguiente contenido con la lgica de la aplicacin:
} // end onCreate()
@Override
public void dateDialogFragmentDateSet(int year, int
monthOfYear, int dayOfMonth) {
diaAct=dayOfMonth;
mesAct= monthOfYear;
anioAct=year;
// Buscamos los eventos del nuevo da seleccionado
cargaEventos();
}
}); // end setDateDialogFragmentListener
// Mostramos el selector de fecha
ddf.show(getFragmentManager(), date picker dialog fragment);
}
// El Evento es recurrente
intent.putExtra(Events.RRULE,
FREQ=WEEKLY;COUNT=11;WKST=SU;BYDAY=TU,TH);
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
// Siempre actualizamos los datos independientemente del
// resultado del Intent
if (requestCode == 1) {
cargaEventos();
}
}//onActivityResult
}
// En funcin del botn sobre el que hace clic el usuario
// avanzamos o retrocedemos un registro del cursor
switch(v.getId()) {
case R.id.siguiente:
if(!mCursor.isLast())
mCursor.moveToNext();
break;
case R.id.anterior:
botonSig.setEnabled(false);
if(!mCursor.isFirst())
mCursor.moveToPrevious();
break;
} // en case
// Habilitamos los botones de bsqueda
botonSig.setEnabled(!mCursor.isLast());
botonAnt.setEnabled(!mCursor.isFirst());
// Formateador de fechas
Format df = DateFormat.getDateFormat(this);
Format tf = DateFormat.getTimeFormat(this);
// Variables temporales
String titulo = ;
String ubicacion = ;
Long inicio = 0L;
idEventoAct = -1;
362
// Lee
try {
idEventoAct = mCursor.getInt(0);
titulo = mCursor.getString(1);
inicio = mCursor.getLong(2);
ubicacion = mCursor.getString(3);
} catch (Exception e) {
Log.d(Calendario, Error la leer registro de la BD
de Eventos);
}
// Deshabilitamos los botones Editar y Borrar si no hemos
// encontrado un registro
botonBorrar.setEnabled(idEventoAct > -1);
botonEditar.setEnabled(idEventoAct > -1);
// Mostramos la informacin del Evento al usuario
tvEvento.setText(idEventoAct+: +titulo+ el da +
df.format(inicio) + desde las +
tf.format(inicio) + en + ubicacion);
} // end onClick
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Tambin utilizamos un Cursor para buscar todos los Calendarios
disponibles en el dispositivo y seleccionamos los campos _ID y DISPLAY_NAME. En este caso no
incluimos el campo Calendars.ACCOUNT_TYPE ya que vamos a mostrar todos los calendarios
independientemente de su tipo. A continuacin, recorremos todos los registros del Cursor y
U4 Bibliotecas, APIs y Servicios de Android
pasamos la informacin a una matriz del tipo MiCalendario, que definimos al inicio de esta
clase principal. Finalmente, creamos un adaptador de tipo matriz para asociarlos al Spinner con
los nombres de los Calendarios y definimos su evento OnItemSelected(), que obtiene el objeto
correspondiente cada vez que el usuario selecciona otro calendario.
El mtodo cargaEventos() busca todos los eventos del Calendario y da seleccionados
por el usuario. Para ello, seleccionamos los campos que deseamos mostrar: _ID, ttulo, fecha
inicio/fin y ubicacin. Definimos el criterio de seleccin indicamos el _ID del Calendario y la
fecha de inicio/final de los Eventos. Despus, buscamos los Eventos del Calendario mediante el
mtodo query() del objeto ContentResolver y los ordenamos decrecientemente por fecha
de inicio.
El mtodo showDatePickerDialog() muestra una ventana de dilogo con un selector
de fecha a partir de la clase DateDialogFragment. Adems, definimos su evento dateDialo-
gFragmentDateSet() que se lanza cuando el usuario elige una fecha.
El mtodo altaEvento() y editarEvento() ejecuta un Intent que sirve para dar de
alta un nuevo Evento o editarlo, tal y como hemos visto en la teora anterior. En este caso, lo
ejecutamos con la orden startActivityForResult() para recibir el resultado de su ejecucin
en la actividad principal.
El evento borrarEvento() usa la API del calendario para borrar el evento actual.
Finalmente, el mtodo onClick() se lanza cuando el usuario hace clic en los botones Siguiente/
Anterior Evento. Aqu buscamos el evento correspondiente, mostramos su informacin y
habilitamos o deshabilitamos los botones correspondientes cuando ya no se encuentren ms
registros.
En el archivo DateDialogFragment.java hemos definido un selector de fecha a partir
de la clase DialogFragment. De nuevo volvemos a utilizar un fragmento en lugar de la clsica
ventana de tipo Dialog para mostrar una ventana de dilogo. Si abres este archivo, observars
que tiene este aspecto:
tenga permisos de lectura y escritura al Calendario del dispositivo. Para ello, hay que incluir en
el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_CALENDAR/>
<uses-permission android:name=android.permission.WRITE_CALENDAR/>
Adems, para poder depurar la aplicacin en un dispositivo real de Android, es necesario indicarlo
en el archivo manifest en la etiqueta <application> mediante el atributo android:debuggable=true.
Para poder gestionar calendario en un AVD del SDK de Android es necesario poder dar de alta
una cuenta de Google; sin embargo, el SDK no permite hacerlo de forma sencilla ni compatible
entre todas las versiones. Por lo tanto, es mejor probar esta aplicacin en un dispositivo real de
Android.
Si no lo haces, aparecer esta ventana y no podrs dar de alta una cuenta de Google (por
lo menos en las versiones que existen ahora mismo del SDK de Android):
365
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
366
Recomendamos que pruebes las distintas opciones de Alta y Edicin para ver cmo funcionan
al ejecutarse el Intent correspondiente.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
SERVICE). Para que la aplicacin detecte cundo finaliza la descarga del archivo, hay que definir
un BroadcastReceiver y buscar la accin ACTION_DOWNLOAD_COMPLETE para tratar el archivo.
Hay que tener en cuenta que para utilizar esta clase, la aplicacin debe tener el permiso
de acceso a Internet.
Estudiemos ahora, de forma prctica, cmo utilizar el Gestor de descarga. Es recomendable abrir
el Ejemplo 4 de esta Unidad para seguir la explicacin que damos.
La aplicacin que desarrollamos muestra una caja de texto donde el usuario puede intro-
ducir una URL de una imagen y dos botones: Iniciar descarga y Mostrar descargas.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Gestor de descargas de Android
android:gravity=center
android:padding=10dip/>
<EditText
367
android:id=@+id/urlET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text= http://www.aulamentor.picanya.org/imatges/
horizontal_cometa_mentor.png
android:ems=10 >
<requestFocus />
</EditText>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:gravity=center >
<Button
android:id=@+id/boton1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=descargarImagen
android:text=Iniciar descarga />
<Button
android:id=@+id/boton2
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=verDescargas
android:text=Mostrar descargas />
</LinearLayout>
Aula Mentor
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Imagen descargada:
android:gravity=center
android:padding=10dip/>
<ImageView android:layout_height=wrap_content
android:id=@+id/imagenView
android:src=@drawable/ic_launcher
android:layout_width=fill_parent
android:gravity=center/>
</LinearLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
368 super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
urlET = (EditText)findViewById(R.id.urlET);
// Definimos el BroadcastReceiver para detectar cundo
// finaliza la descarga
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String accion = intent.getAction();
// Si la accin es ACTION_DOWNLOAD_COMPLETE entonces
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.
equals(accion)) {
// Creamos un objeto del tipo Query para poder
// consultar el Gestor de descargas
Query query = new Query();
// Filtramos por el ID de peticin
query.setFilterById(idPeticion);
// Filtramos en el gestor de descarga la consulta
// anterior
Cursor c = dm.query(query);
// Si hay algn registro entonces...
if (c.moveToFirst()) {
// Obtenemos la columna STATUS de la descarga
int columnIndex = c.getColumnIndex(
DownloadManager.COLUMN_STATUS);
// Si el STATUS de la descarga es CORRECTO
// entonces
if (DownloadManager.STATUS_SUCCESSFUL ==
U4 Bibliotecas, APIs y Servicios de Android
c.getInt(columnIndex)) {
// Localizamos la imagen de la interfaz
// del usuario
ImageView view = (ImageView)
findViewById(R.id.imagenView);
// Obtenemos la URI del archivo en local
String uriString = c.getString(
c.getColumnIndex(
DownloadManager.COLUMN_LOCAL_URI));
// Cargamos la nueva imagen
view.setImageURI(Uri.parse(uriString));
}
} // end if hay algn registro
}
} // end onReceive
}; // end BroadcastReceiver
// Registramos el BroadcastReceiver
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
} // end onCreate
Como puedes observar, el cdigo fuente es muy sencillo: simplemente utilizamos un objeto
del tipo DownloadManager y definimos el BroadcastReceiver para recibir el mensaje
correspondiente cuando acaba la descarga de la imagen y mostrarla en la interfaz de usuario.
Para mostrar todas las descargas del gestor, utilizamos un Intent del tipo ACTION_VIEW_
DOWNLOADS y lo ejecutamos.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a Internet. Para ello, hay que incluir en el fichero AndroidManifest.xml la siguiente
etiqueta:
<uses-permission android:name=android.permission.INTERNET/>
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 4 en el AVD, vers que se muestra la siguiente aplicacin:
370
Si pulsas el botn Mostrar descargas vers que aparecen todas las descargas del gestor:
U4 Bibliotecas, APIs y Servicios de Android
OAuth (Open Authorization) es un protocolo abierto, que permite a un usuario del sitio A
compartir la informacin de este sitio (proveedor de servicio) con el sitio B (llamado consumidor)
sin compartir toda su identidad compartiendo un token o palabra clave.
Google dispone de soporte para la autenticacin de Gmail utilizando OAuth 2.0. Esto
evita la necesidad de disponer del nombre de usuario y contrasea de la cuenta.
Funciona de la siguiente manera: el usuario autoriza que una aplicacin que implementa
este protocolo acceda a Gmail; entonces, Gmail le transmite un token (palabra clave) a esta
aplicacin para que pueda acceder al correo durante cierto tiempo. La ventaja de este mtodo 371
es que la aplicacin no necesita conocer el usuario y la contrasea de la cuenta, por lo que es
un mtodo ms seguro de autenticacin.
Puedes encontrar ms informacin sobre este tipo de autenticacin en la ayuda de An-
droid en Internet en este enlace.
Como has visto, Android permite enviar mensajes de correo electrnico desde una aplicacin
local; sin embargo, a veces, es necesario que la aplicacin pueda enviar mensajes sin la
intervencin del usuario del dispositivo.
El SDK de Android no incluye de forma nativa una API que permita enviar correos elec-
trnicos. No obstante, podemos utilizar la API JavaMail, que est desarrollada especficamente
para Android.
En el siguiente ejemplo veremos cmo se utiliza.
Aula Mentor
Estudiemos ahora, de forma prctica, cmo utilizar la API JavaMail y/o un Intent para
enviar correos electrnicos. Es recomendable abrir el Ejemplo 5 de esta Unidad para seguir la
explicacin que se ofrece.
La aplicacin que desarrollamos permite enviar un sencillo correo electrnico utilizando
uno de los dos mecanismos.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal
que muestra tres cajas de texto (destinatario, asunto y mensaje) y dos botones (enviar por In-
tent y enviar por JavaMail):
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/linearLayout1
android:layout_width=fill_parent
android:layout_height=fill_parent
android:orientation=vertical >
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Destinatario:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/destinatarioET
android:layout_width=fill_parent
372 android:layout_height=wrap_content
android:inputType=textEmailAddress >
<requestFocus />
</EditText>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Asunto:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/asuntoET
android:layout_width=fill_parent
android:layout_height=wrap_content />
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Mensaje:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/mensajeET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=top
android:inputType=textMultiLine
U4 Bibliotecas, APIs y Servicios de Android
android:lines=5 />
<Button
android:id=@+id/enviarIntent
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Enviar x Intent />
<Button
android:id=@+id/enviarSMTP
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Enviar x JavaMail />
</LinearLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
final Button enviarSMTP = (Button)
this.findViewById(R.id.enviarSMTP);
final Button enviarIntent = (Button)
findViewById(R.id.enviarIntent);
destinatarioET = (EditText) findViewById(R.id.destinatarioET);
asuntoET = (EditText) findViewById(R.id.asuntoET);
mensajeET = (EditText) findViewById(R.id.mensajeET);
destinatarios = new String[]{
destinatarioET.getText().toString()};
// Definimos el evento onClick de los botones
enviarSMTP.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Creamos el objeto que enva el email
m = new MailSender(usuario, clave);
// Cargamos la matriz de destinatarios
String[] destinatarios = {
destinatarioET.getText().toString().trim()};
m.setDestinatarios(destinatarios);
// Indicamos quin enva el correo
m.setDe(usuario);
Aula Mentor
@Override
protected void onPreExecute()
{
// Deshabilitamos el botn de enviar
enviarSMTP.setEnabled(false);
Log.d(Debug, onPreExecute());
}
@Override
protected Void doInBackground(Void... params)
{
Log.d(Debug, doInBackground() -- Enviamos
el mensaje por SMTP);
374 try {
// Si quisiramos aadir un fichero
// adjunto descomentaramos la siguiente
// sentencia
//m.addAttachment(/sdcard/imagen.jpg);
// Si enviamos el mensaje...
if(m.send()) {
Log.d(Debug, Enviado OK);
resultado= El correo se ha enviado
correctamente.;
} // Si no lo enviamos...
else {
Log.d(Debug, Enviado Error);
resultado=Ha ocurrido un error al
enviar el mensaje!;
}
} catch(Exception e) {
// Si ocurre una excepcin, guardamos su
// informacin
Log.d(Debug, Excepcin +
e.getMessage().toString());
resultado= Error al enviar el mensaje:
+ e.getMessage().toString();
}
return null;
}
@Override
protected void onPostExecute(Void res)
U4 Bibliotecas, APIs y Servicios de Android
{
Log.d(Debug, onPostExecute());
// Habilitamos el botn de enviar y mostramos
// un mensaje con el resultado
enviarSMTP.setEnabled(true);
Toast.makeText(MainActivity.this, resultado,
Toast.LENGTH_LONG).show();
}
}.execute(); // end AsyncTask
}
}); // end onClick botn
enviarIntent.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Utilizamos un Intent para crear el mensaje
Intent email = new Intent(Intent.ACTION_SEND);
// Cargamos la matriz de destinatarios
String[] destinatarios = {
destinatarioET.getText().toString().trim()};
email.putExtra(Intent.EXTRA_EMAIL, destinatarios);
// Podemos incluir en copia o en copia oculta a ms
// destinatarios
//email.putExtra(Intent.EXTRA_CC,new String[]{to});
//email.putExtra(Intent.EXTRA_BCC,
new String[]{to});
email.putExtra(Intent.EXTRA_SUBJECT, 375
asuntoET.getText().toString());
email.putExtra(Intent.EXTRA_TEXT,
mensajeET.getText().toString());
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para definir el evento
onClick() de ambos botones.
El botn enviarSMTP enva un correo electrnico mediante la API JavaMail. Para ello,
utilizamos un objeto de la clase local MailSender que se define en otro archivo y que sirve de
interfaz a esta biblioteca.
Aula Mentor
Para enviar un correo mediante la API JavaMail hay que utilizar un hilo separado,
pues si no lo hacemos, salta la excepcin android.os.NetworkOnMainThreadE
xception, ya que a partir de la versin 3.0 de Android no podemos utilizar la co-
nexin de red en el hilo principal porque lo bloquea.
El botn enviarIntent enva un correo electrnico creando un Intent del tipo message/
rfc822 con la accin ACTION_SEND, en la que completamos los datos EXTRA_EMAIL (destinatario),
EXTRA_SUBJECT (asunto) y EXTRA_TEXT (cuerpo del mensaje). Posteriormente, con la orden
Intent.createChooser() mostramos un ventana de dilogo donde el usuario puede elegir la
aplicacin con la que desea completar la accin.
Como hemos comentado, en el fichero MailSender.java hemos definido una clase que
sirve de interfaz con la API MailJava y que se extiende de la clase javax.mail.Authenticator.
Conozcamos su contenido:
debug = false;
// Indica si autenticamos por SMTP
autenticado = true;
// Variable utilizada para cargar los ficheros adjuntos
multipart = new MimeMultipart();
// Sentencias necesarias para que se puedan cargar ficheros
// adjuntos. Se trata de algo interno de
// la biblioteca javax.mail.Authenticator
MailcapCommandMap mc = (MailcapCommandMap)
CommandMap.getDefaultCommandMap();
mc.addMailcap(text/html;; x-java-content-
handler=com.sun.mail.handlers.text_html);
mc.addMailcap(text/xml;; x-java-content-
handler=com.sun.mail.handlers.text_xml);
mc.addMailcap(text/plain;; x-java-content-
handler=com.sun.mail.handlers.text_plain);
mc.addMailcap(multipart/*;; x-java-content-
handler=com.sun.mail.handlers.multipart_mixed);
mc.addMailcap(message/rfc822;; x-java-content-
handler=com.sun.mail.handlers.message_rfc822);
CommandMap.setDefaultCommandMap(mc);
}
// Contructor donde aadimos el usuario y la clave
public MailSender(String usuario, String clave) {
// Invocamos al constructor genrico
this(); 377
this.usuario = usuario;
this.clave = clave;
}
// Mtodo que enva un mensaje y crear excepciones
public boolean send() throws Exception {
// Establecemos las propiedades de la sesin
Properties props = setProperties();
// Si tenemos cuenta de usuario, su clave, destinatarios,
// asunto y cuerpo del mensaje...
if(!usuario.equals() && !clave.equals() &&
destinatarios.length > 0 && !de.equals() &&
!asunto.equals() && !cuerpo.equals()) {
// Creamos una sesin con las propiedades anteriores
Session session = Session.getInstance(props, this);
// Creamos un eMail en el objeto siguiente
MimeMessage msg = new MimeMessage(session);
// Aadimos los campos De
msg.setFrom(new InternetAddress(de));
// Indicamos los destinatarios
InternetAddress[] addressTo = new
InternetAddress[destinatarios.length];
for (int i = 0; i < destinatarios.length; i++) {
addressTo[i] = new
InternetAddress(destinatarios[i]);
}
Aula Mentor
msg.setRecipients(MimeMessage.RecipientType.TO,
addressTo);
// Indicamos asunto y fecha de envo
msg.setSubject(asunto);
msg.setSentDate(new Date());
// Cargamos el cuerpo del mensaje en formato MIME
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(cuerpo);
multipart.addBodyPart(messageBodyPart);
msg.setContent(multipart);
// Enviamos el mensaje
Transport.send(msg);
return true;
} else {
return false;
}
}
El mtodo que realiza todo el trabajo enviando un mensaje y creando las excepciones (cuando
es necesario) es send(). Aqu puedes observar que utilizamos las clases de JavaMail siguientes:
- Session: crea una sesin con las propiedades anteriores.
- MimeMessage: compone un mensaje de tipo MIME. Sus mtodos setFrom() indica el remi-
tente, setRecipients() establece los destinatarios, setSubject(), el asunto del mensaje, 379
setSentDate(), la fecha del envo y setContent() aade el cuerpo del mensaje y los
ficheros adjuntos en formato multipart.
- MimeMultipart: gestiona el cuerpo del mensaje y los ficheros adjunto.
- InternetAddress: genera una direccin de correo electrnico vlida.
- BodyPart: se usa para crear el cuerpo del mensaje con setText() y/o adjuntar un archivo
con setFileName().
- Transport: sirve para iniciar la conexin y enviar el correo electrnico.
- DataSource: se utiliza para leer archivos del dispositivo que se adjuntarn al mensaje.
- PasswordAuthentication: clase que implementa el mtodo de autenticacin.
Para poder utilizar las clases anteriores es necesario copiar los archivos .jar que forman la
biblioteca JavaMail en el directorio libs del proyecto e importarlas al mismo, tal y como ya
hemos hecho anteriormente en el curso:
Aula Mentor
380
Finalmente, acabamos la explicacin de todo el cdigo indicando que, para poder ejecutar esta
aplicacin, es necesario que tenga permisos de acceso a Internet. Para ello, hay que incluir en
el fichero AndroidManifest.xml la siguiente etiqueta:
<uses-permission android:name= android.permission.INTERNET/>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 5 (Enviar Correo electrni-
co) de la Unidad 4. Estudia el cdigo fuente y ejectalo en un dispositivo fsico de
Android para ver el resultado del programa anterior.
Para poder enviar correos electrnicos, es necesario tener acceso directo a Internet; por lo tanto,
es mejor probar esta aplicacin en un dispositivo real de Android.
U4 Bibliotecas, APIs y Servicios de Android
Si ejecutas en Eclipse ADT este Ejemplo 5 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
381
Si pulsas en el botn Enviar x Intent, vers que el sistema te ofrece un listado con aplicaciones
para completar la accin (dependiendo del dispositivo aparecern ms o menos):
Aula Mentor
Te recomendamos que pruebes las distintas opciones de envo de correo electrnico para ver
cmo funcionan.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Para que funcione esta aplicacin con tu cuenta de Gmail es necesario que, en su configuracin,
tengas habilitado el Acceso IMAP. Mira cmo puedes configurarlo.
382
Como ya sabes, la plataforma Android ofrece una gran cantidad de servicios predefinidos en
el sistema a los que podemos acceder a travs de las clases de tipo Manager. En una Actividad
podemos acceder a estos servicios a travs del mtodo getSystemService().
Sin embargo, si una aplicacin Android define sus propios Servicios, stos deben declarase en
U4 Bibliotecas, APIs y Servicios de Android
Un servicio puede funcionar de las dos formas anteriores simultneamente, es decir, se puede
arrancar en modo Autnomo (de manera indefinida) y tambin en modo Ligado. Simplemente 383
hay que implementar los mtodos onStartCommand() para el modo Autnomo y onBind()
para el modo Ligado.
Cualquier componente de una aplicacin puede iniciar un servicio. Incluso un compo-
nente de otra aplicacin distinta a la que define el servicio tambin puede iniciarlo de la misma
forma que iniciaramos una Actividad de otra aplicacin mediante Intenciones.
Tambin se puede declarar un servicio como privado en la aplicacin, en el archivo An-
droidManifest.xml, y bloquear el acceso desde otras aplicaciones.
Los servicios tienen que ser declarados en el archivo AndroidManifest.xml con la etiqueta
<service android:name=nombreClase> </service> y la implementacin de la clase
debe heredarse de la clase Service.
Una aplicacin puede declarar su propio servicio para llevar a cabo operaciones que tarden en
ejecutarse y no necesiten interactuar con el usuario, o para suministrar una nueva funcionalidad
a otras aplicaciones.
A continuacin, se muestra un esquema con los mtodos que invoca Android cuando
Aula Mentor
384
Una Actividad puede iniciar un servicio en modo Autnomo a travs del mtodo StartService()
y detenerlo mediante el mtodo StopService(). Cuando lo hacemos, Android invoca su mtodo
onCreate(); despus, se invoca el mtodo onStartCommand() con los datos proporcionados
por la Intencin de la actividad.
Si la actividad quiere interactuar con un servicio (modo Dependiente o Ligado) para, por
ejemplo, mostrar el progreso de una operacin, puede utilizar el mtodo bindService(). Para
esto, hay que usar el objeto ServiceConnection, que permite conectarse al servicio y devuelve
un objeto de tipo IBinder, que la actividad puede utilizar para comunicar con el servicio. Ms
adelante veremos en detalle cmo definir servicios en modo Ligado dentro de las aplicaciones
Android.
Una vez repasados los conceptos de servicios, vamos a aprender un nuevo tipo de ser-
vicio: Intent Service.
U4 Bibliotecas, APIs y Servicios de Android
Hemos estudiado que, normalmente, la clase base para crear Servicios es la Service. Sin
embargo, existe tambin la clase IntentService que podemos utilizar para crear Servicios.
Podemos utilizar la clase IntentService para realizar ciertas tareas en segundo plano, como,
por ejemplo, enviar un mensaje de correo electrnico. La diferencia respecto a la clase Service
es que la instancia del IntentService termina automticamente una vez finaliza la
ejecucin de su tarea.
La clase IntentService dispone del mtodo onHandleIntent(), que ser invocado de
forma asncrona por el sistema y es donde debemos implementar la funcionalidad de este tipo
de servicio.
En este apartado vamos a ver qu ocurre cuando se bloquea el hilo principal con un servicio y
cmo podemos solucionarlo mediante la clase IntentService.
Este ejemplo muestra una caja de texto donde el usuario puede introducir un nmero entero y 385
la aplicacin, utilizando un servicio, le indica si el nmero introducido es primo.
En matemticas, un nmero primo es un nmero natural mayor que 1 que tiene nicamente dos
divisores distintos: l mismo y el 1. Los nmeros primos se contraponen as a los compuestos,
que son aquellos que tienen algn divisor natural aparte de s mismos y del 1. El nmero 1, por
convenio, no se considera ni primo ni compuesto.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content >
<EditText
android:id=@+id/entrada
android:layout_width=0dip
android:layout_height=wrap_content
android:layout_weight=1
android:inputType=numberSigned
android:text=2013 >
<requestFocus/>
</EditText>
Aula Mentor
<Button
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=calcularOperacion
android:text=Es un nmero primo?/>
</LinearLayout>
<TextView
android:id=@+id/salida
android:layout_width=match_parent
android:layout_height=match_parent
android:text=
android:textAppearance=?android:attr/textAppearanceMedium/>
</LinearLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
// Mtodo tpico que busca las Vistas que vamos a utilizar
// despus
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
U4 Bibliotecas, APIs y Servicios de Android
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, registramos un receptor de mensajes cuando utilizamos
el servicio del tipo IntentService.
El mtodo calcularOperacion() se lanza cuando el usuario hace clic en el botn de 387
la Actividad y crea un Intent para lanzar el servicio que calcula si el nmero introducido es
primo o no.
Tambin utilizamos la clase receptorServicio del tipo BroadcastReceiver para re-
cibir mensajes del servicio IntentService.
En el archivo ServicioOperacion.java hemos definido el servicio que utilizamos en
la Actividad principal:
NO es primo + \n);
else MainActivity.salida.append(El nmero + n + ES primo +
\n);
// Paramos un tiempo la ejecucin
SystemClock.sleep(5000);
// Si queremos que la parada dure ms, entonces descomenta la
// siguiente lnea
//SystemClock.sleep(20000);
// El servicio es del siguiente tipo
return START_STICKY;
}
Si te fijas en el cdigo fuente anterior, hemos definido el servicio a partir de la clase Service e
implementado su mtodo onStartCommand() que realiza el clculo para verificar si el nmero
es primo con el algoritmo del mtodo esPrimo(). Ms adelante veremos para qu sirve el resto
del cdigo.
Finalmente, acabamos la explicacin de todo el cdigo indicando que, para poder ejecu-
tar el servicio, hay que incluirlo en el fichero AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (Intent Service) de la Uni-
dad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Intent Service de Android.
Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente
aplicacin:
389
Veamos ahora el resto del cdigo fuente del servicio. Como sabes, a la hora de desarrollar
aplicaciones en Android hay que tener en cuenta que todos los componentes (actividades,
Aula Mentor
390
Es decir, como el servicio anterior se est utilizando en el hilo principal, se est parando este
hilo y el sistema operativo indica que la aplicacin no responde.
Veamos lo que ocurre si utilizamos un servicio IntentService que, recordamos, se ejecuta en
un hilo distinto al principal.
Para ello, basta con cambiar el tipo de servicio extendiendo su clase de IntentService
y descomentando los mtodos ServicioOperacion() (contructor de la clase) y onHandleIn-
tent() (invocado cuando arranca el IntentService). Adems, hay que comentar el mtodo
onStartCommand().
U4 Bibliotecas, APIs y Servicios de Android
Tampoco es posible utilizar la clase Toast de mensajes flotantes desde otros hilos.
Como el nuevo hilo del IntentService que hemos creado pertenece al mismo proceso que el
hilo principal, podemos compartir entre ellos todas las variables. Es decir, podramos implementar
un nuevo mtodo o una variable pblica para devolver el valor de la funcin esPrimo(), tanto
en la clase del servicio como en la clase de la actividad. Sin embargo, hemos resuelto este
inconveniente utilizando un mecanismo ms elegante: receptores de mensajes.
Si modificamos el tiempo de retardo a 25 segundos para este IntentService y ejecuta-
mos de nuevo la aplicacin, veremos que el sistema no muestra ningn error (aunque s tarda
en dar el resultado):
391
Aula Mentor
A continuacin, vamos a ampliar los conocimientos sobre servicios estudiando las distintas
formas de comunicarnos con ellos.
Existen varias maneras de comunicacin bidireccional entre una actividad y un servicio. En esta
seccin veremos las diferentes formas y daremos recomendaciones sobre su uso. A continuacin,
se muestra el listado:
- Actividad ligada (bind) al servicio
Ya hemos comentado anteriormente que, si el servicio se inicia en el mismo proceso que la
actividad, sta puede conectarse directamente al servicio. Esta es una manera relativamente
sencilla y eficiente para la comunicacin. Es importante no realizar operaciones comple-
jas que bloqueen el hilo principal.
- Handler y Messenger
Si el Servicio debe comunicar informacin a la Actividad, sta puede recibirla mediante un
objeto de tipo Message mediante un Intent. En el Ejemplo 7 ( Juego) de la Unidad 3
392 hemos visto cmo utilizar este mecanismo de comunicacin.
Si es la Actividad la que quiere comunicarse con un servicio, podemos usar la clase
Messenger. Esta clase es parcelable, se puede delegar en otro proceso, es decir, tambin
sirve para enviar objetos del tipo Messages al Handler de la Actividad.
Messenger dispone del mtodo getBinder(), que permite a la Actividad enviar
mensajes al servicio.
Este mecanismo se debe emplear slo si es necesario que el servicio est dispo-
nible a otras aplicaciones; de lo contrario deberamos utilizar un servicio definido
localmente.
Este ejemplo muestra dos cajas de texto donde el usuario puede introducir dos nmeros enteros
y la aplicacin, utilizando un servicio de tipo AIDL, le indica el resultado de multiplicar ambos
nmeros.
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent
android:gravity=center_horizontal >
<TextView android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Ejemplo 7: Servicio AIDL
android:textSize=22sp/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
393
android:layout_marginBottom=6dip
android:background=#000000 />
<EditText
android:id=@+id/valor1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:hint=Nmero 1 />
<TextView android:layout_width=wrap_content
android:gravity=center
android:layout_height=wrap_content android:text=x
android:textSize=36sp/>
<EditText
android:id=@+id/valor2
android:layout_width=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:layout_height=wrap_content
android:hint=Nmero 2/>
<Button android:id=@+id/botonCalc
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text==/>
Aula Mentor
<TextView
android:id=@+id/resultado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado
android:textSize=22sp />
</LinearLayout>
// Declaramos la interfaz.
interface IMultiplicacionServicio {
// Los tipos de datos utilizados pueden ser del tipo int, boolean,
// etc.
int multiplicar(in int valor1, in int valor2);
}
Como puedes observar, simplemente definimos la interface del servicio con los mtodos
disponibles. En este caso, el mtodo multiplicar.
Los parmetros de los mtodos definidos en la interfaz tienen una etiqueta que marca el cometido
del mismo. Puede contener in (entrada), out (salida) o inout (entrada/salida).
- Por defecto, AIDL soporta los siguientes tipos de datos:
- Todas las primitivas del lenguaje de programacin Java (como int, long, char, boolean,
394 etctera).
- String
- CharSequence
- List (todos los elementos de la lista deben ser uno de los tipos de datos soportados ante-
riores).
- Map (todos los elementos de la lista deben ser uno de los tipos de datos soportados anterio-
res).
- Todas las clases que sean parceables (que se puede partir o serializar).
Ahora vamos a definir el Stub del servicio, es decir, el bloque del cdigo que implementa la
funcin anterior. Si abrimos el archivo ServicioOperacion.java vemos que contiene:
@Override
public void onDestroy() {
super.onDestroy();
}
} // end clase servicio
servicio = null;
// Mostramos un mensaje al usuario
Log.d(AIDL, onServiceDisconnected() desconectado);
Toast.makeText(MainActivity.this, Service remoto
desconectado, Toast.LENGTH_LONG).show();
}
} // end clase ServicioOperacionConexion
@Override
public void onCreate(Bundle savedInstanceState) {
// Mtodo tpico que busca las Vistas que vamos a utilizar
// despus
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
resultado = (TextView) findViewById(R.id.resultado);
valor1 = (EditText) findViewById(R.id.valor1);
valor2 = (EditText) findViewById(R.id.valor2);
boton = (Button) findViewById(R.id.botonCalc);
// Definimos el onClick del botn
boton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int v1, v2, res = -1;
// Validamos que el usuario ha introducido los operandos
if (valor1.getText().toString().isEmpty()
|| valor2.getText().toString().isEmpty()) {
Toast.makeText(MainActivity.this,
ERROR: debes introducir al menos dos
nmero enteros.,
Toast.LENGTH_SHORT).show();
return;
U4 Bibliotecas, APIs y Servicios de Android
}
// Buscamos el valor de los operandos
v1 = Integer.parseInt(valor1.getText().toString());
v2 = Integer.parseInt(valor2.getText().toString());
// Invocamos el servicio
try {
res = servicio.multiplicar(v1, v2);
} catch (RemoteException e) {
Log.d(AIDL, Error al invocar el servicio: + e);
e.printStackTrace();
}
// Mostramos el resultado
resultado.setText(String.valueOf(res));
}
}); // end evento onClick botn
// Iniciamos el servicio
iniciarServicio();
} // end onCreate()
@Override
protected void onDestroy() {
// Al acabar la Actividad liberamos el servicio
pararServicio();
super.onDestroy();
}
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, definimos el evento onClick() del botn de la
aplicacin y conectamos con el servicio AIDL.
El mtodo iniciarServicio() liga la Actividad con el servicio. Para ello, definimos la
conexin del servicio mediante la clase local ServicioOperacionConexion y un Intent en el
que indicamos la clase que define el servicio en la orden bindService().
La clase ServicioOperacionConexion se implementa a partir de la clase ServiceCon-
nection y sus mtodos onServiceConnected() y onServiceDiconnected(), que obtiene
el cdigo Stub del servicio remoto haciendo un typecasting del servicio definido en el archivo
IMultiplicacionServicio.aidl. As, ya tenemos acceso al mtodo multiplicar del citado
servicio.
Una aplicacin que quisiera conectarse a este servicio debe contar con el archivo
.aidl correcto e implementar su clase ServiceConnection correspondiente.
Finalmente, y como es habitual, para poder ejecutar el servicio hay que incluirlo en el fichero
AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>
Aula Mentor
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (Servicio AIDL) de la Uni-
dad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Servicio AIDL de Android.
Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente
aplicacin:
398
El alumno debe tener en cuenta que los servicios SOAP pueden ser complejos. El
objetivo de este apartado es demostrar cmo usarlos en una aplicacin Android.
U4 Bibliotecas, APIs y Servicios de Android
Lo primero que vamos a exponer es cmo incluir las bibliotecas necesarias en Eclipse ADT
para poder desarrollar un servidor de aplicaciones SOAP en Java.
Para ello, hacemos clic en la opcin del men Help -> Install New Software de
Eclipse ADT. En la ventana que aparece a continuacin hay que seleccionar la opcin --All
Available Sites-- en el campo Work with:
399
401
Pulsamos el botn Yes para que Eclipse ADT se reinicie.
Una vez se haya reiniciado Eclipse ADT, vamos a configurar el servidor Web de apli-
caciones Tomcat. Para ello, hacemos clic en la opcin del men Windows -> Preferences y
seleccionamos el elemento Server-Runtime Environment:
Aula Mentor
Si pulsamos el botn Add aparece una ventana donde tenemos que seleccionar Apache
Tomcat v7.0; despus, pulsamos el botn Next:
402
A continuacin, aparece una nueva ventana superpuesta sobre la anterior. Hay que pulsar el
botn Finish para instalar el servidor Tomcat:
403
Una vez acaba la instalacin, aparecer el nuevo servidor Tomcat como disponible en las
opciones de Eclipse ADT:
Aula Mentor
Y en Eclipse ADT aparece tambin el nuevo servidor como si se tratara de un proyecto ms:
Seguidamente, vamos a crear el servicio SOAP en el servidor de aplicaciones Tomcat que hemos
creado en el paso anterior.
Hacemos clic en la opcin File > New -> Other del men principal de Eclipse ADT y
seleccionamos la creacin de un nuevo proyecto del tipo Dynamic Web Proyect:
404
U4 Bibliotecas, APIs y Servicios de Android
405
En la siguiente ventana podemos modificar el directorio donde se almacena los contenidos Web
e indicar que se cree el archivo web.xml que describe el servidor:
Finalmente, pulsamos el botn Finish y Eclipse ADT ofrecer cambiar la perspectiva a Java
EE. Es recomendable hacerlo para gestionar el servidor de forma ms sencilla.
406
Ahora que ya hemos creado el proyecto del servicio SOAP, hacemos clic con el botn derecho
del ratn sobre el proyecto y seleccionamos la opcin New -> Package:
U4 Bibliotecas, APIs y Servicios de Android
Rellenamos la ventana anterior con el nombre del paquete y pulsamos Finish para crearla.
Dentro del paquete anterior, con la opcin New -> Class creamos la clase que implementar el
servicio SOAP. Completamos la ventana tal y como aparece en la siguiente imagen:
407
} // end WebService
Como puedes ver, se trata de un sencillo mtodo que cambia un valor temperatura de las
unidades Fahrenheit a Celsius.
Despus, vamos a crear el servicio SOAP haciendo clic con el botn derecho del ratn
sobre el proyecto y en la opcin File > New -> Other del men principal de Eclipse ADT
seleccionamos Web Service:
Aula Mentor
408
U4 Bibliotecas, APIs y Servicios de Android
Si pulsamos el botn Finish, aparecer la siguiente ventana donde podemos iniciar el servicio:
409
Una vez se ha creado el servicio SOAP, podemos ver su definicin en el archivo ServidorSoap.
wsdl del proyecto:
Aula Mentor
En el caso de que no hayamos iniciado el servicio con el botn Start Server anterior, podemos
hacerlo desde el proyecto con el botn derecho del ratn en la opcin Run as > Run on
Server:
410
411
Al ejecutar el servicio, el sistema operativo Windows puede mostrar ventanas de este tipo
indicando que es necesario desbloquear Eclipse ADT para que funcione como servidor.
Aula Mentor
Pulsaremos el botn Desbloquear y veremos en Eclipse ADT que el servicio SOAP se ejecuta
correctamente:
Adems, observars que se abre en Eclipse ADT una ventana interna con un navegador
donde podemos probar el servicio SOAP anterior, este es el cliente de pruebas (http://
localhost:8080/unidad4.eje8.soapClient/sampleWebServiceProxy/TestClient.
jsp):
412
En la fecha de redaccin del curso el SDK de Android no incluye ningn soporte para el acceso
a servicios Web de tipo SOAP.
Para poder utilizarlo, vamos a emplear la popular biblioteca externa ksoap2-android.
Esta biblioteca, que es un fork (migracin) de la biblioteca kSOAP2, especialmente adaptada para
Android, nos permitir acceder a servicios Web que utilicen el estndar SOAP.
La ltima versin de esta biblioteca en el momento de escribir este texto es la 3.0.0. Puedes
descargarla deeste enlace.
Este ejemplo muestra una caja de texto donde el usuario puede introducir una tempe-
ratura en unidades Fahrenheit y la aplicacin, utilizando un servicio SOAP, obtiene su valor en
unidades Celsius.
U4 Bibliotecas, APIs y Servicios de Android
Estudiemos ahora, de forma prctica, cmo utilizar esta biblioteca SOAP. Es recomendable abrir
el Ejemplo 8 de esta Unidad para entender la explicacin siguiente.
Ya hemos estudiado en el curso cmo incluir bibliotecas externas en proyectos Android. Basta
con copiar su archivo .jar en la carpeta libs del proyecto e importarla al proyecto.
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent
android:gravity=center_horizontal >
<TextView android:layout_width=fill_parent
android:layout_height=wrap_content android:text=Ejemplo 8:
Servicio SOAP
android:textSize=22sp/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<EditText
android:id=@+id/grados
android:layout_width=wrap_content
android:layout_height=wrap_content 413
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:hint=Grados fahrenheit />
<Button android:id=@+id/botonCalc
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Obtener grados Celsius/>
<TextView
android:id=@+id/resultado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado
android:textSize=22sp />
</LinearLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
// Mtodo tpico que busca las Vistas que vamos a utilizar
// despus
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
resultado = (TextView) findViewById(R.id.resultado);
grados = (EditText) findViewById(R.id.grados);
boton = (Button) findViewById(R.id.botonCalc);
// Definimos el onClick del botn
boton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int valor = -1;
// Validamos que el usuario ha introducido los operandos
if (grados.getText().toString().isEmpty()) {
Toast.makeText(MainActivity.this,
ERROR: debes introducir los grados.,
Toast.LENGTH_SHORT).show();
return;
}
// Buscamos los grados introducidos
valor = Integer.parseInt(grados.getText().toString());
// Invocamos el servicio a travs de una tarea asncrona
// (Puede tardar en responder el servidor y no queremos
// bloquear el hilo principal de la aplicacin!)
TareaWebService tarea = new TareaWebService();
414 // Le pasamos como parmetro los grados introducidos por
// el usuario
tarea.execute(valor);
}
}); // end evento onClick botn
} // end onCreate()
// El nombre del espacio del servicio SOAP
final String NAMESPACE =
http://soap.eje8.unidad4.mentor.es;
// La URL a la que debemos conectarnos
// ES NECESARIO CAMBIAR LA IP A TU ORDENADOR!!!!
final String URL=http://10.X.X.X:8080/unidad4.eje8.soap/
services/WebService?wsdl;
// Mtodo que vamos a invocar
final String METHOD_NAME = getGradosCelsius;
U4 Bibliotecas, APIs y Servicios de Android
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Aula Mentor
Vistas de la interfaz del usuario y definimos el onClick del botn Obtener grados Celsius, con
el que invocamos el servicio SOAP a travs de una tarea asncrona.
Podemos conocer estos valores del archivo WebService.wsdl del servidor SOAP accediendo
desde el navegador de Internet a la pgina http://localhost:8080/unidad4.eje8.soap/services/
WebService?wsdl. Veremos lo siguiente:
416
U4 Bibliotecas, APIs y Servicios de Android
En la imagen anterior se muestran resaltados en amarillo los valores de tres de las cuatro
constantes que debemos utilizar en la aplicacin Android. La cuarta constante se define a partir
de las dos anteriores.
417
Antes de continuar, vamos a entender el formato de una peticin de este servidor SOAP en
concreto. Veamos la estructura que tendra si la capturamos:
User-Agent: ksoap2-android/2.6.0+
SOAPAction: http://soap.eje8.unidad4.mentor.es/getGradosCelsius
Content-Type: text/xml;charset=utf-8
Connection: close
Accept-Encoding: gzip
Host: 10.17.19.63:8080
Content-Length: 398
<v:Envelope xmlns:i=http://www.w3.org/2001/XMLSchema-instance
xmlns:d=http://www.w3.org/2001/XMLSchema
xmlns:c=http://schemas.xmlsoap.org/soap/encoding/
xmlns:v=http://schemas.xmlsoap.org/soap/envelope/>
<v:Header />
<v:Body>
</v:Body>
</v:Envelope>
Puedes ver en la captura anterior varias zonas marcadas correspondientes a las tres partes
418 principales de una peticin SOAP.
- Primero, en color rojo, se encuentra la CABECERA HTTP ya que el envo de una peticin
SOAP al servidor se realiza mediante este protocolo http.
- Despus, en color azul, se incluye otra serie de etiquetas y datos a modo de CONTENEDOR
estndar que recibe el nombre de Envelope. La informacin indicada en este contenedor no
es especfica de la llamada al servicio, pero s contiene informacin sobre formatos y esque-
mas de validacin del estndar SOAP.
- Finalmente, en color verde, en la parte interna del XML, encontramos los datos de la PE-
TICIN (Request) en s, que contiene el nombre del mtodo invocado y los nombres y
valores de los parmetros en entrada.
Todo esto junto har que el servidor sea capaz de interpretar correctamente la peticin SOAP,
llame al mtodo interno correcto y devuelva el resultado en un formato similar al anterior:
<soapenv:Body>
<getGradosCelsiusResponse xmlns=http://soap.eje8.unidad4.mentor.es>
<getGradosCelsiusReturn>43</getGradosCelsiusReturn>
</getGradosCelsiusResponse>
</soapenv:Body>
</soapenv:Envelope>
Una vez estudiada la estructura y funcionamiento general de una peticin SOAP y su respuesta,
veamos cmo incluirla en una aplicacin Android.
Si continuamos examinando la clase TareaWebService, observamos que crea la peticin
SOAP al servicio Web, la enva y recibe su respuesta correspondiente.
Primero creamos el objeto del tipo HttpTransportSE que se encargar de realizar la comunicacin
HTTP con el servidor SOAP. A este objeto le pasaremos la URL de la conexin al servicio web.
Posteriormente, creamos la peticin (request) al mtodo correspondiente mediante un
nuevo objeto SoapObject indicando como parmetros el NAMESPACE y el nombre del mtodo
Web. Mediante el mtodo addProperty() de esta clase incluimos los parmetros de entrada del
mtodo anterior indicando el nombre del parmetro y su valor.
Despus, definimos el contenedor SOAP (envelope) mediante su mtodo SoapSeriali-
zationEnvelope indicando la versin de SOAP que vamos a utilizar (versin 1.1).
Si vamos a conectar a un servicio SOAP de Net-Services (Microsoft), debemos indicar dotNet =
true del contenedor. Como ste no es el caso, en el cdigo fuente original lo hemos comentado.
419
A continuacin, asociamos la peticin creada anteriormente a este contenedor mediante el
mtodo setOutputSoapObject().
Finalmente, para invocar al mtodo remoto del servidor SOAP. usamos el mtodo call()
del objeto del tipo HttpTransportSE.
Despus de la llamada al servicio SOAP, vamos a recibir su resultado mediante el mtodo
getResponse(). Dependiendo del tipo de resultado que devuelva el mtodo invocado debemos
convertir la respuesta a un tipo u otro.
En este caso, como el resultado que esperamos es un valor simple (un nmero entero),
convertimos la respuesta a un objeto del tipo SoapPrimitive que, directamente, transformamos
a una cadena de tipo cadena invocando toString().
Finalmente, y como es habitual, para poder acceder a Internet es necesario indicarlo en
el fichero AndroidManifest.xml:
<uses-permission android:name=android.permission.INTERNET/>
Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:
Para que este ejemplo funcione, debes definir tu servidor Tomcat en Eclipse ADT e importar el
cdigo fuente del servidor SOAP y ejecutarlo. Con el servidor SOAP lanzado debes ejecutar la
aplicacin Android. No te olvides de escribir la direccin IP de tu ordenador para que funcione
420 bien!
Como el resultado devuelto por el servicio es una matriz del tipo Dato, lo primero que hemos
hecho es crear la matriz local con su misma longitud, que se obtiene mediante su mtodo
getPropertyCount().
Despus, recorremos los distintos elementos de la matriz devuelta mediante el mtodo
getProperty(indice). A su vez, cada uno de estos elementos es otro objeto del tipo SoapOb-
ject que representa a un Dato.
Para acceder a las propiedades de cada elemento, invocamos el mtodo
getProperty(indice) con el ndice de cada atributo.
Si deseamos invocar un mtodo Web que recibe como parmetro algn objeto complejo.
debemos modificar la clase Dato de forma que sea serializable.
Para ello, implementamos la interfaz KvmSerializable en esta clase Dato que obliga a
desarrollar los siguientes mtodos:
- getProperty(int indice): devuelve el valor de cada atributo de la clase a partir de su
ndice de orden. Por ejemplo, el ndice 0 retorna el valor del atributo Id, el ndice 1 el del
atributo Nombre, etctera:
421
@Override
public Object getProperty(int arg0) {
switch(arg0) {
case 0:
return id;
case 1:
return nombre;
case 2:
return edad;
}
return null;
}
@Override
public int getPropertyCount() {
return 3;
}
@Override
public void getPropertyInfo(int ind, Hashtable ht, PropertyInfo info) {
Aula Mentor
switch(ind) {
case 0:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = Id;
break;
case 1:
info.type = PropertyInfo.STRING_CLASS;
info.name = Nombre;
break;
case 2:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = Edad;
break;
default:break;
}
}
@Override
public void setProperty(int ind, Object val) {
switch(ind) {
case 0:
id = Integer.parseInt(val.toString());
break;
case 1:
422 nombre = val.toString();
break;
case 2:
edad = Integer.parseInt(val.toString());
break;
default:
break;
}
}
Si deseamos invocar un mtodo pasndole un objeto Dato como parmetro podemos escribir
las siguientes sentencias de ejemplo:
// Contenedor de la peticin
SoapSerializationEnvelope envelope = new
U4 Bibliotecas, APIs y Servicios de Android
SoapSerializationEnvelope(SoapEnvelope.VER11);
// Aadimos la peticin al contenedor
envelope.setOutputSoapObject(request);
// Asociamos al espacio de nombres la clase Dato con la clase real //
Java
envelope.addMapping(NAMESPACE, Dato, dato.getClass());
// Aqu van el resto de sentencias de la llamda
Como puedes ver, con esta biblioteca se puede desarrollar una conexin completa con un
servidor de aplicaciones de tipo SOAP.
9. Resumen
1. Introduccin
En esta Unidad vamos a explicar cmo acceder al portapapeles de Android. Adems,
implementaremos Drag and Drop (arrastrar y soltar) y gestionaremos los toques de usuario
sobre la pantalla del dispositivo.
Tambin, repasaremos las recomendaciones de diseo en funcin del tamao y de la
resolucin de la pantalla del dispositivo Android.
Estudiaremos cmo desarrollar aplicaciones multi-idioma mediante un ejemplo prc-
tico.
Finalmente, repasaremos herramientas que ayudan al desarrollo rpido de cdigo en
Android.
2. Portapapeles de Android
Android proporciona un potente portapapeles para copiar y pegar contenidos, que es compatible 425
tanto con tipos de datos simples como complejos, incluyendo cadenas de texto, estructuras de
datos complejas, datos binarios e, incluso, recursos de aplicaciones.
Los datos de tipo cadena se almacenan directamente en el portapapeles, mientras que en
el caso de los datos complejos lo que se almacena es una referencia que luego utiliza la aplica-
cin que lee el portapapeles mediante el proveedor de contenidos correspondiente.
La clase principal que se usa para acceder al portapapeles se denomina ClipboardManager, que es
un gestor que obtiene la referencia a la misma invocando la orden getSystemService(CLIPBOARD_
SERVICE).
Para aadir informacin al portapapeles debemos crear un objeto del tipo ClipData, que
contiene la descripcin de la informacin (objeto ClipDescription de tipo matriz) y los datos
en s mismos (uno o ms objetos del tipo ClipData.Item).
El portapapeles slo puede contener un nico dato del tipo ClipData al mismo tiempo.
El objeto del tipo ClipDescription contiene la descripcin de los datos guardados en
una matriz. Estos datos son de tipo MIME. As, las otras aplicaciones pueden gestionar tambin
estos datos del portapapeles en funcin de su tipo MIME.
El objeto del tipo ClipData.Item contiene los datos almacenados y puede ser uno de los
siguientes objetos:
- Cadena de texto.
- URI: se usa para copiar datos complejos de un proveedor de contenidos.
- Intencin: as se copia accesos directos a aplicaciones.
Es posible aadir al portapapeles varios objetos del tipo ClipData.Item al mismo tiempo.
De esta forma, podemos incluir selecciones mltiples en l, por ejemplo, podemos seleccionar
varias opciones de un listado y copiarlas todas al mismo tiempo. Cada una de estas opciones
Aula Mentor
La clase ClipData dispone de los siguientes mtodos bsicos para aadir un nico dato del tipo
ClipData.Item:
- newPlainText(descripcin, texto): aade un objeto del tipo ClipData.Item que
contiene una cadena de texto. El tipo MIME que parecer en ClipDescription ser MIME-
TYPE_TEXT_PLAIN.
- newUri(resolver, descripcin, URI): pega un objeto del tipo ClipData.Item que
contiene una cadena de tipo URI. El mtodo utiliza el resolver como proveedor de conteni-
dos para resolver el tipo MIME que parecer en ClipDescription. Si la URI no se resuelve,
el tipo MIME ser MIMETYPE_TEXT_URILIST.
- newIntent(label, intent): incorpora un objeto del tipo ClipData.Item que contiene
una cadena con una Intencin. El tipo MIME que parecer en ClipDescription ser MI-
METYPE_TEXT_INTENT..
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical>
<TextView
android:id=@+id/texto_estilo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal
android:textSize=20sp />
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:orientation=horizontal>
<Button
android:id=@+id/copiar_texto_estilo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarTextoEstilo
android:text=@string/copiar_texto_estilo />
<Button
U5 Utilidades avanzadas
android:id=@+id/copiar_texto_plano
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarTextoPlano
android:text=@string/copiar_texto />
</LinearLayout>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:orientation=horizontal>
<Button android:id=@+id/copiar_intent
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarIntent
android:text=@string/copiar_intent />
<Button android:id=@+id/copiar_uri
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarUri
android:text=@string/copiar_uri />
</LinearLayout>
<TextView
android:id=@+id/tipo_contenido_portapapeles 427
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal
android:textStyle=normal
android:textSize=16sp/>
<EditText
android:id=@+id/clip_text
android:layout_width=match_parent
android:layout_height=0px
android:layout_weight=1
android:textStyle=normal/>
</LinearLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las vistas de la interfaz del usuario
setContentView(R.layout.activity_main);
contenidoPortapapeles =
(TextView)findViewById(R.id.tipo_contenido_portapapeles);
mEditText = (EditText)findViewById(R.id.clip_text);
// Buscamos el texto con estilo del fichero /res/strings.xml
// y obtenemos su texto plano
textoEstilo = getText(R.string.texto_estilo);
textoPlano = textoEstilo.toString();
// Lo pasamos a la etiqueta
TextView tv = (TextView)findViewById(R.id.texto_estilo);
tv.setText(textoEstilo);
// Obtenemos referencia al gestor del portapapeles
mClipboard =
(ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
428 // Aadimos el listener al portapapeles
mClipboard.addPrimaryClipChangedListener(
mPrimaryChangeListener);
// Actualizamos el estado del portapapeles
actualizaPortapapeles();
} // end onCreate
@Override
protected void onDestroy() {
super.onDestroy();
// Hay que liberar el listener!
mClipboard.removePrimaryClipChangedListener(
mPrimaryChangeListener);
}
Uri.parse(http://www.mentor.educacion.es/));
// Lo copiamos al portapapeles
mClipboard.setPrimaryClip(ClipData.newIntent(VIEW intent,
intent));
}
// Mtodo que copia una URI
public void copiarUri(View button) {
mClipboard.setPrimaryClip(ClipData.newRawUri(URI,
Uri.parse(http://www.mentor.educacion.es/)));
}
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para obtener referencia
al gestor del portapapeles y establecer su listener que salta cuando hay cambios en el
Aula Mentor
portapapeles.
En el mtodo onDestroy()liberamos este listener. Es muy importante no olvidarse
de liberar siempre esta variable.
A continuacin, definimos distintos mtodos que copian diferentes tipos de contenidos
mediante el mtodo setPrimaryClip() del portapapeles.
Finalmente, el mtodo actualizaPortapapeles() se encarga de actualizar la interfaz
del usuario mostrando el contenido del portapapeles. Para ello, obtiene los datos del portapa-
peles con el mtodo getPrimaryClip(), que devuelve un objeto del tipo ClipData para, pos-
teriormente, utilizar este ltimo objeto y leer su descripcin con el mtodo getDescription()
y el contenido en s mismo del primer elemento con getItemAt(0) y aplicar el mtodo que
corresponda en funcin del dato almacenado.
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente
aplicacin:
430
U5 Utilidades avanzadas
A continuacin, vamos a ver los diferentes estados por los que puede pasar el proceso de
arrastrar y soltar:
- Iniciado (Started): ocurre cuando un usuario hace un gesto sobre una Vista, como, por
ejemplo, un clic normal o un clic largo sobre ella, podemos iniciar su arrastre invocando 431
su mtodo startDrag(). Para definir los gestos citados sobre la Vista, podemos utilizar los
listeners OnTouchListener o LongClickListener.
El mtodo se define as: startDrag(ClipData datos, View.DragShadowBuil-
der sombra, Object vista, int flags). Como puedes observar, al iniciar el arrastre
de una Vista podemos indicar cuatro parmetros: un objeto opcional del tipo ClipData
(Portapapeles) para pasar informacin en la operacin, la sombra creada del tipo DragSha-
dowBuilder, la Vista que se arrastra y el parmetro flags, que no tiene aplicacin hasta
ahora y debe contener el valor 0.
Despus, el sistema enva un evento de arrastre con el tipo de accin ACTION_DRAG_
STARTED a los listeners OnDragListener de todos las Vista del layout activo. Estos lis-
teners deben devolver true para indicar que sus Vistas estn interesadas en ser receptoras
de la Vista arrastrada. En caso contrario, se debe devolver false.
- Arrastrando (Dragging): el usuario sigue arrastrando una Vista. Si su sombra se superpo-
ne con una Vista receptora, que tiene definido el listener OnDragListener, esta ltima
puede reaccionar al evento cambiando su apariencia para indicar que es una Vista activa
y est disponible para aceptar la Vista arrastrada. En este caso, el sistema enva el tipo de
accin ACTION_DRAG_ENTERED.
- Soltado (Dropped): el usuario suelta la Vista arrastrada encima de la Vista receptora que
acepta el objeto. En este caso, el sistema enva el tipo de accin ACTION_DROP. Este evento
contiene los datos pasados en el mtodo startDrag(). En este listener debemos indicar
con true o false si aceptamos la Vista.
- Terminado (Ended): ocurre cuando el usuario suelta la Vista arrastrada, tanto si la ha solta-
do en una Vista receptora como si renuncia a la operacin. Este evento se enva a todos los
listeners registrados para que puedan volver a dejar sus Vistas en su estado visual normal,
es decir, ya no est activas para recibir una Vista.
Aula Mentor
Como puedes observar, la teora es bastante sencilla. Vamos a experimentar con un ejemplo
prctico cmo se aplica.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=match_parent
android:orientation=vertical >
<LinearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
432 android:orientation=vertical
android:id=@+id/cestaIV >
<ImageView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_margin=15dp
android:src=@drawable/cesta
android:layout_gravity=top|center_horizontal />
</LinearLayout>
<TextView android:id=@+id/mensaje
android:text=No hay ninguna en la cesta
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
android:textSize=17sp />
<LinearLayout
android:layout_width=fill_parent
android:layout_height=match_parent
android:orientation=horizontal >
<ImageView
android:id=@+id/manzanaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/manzana
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
U5 Utilidades avanzadas
<ImageView
android:id=@+id/naranjaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/naranja
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
<ImageView
android:id=@+id/peraIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/pera
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
<TextView
android:text=Arrastra frutas a la cesta!
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=bottom
android:layout_marginLeft=5dp
android:layout_marginBottom=3dp
android:textSize=17sp />
</LinearLayout>
</LinearLayout>
433
Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente
contenido con la lgica de la aplicacin:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Establecemos la interfaz del usuario
setContentView(R.layout.activity_main);
// Buscamos las vistas y establecemos su evento onTouch
findViewById(R.id.manzanaIV).setOnTouchListener(new
MiTouchListener());
findViewById(R.id.naranjaIV).setOnTouchListener(new
MiTouchListener());
findViewById(R.id.peraIV).setOnTouchListener(new
MiTouchListener());
// Buscamos la vista de destino y establecemos su evento
// onDrag
findViewById(R.id.cestaIV).setOnDragListener(new
MiDragListener());
// Vista que muestra el mensaje al usuario con el n de frutas
mensaje= (TextView) findViewById(R.id.mensaje);
} // end onCreate()
@Override
public boolean onDrag(View v, DragEvent event) {
// Obtenemos la accin
int accion = event.getAction();
switch (accion) {
// Si se ha iniciado el DRAG no hacemos nada
case DragEvent.ACTION_DRAG_STARTED:
break;
// Si entramos en la cesta
// haciendo drag cambiamos su fondo con la forma
// enterShape
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackground(enterShape);
break;
// Al salir del drag quitamos la forma del fondo de
// la cesta
case DragEvent.ACTION_DRAG_EXITED:
// A partir de la API 16 ya no hay que usar
// setBackgroundDrawable()
v.setBackground(null);
break;
// Si dejamos caer la fruta en la cesta
case DragEvent.ACTION_DROP:
// Una fruta ms!!!!
nFrutas++;
if (nFrutas==0) mensaje.setText(No hay ninguna
U5 Utilidades avanzadas
en la cesta);
else if (nFrutas==1) mensaje.setText(1 fruta
en la cesta);
else mensaje.setText(nFrutas + frutas en la
cesta);
break;
// Si acaba el drag de la cesta, quitamos el fondo en
// la vista
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(null);
default:
break;
}
// Indicamos que hemos gestionado el evento
return true;
}
} // end MiDragListener
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para establecer los
listeners OnTouchListener de las frutas que son las Vistas que se arrastran y OnDragListener
de la cesta que las recibe.
Adems, hemos definido el listener MiTouchListener que establece el evento on-
Touch() de las frutas para la accin ACTION_DOWN (pulsado). En este evento definimos un ob-
jeto portapapeles sin datos, construimos la sombra del arrastre de la Vista e iniciamos el arrastre
de la vista con startDrag(). 435
En el cdigo anterior utilizamos la forma (shape) enterShape que toma la vista cuando
entramos en ella. Esta forma se define en el archivo shape_droptarget.xml que contiene
un crculo de color amarillo:
<shape xmlns:android=http://schemas.android.com/apk/res/android
android:shape=oval >
<solid android:color=#DDFFFF00/>
</shape>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Drag and Drop) de la
Unidad 5. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado el mecanismo de Arrastrar y soltar
de Android.
Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente
aplicacin:
436
Tambin debemos conocer el concepto de multitctil que permite detectar una entrada de
varios dedos en la pantalla. Hay que tener en cuenta que esta funcionalidad est disponible
a partir de Android 2.0 y que la pantalla del dispositivo fsico debe ser compatible con esta
tecnologa.
Desde el punto de vista del programador, para averiguar si el dispositivo tiene esta capa-
cidad puedes utilizar la siguiente sentencia:
boolean multiTouch = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
Cada vez que el usuario toca la pantalla con un dedo, el sistema define un punto con la clase
PointF, que almacena la posicin (X, Y) del dedo, el Id para identificarlo y si ndice Index.
Fjate en el siguiente esquema visual:
Aula Mentor
En este caso, un objeto de la clase MotionEvent contendr informacin de todos estos puntos.
Un punto estar activo desde que el usuario pulsa sobre la pantalla hasta que deja de presionar.
Se puede obtener el nmero de puntos activos llamando al mtodo getPointerCount()
de la clase anterior.
A partir de Android 2.0, esta clase MotionEvent ampla la lista de constantes para identificar
las acciones posibles de multi-touch. Veamos los nuevos valores y cmo afectan a las acciones
anteriores:
- ACTION_POINTER_DOWN: aparece un nuevo punto (dedo) distinto de los existentes.
- ACTION_POINTER_UP: un punto se levanta (se quita el dedo), pero sigue quedando alguno
activo.
- ACTION_DOWN: se pulsa en la pantalla sin que haya otro punto activo.
- ACTION_UP: se deja de presionar el ltimo punto activo
- ACTION_MOVE: se mueve cualquiera de los puntos activos.
- ACTION_CANCEL: se cancela un gesto.
- ACTION_OUTSIDE: el punto se sale de la vista.
Es posible gestionar tambin las pulsaciones del usuario mediante gestos (del ingls, gestures).
Esta funcionalidad est disponible desde Android 1.6 y veremos un ejemplo muy sencillo al final
de este apartado.
438 Estudiemos ahora, de forma prctica, cmo gestionar los toques de usuario en la pantalla
de un dispositivo Android. Es recomendable abrir el Ejemplo 3 de esta Unidad para seguir la
explicacin que se ofrece.
<android.support.v4.app.FragmentTabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@android:id/tabhost
android:layout_width=match_parent
android:layout_height=match_parent >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs
U5 Utilidades avanzadas
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en
// el archivo activity_main.xml
FragmentTabHost mTabHost = (FragmentTabHost)
findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la
439
// pestaa
mTabHost.setup(this, getSupportFragmentManager(),
R.id.tabFrameLayout);
mTabHost.addTab(
mTabHost.newTabSpec(tab2).setIndicator(Multi-
touch,
getResources().getDrawable(R.drawable.touch)),
MultiTouchFragmentTab.class, null);
mTabHost.addTab(mTabHost.newTabSpec(tab3).setIndicator(
Cambio Tamao,
getResources().getDrawable(R.drawable.resize)),
ResizeFragmentTab.class, null);
} // end onCreate()
} // end clase
Como puedes observar, en el cdigo anterior hemos implementado con fragmentos las pestaas.
Como ya hemos utilizado este tipo de estructuras en la Unidad 2 no vamos a explicar su sintaxis.
Como puedes ver, esta clase se extiende de la clase bsica View, que sobrescribe sus mtodos
onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear 441
para dibujar en esta Vista.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos el camino (clase Path) que se va creando segn el usuario toca la pantalla y desplaza
el dedo por ella.
Finalmente, en el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla,
obtenemos la posicin del dedo y ejecutamos las siguientes sentencias en funcin de la accin:
- ACTION_DOWN: si el dedo toca la pantalla, entonces movemos el camino a la posicin X, Y.
- ACTION_MOVE: si el dedo se mueve, entonces creamos el camino hasta X, Y mediante una
lnea.
Al final de este mtodo indicamos que se debe redibujar la Vista cada vez que ocurre un evento
onTouch.
La clase MultitouchView se encarga de mostrar crculos cuando el usuario toca la pantalla con
varios dedos a la vez y contiene las siguientes sentencias:
switch (mascaraAction) {
// En el caso de que el usuario tenga el dedo en la pantalla
U5 Utilidades avanzadas
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
// Cuando aparece un nuevo punto entonces lo guardamos en
// la matriz
PointF punto = new PointF();
punto.x = event.getX(pointerIndex);
punto.y = event.getY(pointerIndex);
puntosActivos.put(pointerId, punto);
break;
}
// Si se mueve el dedo
case MotionEvent.ACTION_MOVE: {
// Recorremos todos los puntos y actualizamos su nueva
// posicin
for (int size = event.getPointerCount(), i = 0; i < size;
i++) {
PointF punto = puntosActivos.get(event.getPointerId(i));
if (punto != null) {
punto.x = event.getX(i);
punto.y = event.getY(i);
}
}
break;
}
// Si el dedo se levanta o se cancela
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: { 443
// Quitamos el punto activo
puntosActivos.remove(pointerId);
break;
}
} // end switch
// Redibujamos la Vista
invalidate();
// Indicamos que gestionamos el evento
return true;
} // end onTouchEvent
} // end clase
Como puedes observar, de nuevo esta clase se extiende de la clase bsica View, que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear
para dibujar los crculos en esta Vista. Tambin creamos una matriz del tipo SparseArray del
tipo PointF que almacenar las posiciones de los dedos en la pantalla.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos los crculos de todos estos puntos que representan dedos en la pantalla utilizando un
bucle de la matriz de puntos.
Finalmente, el evento onTouchEvent() se lanza cuando el usuario toca la pantalla.
Por motivos pedaggicos, hemos incluido en este mtodo un Log del objeto MotionEvent para
ver el valor que toma el objeto cuando se ejecuta la aplicacin y se desplazan los dedos por la
pantalla y se reciben varios eventos. El resultado puede ser similar al siguiente:
Aula Mentor
Como puedes ver, cuando hay ms de un punto en la pantalla, el evento resulta complejo de
interpretar. Estas acciones las ha podido hacer cualquier punto de los activos o uno nuevo.
Se codifica simultneamente el cdigo de la accin (8 bits menos significativos) e ndice del
punto (siguientes 8 bits). Para extraer la informacin interna por separado, puedes realizar una
operacin lgica utilizando las mscaras de bits correspondientes. Fjate en el siguiente cdigo:
Una vez conocemos la estructura de la informacin del objeto del tipo MotionEvent y obtenemos
las posiciones de los dedos, ejecutamos las siguientes sentencias en funcin de la accin:
- ACTION_DOWN o ACTION_POINTER_DOWN: si uno o varios dedos tocan la pantalla, entonces
lo guardamos en la matriz de puntos.
- ACTION_MOVE: si el dedo se mueve, entonces recorremos con un bucle todos los puntos y
actualizamos su nueva posicin en la matriz de datos.
- ACTION_UP o ACTION_POINTER_UP o ACTION_CANCEL: si uno o ms dedos se levantan o se
cancela la operacin, quitamos el punto de la matriz de datos.
Al final de este mtodo indicamos que se debe redibujar la Vista cada vez que ocurre un evento
onTouch.
La clase ZoomImageView se encarga de escalar una imagen mediante el mecanismo pitch (dos
dedos que se separan o juntas para agrandar o disminuir la imagen respectivamente). Contiene
las siguientes sentencias:
// Constructor de la clase
public ZoomImageView(Context context) {
super(context);
// Obtenemos la imagen que es el icono de la aplicacin
imagen = context.getResources().getDrawable(
R.drawable.ic_launcher);
// Indicamos que esta Vista puede obtener el foco de la
// aplicacin
setFocusable(true);
// Establecemos los bordes de la imagen
imagen.setBounds(0, 0, imagen.getIntrinsicWidth(),
imagen.getIntrinsicHeight());
// Definimos el detector de cambios de escala con su
// listener correspondiente
scaleGestureDetector = new ScaleGestureDetector(context, new
ScaleListener());
} // end constructor
// Evento que se lanza cuando es necesario dibujar la Vista
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Guarda el estado del lienzo. As evitamos que parpadee
canvas.save();
// Escalamos el lienzo en Ancho y Alto
canvas.scale(factorEscalado, factorEscalado);
Aula Mentor
// Dibujamos la imagen
imagen.draw(canvas);
// Restauramos el lienzo
canvas.restore();
} // end onDraw()
// Evento que salta cuando el usuario toca la pantalla
@Override
public boolean onTouchEvent(MotionEvent event) {
// Pasamos el evento al detector de escala
scaleGestureDetector.onTouchEvent(event);
// Redibujamos la Vista
invalidate();
return true;
}
// Listener del ScaleGestureDetector
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
// Si se lanza el evento onScale...
@Override
public boolean onScale(ScaleGestureDetector detector) {
// El nuevo factor de escalado es el anterior
// multiplicado por el valor de escala del evento
factorEscalado *= detector.getScaleFactor();
// No dejamos que el objeto sea muy pequeo o muy grande.
factorEscalado = Math.max(0.2f, Math.min(factorEscalado,
446 5.0f));
// Indicamos que se debe redibujar la Vista
invalidate();
return true;
} // end onScale()
} // end ScaleListener
} // end clase
Como puedes observar, de nuevo esta clase se extiende de la clase bsica View que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el detector de cambios de escala con su lis-
tener correspondiente (clase ScaleGestureDetector).
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, se guarda el
estado del lienzo y as evitamos que parpadee, escalamos el lienzo en ancho y alto, dibujamos
la imagen y restauramos el lienzo.
En el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla, pasamos
el evento al detector de la escala anterior y redibujamos la Vista.
Finalmente, definimos listener del ScaleGestureDetector del tipo SimpleOnSca-
leGestureListener que lanza el evento onScale. En este bloque de cdigo fijamos el nuevo
factor de escalado teniendo en cuenta el valor de escala del evento e indicamos que se debe
redibujar la Vista.
El multi-touch no est disponible en el AVD; por lo tanto, es necesario probar esta apli-
cacin en un dispositivo real de Android.
Adems, para poder depurar la aplicacin en un dispositivo real de Android es nece-
sario indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.
U5 Utilidades avanzadas
Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
447
Aula Mentor
Recomendamos que pruebes las distintas ventanas para ver el comportamiento de la aplicacin.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opcio-
nes siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Si se usan pxeles para definir las dimensiones de una Vista en un dispositivo cuya pantalla tiene
448
una densidad de pxeles baja, obtendramos el siguiente resultado visual:
U5 Utilidades avanzadas
Sin embargo, en un dispositivo cuya pantalla tiene una resolucin alta, el mismo diseo tendra
este aspecto visual:
449
Como puedes ver, la percepcin del usuario sera muy distinta y, realmente, la aplicacin
cambiara tanto que su experiencia sera cambiante.
Los mltiples dispositivos con Android disponibles en el mercado hacen que el diseo
de interfaces visuales en este sistema operativo sea ms compleja. En este apartado vamos a dar
una serie de recomendaciones sobre este asunto.
El primer dispositivo Android que estuvo disponible en el mercado tena una pantalla del tipo
HVGA de 320 x 480 pxeles. HVGA significa la mitad de la matriz de grficos de video (o VGA
de medio tamao).
Android clasifica las pantallas, tomando en cuenta la longitud de la diagonal que va des-
de la esquina superior izquierda a la esquina inferior derecha de la pantalla del dispositivo, en
cuatro tamaos generales: pequeo, normal, grande y extra grande.
Aula Mentor
A continuacin, se muestra una tabla con todos los tamaos de pantalla estndar de Android y
su densidad de pixeles:
Densidad
Densidad baja Densidad media Densidad alta
450 muy alta
(120) ldpi (160) mdpi (240) hdpi
(320) xhdpi
Pantalla
QVGA: 240x320 480x640
pequea
WVGA: 480x800
Pantalla WQVGA400 :240x400 WVGA854:
HVGA: 320x480 640x960
normal WQVGA: 240x432 480x854
600x1024
Desde el punto de vista del desarrollador, no es tan importante la resolucin de la pantalla como
su densidad. Android define los siguientes trminos en relacin con este concepto:
- Resolucin de pantalla: nmero total de pixeles fsicos en una pantalla.
- Densidad de la pantalla: cantidad de pixeles en un rea fsica fija de la pantalla, normalmen-
te se conoce como DPI (puntos por pulgada).
U5 Utilidades avanzadas
- Pixeles independientes de la densidad (DP): es una unidad de pixel virtual que se utiliza
en el diseo de la interfaz de usuario con el fin de expresar dimensiones o la posicin de
una Vista de una manera independiente de la densidad de la pantalla. En una pantalla de
160 DPI, 1 DP equivale a un pxel fsico, que es la densidad de referencia asumida por el
sistema como la densidad media de la pantalla. En tiempo de ejecucin, Android modifica,
de forma transparente, cualquier ampliacin de las unidades de DP segn sea necesario, en
base a la densidad real de la pantalla que se est utilizando. La conversin de las unidades
de DP a los pxeles de la pantalla es simple: N de pxeles = DP * (DPI / 160). Por ejemplo,
en una pantalla de 240 DPI, 1 DP es igual a 1,5 pxeles fsicos.
Es recomendable utilizar siempre DP como unidad para definir los diseos de la in-
terfaz de usuario para asegurarse de que se mostrar correctamente en pantallas
con diferentes densidades.
Es recomendable incluir siempre las imgenes MDPI y HDPI para cualquier aplicacin Android
que desarrolles.
Si utilizas un icono de tamao 100100 pixeles en la interfaz del usuario en un layout
cuyo dispositivo tiene una pantalla de 320480 pxeles, este mismo icono se vera demasiado
pequeo en un dispositivo con una pantalla HDPI, que tiene ms puntos por pulgada:
Aula Mentor
Por lo tanto, para ajustar las imgenes a estas densidades de pantalla de los dispositivos,
necesitamos seguir esta relacin de escala. Es decir, podemos crear las siguientes cuatro versiones
diferentes de nuestra imagen:
7575 para pantallas de baja densidad (x0.75);
100100 para pantallas de media densidad (imagen bsica);
150150 para pantallas de alta densidad (x1.5);
200200 para pantallas de muy alta densidad (x2.0)
452
Se recomienda que los objetos que pueda pulsar el usuario con el dedo tengan un
tamao, al menos, de 45x45 dp.
La clase Locale se usa para obtener informacin del idioma activo en el sistema operativo y
para formatear fechas y nmeros.
Aula Mentor
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView
android:id=@+id/etiqueta
android:text=@string/texto_etiqueta
android:layout_width=match_parent
android:layout_height=wrap_content
android:gravity=center
android:layout_margin=20dp
android:isScrollContainer=true
454 android:background=#AAAAAA
android:textColor=#333333/>
<Spinner
android:layout_height=wrap_content
android:layout_width=match_parent
android:id=@+id/spinnerOfCharacterRaces
android:entries=@array/opciones/>
<Button
android:layout_width=match_parent
android:layout_height=wrap_content
android:id=@+id/boton
android:text=@string/texto_boton
android:gravity=center/>
</LinearLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las Vistas de la interfaz del usuario
U5 Utilidades avanzadas
setContentView(R.layout.activity_main);
etiqueta = (TextView) findViewById(R.id.etiqueta);
Button button = (Button) findViewById(R.id.boton);
button.setOnClickListener(this);
// Obtenemos el idioma local
Locale locale = Locale.getDefault();
Toast.makeText(this, getString(R.string.idioma_actual) +
+ locale.getLanguage() + ,Toast.LENGTH_LONG).show();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.boton) {
// Cambiamos el texto de la etiqueta con un literal
etiqueta.setText(getString(R.string.texto_boton2));
}
}
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para mostrar un mensaje
de tipo Toast con el cdigo ISO del idioma actual del dispositivo mediante la clase Locale con
el mtodo getLanguage().
En el evento onClick() del botn cambiamos el texto de la etiqueta con un literal.
Como puedes observar, en ningn momento seleccionamos un idioma en concreto, es Android
el que lo hace automticamente por nosotros. 455
Para definir los idiomas de la aplicacin, primero debemos rellenar los archivos de la
carpeta res\values que establecen los literales del idioma por defecto. En las carpetas con la
estructura de nombre res\values-cdigo_ISO_idioma se incluyen la misma informacin que
la carpeta anterior traducida.
En este ejemplo, hemos aadido el castellano como idioma extra. En Eclipse ADT, la estructura
de carpetas tiene el siguiente aspecto:
Aula Mentor
456
Fjate que en las carpetas values hemos incluido tambin el fichero arrays.xml,
que se traduce en funcin del idioma y se usa para rellenar las opciones del Spin-
ner de la aplicacin.
Si ejecutas en Eclipse ADT este Ejemplo 4 en el AVD, vers que se muestra la siguiente
aplicacin en ingls:
457
Para cambiar el idioma directamente desde el AVD, debes cambiar la configuracin del dispositivo
virtual. Fjate en las siguientes capturas de pantalla:
Aula Mentor
Tambin puedes cambiar el idioma del AVD desde la ventana de comandos mediante la siguiente
orden:
setprop persist.sys.language [cdigo idioma];setprop persist.sys.country
[cdigo pas];stop;sleep 5;start
U5 Utilidades avanzadas
adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached
emulator-5554 device
adb shell
root@android:/ # setprop persist.sys.language es;setprop persist.sys.
country ES; stop;sleep 5;start;setprop persist.sys.country ES;stop;sleep
5;start
root@android:/ #
Nota: a los 5 segundos de ejecutar estos comandos, el AVD se reiniciar para volver a arrancar
los nuevos idioma y pas indicados como parmetros.
459
7. Desarrollo rpido de cdigo Android
A todos los programadores nos gusta mejorar una aplicacin reutilizando mtodos y funciones
sencillos, eficientes y ya probados por otro programador que realizan un cometido especfico.
Cuando juntamos todos estos trocitos de cdigo fuente, obtenemos una aplicacin compleja con
mucha funcionalidad adicional.
Para esto, podemos utilizar Snippets. Un snippet es una porcin de cdigo Android que
puedes aadir a un programa y que realiza una funcin especfica.
Existen multitud de pginas en Internet donde puedes encontrarlos. Por ejemplo, Wikico-
de incluye snippets y tutoriales de muchsimos lenguajes de programacin. Adems de Android,
encontramos snippets de PHP, JS, HTML, CSS, SQL, CSV, JAVA, C++, IPHONE, PERL, etctera.
Por ejemplo, si abrimos el snippet Hacer una peticin HTTP POST en Android, vers que
contiene una explicacin clara sobre cmo desarrollar esta funcionalidad:
460
Te recomendamos que busques en Internet snippets que te permitan mejorar las aplicaciones
de forma rpida y sencilla, si bien no recomendamos abusar de ello ya que el programador o
programadora debe entender cmo funciona su cdigo fuente.
U5 Utilidades avanzadas
8. Resumen