Escolar Documentos
Profissional Documentos
Cultura Documentos
SERIE PROGRAMACIN
CamSp
SGALV
Desarrollo de
aplicaciones para
Android II
Ministerio
de Educacin, Cultura
y Deporte
Desarrollo de Aplicaciones
para Android II
Programacin
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 1. Multimedia y Grficos en Android 33
1.Introduccin.............................................................................................................33
2. Android Multimedia.................................................................................................33
3. Libreras de reproduccin y grabacin de audio.....................................................36
3.1 Clase SoundPool 36
3.2 Clase MediaPlayer 37
3.3 Clase MediaRecorder 39
3.3.1 Ejemplo de reproduccin y grabacin de audio 40
3.4 Cmo habilitar USB Debugging en Android 4.2 y superior Jelly Bean 49
3.5 Librera de reproduccin de vdeo 50
3.5.1 Ejemplo de reproduccin de vdeo 50
4. Conceptos bsicos de grficos en Android...............................................................59
4.1 Definicin de colores en Android 59
4.2 Clases de dibujo en Android 60
4.2.1 Clase Paint 60
4.2.2 Clase Rectngulo 60
4.2.3 Clase Path 60
4.2.4 Clase Canvas 61
4.2.4.1 Obtener tamao delCanvas:61
4.2.4.2 Dibujar figuras geomtricas: 61
4.2.4.3 Dibujar lneas y arcos: 62
4.2.4.4 Dibujar texto: 62
4.2.4.5 Colorear todo ellienzo Canvas: 62
4.2.4.6 Dibujar imgenes: 62
4.2.4.7 Definir unClip (rea de seleccin): 62
4.2.4.8 Definir matriz de transformacin (Matrix): 63
U0 Introduccin
Unidad 0. Introduccin
El objetivo de este curso avanzado es que el alumno o alumna perfeccione la programacin en este sistema operativo tratando materias no estudiadas en el curso
de iniciacin. As, podr desarrollar aplicaciones ms complejas utilizando contenidos multimedia, 3D, sensores del dispositivo, servicios, etctera.
1.5 Cupcake
1.6 Donut
2.0/2.1 Eclair
2.2 Froyo
...IceCream
Sandwich
11
Aula Mentor
Quien est familiarizado con el sistema operativo Android ya sabr que los nombres de sus
diferentes versiones tienen el apodo de un postre.
A continuacin, vamos a comentar la evolucin de las diferentes versiones indicando las
mejoras y funcionalidades disponibles en cada una. Partiremos de la versin 3.0 ya que las versiones anteriores a sta se tratan en el curso de Iniciacin de Android de Mentor.
- Android 3.0 (API 15)
Esta versin se dise pensando en las tabletas, que disponen de un hardware mucho ms
potente. Entre sus nuevas funcionalidades podemos encontrar:
Soporte para grandes pantallas, como las tabletas.
Inclusin del concepto de Fragmento (en ingls, Fragment).
Nuevos elementos de interfaz como las barras de accin (action bars) y el arrastrar y
soltar (drag-and-drop).
Instalacin de un nuevo motor OpenGL 2.0 para animacin en 3D.
Esta versin de Android se dise exclusivamente para ser utilizada en tabletas. En otros
dispositivos, como los telfonos, era necesario seguir utilizando la versin 2.3.7 disponible
en ese momento.
12
U0 Introduccin
Definicin de perfiles de usuario limitados que, desde el punto de vista del desarrollador, implican una gestin de las Intenciones implcitas (Implicit Intent) para comprobar si el usuario tiene permisos para acceder a ese tipo de Intencin.
Mejoras en la gestin multimedia y de codecs de archivos de vdeo. Adems, permite
crear un vdeo de una Superficie dinmica.
Nuevos tipos de sensores relacionados con juegos.
Nueva Vista ViewOverlay que permite aadir elementos visuales encima de otros ya
existentes sin necesidad de incluir en un Layout. til para crear animaciones sobre la
interfaz de usuario.
Nuevas opciones de desarrollo como revocar el acceso a la depuracin USB de todos
los ordenadores o mostrar informacin del uso de la GPU del dispositivo.
Notification Listener es un nuevo servicio que permite que las aplicaciones reciban
notificaciones del sistema operativo y sustituye al servicio Accessibility APIs.
Importante
Los contenidos de este curso estn diseados para alumnos que estn familiarizados con el entorno de desarrollo Eclipse / Android / Emulador de Android. Por ello,
los alumnos deben conocer y manejar con soltura Vistas bsicas, Actividades, Mens, Dilogos, Adaptadores, sistema de ficheros, Intenciones, Notificaciones, Content Providers y utilizacin de SQLite. Todos estos conceptos bsicos de desarrollo
en este sistema operativo se tratan en el curso de Iniciacin a Android de Mentor.
13
Aula Mentor
Como sabes, Android est basado en Linux para los servicios base del sistema, como seguridad,
gestin de memoria, procesos y controladores. El diagrama de la arquitectura de Android tiene
este aspecto:
14
Antes del ao 2005, Linux estaba disponible en servidores web, aplicaciones de escritorio de
algunas empresas y administraciones, as como en ordenadores de programadores y entusiastas.
Sin embargo, con el despegue de Android, Linux empieza a estar instalado en nuestros mviles
y tabletas de forma masiva. En este apartado vamos a ver por qu es tan importante la simbiosis
Android y Linux.
El desarrollo de Linux empez el ao 1991 de la mano del famoso estudiante finlands
Linus Torvalds que crea la primera versin de este sistema operativo con el fin de implementar
una versin libre de licencias (Open Source) de Unix que cualquier programador pudiera modificar o mejorar a su antojo.
Al poco tiempo, grandes compaas como Intel e IBM advirtieron su potencial frente a
Windows e invirtieron grandes cantidades de dinero. Su objetivo principal era no depender de
Microsoft y, de paso, obtener un sistema operativo sin tener que empezar de cero.
En la actualidad, los sistemas operativos basados en Linux son sinnimo de estabilidad,
seguridad, eficiencia y rendimiento.
Sin embargo, hasta la aparicin de Android, a Linux le faltaba el xito entre el gran p-
U0 Introduccin
15
Aula Mentor
16
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:
- En el men Apple ( ), selecciona Acerca de este Mac y a continuacin, haz clic en Ms
informacin:
19
Aula Mentor
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 personal.
21
Aula Mentor
Si cerramos la pestaa abierta, podemos ver ya el entorno de desarrollo que deberas conocer si
has hecho del curso de inciacin:
22
Ahora vamos a comprobar en las preferencias que la versin de Java en Eclipse ADT es correcta
para compilar proyectos de Android. Para ello, hacemos clic en la opcin del men Window->
Preferences..., hacemos clic en el panel izquierdo sobre Java->Installed JREs y seleccionamos
jre7 en el campo Installed JREs:
U0 Introduccin
Para finalizar, en esta ventana hay que seleccionar la versin de Java utilizada para compilar los
proyectos de Android. Para ello hacemos clic en Java->Compiler y elegimos 1.6 en el campo
Compiler compliance settings:
23
Aula Mentor
momento de redaccin de la documentacin. No obstante, vamos a emplear sentencias compatibles y recompilables en otras versiones.
Para aadir esta versin hay que hacer clic en la opcin Android SDK Manager del men
principal Window de Eclipse:
24
U0 Introduccin
Para instalar la versin 4.3 (si no lo est ya), seleccionamos los paquetes que se muestran en la
siguiente ventana:
25
Nota: la revisin de las versiones de Android puede ser superior cuando al alumno o alumna
instale el SDK.
Una vez hemos pulsado el botn Install 4 packages, aparece esta ventana y seleccionamos la
opcin Accept All y, despus, hacemos clic en Install:
Aula Mentor
26
El instalador tarda un rato (10-20 minutos) en descargar e instalar los paquetes. Una vez acabado
se indicar que la instalacin ha finalizado correctamente.
U0 Introduccin
27
Aula Mentor
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
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 ltimos 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.
1. Introduccin
En esta Unidad vamos a explicar cmo disear aplicaciones multimedia Android para or
msica, grabar con el micrfono y cargar vdeos desde una tarjeta SD.
Algunas aplicaciones Android deben mostrar un aspecto dinmico o representar algn
dato en forma grfica para que el usuario visualice mejor la informacin que se le est ofreciendo. Como hemos comentado anteriormente en el curso, una aplicacin Android tiene xito si
est bien programada internamente y, adems, si tiene una apariencia atractiva exteriormente.
Para poder desarrollar aplicaciones que incluyan estas funcionalidades es necesario adquirir previamente los Conceptos bsicos de grficos en Android.
Los grficos 2D/3D y las animaciones suelen ser muy tiles para presentar visualmente
informacin al usuario.
Para adquirir estas destrezas como programador Android, aprenderemos a animar imgenes de forma sencilla utilizando la API de animaciones de Android.
Despus, veremos qu es una Vista de tipo Superficie (ViewSurface) y sus aplicaciones ms interesantes.
Finalmente, estudiaremos cmo aplicar a proyectos Android la conocidsima librera
OpenGL para crear grficos en 2D y 3D, aplicarles colores, animarlos y permitir que el usuario interaccione con ellos.
2. Android Multimedia
Hoy en da, los dispositivos mviles han sustituido a muchos antiguos aparatos que utilizbamos
para escuchar msica, grabar conversaciones, ver vdeos, etctera.
En este apartado vamos a ver cmo disear aplicaciones multimedia Android y reproducir este tipo de archivos de audio y vdeo.
Mediante ejemplos prcticos expondremos una explicacin detallada de las funciones
propias del SDK que permitirn implementar una aplicacin multimedia.
La integracin de contenido multimedia en aplicaciones Android resulta muy sencilla e
intuitiva gracias a la gran variedad de clases que proporciona su SDK.
En concreto, podemos reproducir audio y vdeo desde:
- Un fichero almacenado en el dispositivo, normalmente en la tarjeta externa SD.
- Un recurso que est embutido en el paquete de la aplicacin (fichero.apk).
- Medianteel streaming: distribucin de multimedia a travs de una red de manera que el
usuario accede al contenido al mismo tiempo que se descarga. Los protocolos admitidos son
dos: http://yrtp://.
Tambin es posible grabar audio y vdeo, siempre y cuando el hardware del dispositivo lo
permita.
33
Aula Mentor
A continuacin, se muestra un listado de las clases de Android que nos permiten acceder
a estos servicios Multimedia:
- MediaPlayer: reproduce audio y vdeo desde ficheros ode streamings.
- MediaController: representa los controles estndar para MediaPlayer (botones de reproducir, pausa, stop, etctera).
- VideoView: Vista que permite la reproduccin de vdeo.
- MediaRecorder: clase que permite grabar audio y vdeo.
- AsyncPlayer: reproduce una lista de archivos de tipo audio desde un hilo secundario.
- AudioManager: gestor del sonido del sistema operativo de varias propiedades como son el
volumen, los tonos de llamada/notificacin, etctera.
- AudioTrack: reproduce un archivo de audio PCM escribiendo un bfer directamente en
elhardware. PCM son las siglas de Pulse Code Modulation, que es un procedimiento de modulacin utilizado para transformar una seal analgica en una secuencia de bits.
- SoundPool: gestiona y reproduce una coleccin de recursos de audio de corta duracin.
- JetPlayer: reproduce audio y video interactivo creado con SONiVOX JetCreator.
- Camera:clase para tomar fotos y video con la cmara.
- FaceDetector: clase para identificar la cara de las personas en una imagen de tipo bitmap.
El sistema operativo Android soporta una gran cantidad de tipos de formatos multimedia,
la mayora de los cuales pueden ser tanto decodificados como codificados. A continuacin,
mostramos una tabla con los formatos nativos multimedia soportados por Android. Hay
que tener en cuenta que algunos modelos de dispositivos pueden incluir formatos adicionales
que no se incluyen en esta tabla, como DivX.
34
Tipo
Formato
Informacin
Extensin
fichero
3GPP (.3gp)
MPEG-4
(.mp4)
H.264 AVC
a partir
Android 3.0
Baseline Profile
(BP)
3GPP (.3gp)
MPEG-4
(.mp4)
MPEG-4 SP
3GPP (.3gp)
a partir
Android
2.3.3
Streaming a partir
de Android 4.0
WebM
(.webm)
Matroska
(.mkv)
WP8
Imagen
Decodifica
H.263
Video
Tipo
Codifica
Formato
Codifica
Decodifica
Informacin
Extensin
fichero
JPEG
GIF
PNG
BMP
S
S
S
S
Base + progresivo
JPEG (.jpg)
GIF (.gif)
PNG (.png)
BMP (.bmp)
WEBP
a partir
Android 4.0
a partir
Android 4.0
WebP
(.webp)
Tipo
Formato
Codifica
Decodifica
Informacin
Extensin
fichero
AAC LC/LTP
HE-AACv1
a partir
Android 4.1
HE-AACv2
AAC ELD
a partir
Android 4.1
a partir
Android
4.1
Mono/estreo,
16-8kHz
AMR-NB
3GPP (.3gp)
3GPP (.3gp)
Mono/estreo de 8 a
320 Kbps, frecuencia
de muestreo
constante (CBR) o
variable (VBR)
MP3 (.mp3)
Tipo 0 y 1
(.mid, .xmf,
.mxmf).
RTTTL / RTX
(.rtttl, .rtx),
OTA (.ota)
iMelody
(.imy)
Ogg (.ogg)
Matroska
(.mkv
a partir 4.0)
AMR-WB
MP3
Audio
MIDI
Ogg Vorbis
Mono/estreo
con cualquier
combinacin estndar
de frecuencia >
160 Kbps y ratios
de muestreo de 8 a
48kHz
FLAC
a partir
Android
3.1
mono/estereo
(no multicanal)
PCM/WAVE
a partir
Android 4.1
3GPP (.3gp)
MPEG-4(.
mp4)
No soporta
raw AAC
(.aac) ni
MPEG-TS (.ts)
35
FLAC (.flac)
WAVE (.wav)
Aunque el listado anterior pueda parecer muy complicado y amplio, te recomendamos que le
eches un vistazo a la Wikipedia donde se explica los distintos Formatos de archivo de audio.
Aula Mentor
36
- Permite, adems, que el usuario utilice los botones del dispositivo para subir y bajar su volumen.
- Volumen para tono de llamada del telfono
this.setVolumeControlStream(AudioManager.STREAM_RING);
- Volumen de alarma
this.setVolumeControlStream(AudioManager.STREAM_ALARM);
- Volume de notificacin
this.setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);
this.setVolumeControlStream(AudioManager.STREAM_SYSTEM);
this.setVolumeControlStream(AudioManager.STREAM_VOICECALL);
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 audio en un formato crudo (PCM de 16 bits) y mantenerlo cargado en memoria; as, el hardware
lo reproduce rpidamente sin tener que decodificarlas cada vez.
La clase SoundPool realiza esta carga en memoria de los archivos multimedia de forma
asncrona, es decir, el sistema operativo lanzar el sonido con el listener OnLoadCompleteListener cuando se haya completado la carga de cada uno de los archivos.
Es posible repetir los sonidos en un bucle tantas veces como sea necesario, definiendo
un valor de repeticin al reproducirlo, o mantenerlo reproduciendo en un bucle infinito con el
valor -1. En este ltimo caso, es necesario detenerlo con el mtodo stop().
Tambin podemos establecer la velocidad de reproduccin del sonido, cuyo rango puede estar entre 0.5 y 2.0. Una velocidad de reproduccin de 1.0 indica que el sonido se reproduce
en su frecuencia original. Si definimos una velocidad de 2.0, el sonido se reproduce al doble de
su frecuencia original y, por el contrario, si fijamos una velocidad de 0.5, lo har lentamente a la
mitad de la frecuencia original.
Cuando se crea un objeto del tipo SoundPool hay que establecer mediante un parmetro
el nmero mximo de sonidos que se pueden reproducir simultneamente. Este parmetro no
tiene por qu coincidir con el nmero de sonidos cargados. Adems, cuando se reproduce un
sonido con su mtodo play(), hay que indicar su prioridad. As, cuando el nmero de reproducciones activas supere el valor mximo establecido en el constructor, esta prioridad permite
que el sistema detenga el flujo con la prioridad ms baja y, si todos tienen la misma prioridad, se
parar el ms antiguo. Sin embargo, en el caso de que el nuevo flujo sea el de menor prioridad,
ste no se reproducir.
En el ejemplo prctico vamos a estudiar los mtodos ms importantes de esta clase.
37
Aula Mentor
38
39
Aula Mentor
Adicionalmente, la clase MediaRecorder dispone de mtodos que puedes utilizar para configurar
la grabacin de video.
Tal y como ocurre con la clase MediaPlayer, para poder invocar los diferentes mtodos
de la clase MediaRecorder debemos estar en un estado determinado. El siguiente esquema
permite conocer los mtodos que podemos invocar desde cada uno de sus estados y cul es el
nuevo estado al que cambiar el objeto tras invocarlo:
40
pulsa en un botn.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<LinearLayout
android:id=@+id/linearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=vertical
android:gravity=top
android:layout_marginTop=6dp
android:layout_marginBottom=1dp>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignParentLeft=true
android:layout_alignParentTop=true
android:text=Haz clic en un botn
android:textAppearance=?android:attr/textAppearanceMedium/>
<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=65dp
android:layout_width=fill_parent
android:orientation=horizontal>
<Button
android:id=@+id/soundpool1
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Tono SoundPool 1
android:tag=1 />
<Button
android:id=@+id/soundpool2
android:layout_width=wrap_content
android:layout_height=fill_parent
android:text=Tono SoundPool 2
android:tag=2 />
</LinearLayout>
<Button
android:id=@+id/mediaplayer
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp
android:text=Reproducir Cancin con MediaPlayer />
<Button
android:id=@+id/mediaplayer_record
41
Aula Mentor
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=6dp
android:text=Grabar conversacin />
<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Log:/>
</ScrollView>
</LinearLayout>
</RelativeLayout>
42
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
MainActivity.java:
public class MainActivity extends Activity {
// Objetos de las clases SoundPool y MediaPlayer
private SoundPool sPool;
private MediaPlayer mPlayer;
// Guarda los IDs de sonidos que se deben reproducir por SoundPool
private int soundID1=-1, soundID2=-1;
// Vistas de la Actividad
private TextView logTextView;
private ScrollView scrollview;
// Grabador de audio
private MediaRecorder recorder;
// Fichero donde guardamos el audio
private File audiofile = null;
// Botones de la Actividad
private Button boton_spool1, boton_spool2;
private Button boton_mplayer;
private Button boton_mrecorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
// Localizamos las Vistas del layout
logTextView = (TextView) findViewById(R.id.Log);
scrollview = ((ScrollView)findViewById(R.id.ScrollView));
// Establecemos el tipo de flujo de audio que deseamos
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
43
Aula Mentor
boton_spool2.setOnClickListener(click);
44
recorder.release();
addRecordingToMediaLibrary();
// Refrescamos interfaz usuario
boton_mrecorder.setText(Grabar conversacin);
boton_spool.setEnabled(true);
boton_mplayer.setEnabled(true);
// Log de la accin
log(Parada grabacin MediaRecorder);
} else
{
// Cambiamos los botones y hacemos log
boton_mrecorder.setText(Parar grabacin);
boton_spool.setEnabled(false);
boton_mplayer.setEnabled(false);
log(Grabando conversacin);
// Obtenemos el directorio de tarjeta SD
File directorio =
Environment.getExternalStorageDirectory();
try {
// Definimos el archivo de salida
audiofile = File.createTempFile(sonido, .3gp,
directorio);
} catch (IOException e) {
Log.e(ERROR, No se puede acceder a la tarjeta
SD);
return;
}
// Creamos el objeto MediaRecorder
recorder = new MediaRecorder();
// Establecemos el micrfono
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// Tipo de formato de salida
recorder.setOutputFormat(
MediaRecorder.OutputFormat.THREE_GPP);
// Codificacin de la salida
recorder.setAudioEncoder(
MediaRecorder.AudioEncoder.AMR_NB);
// Fichero de salida
recorder.setOutputFile(audiofile.getAbsolutePath());
try {
// Preparamos la grabacin
recorder.prepare();
} catch (IllegalStateException e) {
Log.e(ERROR, Estado incorrecto);
return;
} catch (IOException e) {
Log.e(ERROR, No se puede acceder a la tarjeta
SD);
return;
}
// Iniciamos la grabacin
recorder.start();
} // end else
}
}
45
Aula Mentor
46
Por ltimo, debemos precargar con el objeto SoundPool los archivos de audio con el mtodo siguiente: SoundPool.load(Context context, int resId, int priority). Donde
resId es la Id de nuestro archivo de msica. El parmetro priority permite seleccionar la
prioridad de este sonido frente a otro en caso de que se llegue al mximo nmero de sonidos
simultneos establecidos en el constructor de la clase.
Mediante el listener OnLoadCompleteListener el sistema operativo avisar cada
vez que complete la carga de un archivo de sonido.
Para reproducir un sonido debemos usar el mtodo play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) cuyos parmetros
indican:
- soundID: ID del sonido que ha indicado el mtodo load() al cargarlo.
- leftVolume: volumen del altavoz izquierdo (rango de 0.0 a 1.0)
- rightVolume: volumen del altavoz derecho (rango de 0.0 a 1.0)
- priority: prioridad del sonido (0 es la ms baja)
- loop:
modo en bucle si establecemos el valor -1.
- rate: velocidad de reproduccin (1.0 = normal, rango de 0.5 a 2.0)
En el siguiente bloque de cdigo hemos utilizado la clase Mediaplayer para reproducir una
pista de audio mediante su mtodo start() y pararla con el mtodo stop().
Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en la carpeta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp3 se empaqueta
con la aplicacin y hace que sta ocupe mucho espacio. Si queremos reproducir una cancin
desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
- MediaPlayer mPlayer = new MediaPlayer();
- mPlayer.setDataSource(RUTA+NOMBRE_FICHERO);
- mPlayer.prepare();
- mPlayer.start();
Observa que, en este caso, hay que invocar previamente el mtodo prepare() para cargar el
archivo de audio. En el ejemplo del curso no es necesario hacerlo ya que esta llamada se hace
desde el constructor create().
El ltimo bloque de cdigo realiza una grabacin empleando la clase MediaRecorder. Hemos definido la variable audiofile para guardar la grabacin. Para iniciar la grabacin
utilizamos los mtodos setAudioSource() que establece el micrfono de entrada; setOutputFormat() selecciona el formato de salida; setAudioEncoder() indica la codificacin del
audio; setOutputFile() establece el fichero de salida y start() inicia la grabacin.
A la hora de parar la grabacin, simplemente debemos invocar los mtodos stop() y
release() que libera los recursos del sistema.
Para finalizar con el cdigo Java, hemos desarrollado el mtodo local addRecordingToMediaLibrary() que aade la nueva grabacin a la librera multimedia del dispositivo.
Para ello, vamos a utilizar un Intent del tipo ACTION_MEDIA_SCANNER_SCAN_FILE y enviar
un mensaje Broadcast al sistema operativo para buscar el nuevo contenido multimedia de tipo
47
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente aplicacin:
48
Para poder or en tu AVD los sonidos, debes encender los altavoces de tu ordenador. Prueba a ejecutar sonidos mediante SoundPool simultneamente, incluso si
se est reproduciendo msica con el MediaPlayer.
Sin embargo, la funcionalidad de grabacin de audio no est integrada en el AVD y, para poder
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
50
La clase VideoView permite al programador incluir vdeos en las aplicaciones y abarca una gran
cantidad de mtodos para hacerlo. Los mtodos ms utilizados son los siguientes:
- setVideoPath(String path): especifica el directorio y archivo de vdeo que se reproduce. Podemos indicar tanto una URL para vdeos en Internet como un archive local en el
dispositivo.
- setVideoUri(Uri uri): de igual forma que el mtodo anterior, establece la fuente del
vdeo en formato URI.
- start(): inicia la reproduccin del vdeo.
- stopPlayback(): para la reproduccin del vdeo.
- pause(): pausa la reproduccin del vdeo.
- isPlaying(): devuelve true o false indicando as si se est reproduciendo un vdeo o no.
- setOnPreparedListener(MediaPlayer.OnPreparedListener): define un mtodo
callback que se invoca cuando el vdeo est preparado para reproducirse.
- setOnErrorListener(MediaPlayer.OnErrorListener): establece el mtodo callback
que el sistema invocar en caso de un error en reproduccin del vdeo. Este mtodo es muy
til cuando el vdeo est mal codificado u ocurre un error de conexin a Internet al reproducir un vdeo remoto.
- setOnCompletionListener(MediaPlayer.OnCompletionListener): permite definir un
mtodo callback para detectar que la reproduccin del vdeo ha terminado.
- getDuration(): indica la duracin del vdeo. Devuelve siempre -1 salvo que lo ejecutemos
dentro del evento OnPreparedListener().
- getCurrentPosition(): devuelve la posicin actual de reproduccin del vdeo.
- setMediaController(MediaController): establece el objeto MediaController que veremos a continuacin.
Si reproducimos un vdeo utilizando directamente la clase VideoView, el usuario no podr
controlar la reproduccin, que continuar hasta que finalice el vdeo. Para permitir que el usuario
gestione la visualizacin del vdeo podemos definir una interfaz ad hoc para la aplicacin o
emplear una instancia de la clase MediaController que asignaremos al VideoView.
La clase MediaController muestra un conjunto de controles que permiten al usuario
gestionar la reproduccin del vdeo, por ejemplo, hacer una pausa, buscar hacia atrs y hacia
adelante, etctera.
Estos controles aparecern brevemente cuando el usuario toca en la vista VideoView a
la que estn asignados.
Entre sus mtodos ms importantes podemos destacar:
- setAnchorView(View view): indica la Vista de la Actividad donde aparecern estos controles.
- show() : muestra los controles.
- show(int timeout): muestra los controles durante el tiempo indicado como parmetro en
milisegundos.
- hide(): oculta los controles.
- isShowing():devuelve true o false indicando as si se estn mostrando los controles o
no.
La interfaz de la aplicacin muestra una barra de herramientas en la parte superior con botones
que permiten al usuario controlar la reproduccin del vdeo. En la parte central hemos incluido
un objeto heredado de la clase VideoView. En la parte de abajo de la Actividad hemos incluido
una vista de tipo TextView desplazable que muestra las acciones del usuario.
En cdigo del Layout activity_main.xml se incluye el diseo de la Actividad principal:
<RelativeLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_height=fill_parent
android:layout_width=fill_parent>
<LinearLayout
android:id=@+id/botonesLayout
android:layout_height=wrap_content
android:layout_width=fill_parent
android:orientation=horizontal
android:layout_alignParentTop=true
android:gravity=center_horizontal>
<ImageButton
android:id=@+id/play
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/play/>
<ImageButton
android:id=@+id/pause
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/pause/>
<ImageButton
android:id=@+id/stop
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/stop/>
<ImageButton
android:id=@+id/controls
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/plus/>
<ImageButton
android:id=@+id/logButton
android:layout_height=wrap_content
android:layout_width=wrap_content
android:src=@drawable/log/>
</LinearLayout>
<es.mentor.unidad3.eje2.video.CustomVideoView
android:id=@+id/videoView
android:layout_height=fill_parent
android:layout_width=fill_parent
51
Aula Mentor
android:layout_below=@+id/botonesLayout/>
<ScrollView
android:id=@+id/ScrollView
android:layout_height=150dp
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<TextView
android:id=@+id/Log
android:layout_height=wrap_content
android:layout_width=fill_parent
android:text=Log:/>
</ScrollView>
</RelativeLayout>
Puedes observar que en lugar de utilizar directamente la clase VideoView hemos indicado la
clase heredada de sta es.mentor.unidad3.eje2.video.CustomVideoView. Esto se hace
as porque hemos redefinido los eventos onPlay(), onPause() y onTimeBarSeekChanged()
y, para hacerlo, es necesario extender la clase VideoView.
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero MainActivity.java:
52
// Vistas de la interfaz de usuario de la Actividad
private ImageButton bPlay, bPause, bStop, bLog, bControls;
private TextView logTextView;
ScrollView scrollview;
// VideoView extendido y mejorado con nuevos eventos (listeners)
private CustomVideoView visorVideo;
// Control del vdeo por parte del usuario
private MediaController mediaC;
// Posicin actual de reproduccin del vdeo que usaremos
// cada vez que el usuario vuelva a la aplicacin
private int posActual=0;
@Override
protected void onCreate(Bundle estado) {
super.onCreate(estado);
setContentView(R.layout.main_layout);
// Buscamos las Vistas
scrollview = ((ScrollView)findViewById(R.id.ScrollView));
visorVideo = (CustomVideoView) findViewById(R.id.videoView);
// Definimos la URI del vdeo
Uri uri = Uri.parse(android.resource:// + getPackageName()
+ /+R.raw.video);
visorVideo.setVideoURI(uri);
// Si se encuentra en el sistema de ficheros deberamos
// haber escrito
//visorVideo.setVideoPath(sdcard/video.mp4);
// Creamos el controlador del vdeo y lo asignamos al visor
// de vdeo y viceversa
mediaC = new MediaController(this);
mediaC.setMediaPlayer(visorVideo);
visorVideo.setMediaController(mediaC);
// Definimos el listener cuando el usuario reproduce, para o
// cambia la posicin del vdeo
visorVideo.setPlayPauseListener(new
CustomVideoView.PlayPauseListener() {
// Reproduce vdeo
@Override
public void onPlay() {
// Hacemos log y deshabilitamos botones
log(REPRODUCIENDO VIDEO);
bPause.setEnabled(true);
bStop.setEnabled(true);
bPlay.setEnabled(false);
}
@Override
public void onPause() {
// Hacemos log y deshabilitamos botones
log(VIDEO EN PAUSA);
bPause.setEnabled(false);
bPlay.setEnabled(true);
}
@Override
public void onTimeBarSeekChanged(int currentTime) {
// Hacemos log
log(CAMBIO POSICION VIDEO: +currentTime);
// Si se ha parado el vdeo lo indicamos
if (currentTime==0 && !visorVideo.isPlaying()) {
bStop.setEnabled(false);
log(PARADA VIDEO);
}
}
}); // end setPlayPauseListener
53
Aula Mentor
});
54
logTextView = (TextView)findViewById(R.id.Log);
// Buscamos el botn Reproducir y definimos su evento onClick
bPlay = (ImageButton)findViewById(R.id.play);
bPlay.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pedimos el foco del vdeo y lo reproducimos
visorVideo.requestFocus();
visorVideo.start();
}
});
// Buscamos el botn Pausa y definimos su evento onClick
bPause = (ImageButton)findViewById(R.id.pause);
bPause.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pausamos la reproduccin
visorVideo.pause();
}
});
// Buscamos el botn Parada y definimos su evento onClick
bStop = (ImageButton)findViewById(R.id.stop);
bStop.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
// Pausamos la reproduccin y vamos al principio del
// vdeo
visorVideo.pause();
visorVideo.seekTo(0);
}
});
// Buscamos el botn Controles y definimos su evento onClick
bControls = (ImageButton)findViewById(R.id.controls);
bControls.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
// Mostramos los controles de usuario del vdeo
mediaC.show();
}
});
// Buscamos el botn Log y definimos su evento onClick
bLog = (ImageButton)findViewById(R.id.logButton);
bLog.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (scrollview.getVisibility()==TextView.VISIBLE) {
scrollview.setVisibility(TextView.INVISIBLE);
} else {
scrollview.setVisibility(TextView.VISIBLE);
}
}
});
// Inicializamos Vistas
log();
bPause.setEnabled(true);
bStop.setEnabled(true);
} // end onCreate
// Debemos guardar la posicin de reproduccin en el evento onPause()
// porque en el evento onSaveInstanceState ya est parado el vdeo y
// es tarde.
@Override
protected void onPause() {
super.onPause();
posActual = visorVideo.getCurrentPosition();
}
// Cuando vuelve a estar activa la ACtividad volvemos a reproducir
// donde lo dejamos
@Override
public void onResume() {
super.onResume();
Log.d(POS , onResume called);
// Volvemos a la posicin guardada
visorVideo.seekTo(posActual);
// Continuamos la reproduccin del vdeo
visorVideo.resume();
}
// Mtodo que aade a la etiqueta Log un nuevo evento
private void log(String s) {
logTextView.append(s + \n);
// Movemos el Scroll abajo del todo
scrollview.post(new Runnable() {
@Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
} // end log
} // end clase
55
Aula Mentor
En el cdigo fuente anterior puedes ver que la aplicacin extiende la clase Activity. Adems,
implementa varias interfaces que corresponden a varios eventos. Despus, se sigue con la
declaracin de los diferentes elementos de la aplicacin. La variable posActual almacena la
posicin de reproduccin.
En lugar de utilizar directamente la clase VideoView, la hemos extendido en la nueva
clase CustomVideoView para poder definir los eventos onPlay(), onPause() y onTimeBarSeekChanged() ya que la clase base no los incluye y es necesario redefinir sus mtodos
start(), pause() y seekTo() respectivamente para poder detectar cundo el usuario realiza
una de estas acciones.
Con el mtodo setVideoURI() hemos indicando el fichero local del paquete de la
aplicacin. Por simplificacin, en este ejemplo hemos utilizado un recurso que se incluye en
la carpeta /res/raw/. En una aplicacin real no haramos esto ya que el fichero mp4 se empaqueta con la aplicacin y hace que sta ocupe muchsimo espacio. Si queremos reproducir
vdeo desde el sistema de ficheros externo debemos escribir las siguientes sentencias:
videoView.setVideoURI(Uri.parse(file:// +
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES) + /video.mp4));
56
57
Aula Mentor
// implementarse
interface PlayPauseListener {
void onPlay();
void onPause();
void onTimeBarSeekChanged(int currentTime);
}
} // end clase
Como puedes ver, la clase anterior es muy sencilla: hemos redefinimos los mtodo internos
start(), pause() y seekTo() de la clase VideoView para poder invocar dentro de sta los
mtodos que se definen en la interfaz PlayPauseListener: onPlay(), onPause() y onTimeBarSeekChanged() respectivamente. Mediante su mtodo setPlayPauseListener()
podemos establecer el listener correspondiente.
Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente aplicacin:
58
59
// Rojo opaco
// Rojo transparente
// Rojo
As, si deseamos cambiar los colores de la aplicacin, nicamente debemos modificar este
archivo de recursos.
Aula Mentor
60
La clase Paint se emplea para definir el pincel que utilizaremos para pintar. Podemos definir
su color, su tipo de trazo, transparencia, etctera. Veamos los mtodos de esta clase ms utilizados por el programador:
- setColor(int color): indica el color del pincel utilizando una de las definiciones anteriores de colores.
- setAlpha(int alfa): modifica el grado de transparencia del pincel.
- setStrokeWidth(float grosor): define el grosor del trazado.
- setStyle(Paint.Style estilo): marca el estilo de relleno con las constante FILL (relleno), FILL_AND_STROKE (relleno y borde), STROKE (slo dibuja borde).
- setShadowLayer(float radio, float x, float y, int color): realiza un segundo trazado a modo de sombra.
- setTextAlign(Paint.Align justif): justifica el texto segn las constantes CENTER, LEFT
y RIGHT.
- setTextSize(float size): establece el tamao de la fuente del texto.
- setTypeface (Typeface typeface): indica el tipo de fuente MONOSPACE, SERIF y SANS_
SERIF. Adems, podemos definir negrita e itlica.
- setTextScaleX(float escala): seala el factor de escalado horizontal. Un valor de 1.0
indica sin escalado.
- setTextSkewX(float inclinacion): indica el factor de inclinacin del texto. 0 denota sin
inclinacin.
- setUnderlineText(boolean subrayado): determina si un texto aparece subrayado o no.
- addRect(float left, float top, float right, float bottom, Path.Direction dir):
aade un rectngulo del tamao indicado siguiendo el sentido dir que puede tomar los valores CW (giro de la agujas del reloj) y CCW (sentido contrario a las agujas del reloj).
- addCircle(float x, float y, float radio, Path.Direction dir): aade un crculo de
radio siguiendo el sentido dir que puede tomar los valores CW (giro de la agujas del reloj)
y CCW (sentido contrario a las agujas del reloj).
- offset (float dx, float dy): desplaza el trazado en dx y dy.
- reset(): limpia el trazado actual.
- close(): cierra el trazado actual, es decir, ya no se pueden aadir nuevos elementos al
mismo.
61
Aula Mentor
62
- drawText(String texto, float x, float y, Paint paint): aade un texto en la posicin (x, y) utilizando el pincel paint.
- drawTextOnPath(String texto, Path trazo, float desplazamHor, float desplazamVert, Paint paint): aade un texto a lo largo del trazo con un desplazamiento
horizontal o vertical y utilizando el pincel paint.
- drawPosText(String texto, float[] posicion, Paint paint): aade un texto en la
posicin utilizando el pincel paint.
- booleanclipRegion(Regionregion): selecciona el rea mediante la region (superficie que se define con la clase Path) sobre la que se aplicarn los dibujos.
- booleanclipPath(Pathtrazo): selecciona el rea mediante el path sobre el que se
aplicarn los dibujos.
El cdigo anterior comienza con la creacin de una Activity en la que asociamos un objeto
CanvasView extendido de tipo View mediante el mtodo setContentView() que no est
definido mediante un layout XML.
Sin embargo, si accedes al fichero CanvasView.java vers que define:
zpublic class CanvasView extends View {
// Trazo del dibujo
Path trazo = new Path();
// Pincel del dibujo
Paint pincel = new Paint();
// Bitmap con el icono Mentor
Bitmap miBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.mentor);
// Variables para almacenar largo, ancho, centro_x y centro_y
float l, a, cx, cy;
// Constructor de la clase
public CanvasView(Context context) {
super(context);
}
@Override
63
Aula Mentor
64
// Dibujamos un trazo de tipo rectngulo
trazo.addRect(l/5, a/4, l-l/5, a-a/4, Direction.CW);
// Rellenamos de color blanco todo el fondo del canvas
canvas.drawColor(Color.WHITE);
// Definimos pincel de color azul de ancho 8 y con borde
pincel.setColor(Color.BLUE);
pincel.setStrokeWidth(8);
pincel.setStyle(Style.STROKE);
// Dibujamos el rectngulo
canvas.drawPath(trazo, pincel);
// Limpiamos el trazo
trazo.reset();
// Movemos puntero a posicin
trazo.moveTo(0, a/7);
// Trazamos una lnea
trazo.lineTo(l, a/7);
// Indicamos estilo de relleno
pincel.setStyle(Style.FILL);
// Tamao del texto, tipo de fuente y alineamiento
pincel.setTextSize(25);
pincel.setTypeface(Typeface.SANS_SERIF);
pincel.setTextAlign(Paint.Align.CENTER);
// Imprimimos el texto
canvas.drawTextOnPath(Curso avanzado de Android de
Mentor., trazo, 0, 0, pincel);
// Imprimimos la imagen de Mentor
canvas.drawBitmap(miBitmap, cx-miBitmap.getWidth() / 2, cy miBitmap.getHeight() / 2, null);
} // end onDraw
// Evento que se lanza cada vez que cambia el tamao de la Vista
@Override
protected void onSizeChanged(int ancho, int alto,
int ancho_anterior, int alto_anterior){
// Mostramos un mensaje indicndolo
Toast.makeText(getContext(), Cambio de tamao de pantalla. Alto
nuevo: + alto + - Ancho nuevo: + ancho,
Toast.LENGTH_LONG).show();
}
} // end clase CanvasView
La clase CanvasView se extiende de View cambiando su mtodo onDraw() que es el responsable de dibujar la vista. Puedes ver que hemos utilizado los mtodos que hemos utilizado
anteriormente y vemos su funcin en los comentarios del cdigo fuente. Adems, hemos
implementado el mtodo onSizeChanged() que Android invoca cada vez que cambia el
tamao de la Vista.
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
66
estado.
android:src=@drawable/mentor/>
Drawable miImagen =
context.getResources().getDrawable(R.drawable.mi_imagen);
Este tipo de objetos grficos se utiliza con frecuencia como fondo de botones o de pantalla.
El parmetro angle establece la direccin del degradado. nicamente se pueden definir los
ngulos 0, 90, 180 y 270.
Si guardamos el archivo anterior en res/drawable/degradado.xml entonces
podemos utilizarlo para establecer el fondo de una vista en su Layout en XML introduciendo
el siguiente atributo en el Layout main.xml de la aplicacin:
android:background=@drawable/degradado
Adems, es posible introducir la siguiente sentencia en el constructor de una Actividad
para que este drawable sea utilizado como degradado de fondo:
setBackgroundResource(R.drawable.degradado);
67
Aula Mentor
Con la orden setBounds() hemos establecido los lmites de la forma, es decir, su tamao.
<animation-list
xmlns:android= http://schemas.android.com/apk/res/android
android:oneshot= false>
<item android:drawable=@drawable/android1
android:duration=200 />
<item android:drawable=@drawable/android2
android:duration=200 />
<item android:drawable=@drawable/android3
android:duration=200 />
</animation-list>
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin observars que contiene:
imagen.setBackgroundColor(Color.WHITE);
// Establecemos la animacin en la imagen
imagen.setImageDrawable(animacion);
// Definimos el evento onClick de la imagen que inicia o para la
// animacin
imagen.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (animacion.isRunning()) animacion.stop();
else animacion.start();
}
});
// El contenido de la actividad es la imagen
setContentView(imagen);
} // end onCreate
} // end clase
El cdigo fuente anterior comienza declarando el objeto animacion de la clase AnimationDrawable. ste se inicializa a partir del fichero XML anterior incluido en la carpeta de recursos. Despus, se crea una nueva vista del tipo ImageView que sirve de contenedor de esta
vista animacion. Finalmente, se crea un listener de evento onClick para que la animacin
se inicie o se pare utilizando los mtodos start() y stop() respectivamente.
De igual forma que el ejemplo anterior, se usa la Vista animacion para dibujar mediante
cdigo Java la interfaz del usuario, es decir, no se define fichero layout xml en el proyecto.
Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra la siguiente aplicacin en
el Emulador:
69
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.
70
Una animacin tween (del ingls between, que significa en medio o entre) consiste en
realizar una serie de transformaciones simples en las Vistas de una Actividad, como su posicin,
tamao, rotacin y transparencia.
Por ejemplo, es posible mover, rotar, modificar el tamao o cambiar la transparencia a un
objeto del tipo TextView.
La clase Animation de Android es la que permite crear animaciones en las Vistas de una
Actividad.
Las instrucciones que definen esta animacin son transformaciones donde indicamos
cundo ocurrirn y cunto tiempo tardarn en completarse. Estas transformaciones pueden ejecutarse de forma secuencial o simultnea. Cada tipo de transformacin posee unos parmetros
especficos, si bien existen unos parmetros comunes a todas ellas, como son el tiempo de duracin y de inicio.
Los ficheros XML que definen animaciones deben almacenarse en el directorio res/
anim/ del proyecto Android y deben contener un nico elemento raz que indique las transformaciones que deseamos ejecutar. Esta etiqueta raz debe ser una de las siguientes:
- <translate>: mueve la Vista.
- <rotate>: rota la Vista.
- <scale>: escala la Vista.
- <alpha>: modifica la opacidad de la Vista.
- <set>: conjunto de varias transformaciones anteriores.
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.
<rotate>
Atributo
Descripcin
fromXDelta
toXDelta
fromYDelta
toYDelta
fromDegrees
toDegrees
pivotX
pivotY
<scale>
<alpha>
fromXScale
toXScale
fromYScale
toYScale
pivotX
pivotY
fromAlpha,
toAlpha
71
Aula Mentor
En el Ejemplo 5 de esta Unidad hemos desarrollado una aplicacin sencilla que utiliza este
tipo de animacin Tween.
Si abres el archivo res/anim/animacion.xml advertirs que hemos definido un conjunto de transformaciones de la animacin utilizando la etiqueta set:
<set xmlns:android=http://schemas.android.com/apk/res/android>
72
<alpha
android:startOffset=0
android:duration=3000
android:fromAlpha=0
android:toAlpha=1 />
<rotate
android:startOffset=4000
android:duration=2000
android:fromDegrees=0
android:toDegrees=360
android:pivotX=50%
android:pivotY=50%/>
<translate
android:startOffset=6000
android:duration=500
android:fromXDelta=0
android:fromYDelta=0
android:toXDelta=500
android:toYDelta=0 />
</set>
Es bastante sencillo entender las transformaciones que aplicamos secuencialmente en el archivo anterior.
Si accedes al fichero Java MainActivity.java que describe la lgica de la aplicacin,
observars que contiene las sentencias siguientes:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las imgenes de la animacin
setContentView(R.layout.main);
final TextView texto = (TextView)findViewById(R.id.textoAnimado);
// Definimos la animacin
final Animation animacion = AnimationUtils.loadAnimation(this,
R.anim.animacion);
// Evento que lanza Android cuando le ocurre algo a la animacin
animacion.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// Cuando la animacin acaba, volvemos a iniciarla
// La animacin se repite de forma infinita
texto.startAnimation(animacion);
}
});
// Iniciamos la animacin al texto
texto.startAnimation(animacion);
} // end onCreate
} // end clase
El cdigo fuente anterior comienza declarando el objeto animacion de la clase Animation que
se inicializa mediante el mtodo loadAnimation() a partir del fichero XML anterior incluido en
la carpeta anim.
Despus, se inicia la animacin sobre una etiqueta TextView mediante el mtodo
startAnimation().
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.
Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente aplicacin:
73
Aula Mentor
74
La API de Animacin de Android se denomina tambin Animacin de Propiedades ya que, como hemos dicho, modifica las propiedades propiamente dichas del
objeto que se anima.
5.2.1.1 Animator
Superclase de la API de la que se extienden el resto de clases. Veamos los mtodos ms importantes de esta clase:
- start(): comienza la animacin.
- end(): termina la animacin. El objeto se queda en el ltimo estado marcado por esta animacin.
- cancel(): cancela la animacin y vuelve al estado inicial.
- setDuration(long duration): indica la duracin en milisegundos de la animacin.
- setTarget(): establece el objeto que va a animar.
- addListener(): aade el listener correspondiente a los cambios de estado de la animacin.
- removeAllListeners(): quita todos los listeners de la animacin.
- removeListener(listener): quita el listener indicado como parmetro de la animacin.
- isRunning(): seala si la animacin se est ejecutando.
- setInterpolator(): establece el interpolador que usaremos en la animacin.
- setStartDelay(long retraso): retrasa el inicio de la animacin despus de invocar el
mtodo start(). Como es habitual, el tiempo retraso se indica en milisegundos.
Hay que tener en cuenta que todas las clases siguientes, al extenderse de sta, heredan los
mtodos anteriores.
5.2.1.2 ValueAnimator
Una forma de entender mejor por qu se llama tambin a esta API Animacin de Propiedades es mediante el siguiente ejemplo que define una animacin mediante la clase ValueAnimator. En sta se modifica el valor de una variable de tipo entero durante un intervalo
de tiempo de 5000 milisegundos. Veamos cdigo fuente del ejemplo:
// Definimos el animator que cambia el valor entero de 10 a 200
ValueAnimator animacion = ValueAnimator.ofInt(10f, 200f);
// Indicamos la duracin de la animacin
animacion.setDuration(5000);
// Establecemos el listener para detectar el cambio del valor entero
animacion.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener()
{
75
Aula Mentor
public void onAnimationUpdate(ValueAnimator animation)
{
Int valor = (Int)animation.getAnimatedValue();
// Durante 5 segundos la variable valor va cambiando
// desde 10 hasta 200. Por defecto, esta clase
// modifica el valor cada 10 milisegundos
}
}
);
// Iniciamos la animacin
animacion.start();
La clase ValueAnimator es un mecanismo para hacer algo cada 10 milisegundos (valor por
defecto).
5.2.1.3 ObjectAnimator
76
final de la animacin.
- ofPropertyValuesHolder(Object objeto, PropertyValuesHolder... valores): establece los nuevos conjuntos de valores del tipo PropertyValueHolder que debe tomar el
objeto. Como su nombre indica, PropertyValueHolder se usa para denotar un conjunto
de propiedades del objeto con los valores asociados. En el Ejemplo 6 de esta Unidad veremos cmo se utiliza.
5.2.1.4 AnimatorSet
Esta clase AnimatorSet facilita la creacin de animaciones en secuencia o en paralelo. Veamos los mtodos ms destacables de esta clase:
- playSequentially(Animator... animaciones): las animaciones indicadas se ejecutaran secuencialmente.
mismo tiempo.
5.2.1.5 AnimatorBuilder
La clase AnimatorBuilder permite aadir animaciones a la clase AnimatorSet y relacionarlas de forma ms compleja indicando la interdependencia entre ellas. Veamos un ejemplo
para aclarar el concepto:
// Creamos un objeto de la clase AnimatorSet
AnimatorSet s = new AnimatorSet();
// Ejecutamos la animacin 1 con la animacin 2 (a la vez)
s.play(anim1).with(anim2);
// Ejecutamos la animacin 3 antes de la animacin 2, es decir, antes de
la animacin 1 tambin.
s.play(anim2).before(anim3);
// Ejecutamos la animacin 3 despus de la animacin 4
s.play(anim4).after(anim3);
en el mtodo anterior.
5.2.1.6 AnimationListener
Como es habitual, los eventos de la animacin se definen en la clase de tipo interface:
Como puedes ver, los listeners son bastante intuitivos y sencillos de implementar.
77
Aula Mentor
5.2.1.7 PropertyValuesHolder
Esta clase permite animar mltiples valores durante el ciclo de animacin. Puede entenderse
esta clase como un objeto que contiene la dupla propiedad/valor. Podemos utilizar esta clase
para crear animaciones con ValueAnimator y ObjectAnimator y, as, ejecutar varios cambios de propiedades en paralelo.
Veamos los mtodos ms importantes de esta clase:
- ofFloat(String nombrePropiedad, float... valores): establece los nuevos valores
de tipo float que debe tomar la propiedad nombrePropiedad al final de la animacin.
- ofInt(String nombrePropiedad, int
... valores): establece los nuevos valores de
tipo entero que debe tomar la propiedad nombrePropiedad al final de la animacin.
- ofObject(String nombrePropiedad, TypeEvaluator evaluador, Object... valores): establece los nuevos valores del tipo object que debe tomar la propiedad nombrePropiedad y deben interpretarse con el evaluador al final de la animacin.
- ofKeyframe(Property propiedad, Keyframe... valores): permite crear una animacin de tipo de fotograma para la propiedad.
5.2.1.8 Keyframe
78
Esta clase base permite crear animaciones con fotogramas. Esta clase alberga la dupla tiempo/
valor que se debe aplicar a una animacin. Se puede utilizar con las clases ValueAnimator
y ObjectAnimator.
Para entender cmo funciona, podemos hacer una analoga con el cine, es decir, cada
fotograma Keyframe es un estado del objeto y el movimiento de la animacin se produce al
cambiar el fotograma en el tiempo.
Incluso es posible definir para cada Keyframe un objeto del tipo TimeInterpolator
que define cmo se hace la interpolacin en el cambio de fotograma.
Veamos los mtodos ms importantes de esta clase:
- ofFloat(float tiempo, float... valores): establece los nuevos valores de tipo float
que debe tener la animacin al final del fotograma en la fraccin de tiempo medida como
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.
Vemos ahora un sencillo ejemplo que evala una animacin que utiliza puntos (clase PointF)
de Android:
// Definimos el evaluador de la clase PointF (punto de Android)
public class MiPuntoEvaluator implements TypeEvaluator<PointF>
{
// Definimos su mtodo evaluate
public PointF evaluate(float fraction, PointF valorInicio, PointF
valorFinal)
{
// Obtenemos la posicin actual
PointF valorInicio = (PointF) valorInicio;
PointF valorFinal = (PointF) valorFinal;
// Devolvemos la nueva posicin moviendo el punto la fraccin
// indicada
return new PointF(
valorInicio.x + fraction * (valorFinal.x - valorInicio.x),
valorInicio.y + fraction * (valorFinal.y - valorInicio.y));
}
}
79
5.2.1.10 ViewPropertyAnimator
Esta clase permite animar de forma automtica y optimizada varias propiedades de una Vista
exclusivamente al mismo tiempo.
La sintaxis de esta clase es muy intuitiva y fcil de aplicar ya que nicamente debemos
decirle a la Vista que deseamos animarla indicando el nuevo valor de la propiedad. Es decir,
no tenemos que utilizar todo el mecanismo de Animator.
Esta clase no dispone de constructor, por lo que para crear un objeto de este tipo
debemos invocar la orden animate() en la Vista que deseamos animar y que devolver una
instancia a la clase ViewPropertyAnimator.
Veamos los mtodos ms importantes de esta clase que permiten modificar propiedades de
una Vista:
- alpha(float valor): cambia la opacidad de la Vista segn el valor.
- rotationX(float valor): rota la Vista en el eje X segn el valor.
- rotationY(float valor): rota la Vista en el eje Y segn el valor.
- scaleX(float valor): escala la Vista en el eje X segn el valor.
- scaleY(float valor): escala la Vista en el eje Y segn el valor.
- translationX(float valor): traslada la Vista en el eje X segn el valor.
- translationY(float valor): traslada la Vista en el eje Y segn el valor.
- x(float valor): cambia la posicin X de la Vista segn el valor.
- y(float valor): cambia la posicin Y de la Vista segn el valor.
Aula Mentor
5.2.1.11 LayoutTransition
Esta clase permite animar los Layout mediante transiciones cada vez que se aade o se quita
una Vista de este contenedor. Para ello, debemos invocar el mtodo setLayoutTransition
(LayoutTransition) en el Layout que deseemos animar.
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
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Movimiento
android:layout_column=1 >
</Button>
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton3
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Anim. secuencial
android:layout_column=0 />
<Button
android:id=@+id/boton4
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=Animac. XML
android:layout_column=1 />
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button
android:id=@+id/boton5
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=PropertiesHolder
android:layout_column=0 />
<Button
android:id=@+id/boton6
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=ViewAnimator
android:layout_column=1 />
</TableRow>
<TableRow
android:layout_width=fill_parent
android:layout_height=wrap_content>
81
Aula Mentor
<Button
android:id=@+id/boton7
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=TypeEvaluator
android:layout_column=0 />
<Button
android:id=@+id/boton8
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=animar
android:text=KeyFrames
android:layout_column=1 />
</TableRow>
</TableLayout>
</LinearLayout>
82
<set xmlns:android=http://schemas.android.com/apk/res/android
android:ordering=sequentially>
<objectAnimator
android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=1
android:valueTo=0
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
<objectAnimator
android:interpolator=@android:interpolator/accelerate_cubic
android:valueFrom=0
android:valueTo=1
android:valueType=floatType
android:propertyName=alpha
android:duration=2000 />
</set>
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;
83
Aula Mentor
84
// final de la longitud del texto
dest = 0 - longitudEtiqueta;
// Si la etiqueta ya est fuera de la pantalla
// volvemos a ponerla en su sitio
if (etiqueta.getX() < 0) {
dest = 0;
}
// Definimos una animacin que cambia la propiedad
// X de la etiqueta hacia dest
animacion = ObjectAnimator.ofFloat(etiqueta,x,
dest);
// Definimos la animacin durante 2 segundos
animacion.setDuration(2000);
break;
// Animacin secuencial por lotes
case R.id.boton3:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Definimos una animacin que cambia el color de la
// etiqueta del azul actual al negro
ObjectAnimator color1 =
ObjectAnimator.ofInt(etiqueta, backgroundColor,
getResources().getColor(R.color.negro));
// Definimos una animacin que cambia de nuevo el
// color de la etiqueta del negro actual al azul
ObjectAnimator color2 =
ObjectAnimator.ofInt(etiqueta,backgroundColor,
getResources().getColor(R.color.azul));
// Cambiamos el tamao de la fuente a 40f
ObjectAnimator fuente1 =
ObjectAnimator.ofFloat(etiqueta,
textSize, 40f);
// Dejamos el tamao de la fuente inicial
ObjectAnimator fuente2 =
ObjectAnimator.ofFloat(etiqueta,
textSize, fuenteEtiqueta);
// Definimos un animador por lotes
as = new AnimatorSet();
// Ejecutamos la animacin en secuencia
as.playSequentially(color1,color2, fuente1, fuente2);
// La animacin dura 4 segundos
as.setDuration(4000);
break;
// Animacin desde fichero XML
case R.id.boton4:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Cargamos la animacin de un archivo XML
as = (AnimatorSet)AnimatorInflater.loadAnimator(this,
R.anim.fundidos);
// Indicamos la Vista sobre la que se debe aplicar
as.setTarget(etiqueta);
break;
// Botn PropertiesHolder (Contenedor de propiedades)
case R.id.boton5:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();
// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);
// Creamos un PropertiesHolder en el eje X e Y para
// que vuelva a la posicin inicial
pvhX = PropertyValuesHolder.ofFloat(x, x);
PropertyValuesHolder pvhY =
PropertyValuesHolder.ofFloat(y, y);
// Definimos la animacin con estos PropertiesHolder
animacion =ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhX, pvhY);
// La animacin dura 5 segundos
animacion.setDuration(5000);
// Definimos un acelerador de la animacin
animacion.setInterpolator(new
AccelerateDecelerateInterpolator());
break;
// Botn ViewAnimator
case R.id.boton6:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();
// Movemos la etiqueta a un lado
etiqueta.setX(w);
etiqueta.setY(h);
// Definimos una animacin del tipo ViewAnimator
ViewPropertyAnimator vpa = etiqueta.animate();
// Definimos la posicin final de la etiqueta en la
// animacin
vpa.x(x);
vpa.y(y);
// La animacin dura 5 segundos
vpa.setDuration(5000);
// Definimos el listener de la animacin
vpa.setListener(this);
// Definimos un acelerador de la animacin
vpa.setInterpolator(new
AccelerateDecelerateInterpolator());
break;
// Botn TypeEvaluator
case R.id.boton7:
// Ponemos la etiqueta en su sitio y sin fundido
85
Aula Mentor
86
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Definimos el color inicial y final de la animacin
Integer colorIni =
getResources().getColor(R.color.rojo);
Integer colorFin =
getResources().getColor(R.color.azul);
// Definimos la animacin utilizando el TypeEvaluator
// ArgbEvaluator e indicando los colores inicial y
// final
ValueAnimator colorAnimation = ValueAnimator.ofObject(
new ArgbEvaluator(), colorIni, colorFin);
// Definimos el listener que se lanza cada vez que es
// necesario actualizar la animacin
colorAnimation.addUpdateListener(
new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator
animator) {
// Cambiamos el color de la etiqueta
etiqueta.setBackgroundColor(
(Integer)animator.getAnimatedValue());
}
});
// La animacin dura 5 segundos
colorAnimation.setDuration(5000);
// Quitamos todos los listeners de la animacin
colorAnimation.removeAllListeners();
// Aadimos el listener local
colorAnimation.addListener(this);
// Iniciamos la animacin
colorAnimation.start();
break;
// Botn KeyFrames (Fotogramas)
case R.id.boton8:
// Ponemos la etiqueta en su sitio y sin fundido
etiqueta.setX(0);
etiqueta.setAlpha(1f);
// Obtenemos el tamao de la etiqueta
h = etiqueta.getHeight();
w = etiqueta.getWidth();
x = etiqueta.getX();
y = etiqueta.getY();
// Fotograma de inicio : 0.2
// Valor alpha: 0.8
Keyframe kf0 = Keyframe.ofFloat(0.2f, 0.8f);
// Fotograma intermedio: 0.5
// Valor alpha: 0.2
Keyframe kf1 = Keyframe.ofFloat(.5f, 0.2f);
// Fotograma final: 0.8
// Valor alpha: 0.8
Keyframe kf2 = Keyframe.ofFloat(0.8f, 0.8f);
// Definimos un PropertyHolder para almacenar los
// cambios
PropertyValuesHolder pvhAlpha =
PropertyValuesHolder.ofKeyframe(alpha, kf0,
kf1, kf2);
// Definimos tambin el movimiento horizontal
pvhX = PropertyValuesHolder.ofFloat(x, w, x);
// Definimos la animacin a partir de fotogramas
animacion =
ObjectAnimator.ofPropertyValuesHolder(
etiqueta, pvhAlpha,pvhX);
// La animacin dura 5 segundos
animacion.setDuration(5000);
break;
default:
break;
}
if (animacion!=null) {
// Quitamos todos los listeners de la animacin
animacion.removeAllListeners();
// Aadimos el listener local
animacion.addListener(this);
// Iniciamos la animacin
animacion.start();
} else
if (as!=null) {
// Quitamos todos los listeners de la animacin
as.removeAllListeners();
// Aadimos el listener local
as.addListener(this);
as.start();
}
} // end animar
// Mtodos que lanza Android cuando ocurre un evento en la animacin
@Override
public void onAnimationCancel(Animator animation) {
Toast.makeText(this, Se cancela la animacin,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationEnd(Animator animation) {
Toast.makeText(this, Finaliza la animacin,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationRepeat(Animator animation) {}
@Override
public void onAnimationStart(Animator animation) {
Toast.makeText(this, Empieza la animacin,
Toast.LENGTH_SHORT).show();
}
} // end clase
El cdigo fuente anterior utiliza la clase ObjectAnimator aprovechando sus mtodos ofFloat
y ofInt para cambiar la opacidad (propiedad alpha) y mover la etiqueta (propiedad x)
87
Aula Mentor
respectivamente.
Mediante la clase AnimatorSet y su mtodo playSequentially() hemos ejecutado
dos animaciones de forma secuencial que llevan a cabo un fundido a negro y a blanco.
Tambin hemos desarrollado una animacin desde un fichero XML mediante la orden
AnimatorInflater.loadAnimator(this, R.anim.fundidos). El fichero fundidos.xml se
ha descrito anteriormente.
La clase PropertyValuesHolder mueve la etiqueta en el eje X e Y con el mtodo
ofFloat().
De forma similar, la clase ViewPropertyAnimator desplaza la etiqueta con los mtodos
x() e y().
En el botn sptimo hemos utilizado la subclase ArgbEvaluator de TypeEvaluator
para cambiar los colores de fondo de la etiqueta.
La clase KeyFrames desarrolla una animacin de tipo fotograma mediante sus mtodos
ofFloat() de la propiedad alpha de la etiqueta.
Finalmente, se crea un listener del tipo AnimationListener para detectar cundo la
animacin se inicia, se para o se repite, y mostrar al usuario un mensaje de tipo Toast.
El resto de mtodos son intuitivos y se ha explicado en la parte terica de este apartado,
te sugerimos que le eches un vistazo detallado al cdigo fuente completo.
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.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
89
Aula Mentor
//cambian el interpolador.
final Animation animAccelerateDecelerate =
AnimationUtils.loadAnimation(this,
R.anim.accelerate_decelerate);
final Animation animAccelerate =
AnimationUtils.loadAnimation(this, R.anim.accelerate);
final Animation animAnticipate =
AnimationUtils.loadAnimation(this, R.anim.anticipate);
final Animation animAnticipateOvershoot =
AnimationUtils.loadAnimation(this,
R.anim.anticipate_overshoot);
final Animation animBounce = AnimationUtils.loadAnimation(this,
R.anim.bounce);
final Animation animCycle = AnimationUtils.loadAnimation(this,
R.anim.cycle);
final Animation animDecelerate =
AnimationUtils.loadAnimation(this, R.anim.decelerate);
final Animation animLinear = AnimationUtils.loadAnimation(this,
R.anim.linear);
final Animation animOvershoot = AnimationUtils.loadAnimation(this,
R.anim.overshoot);
// Buscamos la imagen que vamos a animar
final ImageView imagen = (ImageView)findViewById(R.id.imagen);
// Botones de la interfaz de usuario
Button botonAccelerateDecelerate =
(Button)findViewById(R.id.acceleratedecelerate);
Button botonAccelerate = (Button)findViewById(R.id.accelerate);
Button botonAnticipate = (Button)findViewById(R.id.anticipate);
Button botonAnticipateOvershoot =
(Button)findViewById(R.id.anticipateovershoot);
Button botonBounce = (Button)findViewById(R.id.bounce);
Button botonCycle = (Button)findViewById(R.id.cycle);
Button botonDecelerate = (Button)findViewById(R.id.decelerate);
Button botonLinear = (Button)findViewById(R.id.linear);
Button botonOvershoot = (Button)findViewById(R.id.overshoot);
90
botonAnticipate.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animAnticipate);
}
});
botonAnticipateOvershoot.setOnClickListener(new
Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animAnticipateOvershoot);
}
});
botonBounce.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animBounce);
}
});
botonCycle.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animCycle);
}
});
botonDecelerate.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animDecelerate);
}
});
botonLinear.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animLinear);
}
});
botonOvershoot.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View arg0) {
imagen.startAnimation(animOvershoot);
}
});
} // end onCreate
} // end clase
Como puedes observar, el cdigo anterior carga las animaciones a partir de archivos XML y asocia
a cada botn la animacin de la imagen que corresponde teniendo en cuenta el interpolador.
91
Aula Mentor
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, ImageView, etctera) en la superficie.
- Finalmente, se componen todas las superficies visibles en la pantalla del dispositivo, es decir,
la de la Actividad que aparece en la pantalla y la de la barra de notificacin.
Aula Mentor
Fjate que en el cdigo fuente de esta aplicacin no hemos incluido ningn archivo
Layout que defina la interfaz de usuario.
94
La interfaz de usuario de la aplicacin se compone de una Actividad que crea una superficie y
la asigna a la interfaz de usuario. Veamos su contenido en el fichero MainActivity.java:
public class MainActivity
extends Activity {
A continuacin, se muestra cmo se implementa la interfaz visual del usuario mediante esta
superficie definida en el fichero SuperficieView.java:
// Clase que se extiende de SurfaceView e implementa el mtodo
// Callback de SurfaceHolder
public class SuperficieView extends SurfaceView implements
SurfaceHolder.Callback {
// Hilo que se encarga de actualizar la superficie
private BucleThread bucleThread;
// Constructor de la Superficie
public SuperficieView(Context context) {
super(context);
// Creamos el hilo
bucleThread = new BucleThread(this);
// Obtenemos el Holder de la superficie y le asignamos los
// eventos definidos en esta clase
getHolder().addCallback(this);
// Delegamos el evento onTouch al Hilo
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(bucleThread!=null) {
return bucleThread.onTouch(event);
}
else return false;
}
}); // end onTouch
} // end onCreate
// Si la superficie cambia, entonces guardamos el tamao de la
// pantalla
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int
width, int height) {
// Indicamos el nuevo tamao de la superficie al hilo
bucleThread.setSurfaceSize(width, height);
}
// Cuando la superficie se ha creado, marcamos que se ejecuta el
// hilo y lo iniciamos
@Override
public void surfaceCreated(SurfaceHolder holder) {
bucleThread.setRunning(true);
bucleThread.start();
}
// Si la superficie se destruye, entonces paramos el hilo
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean reintentar = true;
bucleThread.setRunning(false);
// Mientras el hilo est activo lo intentamos parar
while (reintentar) {
try {
// Unimos el Hilo de actualizacin con el Hilo
// principal de la aplicacin invocando la orden
95
Aula Mentor
96
En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
getHolder().addCallback(this) para gestionar los eventos del SurfaceView y poder
acceder a la superficie.
En este caso hemos implementado algunos de los eventos anteriores. Veamos las sentencias ms novedosas de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos
creado en el constructor de esta clase y al que hemos pasado como parmetro la referencia del
surfaceholder. Despus, establecemos el estado de ejecucin del hilo y lo arrancamos con la
orden start(). La funcin de este hilo es actualizar la interfaz de usuario dibujndola cuando
lo consideremos necesario.
El mtodo SurfaceDestroyed() se lanza cada vez que la aplicacin pasa a segundo
plano y el SurfaceView se va a destruir junto con todo su contenido. Por lo tanto, es necesario
parar el hilo. Para ello, unimos el Hilo de actualizacin con el Hilo principal de la aplicacin
invocando la orden join hasta que lo conseguimos. Es necesario unir todos los hilos para destruirlos correctamente.
Es imprescindible que la interaccin del usuario con la aplicacin sea suave y
los grficos se muestren sin parpadeos. Para conseguirlo, tenemos que ejecutar dos hilos
en la aplicacin: el hilo principal, que se encarga de gestionar la Actividad de la aplicacin, y el
segundo hilo, que dibuja la superficie y gestiona la interaccin del usuario con sta.
La razn principal de esta divisin de tareas est en que, como estudiamos en el curso
de Iniciacin a Android, no debemos bloquear nunca el hilo principal de una aplicacin. Por
ejemplo, si el usuario presiona la pantalla tctil puede crear paradas en la ejecucin del cdigo.
Hemos implementado el Hilo BucleThread para actualizar el dibujo del objeto SurfaceView
y gestionar los toques sobre ste. En el constructor de este hilo le pasamos como parmetro la
referencia (this) al objeto SuperficieView para poder modificar el contenido de sta.
Por esto, en el mtodo surfaceChanged() del SuperficieView obtenemos el tamao
de la pantalla del dispositivo Android y se lo pasamos al hilo BucleThread.
Adems, en el constructor de esta clase, hemos delegado el mtodo onTouchEvent() en
este Hilo mediante la sentencia setOnTouchListener().
Finalmente, no hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo
quien se encargue de dibujarla.
A continuacin, se muestra cmo se implementa este hilo en el archivo BucleThread.
java:
// Clase que se extiende de la clase Thread (Hilo)
public class BucleThread extends Thread {
// Nmero de actualizaciones por segundo que hace el hilo
static final long FPS = 10;
// Variable donde guardamos la superficie
private SuperficieView superfView;
// Variables que almacenan el ancho y largo de la pantalla
private int width, height;
// Sirve para saber si se est ejecutando el hilo
private boolean running = false;
// Posicin actual de la pelota
private int pos_x = -1;
private int pos_y = -1;
// Velocidad del movimiento
private int xVelocidad = 10;
private int yVelocidad = 5;
// Variables que indican las coordenadas donde el usuario toc la
// pantalla
public int touched_x, touched_y;
// Indica si se est tocando la pantalla o no
public boolean touched;
// Bitmap donde cargamos la imagen de la pelota
private BitmapDrawable pelota;
// Constructor que guarda la superficie
public BucleThread(SuperficieView view) {
this.superfView = view;
// Buscamos la imagen pelota
pelota = (BitmapDrawable) view.getContext().
getResources().getDrawable(R.drawable.pelota);
}
// Mtodo para establecer la variable running
public void setRunning(boolean run) {
running = run;
}
// Mtodo tpico de un hilo
@Override
public void run() {
// N de actualizaciones que debemos hacer cada segundo
long ticksPS = 1000 / FPS;
97
Aula Mentor
98
// Variables temporales para controlar el tiempo
long startTime;
long sleepTime;
// Mientras estemos ejecutando el hilo
while (running) {
Canvas canvas = null;
// Obtenemos el tiempo actual
startTime = System.currentTimeMillis();
try {
// Bloqueamos el canvas de la superficie para dibujarlo
canvas = superfView.getHolder().lockCanvas();
// Sincronizamos el mtodo draw() de la superficie para
// que se ejecute como un bloque
synchronized (superfView.getHolder()) {
if (canvas!=null)
doDraw(canvas);
}
} finally {
// Liberamos el canvas de la superficie desbloquendolo
if (canvas != null) {
superfView.getHolder().unlockCanvasAndPost(canvas);
}
}
// Tiempo que debemos parar la ejecucin del hilo
sleepTime = ticksPS - System.currentTimeMillis()-startTime;
// Paramos la ejecucin del hilo
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) { }
} // end while
} // end run()
// Evento que se lanza cada vez que es necesario dibujar la
// superficie
protected void doDraw(Canvas canvas) {
// Primera posicin de la pelota en el centro
if (pos_x<0 && pos_y <0) {
pos_x = this.width/2;
pos_y = this.height/2;
} else {
// La nueva posicin es la posicin anterior + la velocidad en
// cada coordenada X e Y
pos_x += xVelocidad;
pos_y += yVelocidad;
// Si el usuario ha tocado la pelota cambiamos el sentido de
// la misma
if (touched && touched_x>(pos_x-pelota.getBitmap().getWidth())
&& touched_x<(pos_x+pelota.getBitmap().getWidth())
&& touched_y>(pos_y-pelota.getBitmap().getHeight())
&& touched_y<(pos_y+pelota.getBitmap().getHeight()))
{
//
//
//
if
touched=false;
xVelocidad = xVelocidad*-1;
yVelocidad = yVelocidad*-1;
Si pos_x es mayor que el ancho de la pantalla teniendo en
cuenta el ancho de la pelota o la nueva posicin es < 0
entonces cambiamos el sentido de la pelota
((pos_x > this.width - pelota.getBitmap().getWidth()) ||
(pos_x < 0)) {
xVelocidad = xVelocidad*-1;
}
// Si pos_y es mayor que el alto de la pantalla teniendo en
// cuenta el alto de la pelota o la nueva posicin es < 0
// entonces cambiamos el sentido de la pelota
if ((pos_y > this.height - pelota.getBitmap().getHeight()) ||
(pos_y < 0)) {
yVelocidad = yVelocidad*-1;
}
}
// Color gris para el fondo de la aplicacin
canvas.drawColor(Color.LTGRAY);
// Dibujamos la pelota en la nueva posicin
canvas.drawBitmap(pelota.getBitmap(), pos_x, pos_y, null);
}
// Evento que se lanza cuando el usuario hace clic sobre la
// superficie
public boolean onTouch(MotionEvent event) {
// Obtenemos la posicin del toque
touched_x = (int) event.getX();
touched_y = (int) event.getY();
// Obtenemos el tipo de Accion
int action = event.getAction();
Log.e(Toque (X,Y), ( + touched_x + , + touched_y + ));
switch (action) {
// Cuando se toca la pantalla
case MotionEvent.ACTION_DOWN:
Log.e(TouchEven ACTION_DOWN, Usuario toca la pantalla );
touched = true;
break;
// Cuando se desplaza el dedo por la pantalla
case MotionEvent.ACTION_MOVE:
touched = true;
Log.e(TouchEven ACTION_MOVE, Usuario desplaza dedo por la
pantalla );
break;
// Cuando levantamos el dedo de la pantalla que estbamos
// tocando
case MotionEvent.ACTION_UP:
touched = false;
Log.e(TouchEven ACTION_UP, Ya no tocamos la pantalla);
break;
// Cuando se cancela el toque. Es similar a ACTION_UP
99
Aula Mentor
case MotionEvent.ACTION_CANCEL:
touched = false;
Log.e(TouchEven ACTION_CANCEL, );
break;
// El usuario ha tocado fuera del rea de la interfaz del
// usuario
case MotionEvent.ACTION_OUTSIDE:
Log.e(TouchEven ACTION_OUTSIDE, );
touched = false;
break;
default:
}
return true;
} // end onTouch
// Se usa para establecer el nuevo tamao de la superficie
public void setSurfaceSize(int width, int height) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (superfView) {
// Guardamos el nuevo tamao
this.width = width;
this.height = height;
}
}
100
} // end clase
En la teora de la Unidad 5 puedes encontrar una descripcin detallada de la gestin de pulsaciones del usuario sobre la pantalla.
Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:
101
Puedes probar a hacer clic sobre la bola para comprobar que su movimiento cambia de sentido.
Si instalas este sencillo juego en un dispositivo real vers que es ms sencillo probar la aplicacin.
7. Grficos en 3D en Android
Android dispone de dos bibliotecas para pintar grficos en 3D:
- OpenGL: biblioteca estndar de diseo 3D.
- RenderScript: nueva biblioteca de bajo nivel de dibujo grfico en 3D disponible desde la
versin 3.0 de Android que aprovecha el procesador GPU de la tarjeta grfica para pintar
dejando as al procesador principal del dispositivo capacidad disponible para otras tareas.
Estas grficas se desarrollan con un lenguaje especfico llamado C99.
Aula Mentor
7.1 OpenGL
En este apartado vamos a aprender a aplicar los conceptos bsicos de la
biblioteca grfica ms utilizada y difundida: OpenGL.
La biblioteca OpenGL (Open Graphics Library) es una especificacin estndar de una API
multilenguaje y multiplataforma que permite escribir aplicaciones que pinten grficos en 2D
y en 3D. Contiene ms de 250 funciones diferentes que pueden usarse para dibujar grficos
tridimensionales complejos a partir de primitivas geomtricas simples, tales como puntos, lneas
y tringulos.
Android soporta las siguientes versiones:
- OpenGL 2.0 ES: a partir de la versin 2.2 de Android.
- OpenGL 1.1 ES: en todas las versiones de Android.
Empezaremos diseando grficos sencillos en Android para acabar aumentando la complejidad
y llegar a dibujar grficos en 3D con texturas.
102
Para entender de forma grfica estas dos clases anteriores, podemos hacer una
analoga con un pintor y su lienzo. En este caso, el pintor es la interfaz GLSurfaceView.Renderer y el lienzo es GLSurfaceView.
Por compatibilidad entre las distintas versiones de Android vamos a utilizar la versin 1.0 de la biblioteca OpenGL para desarrollar los ejemplos del curso.
de geometra:
- Lnea o Recta: conjunto de puntos que se extienden con determinada longitud.
- Vrtice: punto donde dos o ms rectas se encuentran. Veamos unos ejemplos sobre cmo
utilizar vrtices para definir un punto o polgonos en Android:
Punto
// Punto ubicado en la coordenada (1,1,0)
float vertice[] = { 1f ,1f ,0f };
Cuadrado
float vertices[] = {
-1f, 1f, 0f,//
-1f, -1f, 0f, //
1f, -1f, 0f, //
1f,
1f, 0f//
};
vrtice
vrtice
vrtice
vrtice
ubicado
ubicado
ubicado
ubicado
en
en
en
en
(-1,1,0)
(-1,-1,0)
(1,-1,0)
(1,1,0)
vrtice
vrtice
vrtice
vrtice
ubicado
ubicado
ubicado
ubicado
en
en
en
en
(-1,-1,0)
(1,-1,0)
(0,0.8,0)
(0, 0, 2)
float vertices[] = {
-1f, -1f, 0f, //
1f, -1f, 0f, //
0f, 0.8f, 0f, //
0f, 0f, 2f //
};
Como puede observar, para definir vrtices, simplemente debemos indicar las coordenadas
(x,y,z) en una matriz de tipo float.
- Cara: cada lado que forma un polgono. La modificacin de una cara afecta a sus vrtices y
a sus aristas.
- Arista o Borde: unin de dos vrtices. En un modelo 3D, la arista puede estar compartida
por dos caras o polgonos.
- Polgono: figura cerrada que est formada por tres o ms caras que se unen en sus aristas.
- Eje de coordenadas: tipo de coordenadas ortogonales usadas en espacios euclidianos caracterizadas por que usa como referencia ejes ortogonales entre s que se cortan en un punto
origen. En el espacio 3D estos ejes tienen el aspecto que se refleja en el grfico inferior. En
el dispositivo Android, X e Y se encuentran en los ejes planos horizontal y vertical. El eje
Z es un eje abstracto perpendicular a su pantalla, que en grficos 3D se usa para crear la
sensacin de lejana o cercana del grfico respecto al observador. Cuanto ms lejos est un
objeto respecto al observador, menor ser su valor en el eje Z.
103
Aula Mentor
viene dado por la siguiente frmula: Tamao total en memoria = Ancho pantalla x Alto
pantalla x Nmero de canales x bytes por muestra.
- Primitivas de OpenGL: funciones que permiten dibujar puntos, lneas, tringulos, etctera.
En general, es recomendable utilizar tringulos para dibujar figuras complejas, ya que esta
biblioteca es ms eficiente as y el grfico aparece en menos tiempo.
- Estados de OpenGL: OpenGL guarda informacin sobre diferentes flags que determinan
qu factores y qu valores afectarn a la hora de dibujar. Cada vez que cambiamos el estado
mediante la llamada a una funcin de la API debemos tener en cuenta que el estado se quedar as hasta que lo volvamos a cambiar de nuevo. Por ejemplo, la funcin glColor cambia
el color del pincel de todas las funciones primitivas que utilicemos desde ese momento en
adelante. Podemos usar glEnable y glDisable para habilitar y deshabilitar propiedades de
OpenGL segn las necesidades.
- Buffer de profundidad o Z-Buffer: normalmente, en grficos en 3D interesa que OpenGL
tenga en cuenta qu objetos estn delante o detrs de otros (eje Z de perspectiva) a la hora
de pintarlos. El buffer de profundidad (del ingls, DepthBuffer) o Z-Buffer almacena el
valor de profundidad de cada pxel de la imagen en un buffer separado que se usa para
descartar pxeles que quedan detrs de objetos ya existentes en la imagen.
- Sistema de coordenadas: en la biblioteca OpenGL utilizaremos el sistema de coordenadas
ortogonal que hemos visto anteriormente al que aadiremos otra coordenada nueva que
indica la perspectiva (desde donde miramos el objeto). As, el sistema de coordenadas inicial
de OpenGL puede representarse con esta matriz:
104
Transformaciones: funciones que cambian la posicin del objeto relativa al centro de la figura
y pueden trasladarla, escalarla y rotarla. Matemticamente, estas transformaciones multiplican la
matriz anterior de coordenadas por otra matriz que es la que genera la transformacin. Veamos
unos ejemplos grficos:
Esquema
Descripcin
La orden glTranslatef(0.1, 0.1,
0) mueve el objeto en el eje X,Y de
coordenadas.
Esquema
Descripcin
La orden glRotatef(-45, 0, 0, 1)
rota el objeto. Los ngulos positivos
rotan al contrario que el movimiento
de las agujas del reloj.
La orden glScalef(2,
2,
aumenta el tamao del objeto.
1)
Por ejemplo, al trasladar un objeto dos unidades en el eje X, se genera la siguiente matriz de
transformacin:
Si aplicamos esta transformacin a la matriz anterior, nos quedar que la nueva matriz de transformacin es:
Si ahora dibujamos el punto (1, 0, 0) teniendo en cuenta que la matriz de transformacin indica
un desplazamiento de dos unidades en eje X, el punto deber dibujarse en la posicin (3,0,0).
Para esto, se multiplica el punto por la matriz de transformacin:
105
Aula Mentor
cambiar al modo de matriz correspondiente, para que las operaciones afecten a esa matriz
especficamente.
- Modo de dibujo: OpenGL dispone de distintas formas de pintar:
GL_POINTS: dibuja nicamente los vrtices.
GL_LINES: dibuja las lneas entre dos vrtices independientemente de otros vrtices.
GL_LINE_STRIP: dibuja las lneas conectadas entre s por un vrtice.
GL_LINE_LOOP: pinta de la misma forma que el anterior mtodo, pero une el punto
final con el punto inicial.
GL_POLYGON: dibuja una superficie de tipo polgono.
GL_TRIANGLES: dibuja superficies mediante tringulos usando tres puntos de referencia.
GL_TRIANGLE_STRIP: pinta superficies mediante tringulos continuos usando tres
puntos de referencia
GL_TRIANGULE_FAN: pinta superficies mediante tringulos usando un punto en comn, a modo de abanico.
GL_SQUADS: dibuja cuadrilteros de cuatro vrtices.
GL_QUAD_STRIP: dibuja cuadrilteros continuos de cuatro vrtices.
Como una imagen vale ms que mil palabras, veamos esquemticamente estos mecanismos:
106
Una vez explicados los conceptos bsicos de OpenGL, vamos a desarrollarlos mediante ejemplos prcticos, pues es ms sencillo y pedaggico entender en la prctica cmo se utiliza esta
biblioteca.
7.2 Grficos en 2D
En el Ejemplo 9 de esta Unidad hemos dibujado los polgonos tringulo y cuadrado en 2D. Es
recomendable abrir el Ejemplo 9 en Eclipse ADT para entender la explicacin siguiente.
Si abres el archivo res/layout/main_layout.xml, advertirs que hemos definido una
barra de botones que permitirn al usuario elegir el polgono que desea visualizar:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<LinearLayout
android:id=@+id/linearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerHorizontal=true
android:orientation=horizontal
android:gravity=top
android:layout_marginTop=1dp
android:layout_marginBottom=1dp>
<Button
android:id=@+id/cuadrado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Ver cuadrado />
<Button
android:id=@+id/triangulo
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Ver tringulo />
<CheckBox
android:id=@+id/cbColorear
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Colorear />
</LinearLayout>
107
Aula Mentor
<FrameLayout
android:id=@+id/frame
android:layout_width=fill_parent
android:layout_height=fill_parent
android:layout_below=@+id/linearLayout>
</FrameLayout>
</RelativeLayout>
108
findViewById(R.id.cuadrado);
boton_cuadrado.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Recreamos la superficie con el polgono cuadrado e
// indicamos si es necesario aadir el color
glSurface = new GLSurfaceView(MainActivity.this);
glSurface.setRenderer(new
MiRenderer(MiRenderer.MOSTRAR_CUADRADO,
conColor.isChecked()));
// Quitamos las Vistas del Frame y aadimos la nueva
// superficie
frame.removeAllViews();
frame.addView(glSurface, 0);
}
});
} // end onCreate
// Cuando la Actividad vuelve a primer plano, indicamos a la
// superficie que se actualice de nuevo
@Override
protected void onResume() {
super.onResume();
glSurface.onResume();
}
// Cuando la Actividad pasa a segundo plano, indicamos a la
// superficie que NO se actualice
@Override
protected void onPause() {
super.onPause();
glSurface.onPause();
}
} // end clase
Puedes observar en el cdigo anterior que, para crear los grficos, hemos utilizado la clase
superficie GLSurfaceView de OpenGL a la que asociamos con el mtodo setRenderer() el
Renderer que se encargar de dibujar la imagen en funcin de los parmetros pasados en el
constructor de este mtodo.
Adems, hemos utilizado la Layout del tipo FrameLayout para mostrar en la interfaz
del usuario la superficie creada anteriormente.
Hemos detallado el Renderer anterior en la clase MiRenderer del proyecto, que contiene el siguiente cdigo fuente:
public class MiRenderer implements Renderer {
// Constantes que indican el tipo de polgono que debemos dibujar
public static int MOSTRAR_TRIANGULO=1;
public static int MOSTRAR_CUADRADO=2;
// Guarda el polgono que estamos mostrando
private int mostrar_poligono=-1;
// Objeto del tipo Tringulo que describe su geometra
private Triangulo triangulo;
// Objeto del tipo Cuadrado que describe su geometra
109
Aula Mentor
110
gl.glViewport(0, 0, width, height);
// Establecemos la matriz de proyeccin como
// activa para modificarla y poder girar el polgono
gl.glMatrixMode(GL10.GL_PROJECTION);
// Reinicia la matriz de proyeccin del grfico
gl.glLoadIdentity();
// Calculamos el tamao de la perspectiva en funcin del nuevo
// tamao de la pantalla
GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);
// Establecemos de nuevo la matriz de vista/modelo
// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);
// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}
} // end clase
En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos:
- Sombreado suave en el dibujo (renderizado) mediante el mtodo glShadeModel().
- Color del fondo gris mediante glClearColor().
- Configurar el tamao del buffer de profundidad mediante glClearDepth() cuando se vaca.
El buffer de profundidad o z-buffer se utiliza para gestionar la visibilidad de varios grficos
3D superpuestos segn las coordenadas de sus pixeles.
- Indicamos el modo de renderizado mediante glEnable(GL10.GL_DEPTH_TEST), que tambin se conoce como algoritmo de z-Buffer porque gestiona qu elementos de una escena
son visibles y cules permanecern ocultos segn sus posiciones en el eje Z (distancia a cmara). En definitiva, lo que se hace es comparar las profundidades de todos los grficos para
representar el objeto ms cercano o lejano al observador segn el parmetro que indiquemos. Es decir, con el modo GL_DEPTH_TEST, los objetos cercanos ocultan a los ms lejanos.
- Especificamos la funcin que se usar para comparar la profundidad de los objetos mediante
el mtodo glDepthFunc(). El parmetro GL_LEQUAL indica que el valor de profundidad se
almacena si la cercana al observador es menor o igual al valor existente.
- Con el mtodo glHint() establecemos, con la propiedad GL_PERSPECTIVE_CORRECTION_
HINT, la calidad del color y la interpolacin de las coordenadas de la textura al valor GL_NICEST, que es la mxima calidad posible.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de profundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). Como
hemos visto, la matriz de proyeccin se utiliza para poder realizar cambios de perspectiva
(giros, translaciones, etctera). Realmente, lo que estamos haciendo es borrar la matriz de
proyeccin dejndola a 0, es decir, no hay transformacin ninguna.
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, alejamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado anteriormente.
111
Aula Mentor
112
// Si hemos indicado que queremos color...
if (conColor)
// Establecemos el color del tringulo en modo RGBA
gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
// Desactivamos el buffer de los vrtices
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
} // end clase
Podemos ver en el cdigo anterior que hemos definido los vrtices del tringulo en el constructor
utilizando un buffer de tipo float, ya que la biblioteca OpenGL necesita disponer, en este tipo
de variable, de los vrtices del polgono.
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el tringulo, realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW). Este sentido de dibujo es importante ya que determina la
direccin del vector normal de iluminacin; si ste apunta a la direccin equivocada, no veremos nada si utilizamos iluminacin. El orden contrario a las agujas del reloj indica que la
superficie del polgono est dirigida hacia el observador (regla de la mano derecha). Fjate
en este esquema:
113
Aula Mentor
- Si hemos indicado que debemos colorear el polgono, establecemos el color del tringulo en
modo RGBA mediante glColor4f().
- Finalmente, se desactiva el buffer de los vrtices con glDisableClientState().
Existen dos mtodos para dibujar un grfico mediante sus vrtices:
- public abstract void glDrawArrays(int mode, int first, int count): dibuja el
grfico segn el orden definido en la construccin de los vrtices. Ya hemos utilizado este
mtodo en el cdigo anterior.
- public abstract void glDrawElements(int mode, int count, int type, Buffer
indices): el segundo mtodo es similar al anterior, si bien debemos indicar el orden en el
En ambos mtodos debemos indicar el modo de dibujo empleado por OpenGL para pintar el
grfico.
Veamos ahora el cuadrado especificado en el archivo Cuadrado.java:
public class Cuadrado {
114
bufferVertices.put(vertices);
// Movemos la posicin del buffer al inicio
bufferVertices.position(0);
// Guardamos si es necesario colorear el polgono
this.conColor=conColor;
// Si es necesario colorear el cuadrado...
if (conColor) {
// Definimos el buffer de la matriz de colores de igual
// forma que hemos hecho con la matriz de vrtices
byteBuf = ByteBuffer.allocateDirect(colores.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
bufferColores = byteBuf.asFloatBuffer();
bufferColores.put(colores);
bufferColores.position(0);
}
} // end constructor
// Mtodo que invoca el Renderer cuando debe dibujar el cuadrado
public void draw(GL10 gl) {
// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);
// Desactivamos el buffer de los vrtices
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Desactivamos el buffer de los colores si es necesario
if (conColor)
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
} // end clase
Podemos ver en el cdigo anterior que hemos definido los vrtices y colores del cuadrado en el
constructor utilizando dos buffer de tipo float ya que la biblioteca OpenGL necesita disponer,
115
Aula Mentor
116
android:theme=@android:style/Theme.NoTitleBar.Fullscreen
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 9 (OpenGL 2D) de la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la biblioteca OpenGL de Android.
Si ejecutas en Eclipse ADT este Ejemplo 9 en el AVD, vers que se muestra la siguiente
aplicacin que dibuja dos polgonos:
117
Aula Mentor
118
119
Aula Mentor
// Establecemos de nuevo la matriz de vista/modelo
// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);
// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}
} // end clase
En el cdigo anterior podemos ver que en el constructor guardamos el tipo de polgono que
queremos mostrar y creamos la geometra correspondiente.
Despus, en el mtodo onSurfaceCreated()establecemos los valores cuya variacin
ser poca o nula durante la ejecucin del programa. En este caso, fijamos los mismos parmetros
que en el Ejemplo 9.
En el mtodo onDrawFrame() insertamos las sentencias que deben ejecutarse durante el dibujo:
- Cada vez que se dibuja una escena hay que limpiar tanto el buffer de color como el de profundidad mediante el mtodo glClear().
- Se reinicia la matriz de proyeccin del grfico con el mtodo glLoadIdentity(). La matriz
de proyeccin se utiliza para poder realizar cambios de perspectiva (giros, translaciones,
etctera). Realmente, lo que estamos haciendo es mover de nuevo el puntero de dibujo al
centro del eje de coordenadas (0,0,0).
- Movemos la posicin de los ejes en (x,y,z) con el mtodo glTranslatef(). Es decir, alejamos el dibujo en el eje Z dando sensacin de profundidad al observador.
- Rotamos el polgono mediante el mtodo glRotatef(). As, se logra la sensacin de movimiento del polgono.
- Invocamos el mtodo draw del objeto que debemos dibujar y que hemos instanciado anteriormente.
120
};
// Matriz con colores en formato RGBA
private float colores[] = {
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 1.0f, 0.0f, 1.0f, // Verde
0.0f, 0.0f, 1.0f, 1.0f, // Azul
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 0.0f, 1.0f, 1.0f, // Azul
0.0f, 1.0f, 0.0f, 1.0f, // Verde
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 1.0f, 0.0f, 1.0f, // Verde
0.0f, 0.0f, 1.0f, 1.0f, // Azul
1.0f, 0.0f, 0.0f, 1.0f, // Rojo
0.0f, 0.0f, 1.0f, 1.0f, // Azul
0.0f, 1.0f, 0.0f, 1.0f // Verde
};
// Constructor de la pirmide
public Piramide() {
// Definimos el buffer con los vrtices del polgono.
// Un nmero float tiene 4 bytes de longitud, as que
// multiplicaremos x 4 el nmero de vrtices.
ByteBuffer byteBuf=ByteBuffer.allocateDirect(vertices.length*4);
byteBuf.order(ByteOrder.nativeOrder());
bufferVertices = byteBuf.asFloatBuffer();
bufferVertices.put(vertices);
bufferVertices.position(0);
121
Aula Mentor
// Desactivamos el buffer de los vrtices y colores
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
} // end clase
Podemos ver en el cdigo anterior que hemos definido los vrtices de los cuatro tringulos
que forman las superficies de la pirmide en el constructor utilizando un buffer de tipo float.
Adems, definimos tambin un buffer adicional para los colores de sta.
122
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de los
vrtices y colores a la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays()para dibujar la superficie del polgono mediante la matriz en el modo tringulo GL_TRIANGLES.
- Finalmente, se desactiva los buffer de vrtices y colores con glDisableClientState().
Veamos ahora el Cubo especificado en el fichero Cubo.java:
public class Cubo {
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los vrtices del cubo
private FloatBuffer bufferVertices;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los colores del cubo
private FloatBuffer bufferColores;
// Buffer de tipo float que usamos para pasar
// a la librera OpenGL los ndices del cubo
private ByteBuffer bufferIndices;
// Vrtices del cubo
private float vertices[] = {
-1.0f, -1.0f, 1.0f,
// abajo delante izq. (V0)
1.0f, -1.0f, 1.0f,
// abajo delante dcha. (V1)
-1.0f,
1.0f, 1.0f,
// arriba delante izq. (V2)
1.0f,
1.0f, 1.0f,
// arriba delante dcha. (V3)
1.0f, -1.0f, -1.0f,
// abajo detrs dcha. (V4)
-1.0f, -1.0f, -1.0f,
// abajo detrs izq. (V5)
1.0f,
1.0f, -1.0f,
// arriba detrs dcha. (V6)
};
// Matriz con colores en formato RGBA
private float colores[] = {
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f
};
123
Aula Mentor
// Mtodo que invoca el Renderer cuando debe dibujar el cubo
public void draw(GL10 gl) {
// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);
// Indicamos el n de coordenadas (3), el tipo de datos de
// la matriz (float), la separacin en la matriz de los
// vrtices (0) y el buffer con los vrtices
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);
// Indicamos el n de campos que definen el color (4), el
// tipo de datos de la matriz (float), la separacin en la
// matriz de los colores (0) y el buffer con los colores.
gl.glColorPointer(4, GL10.GL_FLOAT, 0, bufferColores);
// Indicamos al motor OpenGL que le hemos pasado una matriz
// de vrtices y de colores
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
124
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:
Cara delantera
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar el cubo, realizamos
los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Con el mtodo glVertexPointer() 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
Aula Mentor
126
// Constructor de la clase
public Superficie(Context context) {
super(context);
// Indicamos que el Renderer se define en esta misma superficie
this.setRenderer(this);
// Solicitamos el foco de la aplicacin para que el usuario
// pueda hacer clic en la imagen. Nota: esto slo es necesario
// si la aplicacin tuviera otras Vistas, como botones
this.requestFocus();
// Indicamos que la superficie responde a toques del usuario,
// es decir, implementa el evento onTouchEvent
this.setFocusableInTouchMode(true);
}
// Evento que se lanza cuando se crea la superficie.
// Aqu debemos indicar todo aquello que no cambia en la superficie
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Deshabilitamos la mezcla de colores
gl.glDisable(GL10.GL_DITHER);
// Habilitamos las texturas en 2D
gl.glEnable(GL10.GL_TEXTURE_2D);
// Establecemos el color NEGRO como fondo de la superficie
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
// Configuramos el buffer de profundidad
gl.glClearDepthf(1.0f);
// Modo de renderizado de la profundidad
gl.glEnable(GL10.GL_DEPTH_TEST);
// Funcin de comparacin de la profundidad
gl.glDepthFunc(GL10.GL_LEQUAL);
// Cmo se calcula la perspectiva
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
// Rotamos en los ejes X e Y el toque del usuario
gl.glRotatef(anguloX, 1.0f, 0.0f, 0.0f);
gl.glRotatef(anguloY, 0.0f, 1.0f, 0.0f);
// Dibujamos la pirmide
piramide.draw(gl);
} // end onDrawFrame
// Evento que se lanza cuando cambia la superficie
public void onSurfaceChanged(GL10 gl, int width, int height) {
127
Aula Mentor
// Nos aseguramos de que no se puede dividir por 0
if(height == 0) {
height = 1;
}
// Reiniciamos la zona de visin
gl.glViewport(0, 0, width, height);
// Establecemos la matriz de proyeccin como
// activa para modificarla y poder girar la pirmide
gl.glMatrixMode(GL10.GL_PROJECTION);
// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
// Calculamos el tamao de la perspectiva en funcin del nuevo
// tamao de la pantalla
GLU.gluPerspective(gl, 45.0f, (float)width / (float)height,
0.1f, 100.0f);
// Establecemos de nuevo la matriz de vista/modelo
// como activa
gl.glMatrixMode(GL10.GL_MODELVIEW);
// Reiniciamos la matriz de proyeccin del grfico
gl.glLoadIdentity();
}
128
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().
129
Aula Mentor
130
};
public
//
//
//
Piramide(int texturaId) {
Usando el mtodo local makeFloatBuffer definimos
los buffer de la pirmide: vrtices, texturas,
vrtices de su base y colores de su base.
bufferVertices = makeFloatBuffer(vertices);
bufferTexturas = makeFloatBuffer(texturas);
bufferVerticesBase = makeFloatBuffer(vertices_base);
bufferColores = makeFloatBuffer(colores);
// Dibujamos al revs que las agujas del reloj
gl.glFrontFace(GL10.GL_CCW);
// Activamos la texturas en 2D (superficie)
gl.glEnable(GL10.GL_TEXTURE_2D);
// Indicamos el ID de la textura
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturaId);
// Indicamos el n de coordenadas (3), el tipo de datos de la
// matriz (float), la separacin en la matriz de los vrtices
// (0) y el buffer con los vrtices
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, bufferVertices);
// Seleccionamos la textura indicando el tamao y tipo de la
// matriz de texturas
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, bufferTexturas);
// Indicamos al motor OpenGL que le hemos pasado una matriz de
// vrtices y de texturas
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
131
Aula Mentor
return fb;
}
} // end clase
132
Despus, en el mtodo draw(), que invoca el Renderer cuando debe dibujar la pirmide,
realizamos los siguientes trabajos:
- Indicamos el sentido del dibujo al contrario que las agujas del reloj mediante el mtodo
glFrontFace(GL10.GL_CCW).
- Activamos la texturas en 2D (tipo superficie) con glEnable() y seleccionamos la textura
activa invocando glBindTexture().
- Con los mtodos glVertexPointer() y glTexCoordPointer() asociamos los buffer de
vrtices y texturas de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y texturas mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la pirmide mediante la
matriz en el modo tringulo GL_TRIANGLES.
- Deshabilitamos la textura para seguir pintando sin sta con el mtodo glDisable().
- Con los mtodos glVertexPointer() y glColorPointer() asociamos los buffer de vrtices y colores de la base de la pirmide.
- Indicamos al motor OpenGL que le hemos pasado las matrices de vrtices y colores mediante el mtodo glEnableClientState().
- Usamos el mtodo glDrawArrays() para dibujar la superficie de la base de la pirmide
mediante la matriz en el modo tringulo GL_TRIANGLES_STRIP.
- Finalmente, se desactiva los buffer de vrtices, colores y texturas con glDisableClientState().
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 11 (3D con texturas e interaccin) de la Unidad 1. Estudia el cdigo fuente y ejectalo en el AVD para ver el
resultado del programa anterior, en el que hemos utilizado la API de OpenGL para
crear una pirmide en 3D con textura y que interacciona con el usuario.
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.
Aula Mentor
8. Resumen
Hay que saber al final de esta unidad:
- Las clases de Android que permiten acceder a los servicios Multimedia son las
siguientes:
MediaPlayer: reproduce audio y vdeo.
MediaController: representa los controles estndar para MediaPlayer.
VideoView: Vista que permite la reproduccin de vdeo.
MediaRecorder: permite grabar audio y vdeo.
AsyncPlayer: reproduce una lista de archivos de tipo audio.
AudioManager: gestor del sonido del sistema operativo.
AudioTrack: reproduce un archivo de audio PCM.
SoundPool: gestiona y reproduce una coleccin de recursos de audio de corta duracin.
JetPlayer: reproduce audio y vdeo interactivo.
Camera: cmara para tomar fotos y vdeo.
FaceDetector: clase para identificar la cara de personas en una imagen.
- No todos los dispositivos Android incluyen el formato DivX de forma nativa.
134
- ViewSurface (Vista de tipo Superficie) proporciona una superficie de dibujo dedicado que se incrusta dentro de una jerarqua de vistas.
- Al utilizar Superficies es imprescindible que la interaccin del usuario con la aplicacin sea suave y los grficos se muestren sin parpadeos. Para conseguirlo,
debemos ejecutar dos hilos en la aplicacin: el hilo principal, que se encarga de
gestionar la Actividad, y un segundo hilo, que dibuja la superficie y gestiona la
interaccin del usuario con sta.
- Android dispone de dos bibliotecas para pintar grficos en 3D:
OpenGL: librera estndar de diseo 3D.
RenderScript: nueva librera de bajo nivel de dibujo grfico en 3D disponible
desde la versin 3.0 de Android, que aprovecha el procesador GPU de la
tarjeta grfica para pintar.
- Las clases bsicas de la biblioteca OpenGL son las siguientes:
GLSurfaceView: clase base que permite escribir aplicaciones con OpenGL.
GLSurfaceView.Renderer: interfaz de dibujo genrica donde se detalla el
cdigo que indica lo que se debe dibujar.
Podemos hacer una analoga con un pintor y su lienzo: el pintor es la interfaz
GLSurfaceView.Renderer y el lienzo es GLSurfaceView.
- Es importante recordar los conceptos bsicos de geometra: Lnea o Recta, Vrtice, Cara, Arista, Polgono y Eje de coordenadas.
- Las posibilidades de diseo grfico con OpenGL son prcticamente infinitas. El
diseador grfico puede incluir texturas complejas, transparencias, luces, sombras, cambios de perspectiva, etctera.
135
Aula Mentor
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.
136
A todos los programadores nos gusta desarrollar funciones, procedimientos o mtodos sencillos
y eficientes que realicen un cometido especfico. Cuando juntamos todos estos trocitos de
cdigo fuente obtenemos una aplicacin tan compleja como queramos. Es decir, para resolver
un problema complejo, hay que separarlo en pequeos problemas.
Aunque la aplicacin desarrollada cumpla con su cometido excelentemente, hoy en da
es necesario que tambin tenga un aspecto visual atractivo con el diseo apropiado. A
todos nos gusta que un plato est bueno y, adems, tenga un buen aspecto.
Para un programador nativo es difcil afrontar el diseo visual de una aplicacin; para eso
existen diseadores que conciben la interfaz visual sin estas ataduras del ingeniero.
Desde el punto de vista de un programador, cuantas menos partes movibles, cambios de
colores o fuentes tenga una aplicacin ms fcil ser desarrollarla.
En este apartado de teora vamos a mostrar ejemplos sobre cmo implementar de forma sencilla
y rpida estas mejoras visuales que hacen que las aplicaciones sean ms agradables. Para ello
utilizaremos temas (themes, en ingls) y estilos (styles, en ingls).
Como sabes, en Android se utilizan Vistas (Views) para disear la interfaz visual de las
Actividades (Activities). Aunque es posible definir las Vistas mediante sentencias de Java dentro
de las Actividades, lo usual es declararlas en un archivo de diseo layout de tipo XML.
El entorno de desarrollo en Eclipse ADT incluye un editor visual para disear estos
archivos xml. Sin embargo, este editor no permite cambiar el aspecto (color, fuente, mrgenes
y otros atributos) que van a tener estas Vistas. En ingls, se denomina look and feel al
aspecto visual de una aplicacin.
Para facilitar el diseo visual, el programador/diseador de Android dispone de temas
y estilos. Si has diseado alguna vez una pgina web, sabrs que para optimizar recursos, el
estilo de cada pgina se incluye en un archivo CSS con los atributos de las diferentes etiquetas
de HTML.
A continuacin, vamos a ver cmo crear un tema en una aplicacin Android.
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>
138
Tambin se puede establecer el tema de una actividad mediante sentencias de Java. Para ello,
debemos utilizar el mtodo setTheme() en el evento onCreate() de la actividad justo antes de
la ejecucin de la sentencia setContentView(). Hay que tener en cuenta que, por lo general,
se debe evitar esta forma de indicar el tema visual, salvo que la aplicacin que desarrollemos
deba cambiar de forma dinmica sus temas.
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
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/>
139
Aula Mentor
<item name=paginaBackground>@style/pagina_background_blanco
</item>
...
<item name=textoTitulo>@style/texto_titulo_blanco</item>
<style name=texto_titulo_blanco>
<item name=android:background>@drawable/barra_titulo_blanco
</item>
<item name=android:gravity>center_vertical|center_horizontal
</item>
<item name=android:textColor>#FFF</item>
<item name=android:textSize>24sp</item>
<item name=android:shadowDx>1.0</item>
<item name=android:shadowDy>1.0</item>
<item name=android:shadowRadius>3</item>
<item name=android:shadowColor>#888</item>
</style>
Aqu ya s se establecen las propiedades visuales de las distintas Vistas teniendo en cuenta las
propiedades que posean.
140
A modo de resumen, para utilizar los temas conjuntamente con estilos debemos dar los siguientes
pasos:
- Definir el nombre del tema y sus atributos (caractersticas de aspecto) disponibles en el archivo res/values/temas.xml del proyecto.
- En el fichero res/values/atributos.xml especificar los mismos atributos anteriores mediante referencia a otro atributo (format=reference).
- Disear el aspecto de cada atributo con las propiedades visuales de una Vista Android en el
fichero res/values/estilos.xml.
- Aplicar el estilo correspondiente a las vistas que define la interfaz del usuario (en el archivo
layout xml) mediante su propiedad style.
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 vers, su utilizacin es tambin muy sencilla; en el cdigo anterior hemos declarado la
etiqueta dimen con su nombre e indicado el tamao en formato sp como unidad de medida.
Despus, para poder aplicar esta dimensin a un estilo o Vista, basta con abrir el archivo estilos.xml y usarla con la nomenclatura @dimen:
<style name=pagina_background_azul>
<item name=android:background>#000</item>
<item name=android:padding>@dimen/pagina_margen_azul</item>
</style>
En este caso hemos establecido un margen interno (padding) del fondo del tema azul.
Las magnitudes de medida en interfaces grficas de Android son las siguientes:
- px: Pxeles - unidad en pxeles independientemente de la resolucin de la pantalla.
- in: Inches (Pulgadas) - unidad en pulgadas independientemente del tamao de la pantalla.
- mm: Milmetros - unidad en milmetros que depende del tamao de la pantalla.
- pt: Points (Puntos) - unidad en puntos por pulgada que depende del tamao de la pantalla
del dispositivo.
- dp: Density-independent Pixels (Densidad independiente en pxeles) - unidad abstracta
basada en la densidad fsica de la pantalla. Es recomendable intentar utilizar siempre esta
unidad ya que los elementos grficos y Vistas se representarn con el mismo tamao independientemente del tamao y resolucin del dispositivo Android.
- sp: Scale-independent Pixels (Independiente de la escala en pxeles) unidad parecida a la
anterior (dp) pero utilizada para definir los tamaos de la fuente de las textos que aparecen
en la interfaz del usuario.
141
Aula Mentor
En este caso hemos definido un degradado que comienza (startColor) en el color gris y termina (endColor) en el color negro. Como puedes ver, estos colores tambin estn definidos
como un recurso de color. Adems, hemos indicado que aplique un giro (angle) al degradado
de 270 grados.
Una vez hemos definido el gradiente, es sencillo aplicarlo al tema en el archivo estilos.xml
as:
<style name=pagina_background_blanco>
<item name=android:background>@drawable/gradiente_gris_background
</item>
<item name=android:padding>@dimen/pagina_margen_blanco</item>
</style>
<style name=Tema.Blanco>
<item name=paginaBackground>@style/pagina_background_blanco</item>
<selector xmlns:android=http://schemas.android.com/apk/res/android>
<item android:state_pressed=true
android:drawable=@drawable/boton_blanco_presionado />
<item android:state_focused=true
android:drawable=@drawable/boton_blanco_seleccionado />
<item android:drawable=@drawable/boton_blanco_normal />
</selector>
Vers que hemos definido un selector que cambia el aspecto de una Vista cuando ocurre alguno de estos eventos:
- state_pressed: si el usuario presiona (hace clic) sobre la Vista que tiene asociado este
estilo, entonces se debe cambiar su aspecto utilizando el elemento dibujable boton_blanco_presionado. Definido tambin dentro de la carpeta drawable con el nombre boton_
blanco_presionado.xml. Puedes abrir este archivo para ver su contenido.
- state_focused: si el usuario selecciona (centra el foco con el tabulador) la Vista que tiene
asociado este estilo, entonces se debe cambiar su aspecto utilizando el elemento dibujable boton_blanco_seleccionado. Definido tambin dentro de la carpeta drawable con
el nombre boton_blanco_seleccionado.xml. Puedes abrir este archivo para ver su contenido.
- Sin evento: en este caso, no se incluye ningn evento y se define el aspecto normal de la
Vista utilizando el elemento dibujable boton_blanco_normal. Definido tambin dentro
de la carpeta drawable con el nombre boton_blanco_normal.xml. Puedes abrir este archivo para ver su contenido.
Si haces clic en los botones del ejemplo del curso, vers que cambia el aspecto de botn:
143
Aula Mentor
Como hemos visto, mediante temas visuales podemos definir en las Vistas una amplia variedad
de atributos que cambian el aspecto de stas. A continuacin, vamos a indicar los ms utilizados
por el programador:
- colorBackgroundCacheHint
Mediante este atributo establecemos el color del fondo de pantalla de una Vista (en los
ejemplos hemos utilizado tambin el atributo background) y debera ser vaco (null)
cuando el fondo es una textura o traslcido, es decir, slo sirve para establecer colores
slidos.
- textAppearance
Atributo para establecer el aspecto por defecto del texto: color, tipo de fuente, tamao y
estilo.
- textColorPrimary
Color principal del texto.
- textAppearanceLarge
Aspecto por defecto del texto del tamao grande: color, tipo de fuente, tamao y estilo.
- textAppearanceMedium
Aspecto por defecto del texto del tamao medio: color, tipo de fuente, tamao y estilo.
- textAppearanceSmall
Aspecto por defecto del texto del tamao pequeo: color, tipo de fuente, tamao y estilo.
- buttonStyle
Estilo del botn normal.
- listDivider
Atributo que define el drawable del divisor de una lista.
- windowBackground
Drawable utilizado como el fondo de la ventana. A diferencia del atributo anterior colorBackgroundCacheHint, podemos establecer tanto texturas, colores slidos y traslcidos
como fondos de pantalla.
- windowFrame
Drawable utilizado para definir el marco de una ventana.
- windowActionBar
Marca que indica que la ventana debe mostrar una Barra de Accin (Action Bar) en lugar
del usual ttulo de la ventana. Ms adelante, en esta misma Unidad 2, veremos en qu consiste un Action Bar.
- alertDialogTheme
Tema que se debe aplicar a una ventana de dilogo de alerta.
- progressBarStyle
Estilo por defecto de la vista ProgressBar. Normalmente, es un crculo que gira.actionBarStyle
Recuerda que para indicar un atributo por defecto del sistema operativo de Android es necesario escribir el texto android: antes del nombre del mismo, por
ejemplo, as: android:windowBackground.
En el manual oficial sobre ATRIBUTOS de Android puedes encontrar un listado completo con
la descripcin de todos los atributos disponibles. En este apartado hemos incluido los ms
interesantes o utilizados por el programador.
Para intercambiar los diferentes temas en tiempo de ejecucin hemos reiniciado la Actividad
principal y utilizado el mtodo setTheme() en el evento onActivityCreateSetTheme() antes de
que la aplicacin invoque el mtodo setContentView():
// Reinicia y establece el tema de una Actividad.
public static void changeToTheme(Activity activity, int tema)
{
145
Aula Mentor
nTema = tema;
// Finalizamos la actividad
activity.finish();
// Volvemos a ejecutar de nuevo la Actividad
activity.startActivity(new Intent(activity,
activity.getClass()));
}
// Establece el tema de la Actividad de acuerdo con la variable nTema
public static void onActivityCreateSetTheme(Activity activity)
{
switch (nTema)
{
default:
case TEMA_DEFECTO:
break;
case TEMA_BLANCO:
activity.setTheme(R.style.Tema_Blanco);
break;
case TEMA_AZUL:
activity.setTheme(R.style.Tema_Azul);
break;
}
}
146
Si ejecutas en Eclipse este Ejemplo 1, vers que se muestra la siguiente aplicacin en el Emulador:
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.
147
Aula Mentor
- Si hemos definido una Actividad de configuracin en el fichero anterior, cada vez que se cree
el Widget se ejecutar esta Actividad para indicar sus parmetros iniciales. Si deseamos que
el usuario pueda reconfigurar el Widget debemos crear un Actividad extra que aparezca en
el escritorio y permita realizar esta tarea.
148
Los Widgets de Android parecen muy simples cuando los utilizamos, sin embargo existen ciertas
limitaciones a la hora de programarlos que debes conocer.
Si desarrollas un Widget sencillo que no requiere la gestin de su estado y se actualiza un par
de veces al da, vers que es muy fcil de implementar.
Si implementas un Widget con gestin del estado que se actualiza con poca frecuencia
con cambios elementales, podemos definir su comportamiento dentro del propio Widget.
Sin embargo, para un Widget complejo que se actualiza cada poco tiempo (segundos o
milisegundos), debemos utilizar el gestor de alarmas (AlarmManager) o, probablemente, sea necesario desarrollar un servicio que gestione el estado del Widget. En el ejemplo del curso hemos
utilizado los servicios, aunque no es estrictamente necesario, para mostrar cmo se implementa
este tipo de Widget complejo.
La nica interaccin posible del usuario con las Vistas incluidas en un Widget es mediante el evento OnClickListener que se lanza cuando el usuario hace clic sobre ste.
En relacin con los gestos, nicamente se permite un toque (Touch) y arrastrar verticalmente (Vertical Swipe) En el Ejemplo 3 veremos cmo funciona este ltimo gesto al cambiar
las imgenes.
Otro factor a considerar es que los objetos RemoteViews que especifican la interfaz del
usuario tienen restricciones sobre los tipos Vistas y layouts que podemos incluir en ellos. Es decir, no podemos incluir en un Widget todas las Vistas disponibles en el SDK de Android. Adems,
el programador no puede gestionar directamente las Vistas internas del RemoteView, sino debe
hacerlo mediante los mtodos proporcionados por esta clase.
Seguidamente, mostramos las Vistas y layouts que podemos incluir en el diseo de un Widget:
FrameLayout
LinearLayout
RelativeLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
Aunque esta lista parezca reducida, aumenta con cada nueva versin de Android. La razn
principal para restringir lo que se puede incluir en una RemoteView es que estas Vistas deben
estar desconectadas de los procesos que las controlan. En realidad, estas Vistas estn alojadas
en otra aplicacin como es Home Application (aplicacin de lanzadera de aplicaciones que
funciona como escritorio en Android que, adems, el usuario puede cambiar a su antojo). Los
gestores de estas Vistas son procesos en segundo plano que se invocan por temporizadores, por
esta razn, a esas vistas se las denomina vistas remotas (remote views) y pueden ser modificadas por otros procesos con los mismos permisos.
149
Aula Mentor
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>
</LinearLayout>
151
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>
Aula Mentor
Existen otras propiedades que se pueden definir como, por ejemplo, el icono de la vista previa
del widget (android:previewImage), aunque slo para Android >3.1. Puedes consultarlas
todas en AppWidgetProviderInfo.
La pantalla inicial de Android se divide en un mnimo de 44 celdas (un dispositivo con
mayor pantalla pueden contener ms) donde se pueden colocar aplicaciones, accesos directos
y Widgets. Teniendo en cuenta las diferentes dimensiones de estas celdas segn el dispositivo y
la orientacin de la pantalla, existe una frmula sencilla para ajustar las dimensiones de nuestro
widget para que ocupe un nmero determinado de celdas sea cual sea la orientacin:
- ancho_mnimo = (num_celdas * 70) 30
- alto_mnimo = (num_celdas * 70) 30
Empleando esta frmula, el widget del Ejemplo del curso ocupa un tamao mnimo de 3 celdas
de ancho por 1 celda de alto; por lo tanto, debemos indicar unas dimensiones de 146dp x 40dp.
En la Gua de diseo de Widgets de Android puedes encontrar recomendaciones de estilo a la hora de disearlos.
152
Esta clase implementa la funcionalidad del widget y deber extenderse de la clase AppWidgetProvider que, a su vez, es una clase derivada de BroadcastReceiver, ya que los widgets de
Android son un caso particular de este tipo de componentes.
En esta clase se implementan los mensajes a los que vamos a responder desde el Widget, entre los que destacan:
- onEnabled(): lanzado cuando se crea la primera instancia de un Widget.
- onUpdate(): ejecutado peridicamente cuando hay que actualizar un Widget cada vez que
se cumple el periodo de tiempo definido por el parmetro updatePeriodMillis descrito
anteriormente o al aadir el Widget al escritorio.
- onDeleted(): lanzado cuando se elimina del escritorio una instancia de un widget.
- onDisabled(): ejecutado cuando se elimina del escritorio la ltima instancia de un Widget.
- onReceive(): se implementa para enviar llamadas a los distintos mtodos de otros AppWidgetProvider o para actualizar un Widget individualmente.
En la mayora de los casos, tendremos que implementar, como mnimo, el evento onUpdate().
El resto de mtodos dependern de la funcionalidad de nuestro Widget. En el Ejemplo del curso implementamos este mtodo onUpdate() y el mtodo onReceive():
// Define la Clase que llama Android cada vez que se crea y actualiza // el
Widget. Debe extenderse de la clase AppWidgetProvider
public class HorarioTrabajoWidgetProvider extends AppWidgetProvider {
private static final String LOG =
es.mentor.unidad2.eje2.widgethomescreen;
// Evento que inicia Android cada vez que tiene que actualizar el
// widget
@Override
public void onUpdate(Context context, AppWidgetManager
}
// Si quisiramos actualizar un nico Widget al hacer clic sobre l
// deberamos implementar aqu la actualizacin
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final int appWidgetId =
intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
Log.i(LOG,Mensaje recibido: + action + del Widget:
+appWidgetId);
super.onReceive(context,intent);
}
// Mtodo que lanza el servicio que actualiza el contenido del
// Widget
public static void actualizaWidget(Context context, AppWidgetManager
appWidgetManager)
{
// Generamos un log para ver si se actualiza el widget
Log.w(LOG, Mtodo onUpdate ejecutado);
// Obtenemos el nombre del componente del Widget
ComponentName
esteWidget = new ComponentName(context,
HorarioTrabajoWidgetProvider.class);
// Obtenemos todos los IDs de los widget mediante el mtodo
// getAppWidgetIds del AppWidgetManager y los copiamos a una
// matriz
int[] todosWidgetIds =
appWidgetManager.getAppWidgetIds(esteWidget);
// Construimos un Intent para llamar al servicio correspondiente
Intent intent = new Intent(context.getApplicationContext(),
ServicioActualizacionWidget.class);
// Incluimos los IDs de todos los Widgets
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
todosWidgetIds);
// Actualizamos el widget invocando el servicio de actualizacin
context.startService(intent);
}
}
153
Aula Mentor
Como hemos visto, un Widget puede actualizar su aspecto/informacin cada cierto tiempo,
definido en su archivo xml de informacin con la propiedad updatePeriodMillis.
Cuando la actualizacin implica operaciones de larga duracin (ms de 10 segundos), podemos aplicar dos mtodos en el mtodo onUpdate() de la clase WidgetProvider para implementarla:
- Mediante un servicio que veremos en el ejemplo de este curso avanzado
- Utilizando el Gestor de alarmas (AlarmManager) estudiado en el curso de iniciacin de
Mentor (Unidad 7 Ejemplo 3).
La diferencia entre un mtodo y otro radica en que el mnimo intervalo que podemos definir
en un servicio es de 1.800.000 milisegundos (30 minutos), mientras que si usamos el Gestor de
alarmas (AlarmManager) podemos aumentar la frecuencia de actualizacin y, adems, es ms
eficiente desde el punto de vista energtico para el dispositivo.
Para utilizar este segundo mtodo debemos definir un servicio similar al primer mtodo
pero, en lugar de llamarlo, hay que programarlo en este Gestor de alarmas (AlarmManager).
En la Unidad 7 del curso de Iniciacin se estudia cmo hacerlo.
154
Cuanto mayor sea la frecuencia de actualizacin del Widget, el dispositivo, despertar ms a menudo para ejecutar esa tarea y consumir ms batera. Muchos
Widget, al configurarlos, permiten al usuario que elija el intervalo de actualizacin;
as, ste decide cmo quiere gastar la batera de su dispositivo.
155
Aula Mentor
156
Es importante destacar que, como hemos comentado repetidamente, las Vistas internas de
un Widget se basan en un tipo especial llamadoRemoteViews. En el cdigo anterior puedes observar que para acceder a estas Vistas remotas que constituyen la interfaz del Widget
construimos un nuevo objeto RemoteViews inflando su layout correspondiente.
Una vez creado este objeto, podemos modificar el contenido de las Vistas internas
mediante una serie de mtodosset (uno para cada tipo de datos bsicos) que permiten establecer las propiedades de cada Vista individual del Widget. Por ejemplo, remoteViews.set-
En el fichero definimos la interfaz de configuracin inicial del widget de forma similar a como
haramos con cualquier otra actividad Android diseando su layout xml. En este caso, el diseo
es muy sencillo con un cuadro de texto para introducir el nombre personalizado y dos botones,
uno para aceptar la configuracin y otro para cancelar, en cuyo caso el widget no se crea en
el escritorio. Veamos su contenido:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical>
<TextView
android:id=@+id/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/MensajeConfiguracion
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
157
Aula Mentor
android:id=@+id/txtNombre
android:layout_width=match_parent
android:layout_height=wrap_content
android:hint=Sin nombre
android:text=Sin texto >
<requestFocus />
</EditText>
<TableRow android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center>
<Button
android:id=@+id/btnAceptar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=@string/Aceptar />
<Button
android:id=@+id/btnCancelar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=@string/Cancelar />
</TableRow>
</LinearLayout>
158
159
Aula Mentor
};
160
En el cdigo anterior podemos ver cmo establecemos el resultado por defecto que devuelve
la Actividad de configuracin mediante el mtodo setResult().
Una actividad de configuracin de un Widget debe siempre devolver un resultado
RESULT_OK si la configuracin es correcta o RESULT_CANCELED si el usuario sale de la configuracin sin aceptar los cambios. En las sentencias anteriores establecemos al principio un
resultado RESULT_CANCELED por defecto para asegurarnos de que, si el usuario abandona la
configuracin pulsando el botn Volver del dispositivo o pulsando el botn Cancelar de esta
Actividad de configuracin, no se aade el Widget al escritorio por error.
El cdigo del botn Aceptar es algo ms complejo:
- Guarda el nombre que ha introducido el usuario utilizando la API de Preferencias ya estudiada en el curso de Iniciacin.
- Actualiza la interfaz del Widget segn la configuracin establecida ejecutando el mtodo
actualizaWidget() de HorarioTrabajoWidgetProvider.
Si incluimos una Actividad de configuracin para un Widget, es necesario que esta misma
Actividad sea la responsable de realizar la primera actualizacin del mismo (si es necesario).
Es decir, tras finalizar la actividad de configuracin Android no lanza automticamente el
evento onUpdate() del Widget (aunque s se lanzar posteriormente y de forma peridica
segn la configuracin del parmetro updatePeriodMillis del WidgetProvider), sino
que tendr que ser la propia actividad quien fuerce esta primera actualizacin. Para ello,
basta con obtener una referencia al WidgetManager de nuestro contexto mediante el mtodo
AppWidgetManager.getInstance() y con esta referencia llamaremos al mtodo esttico
de actualizacin del Widget HorarioTrabajoWidgetProvider.actualizaWidget(), que
actualizar los datos de todos las Vistas internas.
- Devuelve el resultado RESULT_OK incluyendo en el Intent de respuestas el ID del Widget.
Por ltimo, invocamos al mtodo finish() para finalizar la actividad.
Finalmente, para acabar, debemos declarar el Widget dentro del manifest de la aplicacin. As,
incluimos los elementos siguientes dentro de la etiqueta <application>:
<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>
161
Aula Mentor
<receiver
android:name=HorarioTrabajoWidgetProvider
android:icon=@drawable/maletin
android:label=Control de horario >
<intent-filter>
<action android:name=android.appwidget.action.
APPWIDGET_UPDATE />
</intent-filter>
<meta-data
android:name=android.appwidget.provider
android:resource=@xml/widget_info />
</receiver>
<service
android:name=es.mentor.unidad2.eje2.widgethomescreen.
ServicioActualizacionWidget >
</service>
162
<activity
android:name=es.mentor.unidad2.eje2.widgethomescreen.
PreferenciasWidget>
<intent-filter>
<action android:name=android.appwidget.action.
APPWIDGET_CONFIGURE/>
</intent-filter>
</activity>
</application>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Control horario de trabajo) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el
resultado del programa anterior, en el que hemos utilizado Widgets.
Para probarlo, podemos ejecutar el proyecto desde Eclipse ADT en el emulador de Android
y esperar a que se ejecute la aplicacin principal (sin desarrollar, ya que no hemos incluido
ninguna funcionalidad) e ir a la pantalla principal del emulador y aadir nuestro Widget al escritorio tal cmo lo haramos en un dispositivo fsico:
- Hasta Android 2.x: pulsacin larga sobre el escritorio o pulsacin en la tecla Men y seleccionar la opcin Widgets seleccionando el Widget del curso.
- Desde Android 3.0 en adelante: accedemos al men principal, pulsamos la pestaa Widgets y
buscamos el Widget en la lista y realizamos sobre l una pulsacin larga hasta que el sistema
nos permite arrastrarlo y soltarlo sobre el escritorio.
163
Prueba a aadir varias instancias al escritorio, actualizarlos haciendo click sobre ellos, desplazarlos por la pantalla, cambiar su tamao y eliminarlos.
Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin en el
Emulador:
Aula Mentor
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.
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.
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 crear un fondo de pantalla animado es necesario componer un archivo XML que lo describa.
Si abrimos en Eclipse ADT este archivo veremos que contiene lo siguiente:
<wallpaper
android:settingsActivity=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
xmlns:android=http://schemas.android.com/apk/res/android
android:description=@string/wallpaper_descripcion
android:thumbnail=@drawable/sphere />
Este archivo debe incluir la descripcin del fondo animado (atributo android:description)
y puede contener una vista previa o icono del fondo (atributo android:thumbnail) y la referencia a la Actividad de configuracin (atributo android:settingsActivity) que permite
personalizar el fondo de pantalla animado. Como puedes ver, en Android se pueden establecer
preferencias hasta en los fondos animados.
167
Aula Mentor
de orientacin de ste.
168
//
//
//
//
//
// Gestor de runnables
private final Handler handler = new Handler();
// Definimos un objeto Runnable que es el que se
// encargar de procesar la tarea que actualiza el
// fondo animado
private final Runnable drawRunnable = new Runnable() {
@Override
public void run() {
dibuja();
}
};
// 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);
}
169
Aula Mentor
170
}
// Evento que se ejecuta cuando el usuario toca el fondo de
// pantalla
@Override
public void onTouchEvent(MotionEvent event) {
// Si est activado el toque en preferencias
if (touchActivado) {
// Obtenemos las coordenadas del toque
float x = event.getX();
float y = event.getY();
// Obtenemos la referencia al SurfaceHolder donde se
// ejecuta el fondo animado
SurfaceHolder holder = getSurfaceHolder();
// Definimos un canvas donde vamos a dibujar los crculos
Canvas canvas = null;
try {
// Obtenemos el canvas bloqueando el fondo SurfaceHolder
// para que el usuario no pueda manipularlo
canvas = holder.lockCanvas();
// Si lo hemos obtenido correctamente entonces...
if (canvas != null) {
// Definimos el fondo de color negro
canvas.drawColor(Color.BLACK);
// Si no podemos aadir 5 crculos borramos los 5
// primeros
while (circulos.size()+5 > numeroMax) {
circulos.remove(0);
}
// Aadimos nuevos crculos alrededor del punto de
// toque
circulos.add(new MiCirculo(x, y, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x, y-30, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x+30, y, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x, y+30, colorAleatorio(),
radioAleatorio()));
circulos.add(new MiCirculo(x-30, y, colorAleatorio(),
radioAleatorio()));
// Dibujamos los crculos
dibujaCirculos(canvas, circulos);
}
} finally {
// Muy importante! Hay que desbloquear el fondo SurfaceHolder
if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}
super.onTouchEvent(event);
}
}
// Mtodo local que dibuja los crculos en el fondo
private void dibuja() {
// Obtenemos el fondo SurfaceHolder
SurfaceHolder holder = getSurfaceHolder();
// Definimos un canvas donde vamos a dibujar los crculos
Canvas canvas = null;
try {
// Obtenemos el canvas bloqueando el fondo SurfaceHolder para
// que el usuario no pueda manipularlo
canvas = holder.lockCanvas();
// Si lo hemos obtenido correctamente entonces...
if (canvas != null) {
// Si hemos llegado al n mximos de crculos
// borramos el primero de todos para poder aadir otro
if (circulos.size() == numeroMax) {
circulos.remove(0);
}
// Aadimos el crculo
int x = (int) (width * Math.random());
int y = (int) (height * Math.random());
circulos.add(new MiCirculo(x, y, colorAleatorio(),
radioAleatorio()));
// Finalmente, dibujamos los crculos
dibujaCirculos(canvas, circulos);
}
} finally {
// Muy importante! Hay que desbloquear el fondo SurfaceHolder
if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}
// Quitamos de la cola de ejecucin el Runnable
handler.removeCallbacks(drawRunnable);
// Aadimos el Runnable a la cola de ejecucin para
// que se ejecute cada 3 segundos
if (visible) {
handler.postDelayed(drawRunnable, 3000);
}
}
// Mtodo que dibuja los crculos en un canvas
private void dibujaCirculos(Canvas canvas, List<MiPunto>
circulos) {
// Random que usamos para obtener color y radio
// de los crculos aleatorios
Random rnd = new Random();
// El fondo es negro
canvas.drawColor(Color.BLACK);
// Recorremos todos los puntos del listado
for (MiPunto circulo : circulos) {
// Establecemos el color
paint.setColor(circulo.color);
// Dibujamos un crculo en el canvas
canvas.drawCircle(circulo.x, circulo.y, circulo.radio,
paint);
}
}
// Mtodos que devuelven un color y un radio aleatorios para
// dibujar los crculos
private int colorAleatorio() {
return Color.argb(255, (int) (256 * Math.random()), (int)
(256 * Math.random()), (int) (256 * Math.random()));
171
Aula Mentor
}
private int radioAleatorio() {
return (int) (30 * Math.random()) + 5;
}
// Ocurre cuando hay un cambio en las preferencias del
// liveWallpaper
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
// Actualizamos las preferencias
if (key.equals(numeroMaxCirculos))
numeroMax =
Integer.valueOf(sharedPreferences.getString(
numeroMaxCirculos, 10));
else if (key.equals(touch))
touchActivado =sharedPreferences.getBoolean(touch,false);
}
}
}
En el cdigo anterior se ha nombrado mucho el objeto SurfaceHolder de Android. Esta clase es una Interfaz abstracta que permite controlar el tamao, el formato, editar los pxeles y
gestionar los cambios de una superficie, en este caso, es la superficie del fondo de pantalla de
un dispositivo Android. Esta clase hereda propiedades y mtodos de la clase SurfaceView de
Android que hemos estudiado en profundidad en la Unidad 1.
172
Muy importante! Hay que desbloquear el fondo SurfaceHolder una vez hayamos acabado de dibujarlo. Si no lo hacemos, se quedar congelado y ya no ser
animado.
En este fichero establecemos las propiedades de la ventana de preferencias del fondo animado.
En este ejemplo vamos a usar la gestin de preferencias de Android mediante la clase PreferenceScreen:
<PreferenceScreen xmlns:android=http://schemas.android.com/apk/res/
android>
<CheckBoxPreference android:key=touch
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.
En esta clase definimos el comportamiento de la ventana de preferencias. Como puedes observar hemos utilizado Fragmentos para implementar que estudiaremos en el siguiente apartado
de teora de esta Unidad. Las sentencias que forman el cdigo fuente son auto explicativas:
public class PreferenciasActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// A partir de la API 4.1 hay que usar PreferenceFragment
getFragmentManager().beginTransaction().replace(
android.R.id.content, new MiPreferenceFragment()).commit();
// Si no disponemos de ella, usaramos esto:
//addPreferencesFromResource(R.xml.preferencias);
}
// Definimos el fragmento de tipo Preference
public static class MiPreferenceFragment extends PreferenceFragment
{
// Constructor de la clase
@Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Aadimos al fragmento el layout de preferencias
addPreferencesFromResource(R.xml.preferencias);
// Buscamos la preferencia numeroMaxCirculos
Preference nCirculosPreference =
findPreference(numeroMaxCirculos);
// Asignamos listener que valida el formato correcto
nCirculosPreference.setOnPreferenceChangeListener(
compruebaNumeroListener);
}
// Comprobamos que la preferencia introducida es correcta; si
// no, Android no la guarda automticamente
Preference.OnPreferenceChangeListener compruebaNumeroListener =
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
// Comprobamos que el n introducido es un entero cuyo
173
Aula Mentor
// valor es > 5
if (newValue != null && newValue.toString().length() > 0
&& newValue.toString().matches(\\d*) &&
Integer.valueOf(newValue.toString()) > 4) {
return true;
}
<application
android:icon=@drawable/ic_launcher
android:label=@string/app_name>
<service
android:name=MiServicioWallpaper
android:enabled=true
android:label=Ejemplo de live Wallpaper
android:permission=android.permission.BIND_WALLPAPER >
<intent-filter>
<action
android:name=android.service.wallpaper.WallpaperService >
</action>
</intent-filter>
<meta-data android:name=android.service.wallpaper
android:resource=@xml/miwallpaper >
</meta-data>
</service>
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
PreferenciasActivity
android:exported=true
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
</activity>
<activity
android:name=es.mentor.unidad2.eje4.livewallpaper.
EstablecerWallpaperActivity
android:label=@string/app_name
android:theme=@android:style/Theme.Light.WallpaperSettings >
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
</application>
Esto evita que el fondo animado se instale en dispositivos que no tienen hardware compatible.
Adems, hemos definido la Actividad de Preferencias y la principal de la aplicacin.
175
Aula Mentor
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 4 (Live Wallpaper de curso
Mentor) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el
AVD el resultado del programa anterior, en el que hemos diseado un Fondo de
pantalla animado.
Si ejecutas en Eclipse ADT este Ejemplo 4, vers que se muestra el siguiente fondo animado
en el Emulador:
176
Prueba a arrastrar el dedo sobre el mismo y vers cmo se actualiza el fondo de pantalla.
En Internet, existen mltiples listados para fondos animados de pantalla: Veamos un
conjunto de ejemplos segn su funcionalidad:
- Launchers: este tipo de fondos de pantalla permite lanzar aplicaciones y realizar mltiples
acciones de forma rpida. Un ejemplo es Launcher Wall:
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:
WP Clock
Lightwell Live
Superclock
177
Aula Mentor
Analogy
178
TextClock
Prediccin del tiempo: existen mltiples fondos de este tipo como GoWeather
que, adems de ser un Widget estupendo, dispone tambin de un excelente
Fondo Animado que cambia segn el tiempo que hace.
Bubbleator
Notification Bubbles
Aunque este listado pueda parecer muy completo, Google Play est lleno de fondos animados
cada da que proporcionan las funcionalidades ms inverosmiles. Animamos al alumno a que
busque o, mejor, que desarrolle su propio fondo animado: Sirva la lista anterior de ejemplo
para comenzar.
5. Fragmentos
Cuando surgieron los dispositivos con pantallas de gran tamao de tipo Tableta u ordenadores,
Google tuvo que desarrollar la versin 3.0 de Android especficamente diseada para solucionar
el problema de la adaptacin de la interfaz grfica de las aplicaciones a ese nuevo tamao de
pantallas. Una interfaz de usuario diseada para un telfono mvil no se adaptaba fcilmente
a una pantalla 5 7 pulgadas. La solucin fue implementar un nuevo tipo de componente
denominado Fragment.
Un Fragment no puede considerarse ni una Actividad, ni una Vista, es algo parecido a
un contenedor. Un Fragment es un pedazo de la interfaz de usuario que se puede aadir
o eliminar de la interfaz global de usuario de forma independiente al resto de elementos de
la Actividad. Esto permite que pueda reutilizarse en otras Actividades.
As, podemos dividir la interfaz de usuario en varios segmentos o fragmentos; de ah su
nombre, de forma que podamos disear diversas configuraciones de pantalla, dependiendo del
tamao y orientacin de sta sin tener que duplicar cdigo reutilizando los distintos fragmentos
para cada una de las posibles configuraciones de pantalla.
Por ejemplo, supongamos que estamos diseando una aplicacin de correo electrnico
en la que queremos mostrar la lista de mensajes recibidos con los campos tpicos De y Asunto y, por otro lado, mostrar el contenido completo del mensaje seleccionado. En un telfono
mvil, lo habitual es tener una primera Actividad que muestra el listado completo de los mensajes y, cuando el usuario selecciona uno de ellos, ejecutar una nueva Actividad que muestre el
179
Aula Mentor
contenido de dicho mensaje. Sin embargo, en una tableta puede existir espacio ms que suficiente para tener ambas partes de la interfaz en la misma pantalla. Por ejemplo, en una tableta
en posicin apaisada podramos tener una columna a la izquierda con el listado de mensaje y
destinar la zona derecha a presentar el detalle del mensaje seleccionado, todo esto sin necesidad
de cambiar de Actividad.
En las versiones de Android anteriores a la 3.0 (sin Fragments) podramos haber implementado diferentes Actividades con sus respectivos layouts para cada configuracin de pantalla,
pero esto nos hubiera obligado a duplicar gran parte del cdigo fuente en cada Actividad. Si,
en su lugar, utilizamos fragmentos, podramos desarrollar un fragmento que muestra el listado
de mensajes y otro fragmento que muestra el contenido del mensaje seleccionado, cada uno de
ellos acompaado de su lgica de aplicacin y definir nicamente varios layouts en funcin del
tamao de la pantalla que incluya los fragmentos necesarios en cada momento.
180
En el Ejemplo 5 de este Unidad vamos a simular la aplicacin de correo que hemos descrito,
adaptndola a tres configuraciones distintas de pantalla: normal, grande horizontal y grande
vertical. En el primer caso, ubicaremos el listado de mensajes en una Actividad y, en otra
Actividad, el detalle. Sin embargo, en el segundo y tercer caso estos dos elementos visuales
estarn en la misma Actividad, ocupando la parte derecha/izquierda para la pantalla en el modo
apaisado y la parte arriba/abajo en el modo vertical.
Por lo tanto, en estos ds ltimos casos hemos desarrollado dos fragmentos: uno para el
listado de mensajes y el otro para mostrar su contenido.
De la misma forma que una Actividad, un Fragmento se infla a partir de un fichero de
layout XML para la interfaz visual (en la carpeta /res/layout) y una clase Java con las sentencias que incluyen sus funcionalidades.
El primero de los Fragmentos creados contiene una Vista ListView con su Adaptador
personalizado que muestra dos campos por fila: De y Asunto. Durante el curso de Iniciacin
ya estudiamos este tipo de elemento, por lo que aqu no daremos indicaciones sobre su uso.
Si abrimos su layout XML fragmento_listado.xml vemos el siguiente contenido tpico:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:padding=5dp >
<ListView
android:id=@+id/listado
android:layout_width=match_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>
Como hemos comentado, si un fragmento incluye lgica, debe tener asociadasu propia clase
java que indique las sentencias que debe ejecutar y que se extiende de la clase Fragment.
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
Aula Mentor
Veamos dnde se comprueba que la librera est disponible en el entorno de desarrollo Eclipse
ADT:
182
Usando este truco, podemos utilizar Fragmentos en la mayora de versiones de Android, incluso
desde la versin 1.6.
A continuacin, veamos el aspecto de la clase FragmentListado asociada al fragmento del
listado:
public class FragmentListado extends Fragment {
// Matriz que simula un listado de mensajes
private Mensaje[] datos =
new Mensaje[]{
new Mensaje(Pedro del Bosque, Peticin de informacin,
Hola.\n\n Por favor, remteme la informacin lo
antes posible.\n\n Gracias!),
new Mensaje(Alicia Navas, Hola!, Hola!\n\n Te espero
en la fiesta.\n\n Besos!),
new Mensaje(Daniel Fernndez, Sobre viaje, Al final
vienes el fin de semana?\n\n Saludos),
new Mensaje(Jaime del Monte, Ms trabajo, Por favor,
ponte en contacto conmigo.\n\n Gracias)};
// ListView donde mostramos los mensajes
private ListView listado;
// Listener que detecta cundo un usuario hace clic sobre un mensaje
private MensajesListener listener;
// Mtodo equivalente al onCreate() de una Actividad
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
// Devolvemos el xml inflado que define el listado
return inflater.inflate(R.layout.fragmento_listado, container,
false);
}
// Mtodo que se ejecuta cuando la Actividad
// contenedora del fragmento est completamente creada
// Aqu es donde debemos asociar el adaptador al listado
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
// Buscamos el listado del layout
listado = (ListView)getView().findViewById(R.id.listado);
// Asociamos el adaptador
listado.setAdapter(new AdaptadorMensajes(this));
// Definimos el evento onClic del listado
listado.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> list, View view, int
pos, long id) {
if (listener!=null) {
// Pasamos como parmetro la posicin del elemento
// seleccionado
listener.onMensajeSeleccionado(
(Mensaje)listado.getAdapter().getItem(pos));
}
}
}); // end onItemClick
}
// Clase que define el adaptador del Listado
class AdaptadorMensajes extends ArrayAdapter<Mensaje> {
// Variable para guardar el contexto de la Actividad
Activity contexto;
183
Aula Mentor
return(item);
}
}
// Definimos una interfaz que se implementar en la clase principal
// de la aplicacin y que tendr en cuenta el tamao de la pantalla
// disponible
public interface MensajesListener {
void onMensajeSeleccionado(Mensaje mensaje);
}
184
El fichero Mensaje.java contiene una sencilla clase que almacena los campos De, Asunto y
Texto de un mensaje. Puedes abrirla en Eclipse ADT y ver su contenido.
Si analizamos con detenimiento la clase FragmentListado anterior es fcil advertir que
existen muy pocas diferencias de cuando desarrollamos un listado de opciones con su adaptador personalizado en un Actividad normal.
En este caso, las diferencias radican en los mtodos que sobrescribimos. En el caso de
los fragmentos normalmente son los siguientes: onCreateView() y onActivityCreated().
Veamos para qu se usan.
- onCreateView(): es el mtodo equivalente al onCreate() de una Actividad. Es decir, aqu
inflamos el layout asociado al fragmento.
- onActivityCreated(): mtodo que se ejecuta cuando la Actividad contenedora del fragmento est creada por completo. En el ejemplo del curso, aprovechamos este mtodo para
obtener la referencia a la Vista ListView y asociarle su adaptador correspondiente.
Ahora vamos a explicar el segundo fragmento que, como comentamos, se encarga de mostrar
el detalle del mensaje seleccionado. La definicin de este fragmento es ms simple que la del
anterior. Si accedemos a su layout XML fragmento_detalle.xml vemos el siguiente contenido:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
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>
Una vez definidos los dos fragmentos, hay que desarrollar las Actividades de la aplicacin, con
sus respectivos layouts que utilizan los fragmentos anteriores.
Para la Actividad principal hemos diseado tres layouts diferentes:
- un layout cuando la aplicacin se ejecute en una pantalla de tamao normal, como un telfono mvil. Se encuentra en la carpeta por defecto /res/layout.
- dos layouts para pantallas mayores (uno para orientacin apaisada y otro para la vertical). Se
hallan en las carpetas /res/layout-large (pantalla grande con orientacin apaisada) y /
res/layout-large-port (pantalla grande con orientacin vertical) respectivamente.
Notamos que todos estos layouts se denominan con el mismo nombre activity_main.xml; sin
embargo, su uso lo marca la carpeta donde colocamos cada uno. Por lo tanto, Android elegir
automticamente el layout en funcin del tamao y orientacin de la pantalla.
185
Aula Mentor
Para el caso de pantalla normal, la Actividad principal mostrar slo el listado de mensajes; por
lo tanto, el layout incluir nicamente el fragmento FragmentListado:
<fragment xmlns:android=http://schemas.android.com/apk/res/android
class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_width=match_parent
android:layout_height=match_parent />
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=horizontal
android:layout_width=match_parent
android:layout_height=match_parent>
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentListado
android:id=@+id/FrgListado
android:layout_weight=30
android:layout_width=0px
android:layout_height=match_parent />
<fragment class=es.mentor.unidad2.eje5.fragmentos.FragmentDetalle
android:id=@+id/FrgDetalle
android:layout_weight=70
android:layout_width=0px
android:layout_height=match_parent />
</LinearLayout>
En este caso, al haber mucha pantalla disponible, incluimos los dos fragmentos dentro de un
LinearLayout horizontal, asignando al primer fragmento un peso (propiedad layout_weight)
de 30 y al segundo de 70 para que la columna izquierda con el listado de mensajes ocupe un
30% de la pantalla y la de detalle ocupe el resto.
Por ltimo, para el caso de la pantalla grande vertical (directorio layout-large-port)
utilizamos un LinearLayout vertical.
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
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>
Aula Mentor
188
Pantalla grande apaisada (Nexus 7 de 7.3 pulgadas):
189
Aula Mentor
listener.onMensajeSeleccionado(
(Mensaje)listado.getAdapter().getItem(pos));
}
}
}); // end onItemClick
}
// Definimos una interfaz que se implementar en la clase principal
// de la aplicacin y que tendr en cuenta el tamao de la pantalla
// disponible
public interface MensajesListener {
void onMensajeSeleccionado(Mensaje mensaje);
}
public void setMensajesListener(MensajesListener listener) {
this.listener=listener;
}
}
190
Como vemos, una vez definido este truco, debemos en el evento onItemClick() de la lista
lanzar nuestro evento personalizado onMensajeSeleccionado() pasndole como parmetro el
contenido del mensaje, que hemos obtenido mediante el adaptador con el mtodo getAdapter()
y recuperamos el elemento con getItem().
Ahora ya tenemos todas las piezas del puzle que debemos encajar en la clase java MainActivity que define la Actividad principal. Para ello, en su mtodo onCreate(), obtendremos
una referencia al fragmento de listado mediante el mtodo getFragmentById() del gestor de
fragmentos (Fragment Manager es el componente del sistema operativo encargado de
gestionar los fragmentos de una aplicacin) y le asignaremos el evento mediante su mtodo
setMensajesListener() que acabamos de definir anteriormente:
// Clase principal que contiene los fragmentos e implementa la
// interfaz Mensajes Listener
public class MainActivity extends FragmentActivity implements
MensajesListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Definimos el fragmento del listado mediante
// referencia a su id
FragmentListado frgListado
=(FragmentListado)getSupportFragmentManager()
.findFragmentById(R.id.FrgListado);
// Establecemos el listener del listado
frgListado.setMensajesListener(this);
}
// Se implementa el mtodo onMensajeSeleccionado del listener
// MensajesListener
@Override
public void onMensajeSeleccionado(Mensaje mensaje) {
// Buscamos si Android est mostrando el detalle del mensaje
// buscando si est definido el fragmento de detalle
boolean hayDetalle =
(getSupportFragmentManager().findFragmentById
(R.id.FrgDetalle) != null);
// Si se ven los dos fragmentos entonces cuando el usuario haga
// clic en un mensaje se muestra su detalle
if(hayDetalle) {
((FragmentDetalle)getSupportFragmentManager()
.findFragmentById(R.id.FrgDetalle)).mostrarDetalle(
mensaje.getTexto());
}
// Si no existe el fragmento de detalle lanzamos la actividad de
// detalle con un Intent pasando el texto como una propiedad en
// ste
else {
Intent i = new Intent(this, DetalleActivity.class);
i.putExtra(DetalleActivity.EXTRA_TEXTO,
mensaje.getTexto());
startActivity(i);
}
}
}
Se puede observar en el cdigo anterior que esta Actividad principal implementa la interfaz
MensajesListener, por lo que nos basta indicar this al mtodo setMensajesListener().
Adems, esta Actividad no se hereda de la clase Activity como suele ser habitual, sino de
FragmentActivity ya que se utiliza la librera de compatibilidad android-support para
utilizar fragmentos conservando la compatibilidad con versiones de Android anteriores a la
3.0. En caso de no necesitar esta compatibilidad se puede seguir heredando de Activity sin
inconvenientes.
Finalmente, si prestamos atencin al mtodo onMensajeSeleccionado(), vemos que se
ejecutar cada vez que el fragmento del listado indique que se ha seleccionado un determinado
mensaje de la lista y aplicar la lgica ya mencionada, es decir, si en la pantalla existe el fragmento de detalle simplemente lo actualizaremos mediante su mtodo mostrarDetalle() y, en
caso contrario, abriremos la nueva Actividad DetalleActivity utilizando un nuevo Intent
con la referencia a dicha clase y aadiendo como propiedad un campo extra de texto con el
contenido del mensaje seleccionado. Acabamos iniciando la Actividad con el mtodo startActivity().
El cdigo de la segunda Actividad DetalleActivity se limita a recuperar esta propiedad extra pasada en el Intent y mostrarla en el fragmento de detalle mediante su mtodo
mostrarDetalle():
public class DetalleActivity extends FragmentActivity {
// Definimos una constante que usaremos en Intent para pasar la
// informacin al fragmento
public static final String EXTRA_TEXTO =
es.mentor.unidad2.eje5.fragmentos.EXTRA_TEXTO;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalle);
// Buscamos el fragmento de detalle que es un fragmento
FragmentDetalle detalle =
191
Aula Mentor
(FragmentDetalle)getSupportFragmentManager()
.findFragmentById(R.id.FrgDetalle);
// Cargamos el texto del detalle en este fragmento
detalle.mostrarDetalle(getIntent().getStringExtra(EXTRA_TEXTO));
}
}
Animamos al alumno o alumna a que vuelva a probar la aplicacin en Eclipse ADT para
comprobar el correcto funcionamiento de la seleccin de mensajes en las distintas configuraciones
de pantalla.
Una vez hemos visto cmo crear y utilizar un fragmento, vamos a estudiar algunas de sus
funcionalidades avanzadas que pueden interesar al programador.
192
Como puedes ver, el ciclo de vida de un Fragmento est muy relacionado con el ciclo
de vida de la Actividad que lo contiene. Cada invocacin que hace el sistema operativo de
un evento de una Actividad provoca que se invoquen a su vez eventos de los Fragmentos que
contiene.
Por ejemplo, cuando la Actividad recibe el evento onPause(), todos sus fragmentos reciben tambin el mismo evento onPause().
Adems, los Fragmentos disponen de algunos eventos ms en su ciclo de vida:
- onAttach(Activity): Android lo invoca cuando el fragmento se asocia con la Actividad.
- onCreateView(LayoutInflater, ViewGroup, Bundle): Android lo invoca cuando hay
que crear las Vistas internas del fragmento.
- onActivityCreated(Bundle): Android lo invoca cuando la Actividad ya est creada.
- onDestroyView(): Android lo invoca cuando se van a destruir las Vistas del fragmento.
- onDetach(): Android lo invoca cuando el fragmento se desliga de la Actividad.
El resto de eventos se usan de la misma forma que en una Actividad.
Atencin: para sobreescribir cualquier mtodo del ciclo de vida (con excepcin del
mtodo onCreateView()) debemos siempre llamar al mtodo de la clase superior
(mediante super.nombreMetodo()). Si no lo hacemos, al ejecutar la aplicacin
veremos un error de ejecucin.
- Por defecto, Android almacena el estado de todas las Vistas de un fragmento que tengan
asociado un ID, es decir, su contenido o caractersticas se pueden modificar.
- Es posible sobreescribir el mtodo onSaveInstanceState(Bundle) para aadir informacin adicional del fragmento.
- Cuando, por ejemplo, el usuario vuelve a abrir una aplicacin y el sistema operativo crea el
fragmento, se utiliza el parmetro Bundle, guardado con el mtodo anterior, como parmetro en los mtodos onCreate(), onCreateView() y onActivityCreated() para que el
programador devuelva el Fragmento al estado almacenado.
193
Aula Mentor
mticamente.
Sin embargo, la clase Fragment dispone del mtodo setRetainInstance(boolean)
que permite al programador indicar con el parmetro true que se desea no destruir la instancia del Fragmento cuando esto ocurra. Podemos invocar este mtodo en el evento onCreate()
del Fragmento. Examinemos sus caractersticas y cmo se utiliza. Si indicamos con la sentencia
setRetainInstance(true) que deseamos que Android no destruya el Fragmento en los
cambios de configuracin automticos, ocurre lo siguiente:
- Los mtodos onDestroy() y onCreate() no se invocarn de nuevo cuando se produzca
la recreacin de la Actividad.
- El resto de eventos del ciclo de vida de un Fragmento se siguen invocando igualmente y en
la misma secuencia.
- El parmetro Bundle de los mtodos onCreateView() y onActivityCreated() en nulo
(null) porque el Fragmento no se recrea.
194
Slo es posible utilizar el mtodo commit() mientras la Actividad (y, por lo tanto, el
Fragmento) no se encuentre guardando su estado con el mtodo onSaveInstanceState. La sentencia commit mostrar una excepcin si la ejecutamos despus
de haber guardado el estado de la Actividad.
Como puedes observar en el cdigo anterior, hemos encadenado operaciones en una nica
sentencia. Es muy importante no olvidarse nunca de la orden commit(), ya que si no la usamos, no se realizan los cambios.
Adems, debes saber que el orden en que realices las operaciones determina el orden
en que Android las realizar en la interfaz del usuario.
195
Aula Mentor
196
Desde el punto de vista de programacin es importante evitar que las Actividades y Fragmentos sean muy interdependientes entre s ya que esto reduce las posibilidades de poder
reusar el fragmento en otra Actividad o aplicacin. Lo recomendado es que sea la Actividad la
que interaccione con el Fragmento.
Para conseguir este objetivo es recomendable definir interfaces y listerners en el
fragmento que luego implementar la clase que contiene la Actividad. En el Ejemplo 5 de esta
Unidad hemos utilizado este mtodo que recordamos:
197
Aula Mentor
198
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)) {
...
}
}
...
Si slo vas a utilizar el fragmento como una ventana de dilogo, debemos sobreescribir el
mtodo onCreateDialog() devolviendo una instancia de la clase Dialog o de sus subclases.
Si vas a emplear el nuevo fragmento como dilogo y como fragmento propiamente
dicho dentro de una Activiad, debes sobreescribir el mtodo onCreateView() y devolver la
Vista creada.
La clase DialogFragment dispone del mtodo sobrecargado (overload) show() que
199
Aula Mentor
muestra la ventana de dilogo como una transaccin, es decir, crea una transaccin en el objeto
FragmentManager, aade el fragmento y ejecuta commit(). Cuando se cierra la ventana de
dilogo, se crea otra transaccin para quitar el fragmento de la ACtividad.
Como no poda ser de otra forma, la clase DialogFragment dispone del mtodo dismiss() para cerrar el fragmento explcitamente. De igual forma, este mtodo se ejecuta mediante transaciones.
A continuacin, vamos a ver un sencillo ejemplo de ventana de dilogo, utilizando fragmentos,
que pide confirmacin para realizar cualquier operacin.
Cdigo fuente del fragmento:
public class ConfirmacionDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
// Como hemos dicho, la Actividad debe gestionar el listener
private ConfirmacionDialogFragmentListener listener;
// Contructor: ventana esttica de dilogo
public static ConfirmacionDialogFragment newInstance(String titulo){
ConfirmacionDialogFragment frag =
new ConfirmacionDialogFragment();
// Leemos los argumentos que pasa la Actividad que lo contiene
Bundle args = new Bundle();
args.putString(titulo, titulo);
frag.setArguments(args);
return frag;
}
200
201
Aula Mentor
Toast.makeText(this, android.R.string.cancel,
Toast.LENGTH_LONG).show();
}
Icono y Ttulo
Botones
Men
Extendido
203
Aula Mentor
Adems de poder definir mediante un archivo XML el men, es posible definir el tipo de men
dinmicamente mediante sentencias Java de la clase MenuItem con su mtodo menuItem.
setShowAsAction(int actionEnum) e indicando una de las siguientes acciones:
- SHOW_AS_ACTION_ALWAYS
- SHOW_AS_ACTION_IF_ROOM
- SHOW_AS_ACTION_NEVER
- SHOW_AS_ACTION_WITH_TEXT
Por ejemplo, si creas con Eclipse ADT un proyecto nuevo, vers que el men definido por
defecto (llamado normalmente /res/menu/activity_main.xml) tiene este contenido:
<menu xmlns:android=http://schemas.android.com/apk/res/android >
<item
android:id=@+id/menu_settings
android:showAsAction=never
android:title=@string/menu_settings/>
</menu>
En el cdigo anterior puedes ver que aparece un men con una nica opcin denominada
Settings y con el atributo showAsAction=never, es decir, la opcin aparece en el men
extendido.
En el Ejemplo 6 del curso hemos definido la tpica barra de accin con las opciones: Nuevo,
Guardar y Opciones:
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.
En el cdigo anterior hemos empleado las imgenes del propio sistema operativo
@android:drawable/ic_menu_save y @android:drawable/ic_menu_add para
establecer el icono de los botones. Puedes encontrar todas las imgenes disponibles por defecto en Android en tu SDK de Android en el directorio (calidad media):
path_sdk_Android\platforms\android-17\data\res\drawable-mdpi
Una vez definido el men mediante su correspondiente fichero XML, hay que asociarlo a la
Actividad principal utilizando el tpico mtodo OnCreateOptionsMenu(). Para ello, de igual
forma que se hace con un men, vamos a inflarlo llamando al mtodo inflate() e indicando el
ID del fichero XML donde se ha definido dicho men:
public class MainActivity extends Activity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflamos el men -> Infla la barra de accin.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
205
Aula Mentor
Si ejecutas la aplicacin, puedes ver que, dependiendo del tamao de pantalla del AVD, aparecen
diferentes opciones en la barra de accin, por ejemplo, en el modo vertical slo se visualiza:
En este caso hemos ejecutado el proyecto en un AVD de 800 dpi de resolucin de pantalla
y vemos que aparecen como botones de accin las dos opciones que hemos marcado como
showAsAction=ifRoom, pero no aparece el texto de la segunda opcin ni el men extendido
(overflow) ya que no hay espacio disponible con esta pantalla en vertical. Tampoco se visualiza
el ttulo completo de la aplicacin. Sin embargo, si rotamos la pantalla del emulador al modo
horizontal (pulsando la combinacin de teclado [Ctrl + F12]) vemos lo siguiente:
206
}
}
default:
return super.onOptionsItemSelected(item);
Por ejemplo, si Android detecta que hay suficiente espacio disponible en la Barra de
accin, integrar las pestaas dentro de la propia barra de forma que no ocupan espacio extra
en una fila inferior. Por el contrario, si no hubiera espacio suficiente situara las pestaas debajo
de la barra.
A continuacin, vamos a ver cmo se integran pestaas.
Lo primero que vamos a hacer es crear un nuevo fragmento por cada pestaa que tenga la aplicacin. Lo usual en las aplicaciones con pestaas es que cada una de ellas contenga diferente
funcionalidad. En este ejemplo hemos definido dos pestaas que cargan dos fragmentos. En
este apartado ya hemos visto cmo se implementan los fragmentos: con un fichero layout xml
y su clase Java asociada:
Pestaa 1:
- Tab1Fragmento.java
- fragmento1.xml
Pestaa 2:
- Tab2Fragmento.java
- fragmento2.xml
Por motivos pedaggicos, la interfaz de los fragmentos ser minimalista y contendrn nicamente la etiqueta de texto Pestaa 1 o Pestaa 2 para poder detectar el cambio de pestaa.
Por ejemplo, para la pestaa 1 el fichero fragmento1.xml contiene:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TextView
android:id=@+id/textView1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/tab_1 />
</LinearLayout>
207
Aula Mentor
Hemos definido la pestaa 2 de forma similar. Desde Eclipse ADT puedes abrir el proyecto y
observar su contenido.
As, ya disponemos del contenido de las pestaas y vamos a aadirlas al Ejemplo del
curso y enlazarlas en la barra de accin asignndoles un listener desde el que se responde
a los eventos que se produzcan.
Veamos el contenido del listener que incluye el cdigo que gestiona los eventos clsicos de pestaascomo la seleccin, reseleccin y deseleccin.
Para esto, hemos definido la nueva clase MiTabListener que se extiende de ActionBar.TabListener en la que reescribimos los mtodos de sus eventos onTabSelected(), onTabUnselected() y onTabReselected(). Veamos su cdigo fuente:
208
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// Mostramos un mensaje
Toast.makeText(actividad.getApplicationContext(),
Pestaa + tab.getText() + sin
seleccionar,Toast.LENGTH_SHORT).show();
// Quitamos el fragmento del contenedor.
ft.remove(fragment);
}
}
Estos mtodos lo nico que hacen es mostrar u ocultar los fragmentos en funcin de la pestaa
seleccionada por el usuario. As, en el evento onTabSelected() se reemplaza el fragmento
que est visible en la Actividad principal por el de la pestaa seleccionada. Por el contrario,
en el mtodo onTabUnselected() se oculta el fragmento asociado a la pestaa que pierde el
foco.
Ambos eventos utilizan el parmetro del tipo FragmentTransaction que permite gestionar los fragmentos de la actividad. En el primer caso, invoca su mtodo replace() y, en el
segundo, su mtodo remove(). Hemos aadido adems un mensaje para mostrar al usuario
que se ha realizado un cambio sobre las pestaas.
Una vez implementado este listener, en el evento onCreate() de la Actividad principal
de la aplicacin, tenemos que crear las pestaas, asociar sus respectivos fragmentos y engancharlas a la barra de accin.
Veamos el cdigo fuente de esta clase:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
209
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 NAVIGATION_MODE_TABS para que se muestren las pestaas.
El mtodo setNavigationMode() puede tomar los siguientes valores que establecen el tipo
de navegacin en la barra de accin:
- NAVIGATION_MODE_STANDARD: muestra la barra con un ttulo y un icono clsicos.
- NAVIGATION_MODE_LIST: las pestaas se integran en el ttulo de la barra mostrando un listado desplegable en ste.
- NAVIGATION_MODE_TABS: las pestaas se integran en la barra.
210
A continuacin, creamos las pestaas con el mtodo newTab() de la barra y, en la misma sentencia, determinamos su texto con setText(). Despus, instanciamos los dos fragmentos y los
asociamos a cada pestaa utilizando el listener setTabListener(). Para acabar, aadimos las
pestaas a la barra de accin mediante el mtodo addTab().
Nota: hemos incluido en el cdigo fuente el mtodo setDisplayShowTitleEnabled()de la barra de accin que permite dinmicamente mostrar u ocultar el ttulo
de sta.
Si ejecutamos la aplicacin en el emulador, veremos lo siguiente (depende del tamao de pantalla de tu AVD):
Como podemos observar, Android ha colocado las pestaas debajo de la barra de accin porque no hay suficiente espacio disponible. Si cambiamos el emulador a la orientacin horizontal
[Ctrl+F12], vemos que las pestaas ya aparecen integradas en la barra:
Fjate en el espacio que queda libre para definir el resto de la interfaz optimizando al usuario
la comodidad y usabilidad de la aplicacin final.
Si ejecutas el proyecto en Eclipse ADT y cambias varias veces de pestaa vers que se
cambia de fragmento y se muestran los mensajes correspondientes.
211
Aula Mentor
212
Como puedes ver, el conjunto de opciones seleccionables estn distribuidas de forma tabular
o, dicho de otra forma, divididas en filas y columnas como una matriz. Dada la naturaleza de la
Vista, sus propiedades ms importantes son las tpicas de cualquier listado:
- android:numColumns: indica el nmero de columnas de la tabla. Tambin podemos indicar auto_fit si deseamos que el propio sistema operativo las establezca a partir de ciertas
propiedades.
- android:columnWidth: marca el ancho de las columnas de la tabla.
- android:horizontalSpacing: establece el espacio horizontal entre las celdas.
- android:verticalSpacing: indica el espacio vertical entre las celdas.
- android:stretchMode: marca qu hacer con el espacio horizontal sobrante. Si se establece
el valor columnWidth, este espacio ser dividido a partes iguales por las columnas de la
tabla. Por el contrario, si se establece a spacingWidth ser dividido a partes iguales por los
espacios entre las celdas.
Veamos cmo definiramos un GridView en la aplicacin del Ejemplo 7 del curso en el archivo
activity_main.xml:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=match_parent
android:layout_height=match_parent >
<TextView
android:id=@+id/labelMensaje
android:layout_width=match_parent
android:layout_height=wrap_content
android:text=@string/selecciona_una_opcion />
<GridView
android:id=@+id/gridOpciones
android:layout_width=match_parent
android:layout_height=match_parent
android:columnWidth=150dp
android:horizontalSpacing=1dp
android:numColumns=auto_fit
android:stretchMode=columnWidth
android:verticalSpacing=5dp />
</LinearLayout>
Una vez est definida la interfaz de usuario, la forma de asignarle sus opciones es exactamente
la misma que en otro tipo de listados.
Por motivos pedaggicos, vamos a utilizar una matriz simple como adaptador utilizando
la clase ArrayAdaptery un layout genrico (simple_list_item_1, compuesto por un simple
TextView). Asociamos el adaptador al control GridView mediante su mtodo ya conocido
setAdapter(). Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 7
veremos:
public class MainActivity extends Activity {
Por defecto, las opciones de la matriz se aaden al GridView ordenados por filas y, si no caben
todas en la pantalla, se activa el desplazamiento (scroll) sobre la lista.
En cuanto a los eventos disponibles, el ms frecuente es el lanzado cuando un usuario
selecciona una opcin determinada de la lista: onItemClick. Este evento se captura de igual
forma que con las lista Spinner y ListView.
213
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 7, vers que se muestra la siguiente lista con los
meses del ao:
214
Como has visto, hemos mostrado el uso bsico del listado GridView. El alumno o alumna puede aplicar por su cuenta, de forma prcticamente directa, todo lo comentado sobre listas en el
curso de Iniciacin de Android de Mentor.
Entre los conocimientos que ya debe tener podemos citar la personalizacin de las opciones para presentar datos complejos, la creacin de un adaptador personalizado y las distintas
optimizaciones para mejorar el rendimiento de la aplicacin, como la reutilizacin de las Vistas
de las opciones.
Animamos al alumno a que pruebe con todas estas opciones realizando una aplicacin
propia.
Como puedes ver, se trata del clsico interruptor donde el usuario puede activar o desactivar
una opcin con un desplazamiento sobre ste.
Teniendo en cuenta la naturaleza de esta Vista, sus propiedades ms importantes son:
- android:text: indica el texto que debe mostrar la opcin del interruptor en el lado izquierdo de ste.
- android:checked: establece si el interruptor est activado o no por defecto.
- android:textoOn: establece el texto interno del interruptor cuando est activado.
- android:textOff: establece el texto interno del interruptor cuando est desactivado.
Veamos cmo utilizamos varios Switch en la aplicacin del Ejemplo 8 del curso en el archivo
activity_main.xml:
<ScrollView xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent>
<LinearLayout
android:orientation=vertical
android:layout_width=match_parent
android:layout_height=wrap_content>
<Switch android:text=@string/interruptor_por_defecto
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:text=@string/interruptor_encendido
android:checked=true
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch android:id=@+id/switch_gestionado
android:text=@string/interruptor_gestionado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginBottom=32dip />
<Switch
android:text=@string/interruptor_con_texto_personalizado
215
Aula Mentor
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textOn=Encendido
android:textOff=Apagado
android:layout_marginBottom=32dip />
</LinearLayout>
</ScrollView>
Si abrimos la clase principal MainActivity de la aplicacin del Ejemplo 8
observamos:
public class MainActivity extends Activity {
216
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el switch gestionado para definir el evento
// onCheckedChange
Switch s = (Switch) findViewById(R.id.switch_gestionado);
if (s != null) {
s.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
@Override
// Evento que ocurre cuando el usuario enciende o
// apaga un interruptor
public void onCheckedChanged(CompoundButton
buttonView, boolean isChecked) {
// Mostramos un sencillo mensaje
Toast.makeText(MainActivity.this, El
interruptor est + (isChecked ?
ENCENDIDO! : APAGADO!),
Toast.LENGTH_SHORT).show();
}
});
} //end if not null
} // end onCreate
}
En el cdigo fuente anterior puedes observar que es fcil definir el evento onCheckedChanged
que se lanza cuando el usuario activa o desactiva un interruptor.
Es sencillo utilizar el mtodo setCheck() para indicar si el interruptor est o no activado. Adems, entre otros, tambin dispone del mtodo isCheck() para conocer el estado actual
del interruptor. En la ayuda oficial de Android puedes encontrar otros mtodos interesantes.
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 8 (Interruptores en Android) de la Unidad 2. Estudia el cdigo fuente y ejectalo para mostrar en el AVD
el resultado del programa anterior, en el que hemos utilizado la Vista Switch.
Si ejecutas en Eclipse ADT este Ejemplo 8, vers que se muestra la siguiente aplicacin con
interruptores:
El navigation drawer est disponible como parte de la biblioteca de compatibilidad androidsupport; as, es posible incluir este elemento visual desde la versin Android 1.6 (API 4).
217
Aula Mentor
Vamos a partir del Ejemplo 5 de esta Unidad, para desarrollar este ejemplo. Para
mejorarlo lo hemos modificado utilizando la biblioteca de compatibilidad y, as,
poder ejecutarlo desde la versin 2.2 de Android.
218
Veamos cmo empleamos este elemento visual en la aplicacin del Ejemplo 9 del curso en el
archivo activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@+id/drawer_layout
android:layout_width=match_parent
android:layout_height=match_parent >
<!-- Contenido Principal -->
<FrameLayout
android:id=@+id/frame_contenido
android:layout_width=match_parent
android:layout_height=match_parent />
<!-- Men Lateral -->
<ListView
android:id=@+id/menu_lateral
android:layout_width=240dp
android:layout_height=match_parent
android:layout_gravity=start
android:background=#111
android:choiceMode=singleChoice />
</android.support.v4.widget.DrawerLayout>
Para el FrameLayout hemos ajustado con match_parent el ancho y el alto, para que ocupe
todo el espacio disponible en la pantalla.
En este caso del ListView el alto ocupa todo el espacio y el ancho es de 240dp; as,
// Definimos el adaptador del listado con las opciones del
// men lateral
drawerList.setAdapter(new ArrayAdapter<String>(
getSupportActionBar().getThemedContext(),
android.R.layout.simple_list_item_1, opcionesMenu));
// Definimos el evento onClick en el men lateral
drawerList.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View
view, int position, long id) {
// Variable con el fragmento que vamos a crear
Fragment fragmento = null;
//Creamos los fragmentos segn la opcin
// seleccionada
switch (position) {
case 0:
fragmento = new Tab1Fragmento();
break;
case 1:
fragmento = new Tab2Fragmento();
break;
} // end case
219
Aula Mentor
220
getSupportActionBar().setTitle(tituloMenuLateral);
// Cerramos el men lateral
drawerLayout.closeDrawer(drawerList);
}
}); // end onClick
// Inicializamos las variables de ttulos con el ttulo
// inicial de la aplicacin
tituloMenuLateral = getTitle();
tituloAplicacion = getTitle();
// Definimos el icono de apertura del men lateral
drawerToggle = new ActionBarDrawerToggle(this,
drawerLayout,
R.drawable.ic_navigation_drawer,
R.string.drawer_open,
R.string.drawer_close)
{
// Evento que ocurre cuando se cierra el men
// lateral
public void onDrawerClosed(View view) {
// Cambiamos el ttulo de la cabecera a
// la opcin elegida del men
getSupportActionBar().setTitle(
tituloMenuLateral);
// Ejecutamos el evento onPrepareOptionsMenu()
//para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(
MainActivity.this);
}
// Evento que ocurre cuando se abre el men
// lateral
public void onDrawerOpened(View drawerView) {
// Cambiamos el ttulo de la cabecera al
// de la aplicacin
getSupportActionBar().setTitle(
tituloAplicacion);
// Ejecutamos el evento onPrepareOptionsMenu()
// para actualizar la barra accin
ActivityCompat.invalidateOptionsMenu(
MainActivity.this);
}
};
// Definimos el listener del men lateral
drawerLayout.setDrawerListener(drawerToggle);
// Indicamos que el icono de la aplicacin es activo
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} // end onCreate()
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflamos el men -> Infla la barra de accin.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
// En funcin de la opcin seleccionada mostramos un Toast en la
// aplicacin
public boolean onOptionsItemSelected(MenuItem item) {
// Si el usuario hace clic en el icono de la barra
// mostramos el men lateral
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
else // Si no, mostramos el onClick de la opcin del men
// correspondiente
switch (item.getItemId()) {
case R.id.menu_nuevo:
Toast.makeText(this,
Has pulsado en accin Nuevo,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_guardar:
Toast.makeText(this,
Has pulsado en accin Guardar,
Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_buscar:
Toast.makeText(this,
Has pulsado en accin Buscar,
Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
} // end onOptionsItemSelected
// Evento que se lanza antes de mostrar el men
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Vemos si el men lateral est abierto
boolean menuAbierto = drawerLayout.isDrawerOpen(
drawerList);
// Si est abierto, ocultamos la opcin del men buscar
if(menuAbierto)
menu.findItem(R.id.menu_buscar).setVisible(false);
else // Si no, la mostramos
221
Aula Mentor
menu.findItem(R.id.menu_buscar).setVisible(true);
return super.onPrepareOptionsMenu(menu);
}
// Evento que ocurre despus de crear la Actividad
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sincronizamos el listener del men lateral
drawerToggle.syncState();
}
// Evento que ocurre si cambia la configuracin de la Actividad
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Notificamos el cambio al listener del men lateral
drawerToggle.onConfigurationChanged(newConfig);
}
} // end clase
222
En el cdigo anterior, dentro del mtodo onCreate() de la actividad, buscamos las Vistas de
la Interfaz de usuario y definimos las opciones del men lateral en un matriz de tipo String.
Adems, creamos el adaptador del listado con las opciones del men lateral pasando
como parmetro a este adaptador el contexto obtenido invocando el mtodo getThemedContext() de la action bar. Hemos utilizando getSupportActionBar() para acceder a la barra
de accin en lugar del habitual getActionBar() ya que estamos utilizando la biblioteca de
compatibilidad.
Como el layout de los elementos de la lista hemos establecido el estndar
android.R.layout.simple_list_item_1. De esta forma, el listado ser compatible con la
mayora de versiones de Android. Sin embargo, este estilo hace que la opcin seleccionada no
est resaltada en el men cada vez que se cierre y se vuelva a abrir. Para hacer esto, en Android
4, podemos aplicar el layout android.R.layout.simple_list_item_activated_1, si bien
aparecer un error si se ejecuta la aplicacin en versiones anteriores de Android.
Si deseamos evitar este problema de compatibilidad tenemos varias opciones:
Aplicar un layout diferente dependiendo de la versin de Android en la que se ejecuta la
aplicacin:
drawerList.setAdapter(
new ArrayAdapter<String>(getSupportActionBar().getThemedContext(),
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?
android.R.layout.simple_list_item_activated_1 :
android.R.layout.simple_list_item_1, opcionesMenu));
223
Aula Mentor
224
225
Aula Mentor
226
Hay que tener tambin en cuenta que, en versiones anteriores a Android 3.0, no existen los
atributos de los mens utilizados por la funcionalidad de la barra de accin como, por ejemplo,
el atributo android:showAsAction y, por lo tanto, no podemos utilizarlos directamente.
Para solucionar este problema, vamos a utilizar dichos atributos indicando un espacio
de nombres personalizado que definimos en el elemento <menu> y usamos en los elementos
<item>. Veamos el aspecto del archivo unidad2.eje9.navigation_drawer\res\menu\activity_main.xml:
<menu xmlns:android=http://schemas.android.com/apk/res/android
xmlns:aplicacion=http://schemas.android.com/apk/res-auto>
<item
android:id=@+id/menu_guardar
aplicacion:showAsAction=ifRoom
android:icon=@android:drawable/ic_menu_save
android:title=@string/menu_guardar/>
<item
android:id=@+id/menu_nuevo
aplicacion:showAsAction=always
android:icon=@android:drawable/ic_menu_add
android:title=@string/menu_nuevo/>
<item
android:id=@+id/menu_buscar
</menu>
aplicacion:showAsAction=always
android:icon=@drawable/ic_action_search
android:title=@string/menu_buscar/>
Si ejecutas en Eclipse ADT este Ejemplo 9, vers que se muestra la siguiente aplicacin:
227
Si ejecutas en un AVD de Android 2.x puedes ver que la aplicacin funciona correctamente y el
aspecto es muy similar:
Aula Mentor
228
8. Resumen
Hay que saber al final de esta unidad:
- Hoy en da, es necesario que una aplicacin tenga un aspecto visual atractivo
con el diseo apropiado.
- En ingls, se denomina look and feel al aspecto visual de una aplicacin.
- Los temas visuales de Android (themes) son un medio rico y complejo que permiten definir todos los atributos del aspecto de Vistas de Android.
- Un atributo es una propiedad de una Vista que permite modificar su aspecto
visual.
- Los Estilos (Styles) agrupan varios atributos visuales de una Vista en un nico
bloque que podemos aplicar al aspecto de sta.
- Para utilizar temas conjuntamente con estilos debemos dar los siguientes pasos:
Definir el nombre del tema y sus atributos (caractersticas de aspecto de las
Vistas).
Especificar los atributos anteriores mediante referencia a otro atributo
(format=reference).
Disear el aspecto de cada atributo con las propiedades visuales de una Vista
Android.
Aplicar el estilo que corresponda a las vistas que definen la interfaz del usuario mediante su propiedad style.
- Un recurso de color permite definir un color comn a toda la aplicacin.
- Un recurso de tipo Dimensin permite definir una longitud comn a toda la aplicacin.
- Un Gradient Drawable permite al programador dibujar un gradiente de colores
que consiste en mostrar un degradado de dos colores en el fondo de cualquier
Vista.
- Un Selector Drawable es un tipo de elemento que permite realizar cambios automticos basados en el aspecto de una Vista teniendo en cuenta el estado actual
de sta.
- Un Nine-patch Drawable es un tipo especial de imagen que escala o crece tanto
a lo largo como a lo ancho y que mantiene su relacin de aspecto visual.
- Un Widget es una aplicacin reducida o programa de tamao pequeo que permite al usuario visualizar informacin de forma rpida en la pantalla y/o acceder
a funciones utilizadas frecuentemente.
- Debemos saber que los Widgets:
Se pueden arrastrar y cambiar de posicin.
El usuario puede eliminarlos o modificar su tamao.
El usuario puede interactuar con ellos como si fueran una Actividad ms.
Actualizan informacin de forma peridica.
Se puede crear un mismo Widget tantas veces como sea necesario.
Se puede configurar un Widget al crearlo.
- Para crear la interfaz visual de usuario de los Widgets debemos utilizar la clase
RemoteViews.
229
Aula Mentor
230
- Las Vistas ListView, GridView, StackView y AdapterViewFlipper se pueden utilizar en el interior de un Widget a partir de la versin Android 3.0 empleando las
Vistas de Colecciones (Collections).
- Desde la versin 4.2 de Android es posible incluir Widgets en la pantalla de bloqueo (Lock Screen) del dispositivo.
- Los Live Wallpapers son fondos de pantalla animados e interactivos de Android
similares a las aplicaciones de Android.
- La clase SurfaceHolder de Android es una Interfaz abstracta que permite controlar el tamao, el formato, editar los pxeles y gestionar los cambios de una
superficie, en este caso, es la superficie del fondo de pantalla.
- Un Fragmento (Fragment) es un pedazo de la interfaz de usuario que se puede
aadir o eliminar de la interfaz global de usuario de forma independiente al resto
de elementos de la Actividad.
- Android permite utilizar Fragmentos en versiones anteriores a la 3.0 si incluimos
la librera de compatibilidad android-support en el proyecto.
- Fragment Manager es el componente del sistema operativo encargado de gestionar los fragmentos de una aplicacin.
- El ciclo de vida de un Fragmento est muy relacionado con el ciclo de vida de la
Actividad que lo contiene.
- Las transacciones permiten llevar a cabo otras operaciones dinmicas con Fragmentos en tiempo de ejecucin que modifican la interfaz del usuario.
- Android tambin dispone de una pila de ejecucin de Fragmentos llamada back.
- Un fragmento est directamente ligado a la Actividad que lo contiene y, por lo
tanto, es posible una comunicacin entre ambos.
- Desde el punto de vista de programacin es importante evitar que las Actividades y Fragmentos sean muy interdependientes entre s ya que esto reduce las
posibilidades de poder reusar el fragmento.
- A partir de la versin 3.0 de Android, las clsicas ventanas de dilogo (Dialog)
estn en desuso en favor de los fragmentos.
- La Barra de Accin de Android (o Action Bar) es, como su propio nombre indica,
una barra que aparece en la parte superior de una aplicacin que puede incluir
un icono, el ttulo de la Actividad y varios botones de accin o un men extendido desplegable.
- Android permite que el programador pueda aadir pestaas a la Barra de Accin
de forma sencilla.
- GridView es una Vista de tipo matriz que muestra opciones en dos dimensiones
en una matriz desplazable.
- Switch es una Vista del tipo botn compuesto que muestra un interruptor que
permite al usuario indicar si una opcin est activa o inactiva.
- Un Navigation Drawer es un men lateral deslizante donde el usuario puede
seleccionar diferentes opciones.
1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan los sensores y dispositivos de Android en una
aplicacin. Primero, haremos una introduccin a los sensores; despus, veremos cmo se usan
conjuntamente con el Simulador de sensores.
Adems, mostraremos mediante ejemplos la integracin de los dispositivos WIFI, Bluetooth, GPS y Cmara de fotos en una aplicacin Android.
Finalmente, veremos cmo aplicar sensores a un juego sencillo.
231
Aula Mentor
Para acceder a los sensores disponibles en un dispositivo y obtener sus datos, Android
proporciona varias clases e interfaces que realizan una amplia variedad de tareas relacionados
con los sensores. Por ejemplo, se pueden utilizar para:
- Obtener los sensores disponibles en el dispositivo.
- Conseguir las capacidades e informacin de un sensor en particular, como el rango de medida, el fabricante, la potencia y resolucin de la medida.
- Recibir los datos de las magnitudes medidas de los sensores definiendo un intervalo de actualizacin de las medidas.
- Registrar y quitar los listeners asociados a la monitorizacin y medida que realizan los
sensores.
En este apartado vamos a estudiar cmo se usan los sensores de Android. Todos los sensores se
manipulan de forma homognea y con ellos podremos implementar mejorar la interaccin del
dispositivo con el usuario.
No todos los dispositivos disponen de los mismos sensores. Cada modelo y fabricante incluye los que considera apropiados. Adems, para gestionar estos sensores el dispositivo emplea drivers que el fabricante no suele hacer pblicos.
Android permite acceder a los sensores internos del dispositivo a travs de las clases del
paqueteandroid.hardware siguientes:
- Sensor: clase que representa a un sensor con todas sus propiedades.
- SensorEvent: clase que se usa para pasar los datos medidos por el sensor a la aplicacin.
- SensorManager: gestor que permite acceder a los sensores de un dispositivo. Para obtener
una instancia del mismos debemos invocar el mtodo Context.getSystemService() con
el argumento SENSOR_SERVICE.
- SensorEventListener: interfaz utilizada para recibir las notificaciones del SensorManager cuando se comunican nuevas medidas de los sensores.
Desde el punto de vista del desarrollador, las clases Java que debemos usar son pocas y
sencillas. Esto es as porque, para que el dispositivo Android gestione sensores, debe utilizar
drivers cuyo cdigo fuente no suele hacer pblico su fabricante. Por esto, Android tiene
tanto xito entre los fabricantes de dispositivos, ya que stos no tienen que publicar
el cdigo fuente de los drivers que, en realidad, muestra cmo funciona su hardware.
La clase Sensor contiene la informacin y propiedades completas de un sensor. Para
obtener el tipo de sensor que contiene un objeto de esta clase, debemos usar su mtodo getType() que devuelve 11 tipos de sensores mediante alguna de las siguientes constantes:
Tipo / CONSTANTE
Descripcin
Dimensiones
Desde
API
acelermetro
Detectar giros
lgico
Altmetro y barmetro
Detecta giros
14
14
TYPE_ACCELEROMETER
campo magntico
TYPE_MAGNETIC_FIELD
giroscopio
TYPE_GYROSCOPE
orientacin
TYPE_ORIENTATION
luz ambiental
TYPE_LIGHT
proximidad
TYPE_PROXIMITY
presin atmosfrica
TYPE_PRESSURE
temperatura interna
TYPE_TEMPERATURE
gravedad
TYPE_GRAVITY
acelermetro lineal
TYPE_LINEAR_ACCELERATION
vector de rotacin
TYPE_ROTATION_VECTOR
temperatura ambiental
TYPE_AMBIENT_TEMPERATURE
humedad relativa
TYPE_RELATIVE_HUMIDITY
Esta lista anterior se va ampliando con las nuevas versiones de Android, si bien los sensores
disponibles varan mucho en funcin del dispositivo utilizado.
Adems, podemos dividir los sensores en dos categoras ms: real y virtual. Real se
refiere a que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de la combinacin de las medidas de otros sensores o son medidas
relativas.
233
Aula Mentor
234
Puedes observar que los sensores TEMPERATURE y ORIENTATION son obsoletos. El primero se ha
sustituido por el sensor AMBIENT_TEMP. El segundo se quitar de Android porque normalmente
no hay un sensor de orientacin en los dispositivos. Para conocer esta orientacin, el dispositivo
combina los datos de los sensores acelermetro y del campo magntico.
Para obtener la orientacin del dispositivo puedes utilizar el mtodo android.view.Display.
getRotation() de Android.
Para acceder a los sensores de un dispositivo Android proporciona el gestor SensorManager. Para obtener el listado completo de los sensores del dispositivo, debemos usar su
mtodo getSensorList(tipo) que devuelve una lista de tipo Sensor. En el parmetro tipo
hay que marcar el tipo de sensores que queremos obtener mediante uno de los tipos del listado
anterior o Sensor.TYPE_ALL para indicar todos los sensores.
Atencin: para desarrollar aplicaciones que utilizan sensores, es necesario disponer de un dispositivo fsico, ya que el emulador de Android no permite depurar
todos los tipos de sensores correctamente.
Sin embargo, en el apartado siguiente veremos cmo instalar un software de emulacin que
permitir realizar pruebas sobre algunos sensores simulados del emulador.
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=center_horizontal
android:text=LISTADO DE SENSORES
android:textAppearance=?android:attr/textAppearanceLarge
android:textColor=@color/color_fuente
android:typeface=monospace />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<TextView
android:id=@+id/sensores
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=
android:textColor=@color/color_fuente />
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=DATOS
android:textStyle=bold
android:layout_gravity=center_horizontal
android:typeface=monospace
android:textColor=@color/color_fuente />
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=@color/color_fuente />
<ScrollView
android:id=@+id/scrollView1
android:layout_width=match_parent
android:layout_height=wrap_content
android:fadeScrollbars=false >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TextView
android:id=@+id/datos
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Datos />
</LinearLayout>
235
Aula Mentor
</ScrollView>
</LinearLayout>
En el fichero anterior puedes ver que hemos definido la Vista ScrollView con el atributo
android:fadeScrollbars=false para que no se oculte la barra de desplazamiento vertical.
Si ahora abres el archivo Java que define la Actividad, vers el siguiente contenido:
public class MainActivity extends Activity implements
SensorEventListener {
private TextView sensores, datos;
236
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensores = (TextView) findViewById(R.id.sensores);
datos = (TextView) findViewById(R.id.datos);
// Conectamos con el gestor de sensores
SensorManager sensorManager =
(SensorManager)getSystemService(SENSOR_SERVICE);
// Obtenemos el listado con todos los sensores
List<Sensor> listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ALL);
// Mostramos en pantalla el listado de sensores
for(Sensor sensor: listaSensores) {
log(sensor.getName(), sensores);
} // end for
// A continuacin, vamos a buscar los sensores de varios tipos en
// concreto y le asignamos al primero que encontremos a un
// listener para leer sus medidas
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ROTATION_VECTOR);
if (!listaSensores.isEmpty()) {
Sensor orientationSensor = listaSensores.get(0);
// Registramos el lister para este sensor e indicamos que
// se realice una medida cada SENSOR_DELAY_UI=1000 miliseg.
sensorManager.registerListener(this, orientationSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (!listaSensores.isEmpty()) {
Sensor acelerometerSensor = listaSensores.get(0);
sensorManager.registerListener(this, acelerometerSensor,
SensorManager.SENSOR_DELAY_UI);
}
listaSensores =
sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
if (!listaSensores.isEmpty()) {
Sensor magneticSensor = listaSensores.get(0);
sensorManager.registerListener(this, magneticSensor,
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);
}
237
Aula Mentor
} // end onSensorChanged
238
La clase SensorManager dispone, adems, de los tres mtodos: getInclination(), getOrientation() y getRotationMatrix() que se usan para calcular transformaciones de
coordenadas.
Veamos ahora cmo obtener medidas de cada uno de los sensores. Para ello, empezamos consultando si disponemos de determinados tipos de sensores indicando al sistema que
los liste mediante el mtodo getSensorList(). Si esta lista contiene datos, vamos a utilizar
nicamente el primer sensor (el elemento 0).
Para que la Actividad reciba las medidas de un sensor, es necesario registrar un listener mediante el mtodo registerListener(). ste toma como primer parmetro un objeto que implementa la interface SensorEventListener (indicamos this porque la clase
MainActivity implementa esta interfaz para recoger los eventos de sensores). El segundo
parmetro es el tipo de sensor que estamos registrando. Finalmente, el tercer parmetro indica
al sistema con qu frecuencia mxima queremos recibir las medidas del sensor. Podemos establecer cuatro posibles valores (de menor a mayor frecuencia): SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME y SENSOR_DELAY_FASTEST. Este ltimo parmetro sirve
para que el sistema conozca cunta frecuencia de actualizacin de las medidas de los sensores
necesita nuestra aplicacin, pero no garantiza una frecuencia concreta, aunque el sistema s
debe proporcionar una medida en ese tiempo mximo.
Para que nuestra clase implemente la interface que hemos comentado, la clase principal
incluye el cdigo:
implements SensorEventListener
Todo programador en Java sabe que cuando usamos una interface estamos obligados a
implementar todos sus mtodos. En este caso, la interfaz SensorEventListener que se usa
para recibir las medidas de los sensores tiene dos mtodos que debemos implementar:
- onAccuracyChanged: invocada cuando cambia la exactitud de la medida del sensor. Por
ejemplo, cuando el modo la deteccin de localizacin geogrfica cambia desde la red de
telefona mvil al sensor GPS del dispositivo (ms preciso).
- onSensorChanged: se lanza cuando un sensor indica su medida. Android indica en su parmetro de la clase SensorEvent la informacin del sensor que lo ha causado y podemos
leer los datos medidos por ste.
Los campos que contiene la clase SensorEvent son los siguientes:
accuracy
sensor
timestamp
values
239
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 1, vers que se muestra la siguiente aplicacin:
240
241
Aula Mentor
242
Quick Settings: podemos establecer las propiedades iniciales de los sensores bsicos.
Sensors Parameters: podemos establecer las propiedades iniciales del resto de sensores:
El botn derecho que aparece en la ventana principal nos permite acceder a las siguientes
opciones adicionales:
243
Aula Mentor
Success
244
Debemos dejar los campos IP y Socket como estn. Esta IP debe coincidir con la IP que aparece
en el lado izquierdo (parte de abajo) en el SensorSimulator:
Cambiamos en la aplicacin del AVD a la pestaa Testing y hacemos clic en el botn Connect:
245
Aula Mentor
En la ventana anterior podemos ver los datos de los sensores que tenemos habilitados (color
azul ms oscuro) en el Simulador de sensores:
246
Si movemos la imagen del telfono arrastrando el ratn sobre sta en la aplicacin
SensorSimulator, vemos cmo se cambian los valores en el emulador.
Podemos balancear (yaw), girar (roll) y mover (move) el dispositivo arrastrando el ratn sobre
el esquema del dispositivo virtual.
La aplicacin Sensor Simulator Settings se usa de puente entre el AVD y el SensorSimulator. De esta forma, se comunica la informacin del cambio de los valores de los sensores
que luego usar nuestra aplicacin Android.
Una vez hecho esto, debemos abrir las propiedades del proyecto y en la opciones Java Build
Path en la pestaa Order and Export hay que subir la librera a la primera posicin (para
que las clases de esta librera tengan prioridad frente a las de Android) y marcarla como exportable, tal y como vemos en la siguiente pantalla:
Aula Mentor
248
A continuacin, vamos a modificar el cdigo fuente del Ejemplo 1 de esta Unidad copindolo
previamente como Ejemplo 2. A continuacin, abrimos el Ejemplo 2 para ver cmo queda el
cdigo fuente una vez modificado. Empezamos comentando los imports originales y agregando los de las clases del paquete org.openintents.sensorsimulator:
/*import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;*/
import
import
import
import
org.openintents.sensorsimulator.hardware.Sensor;
org.openintents.sensorsimulator.hardware.SensorEvent;
org.openintents.sensorsimulator.hardware.SensorEventListener;
org.openintents.sensorsimulator.hardware.SensorManagerSimulator;
por la sentencia:
sensorManager = SensorManagerSimulator.getSystemService(this, SENSOR_
SERVICE);
06-05
06-05
06-05
06-05
06-05
06-05
06-05
06-05
12:35:38.516:
12:35:38.806:
12:35:38.846:
12:35:38.846:
12:35:38.856:
12:35:38.946:
12:35:38.957:
12:35:38.986:
I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
I/Hardware(2964):
D/Hardware(2964):
D/Hardware(2964):
D/Hardware(2964):
Starting connection...
Connecting to 10.0.2.2 : 8010
Read line...
Received: SensorSimulator
Connected
Sensor pressure not supported
Sensor barcode reader not supported
Sensor gyroscope not supported
249
Aula Mentor
@Override
protected void onStop() {
// Desregistramos todos los listeners de los sensores
sensorManager.unregisterListener(eventListenerAcelerometro);
sensorManager.unregisterListener(eventListenerAcceleracionLineal);
sensorManager.unregisterListener(eventListenerGravedad);
sensorManager.unregisterListener(eventListenerCampoMagnetico);
sensorManager.unregisterListener(eventListenerOrientacion);
sensorManager.unregisterListener(eventListenerTemperatura);
sensorManager.unregisterListener(eventListenerLuz);
sensorManager.unregisterListener(eventListenerPresion);
sensorManager.unregisterListener(eventListenerRotacion);
sensorManager.unregisterListener(eventListenerGiroscopio);
sensorManager.unregisterListener(eventListenerBarcode);
super.onStop();
}
250
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 hacemos, la aplicacin fallar en un dispositivo real.
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (Listado Sensores- SensorSimulator) de la Unidad 3. Estudia el cdigo fuente y ejectalo para mostrar en
el AVD el resultado del programa anterior, en el que hemos utilizado un Simulador
de Sensores.
Si ejecutas en Eclipse ADT este Ejemplo 2, vers que se muestra la siguiente aplicacin:
251
En esta Unidad puedes encontrar el vdeo Cmo utilizar SensorSimulator, que muestra de manera visual los pasos seguidos en
las explicaciones anteriores. Te recomendamos que lo muestres y
leas las explicaciones que aparecen.
Aula Mentor
252
Debes asegurarte de que el dispositivo Android se encuentra en la misma red que la aplicacin Java de tu ordenador (SensorSimulator). Lo ms sencillo es utilizar una red wifi. La IP
que escribas debe corresponder a la IP de tu ordenador. A veces, es conveniente tambin
deshabilitar el firewall del ordenador. Cuando hayamos, acabado debemos pulsar el botn
Stop.
- En la aplicacin externa del SensorSimulator seleccionar la pestaa Scenario Simulator.
En esta ventana veremos cmo se van cargando los valores de los sensores seleccionados:
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.
253
Aula Mentor
Para empezar hemos definido la clase Red que sirve de modelo de datos de una red WIFI. En
un objeto de este tipo guardamos los datos de una red WIFI: el SSID, la seguridad, la potencia
de seal, el BSSID y el canal de frecuencia de cada una. Veamos el sencillo aspecto de la clase:
// Clase que define el modelo de datos que almacena la informacin
// de una red WIFI
public class Red {
private
private
private
private
private
String
String
String
String
String
ssid;
seguridad;
bssid;
frecuencia;
potencia;
254
public String getSeguridad(){
return this.seguridad;
}
public String getBSSID(){
return this.bssid;
}
public String getFrecuencia(){
return this.frecuencia;
}
public String getPotencia(){
return this.potencia;
}
}
Como puedes observar en el cdigo anterior, esta clase contiene todos los atributos y mtodos
necesarios para obtener la informacin mencionada de una red inalmbrica.
La interfaz de usuario de la aplicacin se compone de dos Actividades: la Actividad principal
contiene un botn que, al pulsarlo, realiza una bsqueda de redes WIFI y muestra el resultado
en un listado ListView en la parte inferior de esta Actividad. Si el usuario hace clic en una red
del listado, puede visualizar la informacin detallada de sta al abrir una segunda Actividad.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height= fill_parent
android:background=#FFFFFF>
<Button android:id=@+id/boton
android:text=Buscar redes WIFI...
android:layout_width=260dip
android:layout_height=60dip
android:textSize=20dp
android:layout_marginTop=6dip
android:layout_gravity=center_horizontal/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView
android:id=@+id/listView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:background=#FFFFFF/>
</LinearLayout>
Una vez expuesto el sencillo diseo de la Actividad, veamos la lgica de sta en el fichero
MainActivity.java:
public class MainActivity extends Activity implements OnClickListener,
OnItemClickListener {
// Matriz del tipo Red donde vamos a guardar las redes detectadas
public static ArrayList<Red> redes = new ArrayList<Red>();
private Button boton;
// Lista de redes y su adaptador
public static ListView listaredes;
public static AdapterElements adaptador;
// En la creacin de la Actividad buscamos el ListView
// y le asignamos su adaptador correspondiente
@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);
this.boton = (Button)findViewById(R. id.boton);
this.boton.setOnClickListener(this);
adaptador = new AdapterElements(this);
listaredes = (ListView)findViewById (R.id.listView) ;
listaredes.setAdapter(adaptador);
listaredes.setOnItemClickListener(this);
}
// Si el usuario hace clic en el botn entonces
// arrancamos una tarea asncrona para buscar las
// redes WIFI
@Override
public void onClick(View v) {
switch (v.getId()){
case (R.id.boton):
255
Aula Mentor
@SuppressWarnings(unused)
AsyncTask<?, ?, ?> task = new ProgressTask(this).execute();
break;
}
}
256
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
de objetos de clase Red que permite almacenar todas las redes inalmbricas encontradas en el
escner de redes WIFI.
A continuacin, se muestra cmo se implementa el escner de redes WIFI mediante la clase
ProgressTask:
// Clase que inicia una tarea asncrona para buscar las redes WIFI
public class ProgressTask extends AsyncTask<String, Void, Boolean> {
// Dilogo de progreso
private ProgressDialog dialog;
private Context context;
// Gestor de Android de redes WIFI
private WifiManager manWifi;
// Lista donde guardamos las redes encontradas
private List<ScanResult> wifiList;
// Constructor de la clase
public ProgressTask(Context c){
this.context = c;
dialog = new ProgressDialog(context);
}
// Antes de ejecutar la tarea verificamos si la interfaz WIFI est
// activa
protected void onPreExecute() {
// Buscamos el servicio WIFI
this.manWifi = (WifiManager)this.context.getSystemService
(Context.WIFI_SERVICE);
// Si no esta activo mostramos un mensaje y no seguimos adelante
if (!this.manWifi.isWifiEnabled()){
Toast.makeText(context, ERROR: el dispositivo no dispone
de interfaz WIFI o sta no se encuentra activada.,
Toast.LENGTH_LONG).show();
} else
// Si no, mostramos el dilogo de progreso
{
this.dialog.setMessage(Escaneando redes WiFi...);
this.dialog.show();
}
}
// Al acabar ocultamos el dilogo de progreso y actualizamos el
// adaptador de la Actividad principal
@Override
protected void onPostExecute (
final Boolean success) {
if (dialog.isShowing()) {
dialog.dismiss();
}
if (success) {
MainActivity.adaptador.notifyDataSetChanged();
}
}
// Sentencias que se ejecutan en segundo plano
protected Boolean doInBackground (final String... args) {
257
Aula Mentor
258
En el cdigo anterior se pueden observar los tres estados por los que pasa la clase ProgressTask:
onPreExecute, doInBackground y onPostExecute. La bsqueda de redes se realiza en
segundo plano mediante el mtodo dolnBackground y, mientras dura la ejecucin de dicho
escner, se muestra al usuario una alerta del tipo Dialog que le informa de que se est
realizando una bsqueda de redes WIFI.
La bsqueda de redes se realiza mediante la clase WifiManager usando su mtodo
startScan(). Posteriormente, utilizamos el mtodo getScanResult() para obtener una lista de objetos del tipo ScanResult que contiene la informacin de cada red WIFI. Por ltimo,
recorremos dicha lista procesando cada objeto ScanResult y obteniendo los datos necesarios
para el presente ejemplo.
Una vez terminada la lgica del mtodo doInBackground, se finaliza la ejecucin de la
clase ProgressTask mediante el mtodo onPostExecute() liberando el Dialog y refrescando
los datos del adaptador de la Actividad principal.
Esta clase principal implementa la interface OnItemClickListener que indica las
sentencias que se deben ejecutar cuando el usuario hace clic en una opcin de la lista.
En este bloque de cdigo obtenemos la informacin de la red WIFI pulsada y la enviamos a otra
Actividad que se encarga de mostrar este detalle. Como sabes, el envo de datos o parmetros
entre Actividades se realiza a travs del mtodo putExtra() de la clase Intent.
El diseo layout de la Actividad del fichero activity_info_red.xml muestra los detalles de la
red WIFI seleccionada. Es el siguiente:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent
android:background=#FFFFFF
android:gravity=left
android:orientation=vertical
android:padding=10dp >
<TextView
android:id=@+id/SSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=25sp
android:textColor=#000000/>
<TextView
android:id=@+id/SeguridadTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Seguridad:
android:textColor=#000000/>
<TextView
android:id=@+id/BSSIDTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=BSSID:
android:textColor=#000000/>
<TextView
android:id=@+id/FrecuenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Frecuencia:
android:textColor=#000000/>
<TextView
android:id=@+id/PotenciaTV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:textSize=20sp
android:text=Potencia:
android:textColor=#000000/>
</LinearLayout>
El diseo de esta Actividad consta de cinco Vistas del tipo TextView que se encargan de
mostrar la informacin enviada por la Actividad principal de la aplicacin. Veamos la lgica de
esta Actividad secundaria llamada InfoRed:
public class InfoRed extends Activity {
private TextView tv_ssid;
private TextView tv_seguridad;
private TextView tv_bssid;
private TextView tv_frecuencia;
private TextView tv_potencia;
// OnCreate de la Actividad
public void onCreate(Bundle savedlnstanceState){
super.onCreate(savedlnstanceState);
// Asignamos el layout y buscamos sus Vistas
setContentView(R.layout.activity_info_red);
this.tv_ssid =(TextView)findViewById(R.id.SSIDTV);
this.tv_seguridad = (TextView)findViewById(R.id.SeguridadTV) ;
this.tv_bssid = (TextView)findViewById(R.id.BSSIDTV) ;
259
Aula Mentor
this.tv_frecuencia = (TextView)findViewById(R.id.FrecuenciaTV) ;
this.tv_potencia = (TextView)findViewById(R.id.PotenciaTV) ;
Bundle extras = getIntent().getExtras();
if (extras != null){
String ssid = extras.getString(SSID);
String security = extras.getString(SEGURIDAD);
String bssid = extras.getString(BSSID);
String frec = extras.getString(FRECUENCIA);
String power = extras.getString(POTENCIA);
this.tv_ssid.append(ssid);
this.tv_seguridad.append(security);
this.tv_bssid.append(bssid);
this.tv_frecuencia.append(frec);
this.tv_potencia.append(power);
}
} // end OnCreate
}
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso a la
interfaz WIFI del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las
siguientes etiquetas:
<uses-permission android:name=android.permission.ACCESS_WIFI_STATE/>
<uses-permission android:name=android.permission.CHANGE_WIFI_STATE/>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 3 (WIFI) de la Unidad 3. Estudia el cdigo fuente y ejectalo en un dispositivo Android para ver el resultado
del programa anterior, en el que hemos utilizado su interfaz WIFI.
Desgraciadamente, el AVD del SDK de Android no incluye la interfaz WIFI, ni siquiera mediante
el Simulador de Sensores. Por lo tanto, es necesario probar esta aplicacin en un dispositivo
real de Android.
Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
261
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Aula Mentor
Las clases ms importantes (no incluimos todas) de la API Bluetooth de Android son:
- BluetoothAdapter: permite realizar todo tipo de operaciones sobre este mdulo, como
buscar dispositivos bluetooth con el mtodo startDiscovery() y obtener informacin
de stos.
- BluetoothDevice: representa un dispositivo bluetooth remoto con sus atributos y propiedades correspondientes.
- BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin de datos.
La diferencia principal con el mdulo anterior WIFI est en que en este mdulo Bluetooth
es necesario definir un receptor de mensajes del tipo BroadcastReceiver para recibir
asncronamente los dispositivos encontrados mediante mensajes del BluetoothAdapter.
En este apartado, se pretende mostrar al alumno de forma prctica cmo buscar dispositivos Bluetooth en el sistema operativo Android. Es recomendable abrir el Ejemplo 4 de esta
Unidad para seguir la explicacin siguiente.
Hemos desarrollado este Ejemplo 4 con la misma estructura que el anterior Ejemplo 3. Por
esto, no mostraremos el cdigo fuente completo del mismo ya que se repiten algunas partes del
cdigo. Slo estudiaremos la parte nueva.
Podemos encontrar las diferencias en la clase principal de la aplicacin MainActivity, que
tiene este contenido:
262
getMajorDeviceClass()), device.getBondState() !=
BluetoothDevice.BOND_BONDED ? DISPONIBLE :
VINCULADO));
}
} else
// Si hemos acabado la bsqueda ocultamos el dilogo y
// refrescamos el adaptador del ListView
if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
{
if (dialog.isShowing()) {
dialog.dismiss();
}
adaptador.notifyDataSetChanged();
}
} // end onReceive
// Mtodo que traduce el tipo de dispositivo Bluetooth
private String getBTMajorDeviceClass(int major){
switch(major){
case BluetoothClass.Device.Major.AUDIO_VIDEO:
return AUDIO_VIDEO;
case BluetoothClass.Device.Major.COMPUTER:
return ORDENADOR;
case BluetoothClass.Device.Major.HEALTH:
return SALUD;
case BluetoothClass.Device.Major.IMAGING:
return FOTOGRAFA;
case BluetoothClass.Device.Major.MISC:
return VARIOS;
case BluetoothClass.Device.Major.NETWORKING:
return REDES;
case BluetoothClass.Device.Major.PERIPHERAL:
return PERIFRICO;
case BluetoothClass.Device.Major.PHONE:
return TELFONO;
case BluetoothClass.Device.Major.TOY:
return JUGUETE;
case BluetoothClass.Device.Major.UNCATEGORIZED:
return SIN CATEGORA;
case BluetoothClass.Device.Major.WEARABLE:
return PORTABLE;
default: return DESCONOCIDO;
}
}
}; //fin de la creacin de blueReceiver
// En la creacin de la Actividad buscamos el ListView
// y le asignamos su adaptador correspondiente
@Override
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
// La orientacin es siempre vertical
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_POR
TRAIT);
setContentView(R.layout.activity_main);
263
Aula Mentor
Button boton = (Button)findViewById(R.id.boton);
boton.setOnClickListener(this);
dialog = new ProgressDialog(this);
adaptador = new AdapterElements(this);
listadispositivos = (ListView)findViewById(R.id.listView) ;
listadispositivos.setAdapter(adaptador);
listadispositivos.setOnItemClickListener(this);
}
// Si el usuario hace clic en el botn entonces
// arrancamos las sentencias de bsqueda de
// dispositivos Bluetooth
264
@Override
public void onClick(View v) {
switch (v.getId()) {
case (R.id.boton):
// Buscamos el adaptador Bluetooth
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Si no existe mostramos un mensaje de error
if (mBtAdapter == null) {
Toast.makeText(this, ERROR: dispositivo Bluetooth no
est disponible., Toast.LENGTH_LONG).show();
return;
}
// Si existe el dispositivo pero no est activo solicitamos
// al sistema mediante un Intent que lo active
if (!mBtAdapter.isEnabled()) {
Intent enableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) ;
this.startActivity(enableIntent);
// Nota: podemos ejecutar el Intent anterior mediante
// la orden startActivityForResult y esperar a la
// vuelta de ste para seguir con el descubrimiento
// de los dispositivos.
return;
}
// Mostramos una ventana de progreso
this.dialog.setMessage(Buscando dispositivos
Bluetooth...);
this.dialog.show();
// Limpiamos la matriz de resultados
dispositivos.clear();
// Registramos 2 Intent con un filtro para recepcin de
// eventos cuando se encuentra Intent un dispositivo
// Bluetooth o se acaba la bsqueda
IntentFilter filter = new
IntentFilter(BluetoothDevice.ACTION_FOUND);
// Indicamos el mtodo callback bluereceiver que se
// ejecutar cada vez que se encuentre un dispositivo
this.registerReceiver(blueReceiver, filter);
filter = new
IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
// Indicamos el mtodo callback bluereceiver que se
// ejecutar una vez acabe la bsqueda de dispositivos
this.registerReceiver(blueReceiver, filter);
mBtAdapter.startDiscovery();
break;
}
}
// Evento onClick del listado
@Override
public void onItemClick(AdapterView<?> arg0, View view, int index,
long arg3){
// Obtenemos la informacin del dispositivo
BTooth bluetooth = MainActivity.dispositivos.get(index);
String nombre = bluetooth.getNombre();
String address = bluetooth.getAddress();
String tipo = bluetooth.getTipo();
String vinculado = bluetooth.getVinculado();
// Creamos un Intent nuevo en el que pasamos como parmetros
// la informacin del dispositivo y arranca la Actividad
//InfoBluetooth
Intent i_btinfo = new Intent(this,InfoBluetooth.class);
i_btinfo.putExtra(NOMBRE, nombre);
i_btinfo.putExtra(ADDRESS, address);
i_btinfo.putExtra(TIPO, tipo);
i_btinfo.putExtra(VINCULADO, vinculado);
startActivity(i_btinfo);
}
// Clase que define el adaptador del listado con los mtodos
// habituales
class AdapterElements extends ArrayAdapter<BTooth> {
Activity context;
public AdapterElements(Activity context) {
super(context, R.layout.elementoitems,
MainActivity.dispositivos);
this.context = context;
}
Como podemos ver en el cdigo anterior, para realizar una bsqueda de dispositivos Bluetooth
debemos comprobar que el mdulo Bluetooth est presente en el dispositivo en cuestin.
El mdulo Bluetooth es instanciado a travs del mtodo getDefaultAdapter() de la clase
BluetoothAdapter.
Si dicha instancia devuelve un valor null, significa que el mdulo Bluetooth no est
disponible en el dispositivo Android. Sin embargo, si la instancia devuelve un valor distinto de
265
Aula Mentor
266
null, pero su mtodo isEnabled() devuelve un valor falso, revela que el mdulo Bluetooth
est apagado.
En el caso de que el mdulo Bluetooth se encuentre apagado, es posible encenderlo automticamente creando un Intent del sistema operativo mediante la constante BluetoothAdapter.ACTION_REQUESTENABLE.
Una vez inicializado el mdulo Bluetooth, la aplicacin debe registrar en el sistema que
desea recibir mensajes de los eventos ACTIONFOUND y ACTION_DISCOVERY_FINISHED del sistema operativo. Estos mensajes indican que se ha encontrado un dispositivo Bluetooth durante la
bsqueda y que ha finalizado la bsqueda respectivamente.
Para poder recibir dichos mensajes hay que definir un receptor de mensaje del sistema
de la clase BroadcastReceiver que se encarga de esta tarea.
Adems, debemos llamar al mtodo startDiscovery() de la clase BluetoothAdapter
para iniciar la bsqueda de dispositivos.
Todas estas operaciones anteriores se han implementado en el evento onClick del botn
de la aplicacin.
Mediante un objeto de tipo BroadcastReceiver llamado blueReceiver se filtran los
mensajes recibidos a travs del parmetro arg1 que es un Intent.
Mediante el mtodo getAction() de este Intent podemos obtener el tipo de mensaje (ACTION) recogido y ejecutar el bloque de sentencias que corresponda. Por ejemplo, en el
caso de recibir el mensaje de que se ha encontrado un dispositivo Bluetooth usamos el mtodo
getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) de este Intent para obtener sus
caractersticas.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso a la interfaz WIFI del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.
xml las siguientes etiquetas:
<uses-permission android:name=android.permission.BLUETOOTH/>
<uses-permission android:name=android.permission.BLUETOOTH_ADMIN/>
Si ejecutas en Eclipse ADT este Ejemplo 4 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
267
Si ejecutas esta aplicacin en tu dispositivo fsico Android, vers que en el listado nicamente
aparecen los dispositivos que no estn vinculados a l.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Aula Mentor
268
} catch (IOException e) {
e.printStackTrace();
}
super.onActivityResult(requestCode, resultCode, data);
Como puedes ver en el cdigo anterior, basta con utilizar una Intencin con estas caractersticas:
Tipo: image/*
Accin: ACTION_GET_CONTENT
Categora: CATEGORY_OPENABLE
De esta forma podemos ejecutar la aplicacin interna del dispositivo Android y, posteriormente,
esperar el evento onActivityResult(), para capturar el resultado, que es una imagen.
Puedes incluir este cdigo en tu propia aplicacin cuando desees integrar la captura de
fotos en tus aplicaciones.
269
Aula Mentor
<FrameLayout android:id=@+id/preview
android:layout_weight=1
android:layout_width=fill_parent
android:layout_height=fill_parent>
</FrameLayout>
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=center_horizontal >
270
<Button
android:id=@+id/boton_disparador
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginTop=6dip
android:layout_weight=1
android:text=Disparador />
<Button
android:id=@+id/boton_galeria
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_marginTop=6dip
android:layout_weight=1
android:text=Ver Galera />
</LinearLayout>
</LinearLayout>
En el diseo anterior hemos incluido el elemento FrameLayout, que sirve como contenedor
de la previsualizacin de la cmara de fotos o de la imagen seleccionada por el usuario en la
galera.
Si ahora abrimos la clase principal de la aplicacin MainActivity, vemos el siguiente contenido:
public class MainActivity extends Activity {
// Constantes que se usan en la aplicacin
public final static String DEBUG_TAG = CAMARA_FOTOS;
private static int SELECT_PICTURE = 1;
// Botn que se usa de disparador de la cmara
public Button boton_disparador;
// Objeto que sive de interfaz con la cmara
public static Camera camara;
// Objeto de previsualizacin de la cmara de fotos
Preview preview;
// Id de la cmara seleccionada del dispositivo
public static int cameraId = 0;
// Evento onCreate de la Actividad
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el botn disparador y asociamos su evento onClick
boton_disparador = (Button)findViewById(R. id.boton_disparador);
boton_disparador.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Si la aplicacin est visualizando una imagen entonces...
if (boton_disparador.getText().equals(Ver previsualizacin))
{
// Volvemos a modo previsualizacin de la cmara
boton_disparador.setText(Disparador);
// Intercambiamos en el frame el objeto de previsualizacin
((FrameLayout)findViewById(R.id.preview)).removeAllViews();
((FrameLayout)findViewById(R.id.preview)).addView(preview);
return;
}
// Si no se encuentra la cmara entonces no podemos sacar
// fotos
if (cameraId < 0) {
Toast.makeText(getBaseContext(), ERROR: no se encuentra
la cmara de fotos en este dispositivo.,
Toast.LENGTH_LONG).show();
} else {
// Si encontramos la cmara entonces paramos la
// previsualizacin, tomamos la foto y volvemos a
// previsualizar
camara.stopPreview();
// Tomamos la foto indicando el mtodo callback
// PictureCallback en la clase FotoHandler para que
// trate la foto una vez se ha sacado.
camara.takePicture(null, null, new
FotoHandler(getApplicationContext()));
camara.startPreview();
}
}
}); // end onClick
// Botn galera
Button boton_galeria = (Button)findViewById(R. id.boton_galeria);
// Evento onClick del botn galera
boton_galeria.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Definimos un Intent del tipo siguiente
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.
INTERNAL_CONTENT_URI);
// Iniciamos la Actividad con este Intent indicando que
// queremos seleccionar una imagen
startActivityForResult(intent, SELECT_PICTURE);
}
});
// A continuacin, vamos a comprobar si el dispositivo tiene cmara
// de fotos
if (!getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Toast.makeText(this, ERROR: este dispositivo no tiene cmara de
fotos., Toast.LENGTH_LONG).show();
} else {
271
Aula Mentor
272
}
// Mtodo que busca la cmara principal
private int encuentraCamaraPrincipal(){
cameraId=0;
// Obtenemos el n total de cmaras del dispositivo
int numberOfCameras = Camera.getNumberOfCameras();
// Recorremos todas las cmaras
for (int i = 0; i < numberOfCameras; i++) {
// Obtenemos la informacin de la cmara i
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(i, info);
// Si es la cmara de atrs indicamos que hemos acabado
if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
Log.d(DEBUG_TAG, Cmara encontrada!);
cameraId = i;
break;
}
}
return cameraId;
}
// Evento que ocurre cuando la Actividad pasa a segundo plano
@Override
protected void onPause() {
// Es muy importante parar la previsualizacin para
// no consumir recursos del sistema como CPU
if (camara != null) {
camara.stopPreview();
}
super.onPause();
}
// Evento que se lanza cuando se destruye la Actividad
@Override
protected void onDestroy(){
// Paramos la previsualizacin
camara.setPreviewCallback(null);
preview = null;
// Liberamos el objeto cmara
camara.release();
camara = null;
super.onDestroy();
}
} // end clase
En el cdigo anterior puedes ver que fijamos una constante para identificar la accin realizada
(seleccionar imagen de la galera). Adems, definimos las variables de la clase Camera de Android para gestionar la cmara y Preview de previsualizacin de la cmara definida por esta
aplicacin y que estudiaremos ms adelante.
En el evento onCreate() de esta Actividad hemos incluido las sentencias necesarias
para asignar a los dos botones las operaciones que deben realizar cuando un usuario haga clic
sobre ellos.
En el caso del botn Disparador se aplica la siguiente lgica: si el FrameLayout no est
mostrando la previsualizacin, la incluimos para que el usuario pueda sacar una foto. Si no
ocurre esto anterior y existe una cmara, entonces tomamos una fotografa mediante el mtodo
273
Aula Mentor
takePicture() clase Camera donde pasamos como parmetro un mtodo callback del tipo
PictureCallback que hemos implementado en la clase FotoHandler de esta aplicacin, para que
almacene la foto una vez se ha tomado.
Para la funcionalidad del botn Ver Galera hemos utilizado un Intent del tipo MediaStore.ACTION_IMAGE_CAPTURE y lo hemos ejecutado mediante la orden startActivityForResult() para recibir la imagen seleccionada por el usuario en el mtodo onActivityResult(). En este ltimo mtodo recibimos el URI de la imagen y construimos un Bitmap a
partir de un stream de Bytes para cargarlo en una Vista de tipo imagen y la asignamos al frame
para mostrarla al usuario parando la previsualizacin de la cmara de fotos con la sentencia
camara.stopPreview().
Adems de definir las rdenes que deben ejecutar los botones, hemos insertado la sentencia
siguiente que valida si el dispositivo tiene cmara de fotos:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
Si existe una cmara, entonces buscamos identificarla mediante el cameraId para su uso
posterior mediante el mtodo local encuentraCamaraPrincipal(). Este mtodo obtiene el
nmero total de cmaras del dispositivo con el mtodo Camera.getNumberOfCameras() y, a
travs de un bucle, la clase CameraInfo extrae las caractersticas de cada cmara buscando la
cmara principal trasera que se define con la constante CAMERA_FACING_BACK.
274
Al final del cdigo de esta clase MainActivity hemos definido los mtodos siguientes del
ciclo de vida de la actividad:
- onPause(): cuando la Actividad pasa al segundo plano, debemos parar la previsualizacin
de la cmara con la orden stopPreview(), ya que consume recursos de sistema operativo.
- onResume(): en el caso de que sea necesario, se debe volver a iniciar la previsualizacin de
la cmara con startPreview() cuando la Actividad vuelva al primer plano.
- onDestroy(): si la Actividad se destruye, debemos eliminar el mtodo callback de la previsualizacin con camara.setPreviewCallback(null) y, a continuacin, liberar la cmara
con camara.release().
Veamos ahora el contenido de la clase Preview de la aplicacin que implementa la previsualizacin de la cmara:
class Preview extends SurfaceView implements SurfaceHolder.Callback {
// Superficie que muestra la previsualizacin
SurfaceHolder mHolder;
Preview(Context context) {
super(context);
// Obtenemos el contenedor del SurfaceView y le asignamos
// un mtodo para detectar cundo se crea y se destruye esta
// superficie
mHolder = getHolder();
mHolder.addCallback(this);
275
Aula Mentor
276
// Guardamos la foto
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
Toast.makeText(contexto, Se ha guardado la imagen: +
fotoFile, Toast.LENGTH_LONG).show();
} catch (Exception error) {
Log.d(MainActivity.DEBUG_TAG, File + filename + no
guardada: + error.getMessage());
Toast.makeText(contexto, ERROR: no se puede guardar la
imagen., Toast.LENGTH_LONG).show();
}
// Iniciamos la previsualizacin de la cmara
camera.startPreview();
}
// Mtodo que obtiene el directorio PICTURES del sistema
private File getPicDir() {
File sdDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
return new File(sdDir, CursoMentor_Unidad3_Ejemplo5);
}
} // end clase
Cuando tomamos una fotografa, hay que invocar el mtodo takePicture() de la clase Camera, donde debemos indicar como parmetro un mtodo callback del tipo PictureCallback
que hemos implementado en la clase anterior FotoHandler. Este mtodo se encarga de guardar la foto una vez se ha tomado.
Puedes observar que la clase implementa el mtodo onPictureTaken, que es el que se
encarga de almacenar la imagen en la tarjeta SD del dispositivo Android. Como parmetro de
este mtodo usamos la matriz de bytes data que contiene la informacin de la foto tomada. En
este bloque de cdigo seguimos los pasos habituales para almacenar un fichero de tipo binario
en la tarjeta SD del dispositivo; en concreto, lo guardamos en el directorio de fotos Environment.DIRECTORY_PICTURES del sistema operativo.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a la cmara y tarjeta SD del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.CAMERA/>
<uses-permission
android:name=android.permission.WRITE_EXTERNAL_STORAGE/>
Adems, recordamos que para poder depurar la aplicacin en un dispositivo real de Android,
es necesario indicarlo en el archivo manifest en la etiqueta <application> mediante el
atributo android:debuggable=true.
Es sencillo mejorar esta aplicacin incluyendo funcionalidades nuevas, como la grabacin de
vdeo. En la Gua oficial de Cmara de Android puedes encontrar ms informacin, as como
ejemplos descriptivos.
277
Aula Mentor
En este ejemplo, s podemos utilizar el AVD del SDK de Android configurndolo correctamente.
Para ello, debemos editar el dispositivo virtual y elegir en la cmara trasera (Back Camera) la
opcin Emulated para que simule una cmara (como hemos hecho en las capturas que aparecen despus) o Webcam0 para utilizar la Webcam de tu ordenador como cmara de fotos:
278
Si ejecutas en Eclipse ADT este Ejemplo 5 en el AVD, vers que se muestra la siguiente aplicacin:
Si tomas una foto y, a continuacin, pulsas el botn Ver Galera, vers que no aparece ninguna foto en sta:
279
Esto se debe a que el AVD, por motivos de rendimiento, no busca automticamente nuevos
contenidos multimedia en la tarjeta SD. Para forzar esta bsqueda debemos hacer clic en la
aplicacin Dev Tools que aparece en el escritorio de aplicaciones de Android del AVD:
Aula Mentor
Despus, debemos seleccionar la opcin Media Provider y hacer clic en el botn Scan SD
card para que el sistema busque nuevos contenidos multimedia en la tarjeta SD:
Si ahora haces clic en el botn Galera, vers que ya puedes seleccionar esa imagen correctamente:
280
En lugar de tener que lanzar manualmente el escaneo de nuevos archivos multimedia, es posible implementar en esta aplicacin un mensaje de tipo Broadcast
con el Intent correspondiente que actualice la librera multimedia del sistema
operativo. En el Ejemplo 1 de la Unidad 1 lo hemos hecho en el mtodo addRecordingToMediaLibrary().
281
Aula Mentor
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:gravity=center_vertical
android:text=Mejor provider de localizacin GPS: />
282
<TextView android:id=@+id/lblMejorProvider
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosicion
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Posicin Actual: />
<TextView android:id=@+id/lblPosLatitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosLongitud
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView android:id=@+id/lblPosPrecision
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Estado del GPS: />
<TextView android:id=@+id/lblEstado
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:background=#aaaaaa
android:textColor=#000000 />
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_margin=5dip
android:text=Listado de providers: />
<ListView
android:id=@+id/listView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:background=#FFFFFF/>
</LinearLayout>
Como puedes ver, la interfaz del usuario es sencilla. Se incluyen dos botones que permiten
Activar/Actualizar y Desactivar la localizacin, etiquetas para mostrar los datos al usuario y un
listado de tipo ListView para mostrar los mdulos de localizacin disponibles.
Si ahora abrimos la clase principal de la aplicacin MainActivity vemos el siguiente contenido:
public class MainActivity extends Activity {
// Definimos el gestor de localizaciones
private LocationManager locManager;
//Listener para recibir cambios de localizacin
private LocationListener locListener;
// Matriz del tipo Red donde vamos a guardar los LocationProvider
// detectados
public static ArrayList<LocationProvider> locProviderList = new
ArrayList<LocationProvider>();
// Lista para mostrar los providers de localizacin y su adaptador
public static ListView listaProviders;
public static AdapterElements adaptador;
// Etiquetas y botones de la aplicacin
private TextView lblMejorProvider;
private Button btnActivar;
private Button btnDesactivar;
private TextView lblLatitud;
private TextView lblLongitud;
private TextView lblPrecision;
private TextView lblEstado;
// Evento onCreate de la Actividad
public void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la actividad
lblMejorProvider = (TextView)findViewById(R.id.lblMejorProvider);
btnActivar = (Button)findViewById(R.id.botonActivar);
btnDesactivar = (Button)findViewById(R.id.botonDesactivar);
lblLatitud = (TextView)findViewById(R.id.lblPosLatitud);
lblLongitud = (TextView)findViewById(R.id.lblPosLongitud);
lblPrecision = (TextView)findViewById(R.id.lblPosPrecision);
283
Aula Mentor
lblEstado = (TextView)findViewById(R.id.lblEstado);
btnDesactivar.setEnabled(false);
// Definimos el adaptador del listado de LocationProviders
adaptador = new AdapterElements(this);
listaProviders = (ListView)findViewById(R.id.listView) ;
listaProviders.setAdapter(adaptador);
// Mostramos los datos en blanco
muestraPosicion(null);
// Definimos lo que se debe hacer si el usuario hace clic en los
// botones
btnActivar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Activamos el botn Desactivar
btnDesactivar.setEnabled(true);
// Si ya conocemos el LocationProvider mostramos la
// posicin ltima conocida
if (btnActivar.getText().equals(Actualizar datos))
muestraPosicion(locManager.getLastKnownLocation(
LocationManager.GPS_PROVIDER));
// Si no, buscamos el LocationProvider con este mtodo
else obtenerGPS();
btnActivar.setText(Actualizar datos);
}
});
284
285
Aula Mentor
if(loc != null)
{
lblLatitud.setText(Latitud: +
String.valueOf(loc.getLatitude()));
lblLongitud.setText(Longitud: +
String.valueOf(loc.getLongitude()));
lblPrecision.setText(Precision: +
String.valueOf(loc.getAccuracy()));
Log.i(Localizacin Android,
String.valueOf(loc.getLatitude() + - +
String.valueOf(loc.getLongitude())));
}
else
{
lblLatitud.setText(Latitud: (sin_datos));
lblLongitud.setText(Longitud: (sin_datos));
lblPrecision.setText(Precision: (sin_datos));
lblMejorProvider.setText();
lblEstado.setText();
}
}
286
En el mtodo obtenerGPS() podemos ver que, para obtener la posicin GPS del dispositivo,
lo primero que debemos hacer es conseguir, mediante el mtodo getSystemService(), una
instancia del gestor GPS utilizando la clase LocationManager, en la que se basa la API de
localizacin de Android, del mvil utilizando.
Una vez tenemos una instancia del gestor de GPS, vamos a obtener qu mtodos de localizacin
(Proveedor de localizacin o, en ingls, LocationProvider) posee el dispositivo; as conocemos
sus mdulos GPS disponibles. Como ya hemos comentado, los ms comunes son el GPS y la
localizacin mediante la red de telefona.
Para conocer los proveedores de localizacin disponibles en el dispositivo podemos usar
el mtodo getAllProviders() de la clase LocationManager.
Una vez obtenida la lista completa de proveedores disponibles, accedemos a las propiedades de ellos (precisin, consumo de batera, si puede obtener la altitud, la velocidad, etctera). Podemos obtener una referencia al provider mediante su nombre empleando el mtodo
getProvider(nombre) y, posteriormente, los mtodos disponibles para conocer sus propiedades.
Por ejemplo, para conocer la precisin podemos utilizar getAccuracy(), que devuelve
las constantes Criteria.ACCURACY_FINE para precisin alta y Criteria.ACCURACY_COARSE
para precisin media. El mtodo supportsAltitude() indica si el mdulo proporciona la altitud y el mtodo getPowerRequirement() indica el nivel de consumo de recursos del proveedor (batera). Puedes ver la lista completa de los mtodos con las caractersticas de un proveedor
en el enlace siguiente LocationProvider.
Atencin: el mtodo getAllProviders() devuelve todos los proveedores de localizacin disponibles en el dispositivo, incluso si stos no son accesibles por la
aplicacin (permisos) o no estn activados. En estos casos, la informacin no ser
accesible.
287
Aula Mentor
288
Hemos visto que podemos buscar dinmicamente los proveedores de localizacin segn un
determinado criterio de precisin. Sin embargo, normalmente una aplicacin est diseada para
utilizar un proveedor en concreto, como, por ejemplo, el mdulo GPS y, por lo tanto necesitamos
conocer si ste est activado o no en el dispositivo.
Para esto, la clase LocationManager dispone del mtodo isProviderEnabled(). Debemos pasarle como parmetro el tipo de provider que queremos consultar. En el cdigo
fuente anterior comprobamos si el mtodo seleccionado est activo, mostrando al usuario un
mensaje.
Una vez conocemos el proveedor de localizacin, vamos a definir un listener que monitoriza
el estado del GPS del dispositivo. Este listener debe implementarse a partir de la clase
LocationListener aadiendo los siguientes mtodos:
- onLocationChanged: este mtodo se lanza cada vez el sistema operativo refresca la posicin y la velocidad del dispositivo.
- onProviderEnabled: este mtodo se ejecuta al activarse el proveedor de localizacin.
- onProviderDisabled: este mtodo se lanza al desactivarse el proveedor.
- onStatusChanged: este mtodo se ejecuta cuando cambia el estado del proveedor. Puede
devolver las constantes siguientes: OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE, AVAILABLE.
Despus de definir el listener, debemos indicar al gestor LocationManager este listener
mediante el mtodo requestLocationUpdates() y, adems, establecer el tiempo mnimo en
milisegundos (15 segundos en el Ejemplo del curso) y la distancia mnima recorrida en la que el
listener desea obtener actualizaciones del proveedor de localizacin. Si estos parmetros son
puestos a cero, el listener recibir las actualizaciones de localizacin tan rpido como estn
disponibles por el proveedor de localizaciones.
Una vez que conocemos el proveedor de localizacin y est activado, podemos obtener
la localizacin actual. Para ello, debemos utilizar el mtodo getLastKnownLocation(String
provider) de la clase LocationManager que nos facilita la ltima posicin conocida del dispositivo de un provider determinado.
Este mtodo NO devuelve la posicin actual ni solicita una nueva posicin al proveedor de localizacin, sino que se limita a indicar la ltima posicin obtenida. Por
lo tanto, es recomendable esperar a recibir la primera actualizacin a travs del
LocationListener y no leer los datos mediante este mtodo.
En el mtodo muestraPosicion() vamos mostrar los distintos datos de la posicin en las Vistas
de la interfaz de usuario, utilizando para ello los mtodos siguientes proporcionados por la
clase Location: getLatitude(), getAltitude() y getAccuracy() para obtener la latitud,
longitud y precisin respectivamente. Adems, esta clase dispone de otros mtodos para obtener
la altura, orientacin, velocidad, etctera.
Finalmente, para poder ejecutar esta aplicacin es necesario que tenga permisos de acceso al
mdulo GPS del dispositivo. Para ello, hay que incluir en el fichero AndroidManifest.xml las
siguientes etiquetas:
<uses-permission android:name=android.permission.INTERNET />
<uses-permission android:name=android.permission.ACCESS_FINE_LOCATION />
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (GPS) de la Unidad 3. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa
anterior, en el que hemos utilizado su Mdulo GPS.
289
Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente aplicacin:
Aula Mentor
Para que funcione la deteccin por GPS en el AVD debemos configurar las opciones de
Localizacin abriendo sus Ajustes y configurando lo siguiente:
290
Una vez cargado el archivo, debajo aparecen cuatro botones (de izquierda a derecha) que
permiten las operaciones siguientes:
- Avanzar automticamente por el listado enviando la coordenada al AVD.
- Ir a la posicin anterior del listado de forma manual.
- Ir a la posicin siguiente del listado de forma manual.
- Establecer la velocidad de cambio automtico.
Aula Mentor
A continuacin, puedes probar este listado en la aplicacin del Ejemplo 6 de esta Unidad
ejecutndolo en el AVD y pulsando el botn Activar localizacin para comenzar a detectar
cambios de posicin. Despus, pulsa el botn de avance automtico (botn verde).
Vers que la interfaz de la aplicacin se actualiza varias veces con nuevos valores de latitud y longitud cada poco tiempo. Sin embargo, con el mtodo requestLocationUpdates()hemos
definido que se muestren actualizaciones de posicin cada 15 segundos.
Si abres la Vista de LogCat de Eclipse ADT, advertirs que se han generado los mensajes de
depuracin siguientes (en tu caso deben ser muy parecidos):
06-16 21:48:34.058: I/Localizacin Android(2195): 40.46832333333334 -4.480451666666666
06-16 21:48:49.067: I/Localizacin Android(2195): 40.46773333333333 -4.482106666666667
06-16 21:49:04.088: I/Localizacin Android(2195): 40.46654 -4.483746666666667
06-16 21:49:19.098: I/Localizacin Android(2195): 40.46583833333333 -4.4860066666666665
06-16 21:49:34.108: I/Localizacin Android(2195): 40.46543 - -4.488525
292
Si observamos la fecha y hora vemos que el tiempo entre nueva posicin no coincide con los
15 segundos que ha definido el programador. Sin embargo, Android agrupada varias medidas
dentro de los 15 segundos indicados.
Definir una frecuencia entre actualizaciones de 15 segundos indica al sistema operativo que, al menos, la aplicacin necesita recibir una nueva posicin en ese periodo de tiempo; si bien el sistema puede dar varias posiciones adicionales si tiene
recursos disponibles dentro de ese periodo temporal mximo.
Adems, si investigas en el log de mensajes de Eclipse ADT, observars que cambia el estado
del proveedor a 1 (TEMPORARILY_UNAVAILABLE) despus de recibir un grupo de posiciones y,
antes de recibir de nuevo posiciones, el proveedor pasa a estado 2 (AVAILABLE).
Estos cambios en el estado de los proveedores de localizacin pueden ayudarnos a realizar diversas acciones. Por ejemplo, se puede utilizar el cambio de estado a 1 (se termina de
recibir un grupo de lecturas) para seleccionar la lectura ms precisa del grupo recibido. Esto es
muy importante cuando se estn utilizando varios proveedores de localizacin simultneamente
(GPS o WIFI) que, como sabes, tienen una precisin diferente.
La popularizacin del mvil inteligente (del ingls, Smartphone) ha provocado tambin que
sus dueos y dueas jueguen con sus dispositivos en cualquier sitio. Android no es ajeno a esta
moda y dispone de una amplia y variada galera de juegos, como, por ejemplo, los ya clsicos
Trivial (Atrviate en su versin mvil), Apalabrados y Angry Birds.
Existen en Internet mltiples libreras y herramientas que ayudan al programador a desarrollar, de forma sencilla, juegos en Android. Veamos algunos de ellos.
Libreras de juegos en 3D
- Unity: evolucin del popular motor Unity 3D para dispositivos Android.
- Ogre 3D: evolucin a Android del motor 3D abierto y gratuito. Muy utilizado.
Libreras de juegos en 2D
- Marmalade SDK: potente motor 2D multiplataforma con el que se puede empezar a desarrollar juegos de forma totalmente gratuita.
- Corona SDK: otra opcin para realizar juegos y aplicaciones 2D multiplataforma.
- Cocos 2D x: librera capaz de abarcar una amplia gama de dispositivos, basada en extensiones. Totalmente gratuita.
- LibGDX: librera abierta y multiplataforma que combina grficos y audio. Muy interesante.
- AndEngine: joven motor de juegos 2D gratuito, rpido y divertido.
Estas libreras son muy tiles para desarrollar juegos de forma rpida; sin embargo, no ensean
realmente cmo hacerlo en Android de forma nativa, ya que sirven de pegamento entre el SDK
de Android y el programador.
Adems, hay que tener en cuenta que las libreras se pasan de moda o se quedan obsoletas. Por lo tanto, en este apartado vamos a estudiar el desarrollo de un juego sencillo utilizando
los mtodos disponibles en el SDK de Android.
293
Aula Mentor
Para empezar, el cdigo del layout main.xml incluye el diseo de la Actividad principal:
<FrameLayout xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=fill_parent
android:layout_height=fill_parent>
<es.mentor.unidad3.eje7.juego.JuegoView
android:id=@+id/areajuego
android:layout_width=fill_parent
android:layout_height=fill_parent/>
<RelativeLayout
android:id=@+id/gameLayout
android:layout_width=fill_parent
android:layout_height=fill_parent >
<LinearLayout
android:id=@+id/fuegoLayout
android:layout_width=fill_parent
android:layout_height=33px
android:orientation=vertical
android:gravity=bottom
android:background=@drawable/muromuellearriba
android:layout_alignParentTop=true/>
294
<ImageView
android:id=@+id/imageHome
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_alignTop=@+id/puntuacion
android:layout_centerHorizontal=true
android:layout_alignParentTop=true
android:src=@drawable/home />
<TextView
android:id=@+id/texto
android:text=@string/modo_preparado
android:visibility=visible
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:gravity=center_horizontal
android:textColor=#ff0000ff
android:textSize=40sp/>
<TextView
android:id=@+id/puntuacion
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_centerInParent=true
android:layout_marginTop=3dp
android:gravity=top
android:text=@string/puntuacion_texto
android:textColor=#ff0000ff
android:textSize=24sp
android:visibility=visible
android:layout_below=@+id/texto/>
<LinearLayout
android:id=@+id/IzqLayout
android:layout_width=33px
android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=left
android:background=@drawable/muromuelleizq
android:layout_alignParentLeft=true
android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/DchaLayout
android:layout_width=33px
android:layout_height=fill_parent
android:orientation=horizontal
android:gravity=right
android:background=@drawable/muromuelledcha
android:layout_alignParentRight=true
android:layout_above=@+id/fuegoLayout/>
<LinearLayout
android:id=@+id/fuegoLayout
android:layout_width=fill_parent
android:layout_height=45dp
android:orientation=vertical
android:gravity=bottom
android:background=@drawable/murofuego
android:layout_alignParentBottom=true/>
</RelativeLayout>
</FrameLayout>
295
Aula Mentor
296
mJuegoThread.restoreState(savedInstanceState);
// Si el juego estaba en el modo EJECUCION, lo pasamos
// a PAUSA para que el usuario pueda jugar
if(mJuegoThread.getEstado() ==JuegoThread.ESTADO_EJECUCION)
{
mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);
}
}
else {
// Si no existe el hilo pero ya hemos ejecutado la
// aplicacin, creamos un hilo nuevo.
JuegoThread jThread = new JuegoThread(mJuegoView);
mJuegoView.setHilo(jThread);
mJuegoThread = mJuegoView.getHilo();
// Recuperamos los datos anteriores
mJuegoThread.restoreState(savedInstanceState);
// El estado actual es preparado
mJuegoThread.setEstado(JuegoThread.ESTADO_PREPARADO);
}
}
// Accedemos al gestor de Sensores y se lo pasamos a la vista de
// Juego para que conecte el sensor que corresponda con su
// listener
mJuegoView.conectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
}
// Eventos de cambio de estado de la Actividad
@Override
protected void onPause() {
super.onPause();
// Si el juego est en ejecucin lo paramos
if(mJuegoThread.getEstado() == JuegoThread.ESTADO_EJECUCION) {
mJuegoThread.setEstado(JuegoThread.ESTADO_PAUSA);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Si se destruye la Actividad liberamos memoria de la Vista
// juego
mJuegoView.liberarTodo();
// Desconectamos del gestor de sensores
mJuegoView.desconectaSensor((SensorManager)
getSystemService(Context.SENSOR_SERVICE));
// Liberamos variables locales
mJuegoThread = null;
mJuegoView = null;
// Liberamos los sonidos guardados y la variable soundPool
soundPool.release();
soundPool = null;
}
@Override
// Mtodo que se lanza cuando es necesario guardar el estado de la
297
Aula Mentor
// Actividad
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Guardamos el estado del Hilo si ste existe
if(mJuegoThread != null) {
mJuegoThread.saveState(outState);
}
}
// Mtodo que asigna el men de la aplicacin
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_START, 0, R.string.menu_empezar);
menu.add(0, MENU_STOP, 0, R.string.menu_parar);
menu.add(0, MENU_RESUME, 0, R.string.menu_continuar);
return true;
}
298
299
Aula Mentor
300
@Override
// Definimos los mensajes
public void handleMessage(Message m) {
// Si el hilo indica el tipo de mensaje puntuacion
// actualizamos la Vista correspondiente
if(m.getData().getBoolean(puntuacion)) {
mPuntuacionView.setText(m.getData().getString(texto));
}
else // Si es otro tipo de mensaje entonces es un cambio de
// estado de la aplicacin
{
// Mostramos el TextView si el mensaje lo indica as
mEstadoView.setVisibility(m.getData().getInt(visible));
mEstadoView.setText(m.getData().getString(texto));
}
// Reproducimos un sonido si lo pide el mensaje
if (m.getData().getInt(sonido)>-1) {
if (m.getData().getInt(sonido)==R.raw.rebote)
JuegoActivity.soundPool.play(JuegoActivity.idRebote,
1, 1, 0, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.gameover)
JuegoActivity.soundPool.play(JuegoActivity.idGameOver,
1, 1, 1, 0, 1);
else if (m.getData().getInt(sonido)==R.raw.win)
JuegoActivity.soundPool.play(JuegoActivity.idWin,
1, 1, 1, 0, 1);
}
}; // end Handler
} // end constructor
// Libera todos los recursos
public void liberarTodo() {
// Paramos el Hilo y limpiamos
this.hilo.setRunning(false);
this.hilo.liberarTodo();
// Quitamos el mtodo Callback de esta clase
this.removeCallbacks(hilo);
hilo = null;
// Quitamos el evento onTouch
this.setOnTouchListener(null);
// Liberamos el Listener del sensor
sensorListener = null;
// Quitamos el mtodo Callback de la superficie
SurfaceHolder holder = getHolder();
holder.removeCallback(this);
}
// Mtodo que establece el Hilo a la Vista de Juego
public void setHilo(JuegoThread elHilo) {
// Guardamos el hilo
hilo = elHilo;
// Delegamos el evento onTouch al Hilo
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(hilo!=null) {
return hilo.onTouch(event);
}
else return false;
}
}); // end onTouch
// Indicamos que el usuario puede hacer clic en la
// vista y que sta toma el foco
setClickable(true);
setFocusable(true);
}
// Devuelve el Hilo de esta Vista de juego
public JuegoThread getHilo() {
return hilo;
}
// Mtodos que devuelven o establecen las Vistas internas del juego
public TextView getEstadoView() {
return mEstadoView;
}
301
Aula Mentor
/*
* FUNCIONES DE PANTALLA
*/
// Si la superficie pierde el foco, entonces pausamos
// el juego. Esto ocurre, por ejemplo, si el usuario
// pulsa la tecla men de su dispositivo
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if(hilo!=null) {
if (!hasWindowFocus)
hilo.pausarJuego();
}
}
302
// ejemplo hemos utilizado un nico hilo.
boolean reintentar = true;
while (reintentar) {
try {
if(hilo!=null) {
hilo.join();
}
reintentar = false;
}
catch (InterruptedException e) { }
}
} // end surfaceDestroyed
// Mtodos que conecta y desconecta el tipo de sensor
// TYPE_ORIENTATION con el SensorManager y el listener
// correspondiente
@SuppressWarnings(deprecation)
public void conectaSensor(SensorManager sm) {
// Es importante marcar con SENSOR_DELAY_GAME que el
// dispositivo debe indicar con el menor tiempo posible los
// cambios de orientacin para que el jugador note fluidez en
// las respuestas de sus movimientos.
sm.registerListener(this.sensorListener,
sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_GAME);
}
public void desconectaSensor(SensorManager sm) {
sm.unregisterListener(this.sensorListener);
this.sensorListener = null;
}
} // end class
Veamos el cdigo anterior que implementa la superficie que define el rea del juego.
Tal y como hemos estudiado en la Unidad 1, para utilizar una superficie debemos crear
una subclase que se extienda de la clase SurfaceView e implemente la interfaz SurfaceHolder.Callback para poder gestionar los mtodos relacionados con esta superficie:
En el constructor de la clase del cdigo anterior puedes observar que se invoca al mtodo
addCallback(this) para gestionar los eventos del SurfaceView y poder acceder a la citada
superficie.
Para actualizar las Vistas de esta superficie (hilo principal) desde el Hilo del juego es
recomendable utilizar un Handler de Java. Este mecanismo permite trasmitir informacin entre
hilos de una aplicacin.
Para ello, en el constructor de esta superficie definimos un Handler que comunica informacin desde el Hilo hacia la Vista del juego que recibe mensajes de ste, como actualizar la
puntuacin o cambiar el estado del juego. Para ello, implementamos el mtodo handleMessage() que gestiona los mensajes recibidos del hilo y actualiza las Vistas de la superficie.
En esta aplicacin hemos implementado algunos de los eventos que lanza una superficie.
Estudiemos las sentencias ms importantes de stos.
En el mtodo surfaceCreated() del SurfaceView ejecutamos un hilo que hemos creado en el constructor de la clase JuegoActivity y que se ha recibido en la superficie mediante
el mtodo setHilo() desde la Actividad principal. Despus, establecemos el estado del hilo al
modo ejecucin y lo arrancamos con la orden start(). La funcin de este hilo es actualizar la
303
Aula Mentor
Hemos implementado el Hilo JuegoThread para actualizar el dibujo del objeto SurfaceView,
gestionar los toques sobre ste y obtener datos de los sensores. En el constructor de este hilo
pasamos como parmetro la referencia al objeto JuegoView, para poder modificar el contenido
de sta.
Por esto, en el mtodo surfaceChanged() del JuegoView obtenemos el tamao de la
pantalla del dispositivo Android y se lo pasamos al hilo JuegoThread.
No hemos definimos el mtodo onDraw() de la superficie ya que ser el hilo el encargado de dibujarla.
En el mtodo setHilo() de esta clase hemos delegado el mtodo onTouchEvent() al
Hilo mediante la sentencia setOnTouchListener(). Tambin definimos, en este mtodo, el
listener del sensor y lo delegamos nuevamente al Hilo.
Este listener lo utilizamos en el mtodo conectaSensor(), donde mediante la orden
registerListener() del ServiceManager lo registramos indicando con SENSOR_DELAY_
GAME que el dispositivo debe comunicar con el menor tiempo posible los cambios de orientacin
(TYPE_ORIENTATION), para que el jugador note fluidez en las respuestas a sus movimientos.
Aunque el tipo de sensor TYPE_ORIENTATION es obsoleto (deprecated), lo hemos utilizado por sencillez en este Ejemplo. Si no queremos usar un sensor obsoleto, debemos utilizar una
combinacin de los sensores TYPE_ACCELEROMETER y TYPE_MAGNETIC_FIELD.
A continuacin, se muestra cmo se implementa este hilo en el archivo JuegoThread.java:
public class JuegoThread extends Thread{
// Estados del juego
public static final int ESTADO_PERDER = 1;
public static final int ESTADO_PAUSA = 2;
public static final int ESTADO_PREPARADO = 3;
305
Aula Mentor
(eljuegoView.getContext().getResources(),
R.drawable.pelota);
}
// Constructor para cuando se desea jugar otra vez
public JuegoThread(JuegoView gameView, JuegoThread oldThread) {
// Recuperamos las variables anteriores
juegoView = gameView;
mSurfaceHolder = gameView.getHolder();
mHandler = gameView.getmHandler();
mContexto = gameView.getContext();
// Transferimos los valores del juego anterior
mEstado = oldThread.mEstado;
mRun = oldThread.mRun;
canvasWidth = oldThread.canvasWidth;
canvasHeight = oldThread.canvasHeight;
tiempoEjec = oldThread.tiempoEjec;
mBackgroundImage = oldThread.mBackgroundImage;
puntuacion = oldThread.puntuacion;
306
pelota = oldThread.pelota;
pelotaX = oldThread.pelotaX;
pelotaY = oldThread.pelotaY;
pelotaVelocidadX = oldThread.pelotaVelocidadX;
pelotaVelocidadY = oldThread.pelotaVelocidadY;
}
}
return bundle;
} // end saveState
307
Aula Mentor
308
}
finally {
// Acabamos y desbloqueamos la superficie
if (canvas != null) {
if(mSurfaceHolder != null)
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
309
Aula Mentor
310
}
setEstado(ESTADO_EJECUCION);
// Mtodo que establece el estado del juego tanto desde este Hilo
// como desde la Actividad juego
public void setEstado(int estado) {
// Sincronizamos la superficie para que ningn proceso pueda
// acceder a ella
synchronized (mSurfaceHolder) {
// Guardamos el nuevo estado
mEstado = estado;
// Si el estado cambia a ejecucin creamos un nuevo
// mensaje y lo enviamos al JuegoView para que actualice
// las Vistas en funcin del estado del juego
if (mEstado == ESTADO_EJECUCION) {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
// Ocultamos la etiqueta del estado del juego
b.putString(texto, );
b.putInt(visible, View.INVISIBLE);
// Enviamos el mensaje al JuegoView
msg.setData(b);
mHandler.sendMessage(msg);
}
// Si no estamos jugando debemos mostrar el mensaje
// correspondiente
else {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
Resources res = mContexto.getResources();
CharSequence str = ;
if (mEstado == ESTADO_PREPARADO)
str = res.getText(R.string.modo_preparado);
else
if (mEstado == ESTADO_PAUSA)
str = res.getText(R.string.modo_pausa)+ \n +
res.getText(R.string.mensaje_parado);
else
if (mEstado == ESTADO_PERDER) {
str = res.getText(R.string.modo_perder);
// Reproducimos sonido perder
b.putInt(sonido, R.raw.gameover);
} else
if (mEstado == ESTADO_GANAR) {
str =
res.getText(R.string.modo_ganar);
// Sonido de ganar
b.putInt(sonido, R.raw.win);
}
b.putString(texto, str.toString());
// Mostramos la etiqueta del estado del juego
b.putInt(viz, View.VISIBLE);
// Enviamos el mensaje al JuegoView
311
Aula Mentor
msg.setData(b);
mHandler.sendMessage(msg);
}
}
} // end detestado
// Mtodo que enva un mensaje al hilo principal
// para que reproduzca sonido de rebote
public void sonidoRebote() {
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
b.putInt(sonido, R.raw.rebote);
msg.setData(b);
// Enviamos el mensaje del sonido
mHandler.sendMessage(msg);
} // end sonidoRebote
312
Empecemos por comentar que, en el cdigo anterior, hemos definido las constantes que
empiezan por ESTADO_ para controlar el estado del juego y actuar en consecuencia.
Puedes ver tambin que esta clase es un hilo tpico de Java con su correspondiente mtodo run(). En ste puedes ver que bloqueamos el canvas de la superficie para dibujarlo con
la orden mSurfaceHolder.lockCanvas() y, cuando es necesario, sincronizamos los mtodos
internos que mueven y dibujan la pelota. Si el usuario est jugando, entonces llamamos al
mtodo muevePelota(), que desplaza la pelota en la zona de juego. Habitualmente los desarrolladores de juegos a este mtodo lo denominan physics() ya que simula la fsica (movimiento)
del juego. Adems, en este bloque run() dibujamos la superficie con la orden doDraw(). Una
vez hemos ejecutado este bloque de cdigo, liberamos el canvas de la superficie desbloquendolo con la orden unlockCanvasAndPost(canvas).
Si te fijas en el mtodo doDraw() vers que nicamente pinta la superficie de juego con
la pelota en la posicin que corresponda.
Sin embargo, muevePelota() calcula la posicin siguiente de la pelota en funcin del
tiempo pasado (as simulamos el movimiento) y la direccin de la pelota. Tambin cambia el
estado del juego si la pelota acaba en ciertas posiciones de la pantalla.
El mtodo onTouch() sirve para interactuar con la pulsacin del usuario en la pantalla.
Este mtodo tiene como parmetro la clase MotionEvent que permite conocer la accin del
usuario mediante la orden getAction(). As, slo utilizamos la accin MotionEvent.ACTION_
DOWN cuando toca la pantalla para continuar o empezar el juego.
Para que el jugador controle el movimiento de la pelota, hemos utilizando el sensor de
orientacin y desarrollado el mtodo onSensorChanged() que nos proporciona en su parmetro SensorEvent la inclinacin alrededor del eje x (-90 a 90) y la rotacin sobre el eje Y (-180
a 180), que empleamos para obtener la nueva velocidad y sentido de la pelota.
Es importante notar que esta clase tiene dos constructores. El tradicional
JuegoThread(JuegoView eljuegoView) que obtiene la superficie del juego, su Handler
y el Contexto de la aplicacin. Y un segundo mtodo JuegoThread(JuegoView gameView,
JuegoThread oldThread) que utilizamos en el mtodo surfaceCreated() de JuegoView
para comprobar con la constante Thread.State.NEW si el hilo se ha ejecutado alguna vez. En
la documentacin de Android se indica que es obsoleto parar y reiniciar hilos. Ten en cuenta
que un usuario puede decidir abrir otra aplicacin mientras est jugando y esto pausa el juego
destruyendo la superficie, por lo que volver a recrearla.
Por lo tanto, una forma de recrear un hilo nuevo es definir un segundo mtodo JuegoThread() donde copiamos todos los atributos del hilo existente y, as, el nuevo hilo comienza
en el estado en el que se qued anteriormente garantizando la experiencia del usuario al continuar el juego en el estado donde se qued.
Te recomendamos que le eches un vistazo a los mtodos setPuntuacion() y setEstado() que establecen en la superficie JuegoView la puntuacin y el estado del juego,
respectivamente, desde este hilo utilizando un objeto de la clase Bundle. En esta clase vamos a
introducir la informacin que deseamos pasar al hilo principal, a aadirla al objeto Message con
setData(bundle) y a enviar este mensaje con la orden sendMessage(mensaje) de Handler.
Como no podemos bloquear el hilo del juego, hemos aprovechado estos mensajes para
notificar al hilo principal que debe reproducir sonidos con la clase asncrona SoundPool en
funcin del estado del juego o cuando la pelota rebota en los muelles.
Finalmente, hemos definido algunos mtodos auxiliares, como empezarJuego() que inicia el
juego, saveState() y restoreState() que guardan y recuperan el estado de ejecucin del
Hilo, respectivamente, y varios mtodos ms del tipo set() y get() necesarios en el hilo.
313
Aula Mentor
Cuando se utilizan hilos es imprescindible usar la orden synchronized en el objeto dinmico correspondiente para evitar accesos simultneamente a ste que
lo dejan en un estado incompleto, de forma que el usuario tendr la sensacin de
que la aplicacin se mueve a trompicones. En este caso, lo hacemos siempre que
vamos a modificar la superficie.
Aunque es posible utilizar el SensorSimulator para depurar la aplicacin, es lento y poco prctico;
por lo tanto, recomendamos probar esta aplicacin en un dispositivo real de Android.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
314
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!
6. Resumen
Hay que saber al final de esta unidad:
- La mayora de los dispositivos de Android incorporan sensores que miden su
movimiento, orientacin y otras varias magnitudes fsicas.
- Los tipos de sensores de Android son:
Sensores de Movimiento
Sensores del Medioambiente
Sensores de Posicin
- Android permite acceder a los sensores del dispositivo a travs de las clases:
Sensor: clase que representa a un sensor con todas sus propiedades.
SensorEvent: clase que almacena los datos medidos por el sensor.
SensorManager: gestor que permite acceder a los sensores del dispositivo.
SensorEventListener: interfaz utilizada para recibir las notificaciones del
SensorManager.
- Se pueden dividir los sensores en dos categoras ms: real y virtual. Real seala
que el sensor indica una medida de una magnitud fsica. Virtual significa que son
medidas obtenidas a partir de otros sensores o son medidas relativas.
- Para desarrollar aplicaciones que utilizan sensores, es necesario disponer de un
dispositivo fsico ya que el emulador de Android no permite depurarlas correctamente.
- Por defecto, en el emulador de dispositivos virtuales (AVD) del SDK de Android
no se pueden utilizar sensores para depurar aplicaciones.
- SensorSimulator es un simulador de sensores que permite depurar una aplicacin Android que los use.
- El SDK de Android dispone de la clase WifiManager que permite hacer uso del
mdulo WIFI, siempre y cuando el dispositivo disponga de l.
- El SDK de Android tambin ofrece soporte para la tecnologa Bluetooth que permite a un dispositivo intercambiar datos de forma inalmbrica con otros dispositivos cercanos.
- Las clases ms importantes del SDK Bluetooth de Android son:
BluetoothAdapter: permite realizar todo tipo de operaciones sobre este
mdulo bluetooth.
BluetoothDevice: representa un dispositivo bluetooth remoto con sus atributos y propiedades correspondientes.
BluetoothSocket: permite la vinculacin entre dispositivos y la trasmisin
de datos.
- Para utilizar este mdulo Bluetooth es necesario definir un receptor de mensajes
del tipo BroadcastReceiver para recibir los eventos del BluetoothAdapter cada
vez que encuentre un dispositivo.
- El AVD de Android no incluye los mdulos WIFI ni de Bluetooth; por lo tanto, es
necesario depurar aplicaciones que los usen en un dispositivo real de Android.
- Prcticamente todos los dispositivos Android integran una o varias cmaras de
fotos. El SDK de Android incluye las clases Java necesarias para gestionar cmaras de fotos.
315
Aula Mentor
1. Introduccin
En esta Unidad vamos a explicar cmo se utilizan bibliotecas de una aplicacin para ampliar su
funcionalidad.
Adems, usaremos la API de Telefona de Android para gestionar llamadas de telfono y
mensajes cortos (SMS) desde un dispositivo.
Despus, utilizaremos el Calendario de Android para administrar las citas y eventos de
ste.
Tambin veremos cmo aprovechar la funcionalidad del gestor de descargas (Download
manager) de Android.
Finalmente, estudiaremos cmo enviar un correo electrnico e implementaremos Servicios avanzados en Android.
317
Aula Mentor
- A excepcin de los archivos de Java, el resto de los archivos que pertenecen a una biblioteca,
por ejemplo, recursos de tipo imagen, se mantiene en el proyecto de tipo biblioteca.
- Si utilizamos una biblioteca en un proyecto Android, es necesario incluirla en ste como
dependencia del proyecto.
- A partir del SDK 15 de Android los IDs de recursos de la biblioteca no son finales (final).
- Tanto el proyecto de tipo biblioteca como el proyecto principal pueden acceder a los recursos internos de la biblioteca a travs de sus respectivas referencias a R.java.
- Es posible definir IDs de recursos repetidos en el proyecto principal y en la biblioteca. Los
IDs del proyecto principal tendrn prioridad sobre los de la biblioteca. Por esto, es recomendable distinguir los IDs de recursos entre los dos proyectos utilizando diferentes prefijos
de recursos, por ejemplo, anteponiendo el prefijo lib_ para los recursos de la biblioteca.
- Un proyecto principal puede hacer referencia a varias bibliotecas al mismo tiempo. Es posible establecer la prioridad de las bibliotecas para marcar qu recursos son ms importantes.
- Si queremos utilizar una Actividad (Activity) o un Servicio (Service) desarrollados en una
biblioteca, deben incluirse en el archivo manifest.xml del proyecto principal. Adems, es
necesario indicar el nombre del paquete completo de la biblioteca al incluir la Actividad o
el Servicio.
- Un proyecto de tipo biblioteca no puede hacer referencia a otra biblioteca, si bien en futuras
versiones del SDK de Android se podr hacer.
318
Es importante saber integrar bibliotecas en proyectos Android ya que existen muchas bibliotecas de cdigo libre disponibles en Internet que pueden simplificar la
programacin de un proyecto.
Para crear un proyecto de tipo biblioteca, debemos crear un proyecto normal de Android y
luego marcar la opcin biblioteca (library). Veamos de forma prctica cmo definir y utilizar una
biblioteca en Android.
319
Aula Mentor
En la ventana anterior es muy importante marcar la opcin Mark this Project as a library.
Despus, hacemos clic de nuevo en el botn Next donde podramos cambiar el icono de la
biblioteca:
320
Volvemos a pulsar el botn Next para pasar a la siguiente pantalla:
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
322
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical
android:paddingBottom=@dimen/activity_vertical_margin
android:paddingLeft=@dimen/activity_horizontal_margin
android:paddingRight=@dimen/activity_horizontal_margin
android:paddingTop=@dimen/activity_vertical_margin
tools:context=.ActividadSecundaria >
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=@string/etiqueta />
<LinearLayout
android:id=@+id/linearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
android:gravity=top
android:layout_marginTop=10dp>
<Button
android:id=@+id/botonOK
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado OK
android:layout_weight=1
android:onClick=botonClick />
<Button
android:id=@+id/botonCANCEL
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado CANCEL
android:layout_weight=1
android:onClick=botonClick/>
</LinearLayout>
</LinearLayout>
Se trata de una sencilla Actividad que devuelve RESULT_OK o RESULT_CANCELED segn el botn
donde pulse el usuario.
Ahora vamos a crear una aplicacin Android como viene siendo habitual. No se incluye
el proceso ya que el alumno o alumna debe saber crear el proyecto.
Despus, aadimos la biblioteca creada anteriormente haciendo clic con el botn derecho del ratn y seleccionando la opcin del men desplegable Properties. Accediendo a la
opcin Android, pulsamos el botn Add para aadir la biblioteca creada anteriormente:
323
Aula Mentor
Una vez importada la librera biblioteca, vers que aparecen dos archivos de recursos en la
carpeta gen: uno de la aplicacin que contiene la biblioteca (es.mentor.unidad4.eje1.
biblioteca_aplicacion) y el otro de la biblioteca (es.mentor.unidad4.eje1.biblioteca)
propiamente dicho:
Estudiemos ahora cmo utilizar los recursos (imagen robot.png) y clase (ActividadSecundaria)
que define esta biblioteca.
324
android:layout_gravity=center
android:layout_margin=10dp
android:src=@drawable/robot />
</LinearLayout>
Lo primero que observamos en el fichero anterior es que la imagen de la biblioteca est disponible
como recurso del proyecto y basta escribir @drawable/robot para acceder a ella.
En el archivo MainActivity.java desarrollamos la lgica de la aplicacin:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
325
Aula Mentor
android:icon=@drawable/ic_launcher
android:label=@string/app_name
android:theme=@style/AppTheme android:allowBackup=true>
<activity
android:name=
es.mentor.unidad4.eje1.biblioteca_aplicacion.MainActivity>
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category
android:name=android.intent.category.LAUNCHER />
</intent-filter>
</activity>
<activity android:name=
es.mentor.unidad4.eje1.biblioteca.ActividadSecundaria
android:label=Actividad secundaria/>
</application>
Para que la ActividadSecundaria est disponible en el proyecto es muy importante incluir el nombre completo del paquete de la librera donde se define.
326
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente aplicacin:
3.1 TelephonyManager
TelephonyManager, gestor que permite determinar la siguiente informacin del servicio de
telefona (se incluye un listado bsico):
- IMEI del dispositivo
- N de Telfono
- Nombre del Operador en varios formatos
- Tipo y Potencia de la red conectada
- Datos de la tarjeta SIM
- N del buzn de mensajes de voz
- Datos de la Celda de Telefona (antena a la que est conectado)
327
Aula Mentor
3.2 SMSManager
SMSManager, este Gestor permite enviar mensajes cortos (SMS) y leer su formato interno PDU
que significa Protocol Description Unit, que es el formato estndar de los mensajes cortos SMS.
328
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en
// el archivo activity_main.xml
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la
// pestaa
mTabHost.setup(this, getSupportFragmentManager(),
R.id.tabFrameLayout);
// Aadimos la pestaa Telfono utilizando el fragmento
// TelefonoFragmentTab
mTabHost.addTab(
mTabHost.newTabSpec(tab1).setIndicator(Telfono,
getResources().getDrawable(R.drawable.telefono)),
TelefonoFragmentTab.class, null);
// Aadimos la pestaa SMS utilizando el fragmento
// SMSFragmentTab
mTabHost.addTab(
mTabHost.newTabSpec(tab2).setIndicator(Mensajes
cortos (SMS),
getResources().getDrawable(R.drawable.sms)),
SMSFragmentTab.class, null);
// Establecemos el listener del cambio de pestaa
mTabHost.setOnTabChangedListener(this);
} // end onCreate()
// Evento que se lanza cada vez que se selecciona una pestaa nueva
@Override
public void onTabChanged(String tabId) {
// Mostramos un mensaje al usuario
Toast.makeText(this, Has cambiado a la pestaa + tabId,
Toast.LENGTH_SHORT).show();
}
} // end clase
329
Aula Mentor
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical >
330
<LinearLayout
android:orientation=horizontal
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_marginTop=5dp>
<Button
android:id=@+id/llamarBtn
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Llamar por telfono/>
<EditText
android:id=@+id/nTelefono
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_weight=1
android:ems=10
android:text=091334455
android:inputType=phone >
</EditText>
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=?android:attr/textAppearanceMedium
android:text=Datos Telfono:/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ScrollView
android:id=@+id/ScrollView
android:layout_height=fill_parent
android:layout_width=fill_parent
android:layout_alignParentBottom=true
android:scrollbarAlwaysDrawVerticalTrack=true
android:fadeScrollbars=false>
<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>
331
Aula Mentor
});
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);
}
333
Aula Mentor
En el cdigo anterior puedes observar que, al tratar con un Fragmento, hemos empleado el
mtodo onCreateView() para buscar las Vistas.
En este mismo mtodo hemos definido el evento onClick() del botn Llamar por telfono
que crea una Intencin de tipo Intent.ACTION_CALL e inicia la Actividad correspondiente.
Tambin podramos utilizar los tipos ACTION_DIAL o ACTION_VIEW para ver el marcador de
telfono pero sin iniciar la llamada.
334
Despus, hemos obtenido el gestor TelephonyManager de la API del telfono mediante la orden
getSystemService(Context.TELEPHONY_SERVICE) y utilizado sus mtodos extraemos los
datos siguientes:
- getPhoneType(): tipo de telfono del dispositivo.
- getDeviceId(): IMEI del dispositivo.
- getSubscriberId(): n de telfono.
- getSimCountryIso(): pas de la tarjeta SIM.
- getNetworkOperatorName(): nombre del operador de telefona.
- getVoiceMailNumber(): n de buzn de voz.
En este mismo mtodo creamos el listener que se extiende de PhoneStateListener, que recibir
los eventos del telfono y que se lanza cuando cambia el estado de llamada del telfono con
onCallStateChanged(). Cuando esto ocurre, simplemente aadimos un texto al Log de la
interfaz del usuario.
En los eventos onResume() y onDestroy() del fragmento indicamos al gestor de la API
del telfono que escuche o deje de escuchar respectivamente los eventos del telfono con el
listener que hemos creado anteriormente.
Para ello, utilizamos el mtodo listen(PhoneStateListener listener, int
events) del gestor TelephonyManager e indicamos en el segundo parmetro el tipo de evento
que queremos escuchar: LISTEN_CALL_STATE (cambios en el estado de llamada). Tambin es
posible escuchas otro tipo de eventos, como envo de datos, cambios en la potencia de la seal,
informacin de la celda, etctera.
Sin embargo, para detectar llamadas salientes es necesario definir en el mtodo onCreate() del fragmento un receptor de mensajes para el Intent del tipo android.intent.action.
NEW_OUTGOING_CALL mediante la orden registerReceiver(). Hemos incluido el receptor de
mensajes llamado OutGoingCallReceiver del tipo BroadcastReceiver para recibir el evento del
sistema operativo cuando se produzca una nueva llamada y se lance el evento onReceive(). En
este caso, aadimos un nuevo mensaje de Log al usuario.
Veamos ahora cmo se implementa el fragmento de la segunda pestaa que gestiona
mensajes cortos (SMS).
Si abres en Eclipse ADT el archivo sms_fragment_layout.xml, vers que contiene
un diseo muy sencillo que consiste en dos cajas de texto (telfono y mensaje corto) con su
respectivo botn para enviar el mensaje y un listado ListView para mostrar los mensajes cortos
recibidos:
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:layout_width=match_parent
android:layout_height=match_parent
android:gravity=center_horizontal
android:orientation=vertical
android:layout_margin=2dp >
<LinearLayout
android:orientation=horizontal
android:layout_width=fill_parent
android:layout_height=wrap_content>
<Button android:id=@+id/enviarBoton
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Enviar SMS/>
<TextView android:layout_width=wrap_content
android:textAppearance=
?android:attr/textAppearanceMedium
android:layout_margin=4dp
android:layout_height=wrap_content
android:text=Telfono: />
<EditText
android:id=@+id/nTelefono
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_weight=1
android:ems=10
android:text=091334455
android:inputType=phone >
</EditText>
</LinearLayout>
<LinearLayout
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=wrap_content>
<TextView android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Mensaje: />
<EditText android:id=@+id/mensajeET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=prueba de mensaje />
</LinearLayout>
<TextView
android:layout_width=fill_parent
335
Aula Mentor
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<TextView
android:id=@+id/datosTfno
android:layout_margin=3dp
android:layout_height=wrap_content
android:layout_width=fill_parent
android:textAppearance=
?android:attr/textAppearanceMedium
android:text=Mensajes recibidos:/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<ListView
android:id=@+id/listado
android:layout_width=fill_parent
android:layout_height=wrap_content >
</ListView>
</LinearLayout>
336
337
Aula Mentor
338
String str = ;
// Si el mensaje contiene algo
if (bundle != null)
{
// Obtenemos la informacin del SMS recibido.
// El campo PDU significa Protocol Description Unit
// y es el formato estndar de los mensajes cortos
// SMS
Object[] pdus = (Object[]) bundle.get(pdus);
// Pasamos todos los mensajes recibidos en formato
// pdu a una matriz del tipo SmsMessage que contendr
// los mensajes en un formato interno y accesible por
// Android.
// Definimos el tamao de la matriz con el n de
// mensaje recibidos
mensjs = new SmsMessage[pdus.length];
// Recorremos todos los mensajes recibidos
for (int i=0; i<mensjs.length; i++){
// Pasamos el elemento i a la matriz de mensajes
mensjs[i] =
SmsMessage.createFromPdu((byte[])pdus[i]);
// Guardamos en texto plano el mensaje
str += SMS de +
mensjs[i].getOriginatingAddress();
str += . Mensaje: ;
str += mensjs[i].getMessageBody().toString();
str += \n;
} // end for
// Mostramos un mensaje indicando que se ha recibido
// el mensaje
Toast.makeText(context, str,
Toast.LENGTH_SHORT).show();
} // end if bundle != null
} // end onReceive()
};
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.
339
Aula Mentor
con los datos cuando hay cambios de configuracin en el dispositivo (cambio de orientacin).
Esta clase est disponible desde Android 3.0, aunque puede usarse mediante el paquete
de compatibilidad desde la versin 1.6.
Se puede utilizar tanto en un Actividad como en un Fragmento.
Para crear un objeto de tipo Loader debemos invocar el mtodo getLoaderManager().initLoader(0, null, this).
340
Cuando el Cursor deja de ser vlido, entonces Android invoca el mtodo onLoaderReset()
para realizar las acciones pertinentes.
Uno de los retos del programador de Android en la gestin de bases de datos es que su
acceso es lento y que hay que tener en cuenta el ciclo de vida de la Actividad correctamente,
por ejemplo, para abrir y cerrar el Cursor si ocurre un cambio de configuracin.
En versiones anteriores a Android 3.0, para gestionar el ciclo de vida se puede utilizar
el mtodo managedQuery(). A partir de esta versin, este mtodo queda obsoleto y debemos
emplear la clase Loader para acceder a un ContentProvider.
La clase SimpleCursorAdapter, que se puede usar con ListView, dispone del mtodo swapCursor(), que el cargador puede utilizar para actualizar el Cursor en su mtodo
onLoadFinished() del Loader.
Es importante crear el objeto del tipo Loader en onCreate() del Fragmento para
que est disponible en el mtodo OnCreateView() posteriormente.
Si te fijas en el cdigo fuente de este fragmento, vers que hemos creado un Loader de tipo
Cursor que se encargar de conectar a la BD y obtener los registros del ContentProvider.
Al crear el Loader se lanza el evento onCreateLoader() que, a su vez, crea un CursorLoader que accede al contenido content://sms/inbox. Es muy importante indicar en
su constructor los campos que deseamos obtener de la base de datos de mensajes SMS. Es
imprescindible obtener siempre el campo _id de la base de datos ya que Android lo asocia
automticamente al ListView.
Cuando el Loader finaliza la carga los datos, Android invoca el evento onLoadFinished() y es aqu donde actualizamos la interfaz del usuario indicando el nuevo Cursor al adaptador del listado mediante el mtodo swapCursor() de este ltimo.
En el caso de que el Loader se destruya o se reinicie, es decir, de que ya no disponga
de datos, Android invoca el evento onLoaderReset() y es necesario eliminar el Cursor del
adaptador.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga los permisos
necesarios para leer el estado del telfono, realizar llamadas, procesar las llamadas entrantes y
leer, recibir y enviar los mensajes cortos. Para ello, hay que incluir en el fichero AndroidManifest.xml las siguientes etiquetas:
<uses-permission android:name=android.permission.READ_PHONE_STATE/>
<uses-permission android:name=android.permission.CALL_PHONE/>
<!-- Necesario para detectar la llamadas salientes -->
<uses-permission
android:name=android.permission.PROCESS_OUTGOING_CALLS/>
<uses-permission android:name=android.permission.READ_SMS/>
<uses-permission android:name=android.permission.RECEIVE_SMS/>
<uses-permission android:name=android.permission.SEND_SMS/>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 2 (API Telfono) de la Unidad
4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del programa anterior, en el que hemos utilizado la API de telefona de Android.
Si ejecutas en Eclipse ADT este Ejemplo 2 en el AVD, vers que se muestra la siguiente aplicacin:
341
Aula Mentor
342
Para simular una llamada entrante de telfono o la recepcin de un mensaje corto SMS en el
AVD y comprobar que la aplicacin responde correctamente, abrimos la perspectiva de DDMS
de Eclipse ADT y, en la pestaa Emulator Control, en el apartado Telephony Status:
Podemos crear una llamada entrante (botn Call) o un mensaje SMS recibido
(botn Send) de forma manual al AVD en ejecucin.
Si despliegas las distintas opciones, vers que es posible configurar el tipo de llamada
recibida y casi cualquier caracterstica de sta.
4. Calendario de Android
El sistema operativo Android dispone de Calendarios donde el usuario puede administrar citas
y recordatorios. Existen dos formas de gestionarlos:
- Mediante Intenciones (Intents), accediendo indirectamente al Calendario mediante Actividades.
- Utilizando la API oficial, que permite gestionar de forma directa el Calendario del sistema
operativo que Google public en la versin 4.0 y que permite insertar, actualizar y eliminar
calendarios, eventos, recordatorios, etctera. En versiones anteriores a la 4.0 era difcil desarrollar una aplicacin que usase directamente el Calendario, ya que hay que acceder a la
base de datos y sta puede cambiar en funcin del fabricante del dispositivo.
En este apartado vamos a estudiar la API de gestin del Calendario de Android.
343
Aula Mentor
Tabla (Clase)
Descripcin
CalendarContract.Events
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.
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:
Descripcin
NAME
CALENDAR_DISPLAY_NAME
VISIBLE
SYNC_EVENTS
345
Aula Mentor
Por razones de simplicidad, en los ejemplos siguientes las consultas se realizan en el proceso
principal de la aplicacin. En la prctica, estas consultas deben hacerse en un hilo asncrono
mediante Loaders o mediante la clase AsyncQueryHandler para modificar datos.
Por ejemplo, para consultar todos los calendarios dados de alta en el dispositivo podemos
escribir las sentencias:
// Cursor que usamos para la consulta
Cursor cur = null;
// Obtenemos un objeto del tipo ContentResolver
ContentResolver cr = getContentResolver();
// URI con los datos de la tabla Calendarios
Uri uri = Calendars.CONTENT_URI;
// Campos que deseamos obtener de la tabla
public static final String[] CAMPOS = new String[] {
CalendarContract.Calendars._ID,
Calendars.ACCOUNT_NAME,
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}
// Indicamos los criterios de seleccin de los datos
String seleccion = ( + Calendars.ACCOUNT_TYPE + = ?);
String[] seleccionArgs = new String[] {com.google};
// Obtenemos los datos en un cursor.
cur = cr.query(uri, CAMPOS, seleccion, seleccionArgs, null);
346
Hay que tener en cuenta que el nombre de la cuenta del usuario Calendars.
ACCOUNT_NAME (por ejemplo, usuario@gmail.com) se puede repetir en
diferentes tipos de cuentas ACCOUNT_TYPE.
Existe tambin el tipo de cuenta especial ACCOUNT_TYPE_LOCAL que se usa para crear
calendarios no asociados con la cuenta de Google del dispositivo. Y que no se sincroniza.
Para actualizar un calendario hay que buscarlo primero indicando un criterio de seleccin como, por ejemplo, su campo _ID. Fjate en el siguiente ejemplo:
// Indicamos el ID del calendario que deseamos modificar
long calendarioID = 2;
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Modificamos el nombre del calendario
values.put(Calendars.CALENDAR_DISPLAY_NAME, Nueva descripcin del
calendario);
// Aadimos a la Uri del calendario el ID de ste
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI,
calendarioID);
// Actualizamos el nombre del calendario y obtenemos las filas insertadas en
la base de datos de Calendarios
int filas = getContentResolver().update(updateUri, cv, null, null);
Los Calendarios genricos estn pensados para ser manipulados principalmente mediante un
adaptador de sincronizacin (Sync Adapter); por lo tanto, otras aplicaciones slo pueden hacer
cambios superficiales, como cambiar el nombre de visualizacin (ver ejemplo anterior).
Para crear un calendario local desde una aplicacin en el dispositivo debemos indicar
el tipo de cuenta account_type de ACCOUNT_TYPE_LOCAL. Como hemos comentado, los ca-
lendarios de este tipo no se sincronizan con un servidor externo. Fjate en el siguiente ejemplo
de creacin de un calendario local:
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Definimos las caractersticas del calendario
cv.put(Calendars.ACCOUNT_NAME, Nombre de la cuenta usuario);
cv.put(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL);
cv.put(Calendars.NAME, Nombre del calendario);
cv.put(Calendars.CALENDAR_DISPLAY_NAME, Nombre que muestra calendario);
cv.put(Calendars.CALENDAR_COLOR, 0xEA8561);
// Los usuarios solo pueden leer el calendario
cv.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
cv.put(Calendars.OWNER_ACCOUNT, Nombre de la cuenta usuario);
// El calendario es visible y se sincroniza
cv.put(Calendars.VISIBLE, 1);
cv.put(Calendars.SYNC_EVENTS, 1);
// Actualizamos el nombre del calendario y obtenemos las filas
// insertadas en la base de datos de Calendarios
int filas = getContentResolver().update(Calendars.CONTENT_URI, cv, null,
null);
Para borrar un calendario permanentemente as como todos sus eventos asociados podemos
ejecutar las siguientes sentencias:
// Seleccionamos la URI del calendario con el ID que queremos borrar
Uri calUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, CAL_ID);
// Borramos el calendario
getContentResolver().delete(calUri, null, null);
Descripcin
CALENDAR_ID
ORGANIZER
TITLE
EVENT_LOCATION
DESCRIPTION
DTSTART
DTEND
347
Aula Mentor
Nombre
348
Descripcin
EVENT_TIMEZONE
EVENT_END_TIMEZONE
DURATION
ALL_DAY
RRULE
RDATE
AVAILABILITY
GUESTS_CAN_MODIFY
GUESTS_CAN_INVITE_OTHERS
GUESTS_CAN_SEE_GUESTS
Si tenemos en cuenta que para aadir un evento al calendario hay que rellenar muchos campos,
vemos que es recomendable utilizar un Intent para hacerlo. Estudiaremos cmo hacerlo un poco
ms adelante en este apartado.
Sin embargo, si es necesario insertar un evento directamente en el calendario, podemos utilizar
esta API teniendo en cuenta lo siguiente:
- Es imprescindible indicar los campos CALENDAR_IDyDTSTART, es decir, _ID del calendario
y fecha de inicio del evento.
- Tambin debemos sealar la zona horaria del evento EVENT_TIMEZONE.
- Si el evento no se repite, entonces debes incluir el campo DTEND (fecha final evento).
- Para eventos repetitivos, hay que incluir el campo duracin DURATIONadems de los camposRRULEoRDATE.
Veamos un ejemplo que aade un evento al calendario con sentencias Java:
// Creamos la variable donde vamos a almacenar los valores del evento
ContentValues cv = new ContentValues();
// Definimos el Evento
cv.put(Events.CALENDAR_ID, CAL_ID);
cv.put(Events.TITLE, Ttulo del evento);
cv.put(Events.DTSTART, fecha_inicio_en_milisegundos);
cv.put(Events.DTEND, fecha_fin_en_milisegundos);
cv.put(Events.EVENT_LOCATION, Lugar: mi casa);
Para actualizar un evento hay que buscarlo primero indicando un criterio de seleccin, como,
por ejemplo, su campo Events._ID. Fjate en el siguiente ejemplo:
// Creamos la variable donde vamos a almacenar los nuevos valores
ContentValues cv = new ContentValues();
// Definimos las modificaciones del Evento
cv.put(Events.TITLE, Ttulo del evento);
cv.put(Events.DESCRIPTION, Descripcin del evento);
cv.put(Events.EVENT_LOCATION, Nuevo lugar del evento);
// Definimos el criterio de bsqueda del evento y su argumento
String seleccion = (+Events._ID+ = ?);
String[] seleccionArgs = new String[]
{String.valueOf(id_del_evento_a_actualizar)};
// Modificamos el evento
getContentResolver().update(CalendarContract.Events.CONTENT_URI, cv,
seleccion, seleccionArgs);
349
Aula Mentor
Descripcin
EVENT_ID
ATTENDEE_NAME
ATTENDEE_EMAIL
350
ATTENDEE_TYPE
Tipo de invitado:
TYPE_REQUIRED: requerido
TYPE_OPTIONAL: opcional
ATTENDEE_STATUS
De igual forma que hemos hecho en los casos anteriores, es muy sencillo aadir un invitado al
evento con el _ID eventoID:
// Creamos la variable donde vamos a almacenar los valores
ContentValues cv = new ContentValues();
// Definimos los datos del invitado
cv.put(Attendees.ATTENDEE_NAME, Nombre);
cv.put(Attendees.ATTENDEE_EMAIL, usuario@ejemplo.com);
cv.put(Attendees.ATTENDEE_RELATIONSHIP,
Attendees.RELATIONSHIP_ATTENDEE);
cv.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
values.put(Attendees.ATTENDEE_STATUS,
Attendees.ATTENDEE_STATUS_INVITED);
cv.put(Attendees.EVENT_ID, eventoID);
// Aadimos el invitado
getContentResolver().insert(Attendees.CONTENT_URI, cv);
Descripcin
EVENT_ID
MINUTES
Minutos anteriores a la hora del evento en los que se debe iniciar el recordatorio.
METHOD
Es muy sencillo aadir un recordatorio 15 minutos antes al evento con el _ID eventoID:
// Creamos la variable donde vamos a almacenar los valores
ContentValues cv = new ContentValues();
// Definimos los datos del recordatorio
cv.put(Reminders.MINUTES, 15);
cv.put(Reminders.EVENT_ID, eventoID);
cv.put(Reminders.METHOD, Reminders.METHOD_ALERT);
// Aadimos el recordatorio
getContentResolver().insert(Reminders.CONTENT_URI, cv);
Descripcin
BEGIN
END
END_DAY
END_MINUTE
EVENT_ID
_IDdel evento.
START_DAY
START_MINUTE
351
Aula Mentor
352
VIEW
URI
Descripcin
Extras
content://com.android.
calendar/time/
Abre el calendario
Tambin podemos utilizar la especificado en una Ninguno.
constante: CalendarContract. fecha.
CONTENT_URI.
content://com.android.
calendar/events/
VIEW
EDIT
E D I T
Cualquiera
de
los
Crea
o
edita
un
evento.
campos
del
siguiente
Tambin podemos utilizar la
INSERT
listado.
constante:
Events.CONTENT_URI.
Descripcin
Events.TITLE
CalendarContract.EXTRA_EVENT_BEGIN_TIME
CalendarContract.EXTRA_EVENT_END_TIME
CalendarContract.EXTRA_EVENT_ALL_DAY
Events.EVENT_LOCATION
Events.DESCRIPTION
Intent.EXTRA_EMAIL
Events.RRULE
Regla de repeticin
Events.ACCESS_LEVEL
Events.AVAILABILITY
Si deseamos abrir el calendario por una fecha especfica (por ejemplo, dos meses desde la fecha
actual), podemos escribir las siguientes sentencias Java:
// Obtenemos los milisegundos pasados de la fecha actual + 2 meses
Calendar cal = new GregorianCalendar();
cal.setTime(new Date());
cal.add(Calendar.MONTH, 2);
long time = cal.getTime().getTime();
// Generamos la URI correspondiente
Uri.Builder builder =
CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath(time);
builder.appendPath(Long.toString(time));
Intent intent =
new Intent(Intent.ACTION_VIEW, builder.build());
// Iniciamos la Actividad del Intent
startActivity(intent);
Por ejemplo, para lanzar un Intent que cree un evento en el calendario podemos escribir las
sentencias:
// En algunos dispositivos no funciona bien la accin ACTION_INSERT
// y se debe utiliza en su lugar Intent.ACTION_EDIT
Intent intent = new Intent(Intent.ACTION_EDIT);
// URI con los Eventos del Calendario
intent.setData(CalendarContract.Events.CONTENT_URI);
Aula Mentor
Tipo
Ventajas
Desventajas
Intents
API Calendario
355
Aula Mentor
356
android:gravity=center >
<Button android:id=@+id/anterior
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Anterior
android:padding=10dip/>
<Button android:id=@+id/siguiente
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Siguiente
android:padding=10dip/>
</LinearLayout>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<LinearLayout
android:layout_width=fill_parent
android:layout_height=wrap_content
android:orientation=horizontal
android:gravity=center >
<Button
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Alta evento
android:onClick=altaEvento />
<Button android:id=@+id/borrar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Borrar evento
android:onClick=borrarEvento />
<Button android:id=@+id/editar
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Editar evento
android:onClick=editarEvento />
</LinearLayout>
</LinearLayout>
Como puedes observar, la interfaz de usuario es muy sencilla y consta de un selector donde el
usuario elije un calendario. Hay, adems, una serie de botones para ver el siguiente/anterior
evento, dar de alta uno nuevo, editarlo o borrarlo.
Si, a continuacin, abrimos la clase principal de la aplicacin MainActivity, vemos el
siguiente contenido con la lgica de la aplicacin:
// Clase que almacena el _id y nombre del Calendario
class MiCalendario {
public String nombre;
public String id;
public MiCalendario(String _name, String _id) {
nombre = _name;
id = _id;
}
@Override
public String toString() {
return nombre;
}
}
// Clase principal de la Actividad que implementa el evento onClick de los
botones
public class MainActivity extends Activity implements OnClickListener {
//
//
//
//
357
Aula Mentor
358
} // end onCreate()
// Mtodo que busca todos los eventos del Calendario y da
// seleccionados
private void cargaEventos() {
// Creamos dos fechas de inicio y fin de la bsqueda de eventos
GregorianCalendar fechaIni = new GregorianCalendar(anioAct,
mesAct, diaAct);
GregorianCalendar fechaFin = new GregorianCalendar(anioAct,
mesAct, diaAct+1);
// Actualizamos la Etiqueta con la fecha seleccionada
fechaLabel.setText(Integer.toString(diaAct) + / +
Integer.toString(mesAct+1) + / +
Integer.toString(anioAct));
// Seleccionamos los campos que deseamos mostrar: _ID, ttulo,
// fecha inicio/fin y ubicacin
String[] campos = new String[] {
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.EVENT_LOCATION };
// En el criterio de seleccin indicamos el ID del Calendario
// y la fecha de inicio/final de los Eventos
String seleccion = CalendarContract.Events.CALENDAR_ID +
= ? and + CalendarContract.Events.DTSTART +
>= ? and + CalendarContract.Events.DTEND + <= ? ;
// Argumento de la seleccin anterior
String[] seleccionArgs = { String.valueOf(idCalendarioAct),
String.valueOf(fechaIni.getTimeInMillis()),
String.valueOf(fechaFin.getTimeInMillis()) };
// Buscamos los Eventos del Calendario en esas fechas y los
// ordenamos decrecientemente por fecha de inicio
mCursor = getContentResolver().query(
CalendarContract.Events.CONTENT_URI, campos,
seleccion, seleccionArgs,
CalendarContract.Events.DTSTART + ASC);
// Nos movemos al primer registro
mCursor.moveToFirst();
// Actualizamos los botones simulando un clic en el botn
// anterior registro
onClick(findViewById(R.id.anterior));
}
// Mtodo que muestra un selector de fecha a partir de la clase
// DateDialogFragment
public void showDatePickerDialog(View v) {
// Creamos una variable de tipo Calendar con la fecha
// actual para pasrsela al DateDialogFragment
Calendar cal = Calendar.getInstance();
cal.set(anioAct, mesAct, diaAct);
// Creamos el objeto del tipo DateDialogFragment
DateDialogFragment ddf = DateDialogFragment.newInstance(this,
Selecciona la fecha, cal);
// Definimos su evento dateDialogFragmentDateSet
ddf.setDateDialogFragmentListener(new
DateDialogFragmentListener() {
359
Aula Mentor
@Override
public void dateDialogFragmentDateSet(int year, int
monthOfYear, int dayOfMonth) {
diaAct=dayOfMonth;
mesAct= monthOfYear;
anioAct=year;
// Buscamos los eventos del nuevo da seleccionado
cargaEventos();
}
}); // end setDateDialogFragmentListener
// Mostramos el selector de fecha
ddf.show(getFragmentManager(), date picker dialog fragment);
}
360
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
// Siempre actualizamos los datos independientemente del
// resultado del Intent
if (requestCode == 1) {
cargaEventos();
}
}//onActivityResult
// Mtodo que se lanza cuando el usuario hace clic en los botones
// Siguiente/Anterior Evento
@Override
public void onClick(View v) {
// Si no hay eventos deshabilitamos los botones
if (mCursor.getCount()==0) {
botonSig.setEnabled(false);
botonAnt.setEnabled(false);
botonBorrar.setEnabled(false);
botonEditar.setEnabled(false);
tvEvento.setText(No hay eventos este da!);
return;
361
Aula Mentor
362
}
// En funcin del botn sobre el que hace clic el usuario
// avanzamos o retrocedemos un registro del cursor
switch(v.getId()) {
case R.id.siguiente:
if(!mCursor.isLast())
mCursor.moveToNext();
break;
case R.id.anterior:
botonSig.setEnabled(false);
if(!mCursor.isFirst())
mCursor.moveToPrevious();
break;
} // en case
// Habilitamos los botones de bsqueda
botonSig.setEnabled(!mCursor.isLast());
botonAnt.setEnabled(!mCursor.isFirst());
// Formateador de fechas
Format df = DateFormat.getDateFormat(this);
Format tf = DateFormat.getTimeFormat(this);
// Variables temporales
String titulo = ;
String ubicacion = ;
Long inicio = 0L;
idEventoAct = -1;
// Lee
try {
idEventoAct = mCursor.getInt(0);
titulo = mCursor.getString(1);
inicio = mCursor.getLong(2);
ubicacion = mCursor.getString(3);
} catch (Exception e) {
Log.d(Calendario, Error la leer registro de la BD
de Eventos);
}
// Deshabilitamos los botones Editar y Borrar si no hemos
// encontrado un registro
botonBorrar.setEnabled(idEventoAct > -1);
botonEditar.setEnabled(idEventoAct > -1);
// Mostramos la informacin del Evento al usuario
tvEvento.setText(idEventoAct+: +titulo+ el da +
df.format(inicio) + desde las +
tf.format(inicio) + en + ubicacion);
} // end onClick
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Tambin utilizamos un Cursor para buscar todos los Calendarios
disponibles en el dispositivo y seleccionamos los campos _ID y DISPLAY_NAME. En este caso no
incluimos el campo Calendars.ACCOUNT_TYPE ya que vamos a mostrar todos los calendarios
independientemente de su tipo. A continuacin, recorremos todos los registros del Cursor y
pasamos la informacin a una matriz del tipo MiCalendario, que definimos al inicio de esta
clase principal. Finalmente, creamos un adaptador de tipo matriz para asociarlos al Spinner con
los nombres de los Calendarios y definimos su evento OnItemSelected(), que obtiene el objeto
correspondiente cada vez que el usuario selecciona otro calendario.
El mtodo cargaEventos() busca todos los eventos del Calendario y da seleccionados
por el usuario. Para ello, seleccionamos los campos que deseamos mostrar: _ID, ttulo, fecha
inicio/fin y ubicacin. Definimos el criterio de seleccin indicamos el _ID del Calendario y la
fecha de inicio/final de los Eventos. Despus, buscamos los Eventos del Calendario mediante el
mtodo query() del objeto ContentResolver y los ordenamos decrecientemente por fecha
de inicio.
El mtodo showDatePickerDialog() muestra una ventana de dilogo con un selector
de fecha a partir de la clase DateDialogFragment. Adems, definimos su evento dateDialogFragmentDateSet() que se lanza cuando el usuario elige una fecha.
El mtodo altaEvento() y editarEvento() ejecuta un Intent que sirve para dar de
alta un nuevo Evento o editarlo, tal y como hemos visto en la teora anterior. En este caso, lo
ejecutamos con la orden startActivityForResult() para recibir el resultado de su ejecucin
en la actividad principal.
El evento borrarEvento() usa la API del calendario para borrar el evento actual.
Finalmente, el mtodo onClick() se lanza cuando el usuario hace clic en los botones Siguiente/
Anterior Evento. Aqu buscamos el evento correspondiente, mostramos su informacin y
habilitamos o deshabilitamos los botones correspondientes cuando ya no se encuentren ms
registros.
En el archivo DateDialogFragment.java hemos definido un selector de fecha a partir
de la clase DialogFragment. De nuevo volvemos a utilizar un fragmento en lugar de la clsica
ventana de tipo Dialog para mostrar una ventana de dilogo. Si abres este archivo, observars
que tiene este aspecto:
// Clase de tipo DialogFragment que muestra un selector de fecha
public class DateDialogFragment extends DialogFragment {
// Variable para almacenar el contexto, fecha y el listener
static Context sContext;
static Calendar sDate;
static DateDialogFragmentListener sListener;
// Constructor del fragmento
public static DateDialogFragment newInstance(Context context,
String titulo, Calendar date){
// Creamos el nuevo objeto el tipo DateDialogFragment
DateDialogFragment dialog = new DateDialogFragment();
// Guardamos los datos de las variables
sContext = context;
sDate = Calendar.getInstance();
sDate = date;
363
Aula Mentor
364
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 opciones 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.
367
Aula Mentor
<TextView
android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Imagen descargada:
android:gravity=center
android:padding=10dip/>
<ImageView android:layout_height=wrap_content
android:id=@+id/imagenView
android:src=@drawable/ic_launcher
android:layout_width=fill_parent
android:gravity=center/>
</LinearLayout>
368
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
urlET = (EditText)findViewById(R.id.urlET);
// Definimos el BroadcastReceiver para detectar cundo
// finaliza la descarga
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String accion = intent.getAction();
// Si la accin es ACTION_DOWNLOAD_COMPLETE entonces
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.
equals(accion)) {
// Creamos un objeto del tipo Query para poder
// consultar el Gestor de descargas
Query query = new Query();
// Filtramos por el ID de peticin
query.setFilterById(idPeticion);
// Filtramos en el gestor de descarga la consulta
// anterior
Cursor c = dm.query(query);
// Si hay algn registro entonces...
if (c.moveToFirst()) {
// Obtenemos la columna STATUS de la descarga
int columnIndex = c.getColumnIndex(
DownloadManager.COLUMN_STATUS);
// Si el STATUS de la descarga es CORRECTO
// entonces
if (DownloadManager.STATUS_SUCCESSFUL ==
c.getInt(columnIndex)) {
// Localizamos la imagen de la interfaz
// del usuario
ImageView view = (ImageView)
findViewById(R.id.imagenView);
// Obtenemos la URI del archivo en local
String uriString = c.getString(
c.getColumnIndex(
DownloadManager.COLUMN_LOCAL_URI));
// Cargamos la nueva imagen
view.setImageURI(Uri.parse(uriString));
}
} // end if hay algn registro
}
} // end onReceive
}; // end BroadcastReceiver
// Registramos el BroadcastReceiver
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
} // end onCreate
// Mtodo que se lanza cuando el usuario pulsa el botn descargar
public void descargarImagen(View view) {
// Obtenemos referencia al servicio DOWNLOAD_SERVICE
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
// Creamos la peticin con la URL de la imagen
Request peticion = new Request(
Uri.parse(urlET.getText().toString()));
// Arrancamos la descarga y guardamos su identificativo
idPeticion = dm.enqueue(peticion);
}
// Mtodo que se lanza cuando el usuario pulsa el botn Mostrar
// descargas
public void verDescargas(View view) {
// Utilizamos un Intent del tipo ACTION_VIEW_DOWNLOADS y lo
// ejecutamos
Intent i = new Intent();
i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS);
startActivity(i);
}
} // end clase
Como puedes observar, el cdigo fuente es muy sencillo: simplemente utilizamos un objeto
del tipo DownloadManager y definimos el BroadcastReceiver para recibir el mensaje
correspondiente cuando acaba la descarga de la imagen y mostrarla en la interfaz de usuario.
Para mostrar todas las descargas del gestor, utilizamos un Intent del tipo ACTION_VIEW_
DOWNLOADS y lo ejecutamos.
Finalmente, para poder ejecutar esta aplicacin, es necesario que tenga permisos de
acceso a Internet. Para ello, hay que incluir en el fichero AndroidManifest.xml la siguiente
etiqueta:
<uses-permission android:name=android.permission.INTERNET/>
369
Aula Mentor
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:
371
Aula Mentor
372
<EditText
android:id=@+id/destinatarioET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:inputType=textEmailAddress >
<requestFocus />
</EditText>
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Asunto:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/asuntoET
android:layout_width=fill_parent
android:layout_height=wrap_content />
<TextView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Mensaje:
android:textAppearance=?android:attr/textAppearanceLarge />
<EditText
android:id=@+id/mensajeET
android:layout_width=fill_parent
android:layout_height=wrap_content
android:gravity=top
android:inputType=textMultiLine
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>
373
Aula Mentor
// Aadimos el asunto y el cuerpo
m.setAsunto(asuntoET.getText().toString());
m.setCuerpo(mensajeET.getText().toString());
// Es necesario utilizar un hilo separado para enviar
// el correo. Si no, aparece la excepcin
// android.os.NetworkOnMainThreadException
// ya que a partir de la versin 3.0 de Android no
// podemos utilizar la conexin de red en el hilo
// principal porque lo bloquea.
new AsyncTask<Void, Void, Void>() {
// Variable para guardar el resultado
String resultado=;
@Override
protected void onPreExecute()
{
// Deshabilitamos el botn de enviar
enviarSMTP.setEnabled(false);
Log.d(Debug, onPreExecute());
}
374
@Override
protected Void doInBackground(Void... params)
{
Log.d(Debug, doInBackground() -- Enviamos
el mensaje por SMTP);
try {
// Si quisiramos aadir un fichero
// adjunto descomentaramos la siguiente
// sentencia
//m.addAttachment(/sdcard/imagen.jpg);
// Si enviamos el mensaje...
if(m.send()) {
Log.d(Debug, Enviado OK);
resultado= El correo se ha enviado
correctamente.;
} // Si no lo enviamos...
else {
Log.d(Debug, Enviado Error);
resultado=Ha ocurrido un error al
enviar el mensaje!;
}
} catch(Exception e) {
// Si ocurre una excepcin, guardamos su
// informacin
Log.d(Debug, Excepcin +
e.getMessage().toString());
resultado= Error al enviar el mensaje:
+ e.getMessage().toString();
}
return null;
}
@Override
protected void onPostExecute(Void res)
Log.d(Debug, onPostExecute());
// Habilitamos el botn de enviar y mostramos
// un mensaje con el resultado
enviarSMTP.setEnabled(true);
Toast.makeText(MainActivity.this, resultado,
Toast.LENGTH_LONG).show();
}
}.execute(); // end AsyncTask
}
}); // end onClick botn
enviarIntent.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Utilizamos un Intent para crear el mensaje
Intent email = new Intent(Intent.ACTION_SEND);
// Cargamos la matriz de destinatarios
String[] destinatarios = {
destinatarioET.getText().toString().trim()};
email.putExtra(Intent.EXTRA_EMAIL, destinatarios);
// Podemos incluir en copia o en copia oculta a ms
// destinatarios
//email.putExtra(Intent.EXTRA_CC,new String[]{to});
//email.putExtra(Intent.EXTRA_BCC,
new String[]{to});
email.putExtra(Intent.EXTRA_SUBJECT,
asuntoET.getText().toString());
email.putExtra(Intent.EXTRA_TEXT,
mensajeET.getText().toString());
// Indicamos que vamos a enviar un correo
// electrnico
email.setType(message/rfc822);
// Iniciamos el Intent
startActivity(Intent.createChooser(email, Elige el
cliente de correo electrnico:));
}
}); // End onClick botn
} // end onCreate()
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para definir el evento
onClick() de ambos botones.
El botn enviarSMTP enva un correo electrnico mediante la API JavaMail. Para ello,
utilizamos un objeto de la clase local MailSender que se define en otro archivo y que sirve de
interfaz a esta biblioteca.
375
Aula Mentor
Para enviar un correo mediante la API JavaMail hay que utilizar un hilo separado,
pues si no lo hacemos, salta la excepcin android.os.NetworkOnMainThreadE
xception, ya que a partir de la versin 3.0 de Android no podemos utilizar la conexin de red en el hilo principal porque lo bloquea.
El botn enviarIntent enva un correo electrnico creando un Intent del tipo message/
rfc822 con la accin ACTION_SEND, en la que completamos los datos EXTRA_EMAIL (destinatario),
EXTRA_SUBJECT (asunto) y EXTRA_TEXT (cuerpo del mensaje). Posteriormente, con la orden
Intent.createChooser() mostramos un ventana de dilogo donde el usuario puede elegir la
376
debug = false;
// Indica si autenticamos por SMTP
autenticado = true;
// Variable utilizada para cargar los ficheros adjuntos
multipart = new MimeMultipart();
// Sentencias necesarias para que se puedan cargar ficheros
// adjuntos. Se trata de algo interno de
// la biblioteca javax.mail.Authenticator
MailcapCommandMap mc = (MailcapCommandMap)
CommandMap.getDefaultCommandMap();
mc.addMailcap(text/html;; x-java-content
handler=com.sun.mail.handlers.text_html);
mc.addMailcap(text/xml;; x-java-content
handler=com.sun.mail.handlers.text_xml);
mc.addMailcap(text/plain;; x-java-content
handler=com.sun.mail.handlers.text_plain);
mc.addMailcap(multipart/*;; x-java-content
handler=com.sun.mail.handlers.multipart_mixed);
mc.addMailcap(message/rfc822;; x-java-content
handler=com.sun.mail.handlers.message_rfc822);
CommandMap.setDefaultCommandMap(mc);
}
// Contructor donde aadimos el usuario y la clave
public MailSender(String usuario, String clave) {
// Invocamos al constructor genrico
this();
this.usuario = usuario;
this.clave = clave;
}
// Mtodo que enva un mensaje y crear excepciones
public boolean send() throws Exception {
// Establecemos las propiedades de la sesin
Properties props = setProperties();
// Si tenemos cuenta de usuario, su clave, destinatarios,
// asunto y cuerpo del mensaje...
if(!usuario.equals() && !clave.equals() &&
destinatarios.length > 0 && !de.equals() &&
!asunto.equals() && !cuerpo.equals()) {
// Creamos una sesin con las propiedades anteriores
Session session = Session.getInstance(props, this);
// Creamos un eMail en el objeto siguiente
MimeMessage msg = new MimeMessage(session);
// Aadimos los campos De
msg.setFrom(new InternetAddress(de));
// Indicamos los destinatarios
InternetAddress[] addressTo = new
InternetAddress[destinatarios.length];
for (int i = 0; i < destinatarios.length; i++) {
addressTo[i] = new
InternetAddress(destinatarios[i]);
}
377
Aula Mentor
msg.setRecipients(MimeMessage.RecipientType.TO,
addressTo);
// Indicamos asunto y fecha de envo
msg.setSubject(asunto);
msg.setSentDate(new Date());
// Cargamos el cuerpo del mensaje en formato MIME
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(cuerpo);
multipart.addBodyPart(messageBodyPart);
msg.setContent(multipart);
// Enviamos el mensaje
Transport.send(msg);
return true;
} else {
return false;
}
}
378
}
// Mtodo que aade un fichero adjunto
public void addAttachment(String filename) throws Exception {
// Usamos la variable multipart para aadirlo en formato
// MIME
BodyPart messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
}
// Mtodo que establece las propiedades de la sesin con el
// servidor
private Properties setProperties() {
Properties props = new Properties();
props.put(mail.smtp.host, servidor);
if(debug) {
props.put(mail.debug, true);
}
if(autenticado) {
props.put(mail.smtp.auth, true);
}
props.put(mail.smtp.port, puerto);
props.put(mail.smtp.socketFactory.port, puertoOrigen);
props.put(mail.smtp.socketFactory.class,
javax.net.ssl.SSLSocketFactory);
props.put(mail.smtp.socketFactory.fallback, false);
return props;
}
// Mtodo que establece el cuerpo del mensaje
public void setCuerpo(String cuerpo) {
this.cuerpo = cuerpo;
}
// Mtodo que establece los destinatarios del mensaje
public void setDestinatarios(String[] destinatarios) {
this.destinatarios = destinatarios;
}
// Mtodo que establece el remitente del mensaje
public void setDe(String de) {
this.de = de;
}
// Mtodo que establece el asunto del mensaje
public void setAsunto(String asunto) {
this.asunto = asunto;
}
} // end clase
Es imprescindible definir el mtodo de autenticacin con la clase PasswordAuthentication que se usa en Session.getInstance con this para que la
API sepa cmo autenticar la sesin con el servidor de correo.
Para poder utilizar las clases anteriores es necesario copiar los archivos .jar que forman la
biblioteca JavaMail en el directorio libs del proyecto e importarlas al mismo, tal y como ya
hemos hecho anteriormente en el curso:
379
Aula Mentor
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 electrnico) de la Unidad 4. Estudia el cdigo fuente y ejectalo en un dispositivo fsico de
Android para ver el resultado del programa anterior.
Para poder enviar correos electrnicos, es necesario tener acceso directo a Internet; por lo tanto,
es mejor probar esta aplicacin en un dispositivo real de Android.
Si ejecutas en Eclipse ADT este Ejemplo 5 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
381
Si pulsas en el botn Enviar x Intent, vers que el sistema te ofrece un listado con aplicaciones
para completar la accin (dependiendo del dispositivo aparecern ms o menos):
Aula Mentor
Te recomendamos que pruebes las distintas opciones de envo de correo electrnico para ver
cmo funcionan.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Para que funcione esta aplicacin con tu cuenta de Gmail es necesario que, en su configuracin,
tengas habilitado el Acceso IMAP. Mira cmo puedes configurarlo.
382
383
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.
En el mtodo startService() tambin podemos indicar como parmetro el comportamiento
del ciclo de vida de los servicios:
- START_STICKY: se utiliza para indicar que el servicio debe ser explcitamente iniciado o
parado.
- START_NOT_STICKY: el servicio termina automticamente cuando el mtodo onStartCommand() finaliza su ejecucin.
- START_REDELIVER_INTENT: el sistema tratar de volver a crear el servicio.
- START_STICKY_COMPATIBILITY: versin compatible de START_STICKY, que no garantiza
que onStartCommand() sea llamado despus de que el proceso sea matado.
Si la actividad quiere interactuar con un servicio (modo Dependiente o Ligado) para, por
ejemplo, mostrar el progreso de una operacin, puede utilizar el mtodo bindService(). Para
esto, hay que usar el objeto ServiceConnection, que permite conectarse al servicio y devuelve
un objeto de tipo IBinder, que la actividad puede utilizar para comunicar con el servicio. Ms
adelante veremos en detalle cmo definir servicios en modo Ligado dentro de las aplicaciones
Android.
Una vez repasados los conceptos de servicios, vamos a aprender un nuevo tipo de servicio: Intent Service.
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.
385
Aula Mentor
<Button
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=calcularOperacion
android:text=Es un nmero primo?/>
</LinearLayout>
<TextView
android:id=@+id/salida
android:layout_width=match_parent
android:layout_height=match_parent
android:text=
android:textAppearance=?android:attr/textAppearanceMedium/>
</LinearLayout>
386
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, registramos un receptor de mensajes cuando utilizamos
el servicio del tipo IntentService.
El mtodo calcularOperacion() se lanza cuando el usuario hace clic en el botn de
la Actividad y crea un Intent para lanzar el servicio que calcula si el nmero introducido es
primo o no.
Tambin utilizamos la clase receptorServicio del tipo BroadcastReceiver para recibir mensajes del servicio IntentService.
En el archivo ServicioOperacion.java hemos definido el servicio que utilizamos en
la Actividad principal:
// Clase de tipo Servicio
public class ServicioOperacion extends Service {
// extends IntentService {
// Contructor de la clase IntentService
/*public ServicioOperacion() {
super(Nombre del servicio);
}*/
//Mtodo que se ejecuta cuando arranca el servicio (Service)
@Override
public int onStartCommand(Intent intencion, int flags, int
idArranque){
// Obtenemos el nmero
int n = intencion.getExtras().getInt(numero);
// Actualizamos la interfaz del usuario.
// OJO! Podemos hacer esto porque el servicio se ejecuta
// en el mismo hilo de ejecucin, el principal de la Actividad.
if(esPrimo(n)) MainActivity.salida.append(El nmero + n +
387
Aula Mentor
NO es primo + \n);
else MainActivity.salida.append(El nmero + n + ES primo +
\n);
// Paramos un tiempo la ejecucin
SystemClock.sleep(5000);
// Si queremos que la parada dure ms, entonces descomenta la
// siguiente lnea
//SystemClock.sleep(20000);
// El servicio es del siguiente tipo
return START_STICKY;
}
// No definimos este mtodo ya que no es necesario
@Override
public IBinder onBind(Intent arg0) {
return null;
}
388
Si te fijas en el cdigo fuente anterior, hemos definido el servicio a partir de la clase Service e
implementado su mtodo onStartCommand() que realiza el clculo para verificar si el nmero
es primo con el algoritmo del mtodo esPrimo(). Ms adelante veremos para qu sirve el resto
del cdigo.
Finalmente, acabamos la explicacin de todo el cdigo indicando que, para poder ejecutar el servicio, hay que incluirlo en el fichero AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 6 (Intent Service) de la Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Intent Service de Android.
Si ejecutas en Eclipse ADT este Ejemplo 6 en el AVD, vers que se muestra la siguiente
aplicacin:
389
Veamos ahora el resto del cdigo fuente del servicio. Como sabes, a la hora de desarrollar
aplicaciones en Android hay que tener en cuenta que todos los componentes (actividades,
Aula Mentor
390
Es decir, como el servicio anterior se est utilizando en el hilo principal, se est parando este
hilo y el sistema operativo indica que la aplicacin no responde.
Veamos lo que ocurre si utilizamos un servicio IntentService que, recordamos, se ejecuta en
un hilo distinto al principal.
Para ello, basta con cambiar el tipo de servicio extendiendo su clase de IntentService
y descomentando los mtodos ServicioOperacion() (contructor de la clase) y onHandleIntent() (invocado cuando arranca el IntentService). Adems, hay que comentar el mtodo
onStartCommand().
Tampoco es posible utilizar la clase Toast de mensajes flotantes desde otros hilos.
Como el nuevo hilo del IntentService que hemos creado pertenece al mismo proceso que el
hilo principal, podemos compartir entre ellos todas las variables. Es decir, podramos implementar
un nuevo mtodo o una variable pblica para devolver el valor de la funcin esPrimo(), tanto
en la clase del servicio como en la clase de la actividad. Sin embargo, hemos resuelto este
inconveniente utilizando un mecanismo ms elegante: receptores de mensajes.
Si modificamos el tiempo de retardo a 25 segundos para este IntentService y ejecutamos de nuevo la aplicacin, veremos que el sistema no muestra ningn error (aunque s tarda
en dar el resultado):
391
Aula Mentor
392
- Handler y Messenger
Si el Servicio debe comunicar informacin a la Actividad, sta puede recibirla mediante un
objeto de tipo Message mediante un Intent. En el Ejemplo 7 ( Juego) de la Unidad 3
hemos visto cmo utilizar este mecanismo de comunicacin.
Si es la Actividad la que quiere comunicarse con un servicio, podemos usar la clase
Messenger. Esta clase es parcelable, se puede delegar en otro proceso, es decir, tambin
sirve para enviar objetos del tipo Messages al Handler de la Actividad.
Messenger dispone del mtodo getBinder(), que permite a la Actividad enviar
mensajes al servicio.
- Utilizacin de AIDL para servicios en distintos procesos
Para comunicar con un Servicio que se ejecuta en un proceso diferente, podemos hacer uso
de IPC (Inter Process Communication) para enviar datos entre diferentes procesos.
AIDL (Android Interface Definition Language) es similar a otros IPC existentes en el
diseado especficamente para Android.
Para ello, debemos crear un fichero AIDL que, aparentemente, es similar a un interfaz
(interface) de Java pero el fichero tiene la extensin .aidl y slo permite extender a
otros ficheros AIDL.
Este mecanismo se debe emplear slo si es necesario que el servicio est disponible a otras aplicaciones; de lo contrario deberamos utilizar un servicio definido
localmente.
<TextView android:layout_width=fill_parent
android:layout_height=wrap_content
android:text=Ejemplo 7: Servicio AIDL
android:textSize=22sp/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<EditText
android:id=@+id/valor1
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:hint=Nmero 1 />
<TextView android:layout_width=wrap_content
android:gravity=center
android:layout_height=wrap_content android:text=x
android:textSize=36sp/>
<EditText
android:id=@+id/valor2
android:layout_width=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:layout_height=wrap_content
android:hint=Nmero 2/>
<Button android:id=@+id/botonCalc
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text==/>
393
Aula Mentor
<TextView
android:id=@+id/resultado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado
android:textSize=22sp />
</LinearLayout>
394
Como puedes observar, simplemente definimos la interface del servicio con los mtodos
disponibles. En este caso, el mtodo multiplicar.
Los parmetros de los mtodos definidos en la interfaz tienen una etiqueta que marca el cometido
del mismo. Puede contener in (entrada), out (salida) o inout (entrada/salida).
- Por defecto, AIDL soporta los siguientes tipos de datos:
- Todas las primitivas del lenguaje de programacin Java (como int, long, char, boolean,
etctera).
- String
- CharSequence
- List (todos los elementos de la lista deben ser uno de los tipos de datos soportados anteriores).
- Map (todos los elementos de la lista deben ser uno de los tipos de datos soportados anteriores).
- Todas las clases que sean parceables (que se puede partir o serializar).
Ahora vamos a definir el Stub del servicio, es decir, el bloque del cdigo que implementa la
funcin anterior. Si abrimos el archivo ServicioOperacion.java vemos que contiene:
// Clase de tipo Servicio que se ejecuta en segundo plano
// cuando arranca la Actividad
public class ServicioOperacion extends Service {
@Override
public void onCreate() {
super.onCreate();
}
// Definimos el evento onBind que se usa para ligar la Actividad
// al servicio
@Override
public IBinder onBind(Intent intent) {
// Devuelve el resultado de multiplicar dos nmeros
// implementando el mtodo multiplicar definido en
// IMultiplicacionServicio.aidl
return new IMultiplicacionServicio.Stub() {
public int multiplicar(int valor1, int valor2) throws
RemoteException {
return valor1 * valor2;
}
};
}
@Override
public void onDestroy() {
super.onDestroy();
}
} // end clase servicio
395
Aula Mentor
servicio = null;
// Mostramos un mensaje al usuario
Log.d(AIDL, onServiceDisconnected() desconectado);
Toast.makeText(MainActivity.this, Service remoto
desconectado, Toast.LENGTH_LONG).show();
}
} // end clase ServicioOperacionConexion
396
}
// Buscamos el valor de los operandos
v1 = Integer.parseInt(valor1.getText().toString());
v2 = Integer.parseInt(valor2.getText().toString());
// Invocamos el servicio
try {
res = servicio.multiplicar(v1, v2);
} catch (RemoteException e) {
Log.d(AIDL, Error al invocar el servicio: + e);
e.printStackTrace();
}
// Mostramos el resultado
resultado.setText(String.valueOf(res));
}
}); // end evento onClick botn
// Iniciamos el servicio
iniciarServicio();
} // end onCreate()
@Override
protected void onDestroy() {
// Al acabar la Actividad liberamos el servicio
pararServicio();
super.onDestroy();
}
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, definimos el evento onClick() del botn de la
aplicacin y conectamos con el servicio AIDL.
El mtodo iniciarServicio() liga la Actividad con el servicio. Para ello, definimos la
conexin del servicio mediante la clase local ServicioOperacionConexion y un Intent en el
que indicamos la clase que define el servicio en la orden bindService().
La clase ServicioOperacionConexion se implementa a partir de la clase ServiceConnection y sus mtodos onServiceConnected() y onServiceDiconnected(), que obtiene
el cdigo Stub del servicio remoto haciendo un typecasting del servicio definido en el archivo
IMultiplicacionServicio.aidl. As, ya tenemos acceso al mtodo multiplicar del citado
servicio.
Una aplicacin que quisiera conectarse a este servicio debe contar con el archivo
.aidl correcto e implementar su clase ServiceConnection correspondiente.
Finalmente, y como es habitual, para poder ejecutar el servicio hay que incluirlo en el fichero
AndroidManifest.xml:
<service android:name=.ServicioOperacion> </service>
397
Aula Mentor
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 7 (Servicio AIDL) de la Unidad 4. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado la un Servicio AIDL de Android.
Si ejecutas en Eclipse ADT este Ejemplo 7 en el AVD, vers que se muestra la siguiente
aplicacin:
398
399
Aula Mentor
400
401
Aula Mentor
Si pulsamos el botn Add aparece una ventana donde tenemos que seleccionar Apache
Tomcat v7.0; despus, pulsamos el botn Next:
402
En la siguiente ventana indicamos el nombre del servidor y el directorio de su instalacin, y lo
descargamos pulsando el botn Download and Install:
A continuacin, aparece una nueva ventana superpuesta sobre la anterior. Hay que pulsar el
botn Finish para instalar el servidor Tomcat:
Una vez acaba la instalacin, aparecer el nuevo servidor Tomcat como disponible en las
opciones de Eclipse ADT:
403
Aula Mentor
Y en Eclipse ADT aparece tambin el nuevo servidor como si se tratara de un proyecto ms:
404
405
Aula Mentor
En la siguiente ventana podemos modificar el directorio donde se almacena los contenidos Web
e indicar que se cree el archivo web.xml que describe el servidor:
Finalmente, pulsamos el botn Finish y Eclipse ADT ofrecer cambiar la perspectiva a Java
EE. Es recomendable hacerlo para gestionar el servidor de forma ms sencilla.
406
Ahora que ya hemos creado el proyecto del servicio SOAP, hacemos clic con el botn derecho
del ratn sobre el proyecto y seleccionamos la opcin New -> Package:
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
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
409
Una vez se ha creado el servicio SOAP, podemos ver su definicin en el archivo ServidorSoap.
wsdl del proyecto:
Aula Mentor
En el caso de que no hayamos iniciado el servicio con el botn Start Server anterior, podemos
hacerlo desde el proyecto con el botn derecho del ratn en la opcin Run as > Run on
Server:
410
Aparecer esta ventana donde debemos pulsar el botn Next:
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
Estudiemos ahora, de forma prctica, cmo utilizar esta biblioteca SOAP. Es recomendable abrir
el Ejemplo 8 de esta Unidad para entender la explicacin siguiente.
Ya hemos estudiado en el curso cmo incluir bibliotecas externas en proyectos Android. Basta
con copiar su archivo .jar en la carpeta libs del proyecto e importarla al proyecto.
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal:
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical android:layout_width=fill_parent
android:layout_height=fill_parent
android:gravity=center_horizontal >
<TextView android:layout_width=fill_parent
android:layout_height=wrap_content android:text=Ejemplo 8:
Servicio SOAP
android:textSize=22sp/>
<TextView
android:layout_width=fill_parent
android:layout_height=6dip
android:layout_marginBottom=6dip
android:background=#000000 />
<EditText
android:id=@+id/grados
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal|center
android:inputType=numberSigned
android:hint=Grados fahrenheit />
<Button android:id=@+id/botonCalc
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Obtener grados Celsius/>
<TextView
android:id=@+id/resultado
android:layout_width=wrap_content
android:layout_height=wrap_content
android:text=Resultado
android:textSize=22sp />
</LinearLayout>
413
Aula Mentor
414
@Override
public void onCreate(Bundle savedInstanceState) {
// Mtodo tpico que busca las Vistas que vamos a utilizar
// despus
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos las Vistas de la interfaz del usuario
resultado = (TextView) findViewById(R.id.resultado);
grados = (EditText) findViewById(R.id.grados);
boton = (Button) findViewById(R.id.botonCalc);
// Definimos el onClick del botn
boton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int valor = -1;
// Validamos que el usuario ha introducido los operandos
if (grados.getText().toString().isEmpty()) {
Toast.makeText(MainActivity.this,
ERROR: debes introducir los grados.,
Toast.LENGTH_SHORT).show();
return;
}
// Buscamos los grados introducidos
valor = Integer.parseInt(grados.getText().toString());
// Invocamos el servicio a travs de una tarea asncrona
// (Puede tardar en responder el servidor y no queremos
//
bloquear el hilo principal de la aplicacin!)
TareaWebService tarea = new TareaWebService();
// Le pasamos como parmetro los grados introducidos por
// el usuario
tarea.execute(valor);
}
}); // end evento onClick botn
} // end onCreate()
// Tarea Asncrona para llamar al WebService de consulta en
// segundo plano que admite enteros como prametros
private class TareaWebService extends
AsyncTask<Integer,Integer,Boolean> {
// Aqu guardamos el resultado de la ejecucin del servicio
String resultadoTxt=;
protected Boolean doInBackground(Integer... params) {
// Variable que usamos para saber si la ejecucin termina
// bien
boolean resul = true;
// El nombre del espacio del servicio SOAP
final String NAMESPACE =
http://soap.eje8.unidad4.mentor.es;
// La URL a la que debemos conectarnos
// ES NECESARIO CAMBIAR LA IP A TU ORDENADOR!!!!
final String URL=http://10.X.X.X:8080/unidad4.eje8.soap/
services/WebService?wsdl;
// Mtodo que vamos a invocar
final String METHOD_NAME = getGradosCelsius;
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
415
Aula Mentor
Vistas de la interfaz del usuario y definimos el onClick del botn Obtener grados Celsius, con
el que invocamos el servicio SOAP a travs de una tarea asncrona.
416
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:
Send: Return Code: 0x00000000
POST /unidad4.eje8.soap/services/WebService?wsdl HTTP/1.1
Aula Mentor
User-Agent: ksoap2-android/2.6.0+
SOAPAction: http://soap.eje8.unidad4.mentor.es/getGradosCelsius
Content-Type: text/xml;charset=utf-8
Connection: close
Accept-Encoding: gzip
Host: 10.17.19.63:8080
Content-Length: 398
<v:Envelope xmlns:i=http://www.w3.org/2001/XMLSchema-instance
xmlns:d=http://www.w3.org/2001/XMLSchema
xmlns:c=http://schemas.xmlsoap.org/soap/encoding/
xmlns:v=http://schemas.xmlsoap.org/soap/envelope/>
<v:Header />
<v:Body>
<n0:getGradosCelsius id=o0 c:root=1
xmlns:n0=http://soap.eje8.unidad4.mentor.es>
<grados i:type=d:int>110</grados>
</n0:getGradosCelsius>
</v:Body>
</v:Envelope>
Receive: Return Code: 0x00000000
HTTP/1.1 200 OK
418
Puedes ver en la captura anterior varias zonas marcadas correspondientes a las tres partes
principales de una peticin SOAP.
- Primero, en color rojo, se encuentra la CABECERA HTTP ya que el envo de una peticin
SOAP al servidor se realiza mediante este protocolo http.
- Despus, en color azul, se incluye otra serie de etiquetas y datos a modo de CONTENEDOR
estndar que recibe el nombre de Envelope. La informacin indicada en este contenedor no
es especfica de la llamada al servicio, pero s contiene informacin sobre formatos y esquemas de validacin del estndar SOAP.
- Finalmente, en color verde, en la parte interna del XML, encontramos los datos de la PETICIN (Request) en s, que contiene el nombre del mtodo invocado y los nombres y
valores de los parmetros en entrada.
Todo esto junto har que el servidor sea capaz de interpretar correctamente la peticin SOAP,
llame al mtodo interno correcto y devuelva el resultado en un formato similar al anterior:
Send: Return Code: 0x00000000
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Fri, 06 Sep 2013 08:19:05 GMT
Connection: close
198
<?xml
version=1.0 encoding=utf-8?><soapenv:Envelope
xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance>
<soapenv:Body>
<getGradosCelsiusResponse xmlns=http://soap.eje8.unidad4.mentor.es>
<getGradosCelsiusReturn>43</getGradosCelsiusReturn>
</getGradosCelsiusResponse>
</soapenv:Body>
</soapenv:Envelope>
Send: Return Code: 0x00000000
0
Una vez estudiada la estructura y funcionamiento general de una peticin SOAP y su respuesta,
veamos cmo incluirla en una aplicacin Android.
Si continuamos examinando la clase TareaWebService, observamos que crea la peticin
SOAP al servicio Web, la enva y recibe su respuesta correspondiente.
Primero creamos el objeto del tipo HttpTransportSE que se encargar de realizar la comunicacin
HTTP con el servidor SOAP. A este objeto le pasaremos la URL de la conexin al servicio web.
Posteriormente, creamos la peticin (request) al mtodo correspondiente mediante un
nuevo objeto SoapObject indicando como parmetros el NAMESPACE y el nombre del mtodo
Web. Mediante el mtodo addProperty() de esta clase incluimos los parmetros de entrada del
mtodo anterior indicando el nombre del parmetro y su valor.
Despus, definimos el contenedor SOAP (envelope) mediante su mtodo SoapSerializationEnvelope indicando la versin de SOAP que vamos a utilizar (versin 1.1).
Si vamos a conectar a un servicio SOAP de Net-Services (Microsoft), debemos indicar dotNet =
true del contenedor. Como ste no es el caso, en el cdigo fuente original lo hemos comentado.
A continuacin, asociamos la peticin creada anteriormente a este contenedor mediante el
mtodo setOutputSoapObject().
Finalmente, para invocar al mtodo remoto del servidor SOAP. usamos el mtodo call()
del objeto del tipo HttpTransportSE.
Despus de la llamada al servicio SOAP, vamos a recibir su resultado mediante el mtodo
getResponse(). Dependiendo del tipo de resultado que devuelva el mtodo invocado debemos
convertir la respuesta a un tipo u otro.
En este caso, como el resultado que esperamos es un valor simple (un nmero entero),
convertimos la respuesta a un objeto del tipo SoapPrimitive que, directamente, transformamos
a una cadena de tipo cadena invocando toString().
Finalmente, y como es habitual, para poder acceder a Internet es necesario indicarlo en
el fichero AndroidManifest.xml:
<uses-permission android:name=android.permission.INTERNET/>
419
Aula Mentor
Si ejecutas en Eclipse ADT este Ejemplo 8 en el AVD, vers que se muestra la siguiente
aplicacin:
420
Para que este ejemplo funcione, debes definir tu servidor Tomcat en Eclipse ADT e importar el
cdigo fuente del servidor SOAP y ejecutarlo. Con el servidor SOAP lanzado debes ejecutar la
aplicacin Android. No te olvides de escribir la direccin IP de tu ordenador para que funcione
bien!
Como el resultado devuelto por el servicio es una matriz del tipo Dato, lo primero que hemos
hecho es crear la matriz local con su misma longitud, que se obtiene mediante su mtodo
getPropertyCount().
Despus, recorremos los distintos elementos de la matriz devuelta mediante el mtodo
getProperty(indice). A su vez, cada uno de estos elementos es otro objeto del tipo SoapObject que representa a un Dato.
Para acceder a las propiedades de cada elemento, invocamos el mtodo
getProperty(indice) con el ndice de cada atributo.
Si deseamos invocar un mtodo Web que recibe como parmetro algn objeto complejo.
debemos modificar la clase Dato de forma que sea serializable.
Para ello, implementamos la interfaz KvmSerializable en esta clase Dato que obliga a
desarrollar los siguientes mtodos:
- getProperty(int indice): devuelve el valor de cada atributo de la clase a partir de su
ndice de orden. Por ejemplo, el ndice 0 retorna el valor del atributo Id, el ndice 1 el del
atributo Nombre, etctera:
@Override
public Object getProperty(int arg0) {
switch(arg0) {
case 0:
return id;
case 1:
return nombre;
case 2:
return edad;
}
return null;
}
421
Aula Mentor
switch(ind) {
case 0:
info.type =
info.name =
break;
case 1:
info.type =
info.name =
break;
case 2:
info.type =
info.name =
break;
default:break;
PropertyInfo.INTEGER_CLASS;
Id;
PropertyInfo.STRING_CLASS;
Nombre;
PropertyInfo.INTEGER_CLASS;
Edad;
422
@Override
public void setProperty(int ind, Object val) {
switch(ind) {
case 0:
id = Integer.parseInt(val.toString());
break;
case 1:
nombre = val.toString();
break;
case 2:
edad = Integer.parseInt(val.toString());
break;
default:
break;
}
}
Si deseamos invocar un mtodo pasndole un objeto Dato como parmetro podemos escribir
las siguientes sentencias de ejemplo:
// Objeto de peticin al servidor
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
// Objeto de tipo Dato que vamos a enviar en la peticin
Dato dato = new Dato();
dato.nombre = txtNombre.getText().toString();
dato.edad = Integer.parseInt(txtEdad.getText().toString());
PropertyInfo pi = new PropertyInfo();
pi.setName(dato);
pi.setValue(dato);
pi.setType(dato.getClass());
// Aadimos a la peticin el objeto Dato
request.addProperty(dato);
// Contenedor de la peticin
SoapSerializationEnvelope envelope = new
SoapSerializationEnvelope(SoapEnvelope.VER11);
// Aadimos la peticin al contenedor
envelope.setOutputSoapObject(request);
// Asociamos al espacio de nombres la clase Dato con la clase real //
Java
envelope.addMapping(NAMESPACE, Dato, dato.getClass());
// Aqu van el resto de sentencias de la llamda
Como puedes ver, con esta biblioteca se puede desarrollar una conexin completa con un
servidor de aplicaciones de tipo SOAP.
9. Resumen
Hay que saber al final de esta unidad:
- Una biblioteca (del ingls library) es un conjunto de funciones y utilidades que
tienen una interfaz bien definida y que no se puede ejecutar de forma autnoma:
Su fin es ser utilizada por otros programas.
- Las bibliotecas en Android son funciones y recursos que podemos compilar conjuntamente en un proyecto Android.
- Existen muchas bibliotecas de cdigo libre disponibles en Internet que pueden
simplificar la programacin de un proyecto.
- Android dispone de una API de telefona para gestionar llamadas de telfono y
mensajes cortos (SMS) que se encuentra en el paquete android.telephony.
- Las clases ms importantes de esta API de Android se denominan TelephonyManager y SMSManager.
- La clase Loader permite cargar datos asncronamente, monitoriza la fuente y
muestra nuevos resultados cuando cambia.
- Android dispone de Calendarios donde el usuario puede administrar citas y recordatorios. Existen dos formas de gestionarlos:
Mediante Intenciones (Intents), accediendo indirectamente al Calendario
mediante Actividades.
Utilizando la API oficial que permite gestionarlo de forma directa.
- La API del Calendario de Android se basa en la utilizacin de ContentProvider
(proveedor de contenidos) que utiliza la clase principal CalendarContract.
- Mediante Intenciones podemos realizar las siguientes operaciones con el calendario:
Abrir la aplicacin Calendario en una fecha especfica.
Ver eventos.
Crear nuevos eventos.
Editar eventos existentes.
- El gestor de descargas de Android denominado DownloadManager es un servicio en segundo plano que permite gestionar descargas de larga duracin.
- Existen 3 formas de enviar un correo electrnico en Android:
OAuth 2.0 de Gmail
Intent del tipo message/rfc822
Biblioteca externa JavaMail API
423
Aula Mentor
424
U5 Utilidades avanzadas
1. Introduccin
En esta Unidad vamos a explicar cmo acceder al portapapeles de Android. Adems,
implementaremos Drag and Drop (arrastrar y soltar) y gestionaremos los toques de usuario
sobre la pantalla del dispositivo.
Tambin, repasaremos las recomendaciones de diseo en funcin del tamao y de la
resolucin de la pantalla del dispositivo Android.
Estudiaremos cmo desarrollar aplicaciones multi-idioma mediante un ejemplo prctico.
Finalmente, repasaremos herramientas que ayudan al desarrollo rpido de cdigo en
Android.
2. Portapapeles de Android
Android proporciona un potente portapapeles para copiar y pegar contenidos, que es compatible
tanto con tipos de datos simples como complejos, incluyendo cadenas de texto, estructuras de
datos complejas, datos binarios e, incluso, recursos de aplicaciones.
Los datos de tipo cadena se almacenan directamente en el portapapeles, mientras que en
el caso de los datos complejos lo que se almacena es una referencia que luego utiliza la aplicacin que lee el portapapeles mediante el proveedor de contenidos correspondiente.
La clase principal que se usa para acceder al portapapeles se denomina ClipboardManager, que es
un gestor que obtiene la referencia a la misma invocando la orden getSystemService(CLIPBOARD_
SERVICE).
Para aadir informacin al portapapeles debemos crear un objeto del tipo ClipData, que
contiene la descripcin de la informacin (objeto ClipDescription de tipo matriz) y los datos
en s mismos (uno o ms objetos del tipo ClipData.Item).
El portapapeles slo puede contener un nico dato del tipo ClipData al mismo tiempo.
El objeto del tipo ClipDescription contiene la descripcin de los datos guardados en
una matriz. Estos datos son de tipo MIME. As, las otras aplicaciones pueden gestionar tambin
estos datos del portapapeles en funcin de su tipo MIME.
El objeto del tipo ClipData.Item contiene los datos almacenados y puede ser uno de los
siguientes objetos:
- Cadena de texto.
- URI: se usa para copiar datos complejos de un proveedor de contenidos.
- Intencin: as se copia accesos directos a aplicaciones.
Es posible aadir al portapapeles varios objetos del tipo ClipData.Item al mismo tiempo.
De esta forma, podemos incluir selecciones mltiples en l, por ejemplo, podemos seleccionar
varias opciones de un listado y copiarlas todas al mismo tiempo. Cada una de estas opciones
425
Aula Mentor
426
U5 Utilidades avanzadas
android:id=@+id/copiar_texto_plano
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarTextoPlano
android:text=@string/copiar_texto />
</LinearLayout>
<LinearLayout
android:layout_width=match_parent
android:layout_height=wrap_content
android:orientation=horizontal>
<Button android:id=@+id/copiar_intent
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarIntent
android:text=@string/copiar_intent />
<Button android:id=@+id/copiar_uri
android:layout_width=wrap_content
android:layout_height=wrap_content
android:onClick=copiarUri
android:text=@string/copiar_uri />
</LinearLayout>
<TextView
android:id=@+id/tipo_contenido_portapapeles
android:layout_width=wrap_content
android:layout_height=wrap_content
android:gravity=center_horizontal
android:textStyle=normal
android:textSize=16sp/>
<EditText
android:id=@+id/clip_text
android:layout_width=match_parent
android:layout_height=0px
android:layout_weight=1
android:textStyle=normal/>
</LinearLayout>
427
Aula Mentor
428
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Buscamos las vistas de la interfaz del usuario
setContentView(R.layout.activity_main);
contenidoPortapapeles =
(TextView)findViewById(R.id.tipo_contenido_portapapeles);
mEditText = (EditText)findViewById(R.id.clip_text);
// Buscamos el texto con estilo del fichero /res/strings.xml
// y obtenemos su texto plano
textoEstilo = getText(R.string.texto_estilo);
textoPlano = textoEstilo.toString();
// Lo pasamos a la etiqueta
TextView tv = (TextView)findViewById(R.id.texto_estilo);
tv.setText(textoEstilo);
// Obtenemos referencia al gestor del portapapeles
mClipboard =
(ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
// Aadimos el listener al portapapeles
mClipboard.addPrimaryClipChangedListener(
mPrimaryChangeListener);
// Actualizamos el estado del portapapeles
actualizaPortapapeles();
} // end onCreate
@Override
protected void onDestroy() {
super.onDestroy();
// Hay que liberar el listener!
mClipboard.removePrimaryClipChangedListener(
mPrimaryChangeListener);
}
// Mtodo que copia el texto con estilo
public void copiarTextoEstilo(View button) {
mClipboard.setPrimaryClip(ClipData.newPlainText(Texto con
estilo, textoEstilo));
}
// Mtodo que copia el texto plano
public void copiarTextoPlano(View button) {
mClipboard.setPrimaryClip(ClipData.newPlainText(Texto plano,
textoPlano));
}
// Mtodo que copia un Intent
public void copiarIntent(View button) {
// Creamos el intent
Intent intent = new Intent(Intent.ACTION_VIEW,
U5 Utilidades avanzadas
Uri.parse(http://www.mentor.educacion.es/));
// Lo copiamos al portapapeles
mClipboard.setPrimaryClip(ClipData.newIntent(VIEW intent,
intent));
}
// Mtodo que copia una URI
public void copiarUri(View button) {
mClipboard.setPrimaryClip(ClipData.newRawUri(URI,
Uri.parse(http://www.mentor.educacion.es/)));
}
// Mtodo que actualiza la interfaz del usuario mostrando el
// contenido del portapapeles
void actualizaPortapapeles() {
// Obtenemos los datos del portapapeles
ClipData clip = mClipboard.getPrimaryClip();
// Mostramos la informacin en funcin del contenido
if (clip == null) {
contenidoPortapapeles.setText(Error: sin acceso al
portapapeles);
mEditText.setText();
} else if (clip.getItemAt(0).getText() != null) {
contenidoPortapapeles.setText(El portapapeles contiene
texto del tipo: );
mEditText.setText(clip.getItemAt(0).getText());
} else if (clip.getItemAt(0).getIntent() != null) {
contenidoPortapapeles.setText(El portapapeles contiene un
Intent: );
mEditText.setText(clip.getItemAt(0).getIntent().toUri(0));
} else if (clip.getItemAt(0).getUri() != null) {
contenidoPortapapeles.setText(El portapapeles contiene una
URI: );
mEditText.setText(clip.getItemAt(0).getUri().toString());
} else {
contenidoPortapapeles.setText(Sin contenido en el
portapapeles);
mEditText.setText(Sin datos...);
}
// Ahora obtenemos los tipos mime contenidos en el
// portapapeles
String[] mimeTypes = clip != null ?
clip.getDescription().filterMimeTypes(*/*) : null;
if (mimeTypes != null) {
// Pegamos todos los tipos encontrados
for (int i=0; i<mimeTypes.length; i++) {
contenidoPortapapeles.append(mimeTypes[i]);
contenidoPortapapeles.append(\n);
} // end for i
}
} // end actualizaPortapapeles
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para obtener referencia
al gestor del portapapeles y establecer su listener que salta cuando hay cambios en el
429
Aula Mentor
portapapeles.
En el mtodo onDestroy()liberamos este listener. Es muy importante no olvidarse
de liberar siempre esta variable.
A continuacin, definimos distintos mtodos que copian diferentes tipos de contenidos
mediante el mtodo setPrimaryClip() del portapapeles.
Finalmente, el mtodo actualizaPortapapeles() se encarga de actualizar la interfaz
del usuario mostrando el contenido del portapapeles. Para ello, obtiene los datos del portapapeles con el mtodo getPrimaryClip(), que devuelve un objeto del tipo ClipData para, posteriormente, utilizar este ltimo objeto y leer su descripcin con el mtodo getDescription()
y el contenido en s mismo del primer elemento con getItemAt(0) y aplicar el mtodo que
corresponda en funcin del dato almacenado.
Desde Eclipse ADT puedes abrir el proyecto Ejemplo 1 (Portapapeles) de la Unidad 5. Estudia el cdigo fuente y ejectalo en el AVD para ver el resultado del
programa anterior, en el que hemos utilizado el Portapapeles de Android.
Si ejecutas en Eclipse ADT este Ejemplo 1 en el AVD, vers que se muestra la siguiente
aplicacin:
430
U5 Utilidades avanzadas
431
Aula Mentor
Como puedes observar, la teora es bastante sencilla. Vamos a experimentar con un ejemplo
prctico cmo se aplica.
432
<LinearLayout
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
android:orientation=vertical
android:id=@+id/cestaIV >
<ImageView
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_margin=15dp
android:src=@drawable/cesta
android:layout_gravity=top|center_horizontal />
</LinearLayout>
<TextView android:id=@+id/mensaje
android:text=No hay ninguna en la cesta
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=top|center_horizontal
android:textSize=17sp />
<LinearLayout
android:layout_width=fill_parent
android:layout_height=match_parent
android:orientation=horizontal >
<ImageView
android:id=@+id/manzanaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/manzana
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
U5 Utilidades avanzadas
<ImageView
android:id=@+id/naranjaIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/naranja
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
<ImageView
android:id=@+id/peraIV
android:layout_width=wrap_content
android:layout_height=wrap_content
android:src=@drawable/pera
android:layout_marginLeft=5dp
android:layout_gravity=bottom />
<TextView
android:text=Arrastra frutas a la cesta!
android:layout_width=wrap_content
android:layout_height=wrap_content
android:layout_gravity=bottom
android:layout_marginLeft=5dp
android:layout_marginBottom=3dp
android:textSize=17sp />
</LinearLayout>
</LinearLayout>
433
Aula Mentor
434
U5 Utilidades avanzadas
en la cesta);
else if (nFrutas==1) mensaje.setText(1 fruta
en la cesta);
else mensaje.setText(nFrutas + frutas en la
cesta);
break;
// Si acaba el drag de la cesta, quitamos el fondo en
// la vista
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(null);
default:
break;
}
// Indicamos que hemos gestionado el evento
return true;
}
} // end MiDragListener
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos
las Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para establecer los
listeners OnTouchListener de las frutas que son las Vistas que se arrastran y OnDragListener
de la cesta que las recibe.
Adems, hemos definido el listener MiTouchListener que establece el evento onTouch() de las frutas para la accin ACTION_DOWN (pulsado). En este evento definimos un objeto portapapeles sin datos, construimos la sombra del arrastre de la Vista e iniciamos el arrastre
de la vista con startDrag().
Tambin, hemos definido el listener MiDragListener, que establece el evento onDrag()
de la cesta para las acciones siguientes:
- ACTION_DRAG_STARTED: se inicia el arrastre, en cuyo caso no hacemos nada.
- ACTION_DRAG_ENTERED: el usuario entra en la cesta haciendo drag con una fruta y cambiamos el fondo de esta cesta con la forma enterShape.
- ACTION_DRAG_EXITED: el usuario saca la fruta de la cesta y quitamos la forma del fondo de
la cesta.
- ACTION_DROP: el usuario deja caer la fruta en la cesta y aumentamos el contador de frutas
en la cesta y lo mostramos en la etiqueta correspondiente. Aqu podramos haber cambiado
de sitio la Vista hacindola desaparecer del layout original y pasndola al nuevo. Para ello
basta con escribir el siguiente cdigo sencillo:
// Obtenemos la vista arrastrada
View view = (View) event.getLocalState();
// Obtenemos la vista que contiene a la Vista arrastrada
ViewGroup contenedor = (ViewGroup) view.getParent();
// Quitasmos la vista del contenedor original
contenedor.removeView(view);
// Buscamos la vista que contiene a la Vista receptora
LinearLayout contenedorDestino = (LinearLayout) v;
// Aadimos la Vista arrastrada al contenedor destino
contenedorDestino.addView(view);
// La hacemos visible si la habamos ocultado
// onDrag() anterior
view.setVisibility(View.VISIBLE);
en
el
evento
435
Aula Mentor
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
U5 Utilidades avanzadas
Cada vez que el usuario toca la pantalla con un dedo, el sistema define un punto con la clase
PointF, que almacena la posicin (X, Y) del dedo, el Id para identificarlo y si ndice Index.
Fjate en el siguiente esquema visual:
437
Aula Mentor
En este caso, un objeto de la clase MotionEvent contendr informacin de todos estos puntos.
Un punto estar activo desde que el usuario pulsa sobre la pantalla hasta que deja de presionar.
Se puede obtener el nmero de puntos activos llamando al mtodo getPointerCount()
de la clase anterior.
A partir de Android 2.0, esta clase MotionEvent ampla la lista de constantes para identificar
las acciones posibles de multi-touch. Veamos los nuevos valores y cmo afectan a las acciones
anteriores:
- ACTION_POINTER_DOWN: aparece un nuevo punto (dedo) distinto de los existentes.
- ACTION_POINTER_UP: un punto se levanta (se quita el dedo), pero sigue quedando alguno
activo.
- ACTION_DOWN: se pulsa en la pantalla sin que haya otro punto activo.
- ACTION_UP: se deja de presionar el ltimo punto activo
- ACTION_MOVE: se mueve cualquiera de los puntos activos.
- ACTION_CANCEL: se cancela un gesto.
- ACTION_OUTSIDE: el punto se sale de la vista.
Es posible gestionar tambin las pulsaciones del usuario mediante gestos (del ingls, gestures).
Esta funcionalidad est disponible desde Android 1.6 y veremos un ejemplo muy sencillo al final
de este apartado.
Estudiemos ahora, de forma prctica, cmo gestionar los toques de usuario en la pantalla
de un dispositivo Android. Es recomendable abrir el Ejemplo 3 de esta Unidad para seguir la
explicacin que se ofrece.
La aplicacin que desarrollamos muestra tres pestaas:
- Pestaa 1 (clase UnToqueView.java): presenta una pizarra donde el usuario puede dibujar
con el dedo.
- Pestaa 2 (clase MultitouchView.java): permite al usuario tocar la pantalla con dos o ms
dedos y muestra en la pantalla crculos que se mueven con los dedos
- Pestaa 3 (clase ZoomImageView.java): muestra una imagen cuyo tamao puede cambiar el
usuario haciendo el gesto pitch (dos dedos pegados a la pantalla que se acercan o se alejan).
En cdigo del layout activity_main.xml se incluye el diseo de la Actividad principal que
muestra 3 pestaas:
<android.support.v4.app.FragmentTabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=@android:id/tabhost
android:layout_width=match_parent
android:layout_height=match_parent >
<LinearLayout
android:layout_width=match_parent
android:layout_height=match_parent
android:orientation=vertical >
<TabWidget
android:id=@android:id/tabs
U5 Utilidades avanzadas
android:layout_width=match_parent
android:layout_height=wrap_content
android:layout_weight=0
android:orientation=horizontal />
<FrameLayout
android:id=@+id/tabFrameLayout
android:layout_width=match_parent
android:layout_height=0dp
android:layout_weight=1 />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Buscamos el contenedor de Tabs de la interfaz de usuario en
// el archivo activity_main.xml
FragmentTabHost mTabHost = (FragmentTabHost)
findViewById(android.R.id.tabhost);
// Configuramos el frame que contendr el contenido de la
// pestaa
mTabHost.setup(this, getSupportFragmentManager(),
R.id.tabFrameLayout);
// Aadimos las pestaas que son fragmentos
mTabHost.addTab(
mTabHost.newTabSpec(tab1).setIndicator(Pizarra,
getResources().getDrawable(R.drawable.pizarra)),
PizarraFragmentTab.class, null);
mTabHost.addTab(
mTabHost.newTabSpec(tab2).setIndicator(Multi touch,
getResources().getDrawable(R.drawable.touch)),
MultiTouchFragmentTab.class, null);
mTabHost.addTab(mTabHost.newTabSpec(tab3).setIndicator(
Cambio Tamao,
getResources().getDrawable(R.drawable.resize)),
ResizeFragmentTab.class, null);
} // end onCreate()
} // end clase
Como puedes observar, en el cdigo anterior hemos implementado con fragmentos las pestaas.
Como ya hemos utilizado este tipo de estructuras en la Unidad 2 no vamos a explicar su sintaxis.
Si abres el archivo PizarraFragmentTab.java, vers que hemos desarrollado el fragmento
439
Aula Mentor
440
U5 Utilidades avanzadas
// En funcin del tipo de toque
switch (event.getAction()) {
// Si el dedo toca la pantalla
case MotionEvent.ACTION_DOWN:
// Movemos el camino a la posicin X, Y
path.moveTo(eventX, eventY);
return true;
// Si el dedo se mueve
case MotionEvent.ACTION_MOVE:
// Creamos el camino hasta X, Y mediante una lnea
path.lineTo(eventX, eventY);
break;
// Si se levanta el dedo
case MotionEvent.ACTION_UP:
// No hacemos nada
break;
default:
return false;
} // end case
// Indicamos que se debe redibujar la Vista
invalidate();
return true;
} // end onTouchEvent
} // end clase
Como puedes ver, esta clase se extiende de la clase bsica View, que sobrescribe sus mtodos
onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear
para dibujar en esta Vista.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos el camino (clase Path) que se va creando segn el usuario toca la pantalla y desplaza
el dedo por ella.
Finalmente, en el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla,
obtenemos la posicin del dedo y ejecutamos las siguientes sentencias en funcin de la accin:
- ACTION_DOWN: si el dedo toca la pantalla, entonces movemos el camino a la posicin X, Y.
- ACTION_MOVE: si el dedo se mueve, entonces creamos el camino hasta X, Y mediante una
lnea.
Al final de este mtodo indicamos que se debe redibujar la Vista cada vez que ocurre un evento
onTouch.
La clase MultitouchView se encarga de mostrar crculos cuando el usuario toca la pantalla con
varios dedos a la vez y contiene las siguientes sentencias:
// Clase de tipo Vista que muestra crculos cuando
// el usuario toca la pantalla con varios dedos a la vez
public class MultitouchView extends View {
// Radio de los crculos
private static final int RADIO = 50;
// Matriz que guarda los puntos de la pantalla
private SparseArray<PointF> puntosActivos;
// Pincel para dibujar
private Paint paint;
// Matriz con colores para dibujar crculos
441
Aula Mentor
442
private int[] colores = { Color.GREEN, Color.BLUE, Color.RED,
Color.MAGENTA, Color.BLACK, Color.CYAN, Color.GRAY,
Color.DKGRAY, Color.LTGRAY, Color.YELLOW };
// Constructor de la clase
public MultitouchView(Context context) {
super(context);
// Creamos la matriz de puntos
puntosActivos = new SparseArray<PointF>();
// Inicializamos el pincel
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
// Indicamos que se debe rellenar todo lo que dibuja
// para que el crculo se pinte por dentro
paint.setStyle(Paint.Style.FILL_AND_STROKE);
} // end constructor
// Evento que se lanza cuando es necesario dibujar la Vista
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Dibujamos todos los puntos en crculo con un bucle
int size = puntosActivos.size();
for (int i = 0; i < size; i++) {
// Obtenemos el punto i
PointF punto = puntosActivos.valueAt(i);
// Si obtenemos el punto...
if (punto != null)
// Elegimos un color
paint.setColor(colores[i % 9]);
// Dibujamos el crculo
canvas.drawCircle(punto.x, punto.y, RADIO, paint);
} // end for i
} // end onDraw()
// Evento que salta cuando el usuario toca la pantalla
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (mascaraAction) {
// En el caso de que el usuario tenga el dedo en la pantalla
U5 Utilidades avanzadas
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
// Cuando aparece un nuevo punto entonces lo guardamos en
// la matriz
PointF punto = new PointF();
punto.x = event.getX(pointerIndex);
punto.y = event.getY(pointerIndex);
puntosActivos.put(pointerId, punto);
break;
}
// Si se mueve el dedo
case MotionEvent.ACTION_MOVE: {
// Recorremos todos los puntos y actualizamos su nueva
// posicin
for (int size = event.getPointerCount(), i = 0; i < size;
i++) {
PointF punto = puntosActivos.get(event.getPointerId(i));
if (punto != null) {
punto.x = event.getX(i);
punto.y = event.getY(i);
}
}
break;
}
// Si el dedo se levanta o se cancela
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
// Quitamos el punto activo
puntosActivos.remove(pointerId);
break;
}
} // end switch
// Redibujamos la Vista
invalidate();
// Indicamos que gestionamos el evento
return true;
} // end onTouchEvent
} // end clase
Como puedes observar, de nuevo esta clase se extiende de la clase bsica View, que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el pincel (clase Paint) que vamos a emplear
para dibujar los crculos en esta Vista. Tambin creamos una matriz del tipo SparseArray del
tipo PointF que almacenar las posiciones de los dedos en la pantalla.
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, simplemente
dibujamos los crculos de todos estos puntos que representan dedos en la pantalla utilizando un
bucle de la matriz de puntos.
Finalmente, el evento onTouchEvent() se lanza cuando el usuario toca la pantalla.
Por motivos pedaggicos, hemos incluido en este mtodo un Log del objeto MotionEvent para
ver el valor que toma el objeto cuando se ejecuta la aplicacin y se desplazan los dedos por la
pantalla y se reciben varios eventos. El resultado puede ser similar al siguiente:
443
Aula Mentor
444
Como puedes ver, cuando hay ms de un punto en la pantalla, el evento resulta complejo de
interpretar. Estas acciones las ha podido hacer cualquier punto de los activos o uno nuevo.
Se codifica simultneamente el cdigo de la accin (8 bits menos significativos) e ndice del
punto (siguientes 8 bits). Para extraer la informacin interna por separado, puedes realizar una
operacin lgica utilizando las mscaras de bits correspondientes. Fjate en el siguiente cdigo:
// Utilizando las mscaras de bits siguientes obtenemos el ndice del //
punto en concreto
int pointerIndex = (event.getAction() &
MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
// Para obtener la accin que est ocurriendo podemos utilizar la
// mscara de bits siguientes
int mascaraAction = event.getAction() & MotionEvent.ACTION_MASK;
U5 Utilidades avanzadas
445
Aula Mentor
446
// Dibujamos la imagen
imagen.draw(canvas);
// Restauramos el lienzo
canvas.restore();
} // end onDraw()
// Evento que salta cuando el usuario toca la pantalla
@Override
public boolean onTouchEvent(MotionEvent event) {
// Pasamos el evento al detector de escala
scaleGestureDetector.onTouchEvent(event);
// Redibujamos la Vista
invalidate();
return true;
}
// Listener del ScaleGestureDetector
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
// Si se lanza el evento onScale...
@Override
public boolean onScale(ScaleGestureDetector detector) {
// El nuevo factor de escalado es el anterior
// multiplicado por el valor de escala del evento
factorEscalado *= detector.getScaleFactor();
// No dejamos que el objeto sea muy pequeo o muy grande.
factorEscalado = Math.max(0.2f, Math.min(factorEscalado,
5.0f));
// Indicamos que se debe redibujar la Vista
invalidate();
return true;
} // end onScale()
} // end ScaleListener
} // end clase
Como puedes observar, de nuevo esta clase se extiende de la clase bsica View que sobrescribe
igualmente sus mtodos onDraw() y onTouchEvent().
En el constructor de esta clase definimos el detector de cambios de escala con su listener correspondiente (clase ScaleGestureDetector).
En el evento onDraw(), que se lanza cuando es necesario dibujar esta Vista, se guarda el
estado del lienzo y as evitamos que parpadee, escalamos el lienzo en ancho y alto, dibujamos
la imagen y restauramos el lienzo.
En el evento onTouchEvent(), que se lanza cuando el usuario toca la pantalla, pasamos
el evento al detector de la escala anterior y redibujamos la Vista.
Finalmente, definimos listener del ScaleGestureDetector del tipo SimpleOnScaleGestureListener que lanza el evento onScale. En este bloque de cdigo fijamos el nuevo
factor de escalado teniendo en cuenta el valor de escala del evento e indicamos que se debe
redibujar la Vista.
El multi-touch no est disponible en el AVD; por lo tanto, es necesario probar esta aplicacin en un dispositivo real de Android.
Adems, para poder depurar la aplicacin en un dispositivo real de Android es necesario indicarlo en el archivo manifest en la etiqueta <application> mediante el atributo
android:debuggable=true.
U5 Utilidades avanzadas
Si ejecutas en Eclipse ADT este Ejemplo 3 en un dispositivo real, vers que se muestra la
siguiente aplicacin:
447
Aula Mentor
Recomendamos que pruebes las distintas ventanas para ver el comportamiento de la aplicacin.
Para poder usar un dispositivo real desde Eclipse ADT es necesario conectar este
dispositivo mediante un cable al ordenador y modificar sus Ajustes en las opciones siguientes:
En Opciones del desarrollador, marcar Depuracin de USB.
En Seguridad, sealar Fuentes desconocidas.
Si se usan pxeles para definir las dimensiones de una Vista en un dispositivo cuya pantalla tiene
una densidad de pxeles baja, obtendramos el siguiente resultado visual:
U5 Utilidades avanzadas
Sin embargo, en un dispositivo cuya pantalla tiene una resolucin alta, el mismo diseo tendra
este aspecto visual:
449
Como puedes ver, la percepcin del usuario sera muy distinta y, realmente, la aplicacin
cambiara tanto que su experiencia sera cambiante.
Los mltiples dispositivos con Android disponibles en el mercado hacen que el diseo
de interfaces visuales en este sistema operativo sea ms compleja. En este apartado vamos a dar
una serie de recomendaciones sobre este asunto.
Aula Mentor
A continuacin, se muestra una tabla con todos los tamaos de pantalla estndar de Android y
su densidad de pixeles:
Densidad baja
(120) ldpi
450
Densidad media
(160) mdpi
Densidad alta
(240) hdpi
Densidad
muy alta
(320) xhdpi
Pantalla
pequea
QVGA: 240x320
480x640
Pantalla
normal
WQVGA400 :240x400
HVGA: 320x480
WQVGA: 240x432
WVGA: 480x800
WVGA854:
640x960
480x854
600x1024
Pantalla
grande
WVGA: 480x800
WVGA854: 400x854
Pantalla muy
1024x600
grande
WVGA800: 480x800
WVGA854: 480x854
600x1024
WXGA: 1280x800
1024x768
1280x768
1536x1152
1920x1152
1920x1200
2048x1536
2560x1536
2560x1600
U5 Utilidades avanzadas
- Pixeles independientes de la densidad (DP): es una unidad de pixel virtual que se utiliza
en el diseo de la interfaz de usuario con el fin de expresar dimensiones o la posicin de
una Vista de una manera independiente de la densidad de la pantalla. En una pantalla de
160 DPI, 1 DP equivale a un pxel fsico, que es la densidad de referencia asumida por el
sistema como la densidad media de la pantalla. En tiempo de ejecucin, Android modifica,
de forma transparente, cualquier ampliacin de las unidades de DP segn sea necesario, en
base a la densidad real de la pantalla que se est utilizando. La conversin de las unidades
de DP a los pxeles de la pantalla es simple: N de pxeles = DP * (DPI / 160). Por ejemplo,
en una pantalla de 240 DPI, 1 DP es igual a 1,5 pxeles fsicos.
Es recomendable utilizar siempre DP como unidad para definir los diseos de la interfaz de usuario para asegurarse de que se mostrar correctamente en pantallas
con diferentes densidades.
Es recomendable incluir siempre las imgenes MDPI y HDPI para cualquier aplicacin Android
que desarrolles.
Si utilizas un icono de tamao 100100 pixeles en la interfaz del usuario en un layout
cuyo dispositivo tiene una pantalla de 320480 pxeles, este mismo icono se vera demasiado
pequeo en un dispositivo con una pantalla HDPI, que tiene ms puntos por pulgada:
451
Aula Mentor
452
200 x 200
150 x 150
100 x 100
75 x 75
Se recomienda que los objetos que pueda pulsar el usuario con el dedo tengan un
tamao, al menos, de 45x45 dp.
U5 Utilidades avanzadas
La clase Locale se usa para obtener informacin del idioma activo en el sistema operativo y
para formatear fechas y nmeros.
453
Aula Mentor
454
<LinearLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent>
<TextView
android:id=@+id/etiqueta
android:text=@string/texto_etiqueta
android:layout_width=match_parent
android:layout_height=wrap_content
android:gravity=center
android:layout_margin=20dp
android:isScrollContainer=true
android:background=#AAAAAA
android:textColor=#333333/>
<Spinner
android:layout_height=wrap_content
android:layout_width=match_parent
android:id=@+id/spinnerOfCharacterRaces
android:entries=@array/opciones/>
<Button
android:layout_width=match_parent
android:layout_height=wrap_content
android:id=@+id/boton
android:text=@string/texto_boton
android:gravity=center/>
</LinearLayout>
U5 Utilidades avanzadas
setContentView(R.layout.activity_main);
etiqueta = (TextView) findViewById(R.id.etiqueta);
Button button = (Button) findViewById(R.id.boton);
button.setOnClickListener(this);
// Obtenemos el idioma local
Locale locale = Locale.getDefault();
Toast.makeText(this, getString(R.string.idioma_actual) +
+ locale.getLanguage() + ,Toast.LENGTH_LONG).show();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.boton) {
// Cambiamos el texto de la etiqueta con un literal
etiqueta.setText(getString(R.string.texto_boton2));
}
}
} // end clase
En el cdigo anterior puedes ver que en el evento onCreate(), como es habitual, buscamos las
Vistas de la interfaz del usuario. Adems, aprovechamos este mtodo para mostrar un mensaje
de tipo Toast con el cdigo ISO del idioma actual del dispositivo mediante la clase Locale con
el mtodo getLanguage().
En el evento onClick() del botn cambiamos el texto de la etiqueta con un literal.
Como puedes observar, en ningn momento seleccionamos un idioma en concreto, es Android
el que lo hace automticamente por nosotros.
Para definir los idiomas de la aplicacin, primero debemos rellenar los archivos de la
carpeta res\values que establecen los literales del idioma por defecto. En las carpetas con la
estructura de nombre res\values-cdigo_ISO_idioma se incluyen la misma informacin que
la carpeta anterior traducida.
En este ejemplo, hemos aadido el castellano como idioma extra. En Eclipse ADT, la estructura
de carpetas tiene el siguiente aspecto:
455
Aula Mentor
456
Fjate que en las carpetas values hemos incluido tambin el fichero arrays.xml,
que se traduce en funcin del idioma y se usa para rellenar las opciones del Spinner de la aplicacin.
U5 Utilidades avanzadas
Si ejecutas en Eclipse ADT este Ejemplo 4 en el AVD, vers que se muestra la siguiente
aplicacin en ingls:
457
Para cambiar el idioma directamente desde el AVD, debes cambiar la configuracin del dispositivo
virtual. Fjate en las siguientes capturas de pantalla:
Aula Mentor
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
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.
A todos los programadores nos gusta mejorar una aplicacin reutilizando mtodos y funciones
sencillos, eficientes y ya probados por otro programador que realizan un cometido especfico.
Cuando juntamos todos estos trocitos de cdigo fuente, obtenemos una aplicacin compleja con
mucha funcionalidad adicional.
Para esto, podemos utilizar Snippets. Un snippet es una porcin de cdigo Android que
puedes aadir a un programa y que realiza una funcin especfica.
Existen multitud de pginas en Internet donde puedes encontrarlos. Por ejemplo, Wikicode incluye snippets y tutoriales de muchsimos lenguajes de programacin. Adems de Android,
encontramos snippets de PHP, JS, HTML, CSS, SQL, CSV, JAVA, C++, IPHONE, PERL, etctera.
En el apartado de Android hay unos 50 snippets; entre otros, podemos encontrar:
- Obtener versin API Android.
- ImageButton con fondo transparente.
- Activar el Vibrador del movil en Android.
- Crear un men de opciones en Android.
- Detectar conexin de datos en android.
- Hacer consulta con cursores desde Android.
Aula Mentor
460
Te recomendamos que busques en Internet snippets que te permitan mejorar las aplicaciones
de forma rpida y sencilla, si bien no recomendamos abusar de ello ya que el programador o
programadora debe entender cmo funciona su cdigo fuente.
U5 Utilidades avanzadas
8. Resumen
Hay que saber al final de esta unidad:
- Android dispone de un potente portapapeles para copiar y pegar contenidos.
- La clase principal que se usa para acceder al portapapeles es ClipboardManager.
- El portapapeles slo puede contener un nico dato del tipo ClipData al mismo
tiempo.
- Arrastrar y soltar (drag and drop) es una expresin informtica que se refiere a la
accin de mover objetos de la interfaz del usuario entre dos partes visuales de
las aplicaciones.
- Para que otra Vista se convierta en receptora del elemento arrastrado, debemos
indicarlo implementando su listener View.OnDragListener.
- La gestin bsica de la pantalla tctil se basa en implementar el mtodo OnTouchEvent() de la clase View.
- Las pantallas multitctil permiten detectar una entrada de varios dedos. Esta funcionalidad est disponible a partir de Android 2.0.
- Tambin es posible gestionar las pulsaciones del usuario sobre la pantalla mediante gestos (del ingls, gestures). Est disponible desde Android 1.6.
- Internacionalizacin (abreviadamente i18n) es el proceso de agregar diferentes
literales traducidos de distintos idiomas a un programa.
- Para internacionalizar una aplicacin en Android hay que crear una carpeta llamada res\values-sufijo que contenga el archivo strings.xml con los literales de
un idioma.
- Un snippet es una porcin de cdigo Android que puedes aadir a un programa
y que realiza una funcin especfica.
461