Vous êtes sur la page 1sur 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Android / DAM2
IES Chan do Monte

Este obra est bajo una licencia de Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional.

Portions of this document are reproduced from work created and shared by the Android Open Source Project
and used according to terms described in the Creative Commons 2.5 Attribution License.

1 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Contenidos
Versiones de la plataforma y nmero de API.......................................................................................4
Entorno de trabajo y utilidades............................................................................................................5
Entornos de programacin..............................................................................................................5
AVD Manager...................................................................................................................................5
ADB..................................................................................................................................................6
Acceso desde lnea de comandos sqlite3.....................................................................................8
Manifiesto...........................................................................................................................................10
Proceso de construccin de una aplicacin.......................................................................................12
Programando para distintas pantallas................................................................................................14
Conceptos......................................................................................................................................14
Unidades........................................................................................................................................15
Consejos.........................................................................................................................................16
Recursos..............................................................................................................................................17
Acceso............................................................................................................................................17
Tipos de recursos y calificadores de configuracin.......................................................................17
Actividades.........................................................................................................................................19
Ciclo de vida...................................................................................................................................19
Interfaces............................................................................................................................................20
View y ViewGroup..........................................................................................................................20
Layouts...........................................................................................................................................20
Cdigo de manejo del interfaz.......................................................................................................20
Implementacin del clic de un botn.......................................................................................21
Notificaciones.....................................................................................................................................23
Toast...............................................................................................................................................23
Snackbar.........................................................................................................................................23
Intents.................................................................................................................................................24
Almacenamiento................................................................................................................................26
Preferencias compartidas (Shared Preferences)............................................................................26
Pantalla de preferencias............................................................................................................27
Almacenamiento interno...............................................................................................................29
Almacenamiento externo..............................................................................................................29
Almacenamiento a travs de la red...............................................................................................30
Base de datos.................................................................................................................................30
Gestor de base de datos SQLite..........................................................................................................31
Caractersticas bsicas...................................................................................................................31
Creacin y acceso a la base de datos.............................................................................................32
Operaciones con la base de datos.................................................................................................33
Recuperacin de datos a travs de cursores.................................................................................34
Listas...................................................................................................................................................35
Poblar la lista desde el cdigo........................................................................................................35
2 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Poblar la lista desde un recurso string-array.................................................................................35


Poblar un Spinner desde clases propias........................................................................................36
Poblar un ListActivity desde clases propias...................................................................................37
Personalizar una lista estndar......................................................................................................37
Personalizando el interfaz de la lista SIN optimizacin..................................................................39
Personalizando el interfaz de la lista CON optimizacin................................................................40
Ejemplo Mantenimiento de Clientes...............................................................................................42
Fragmentos.........................................................................................................................................47
Soporte para versiones antiguas....................................................................................................47
Ciclo de vida...................................................................................................................................47
Gestin esttica.............................................................................................................................48
Gestin dinmica...........................................................................................................................49
Comunicacin con otros fragmentos.............................................................................................49
Ejemplo: Fragmento para visualizar un importe con dos monedas..............................................50
Ejemplo INCORRECTO de fragmentos: Maestro/Detalle en port/land..........................................52
Ejemplo CORRECTO de fragmentos: Maestro/Detalle en port/land.............................................55
Hilos con AsyncTask............................................................................................................................57
Google Services Google Maps Android API v2................................................................................60
Licencia de uso del servicio............................................................................................................60
Instalacin del servicio...................................................................................................................60
Obtencin de una clave del API.....................................................................................................62
Obtencin del certificado de nuestra aplicacin......................................................................63
Crear un proyecto en la Consola de Google API / Google Developers Console.......................64
Aadir la API key al manifiesto..................................................................................................64
Otros ajustes en el manifiesto.......................................................................................................65
Mapas............................................................................................................................................66
Tipos..........................................................................................................................................66
Interiores: planos de plantas.....................................................................................................68
Estado inicial.............................................................................................................................68
Dibujando sobre el mapa..........................................................................................................69
Cambiando la vista....................................................................................................................69
Ejemplo: Marcadores y lneas...................................................................................................69
Localizacin.........................................................................................................................................71
Paquete android.location..............................................................................................................71
Juntndolo todo.............................................................................................................................73
Bibliografa..........................................................................................................................................75

3 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Versiones de la plataforma y nmero de API


El nmero de API es un entero que identifica de manera unvoca la revisin de cualquier
versin de la plataforma Android.
Puede verse en http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels

Versin de la plataforma

N de API Nombre

...
Android 6.0

23

MARSHMALLOW

Android 5.1

22

LOLLIPOP_MR1

Android 5.0

21

LOLLIPOP

Android 4.4W

20

KITKAT_WATCH (for Wearables)

Android 4.4

19

KITKAT

Android 4.3

18

JELLY_BEAN_MR2

Android 4.2, 4.2.2

17

JELLY_BEAN_MR1

Android 4.1, 4.1.1

16

JELLY_BEAN

Android 4.0.3, 4.0.4

15

ICE_CREAM_SANDWICH_MR1

Android 4.0, 4.0.1, 4.0.2

14

ICE_CREAM_SANDWICH

Android 3.2

13

HONEYCOMB_MR2

Android 3.1.x

12

HONEYCOMB_MR1

Android 3.0.x

11

HONEYCOMB

Android 2.3.4, 2.3.3

10

GINGERBREAD_MR1

Android 2.3.2, 2.3.1, 2.3

GINGERBREAD

Android 2.2.x

FROYO

Android 2.1.x

ECLAIR_MR1

Android 2.0.1

ECLAIR_0_1

Android 2.0

ECLAIR

Android 1.6

DONUT

Android 1.5

CUPCAKE

Android 1.1

BASE_1_1

Android 1.0

BASE

4 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Entorno de trabajo y utilidades


Entornos de programacin
Existen multitud de posibilidades para elegir tanto lenguajes como entornos de
programacin para el desarrollo de aplicaciones en Android.
A da de hoy el lenguaje de programacin ms adecuado es Java, aunque es fcil encontrar
otras posibilidades para desarrollar utilizando C#, Basic, o incluso utilizando HTML5 (+CSS +
JavaScript).
Los entornos ms utilizados desde Java seran, por una parte, utilizar la plataforma Eclipse,
que se puede bajar directamente de developer.android.com/sdk (Eclipse ADT), con el SDK y las
herramientas necesarias ya preconfiguradas.
Otra posibilidad sera utilizar el entorno de desarrollo Android Studio, disponible en
http://developer.android.com/sdk/installing/studio.html , actualmente todava en versin Beta.
Otras posibilidades pasan por utilizar productos tipo Phone Gap http://phonegap.com/
para desarrollar en HTML+CSS+JavaScript, Basic4Android, AIDE, etc.
Por ltimo comentar el producto de Xamarin que nos permite, a partir del mismo cdigo
C#, generar salida para iOS, Android, Windows, Mac, etc. Son los mismos que pertenecan a la
empresa Ximian, creadora de la plataforma Mono (alternativa libre a .NET), y que fu comprada
por Novell, que posteriormente cedi a Xamarn los derechos de dicho producto.

AVD Manager
El gestor AVD (Android Virtual Devices) nos permite de una manera grfica crear y gestionar
distintos dispositivos android necesarios para probar testear nuestras aplicaciones.
Podremos emular multitud de caractersticas en distintos dispositivos de una manera
sencilla. Entre las caractersticas que podemos configurar se encuentran:

5 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Versin del S.O.

Tamao de memoria RAM

Soporte y tamao de tarjeta SD

Tamaos y densidades de pantalla

Soporte de trackball, teclado, GPS, acelermetro, grabacin de audio, etc.

Aunque los AVD's creados funcionan perfectamente, la realidad es que la emulacin resulta
especialmente lenta. Existen otras alternativas ms rpidas como puede ser el emulador
Genymotion, proporcionado como una mquina virtual de Virtual Box.

ADB
ADB (Android Debug Bridge) es una utilidad de lnea de comandos que nos permite
comunicarnos con un dispositivo Android que tengamos conectado al equipo, o bien con un
emulador. Su ubicacin es en la carpeta platform-tools en la carpeta del SDK de Android. Si se va a
trabajar mucho con dicha utilidad, no es mala idea aadirla a la variable de entorno PATH.
Aunque lo habitual es conectar el dispositivo fsico va cable USB, es posible acceder a
travs de Wi-Fi.
C:\>adb
Android Debug Bridge version 1.0.29
-d
-e
-s <serial number>
-p <product name or path>

devices
connect <host>[:<port>]
disconnect [<host>[:<port>]]

device commands:
adb push <local> <remote>
adb pull <remote> [<local>]
adb sync [ <directory> ]
adb
adb
adb
adb
adb

adb

- directs command to the only connected USB device


returns an error if more than one USB device is present.
- directs command to the only running emulator.
returns an error if more than one emulator is running.
- directs command to the USB device or emulator with
the given serial number. Overrides ANDROID_SERIAL
environment variable.
- simple product name like 'sooner', or
a relative/absolute path to a product
out directory like 'out/target/product/sooner'.
If -p is not specified, the ANDROID_PRODUCT_OUT
environment variable is used, which must be an absolute path.
- list all connected devices
- connect to a device via TCP/IP
Port 5555 is used by default if no port number is specified.
- disconnect from a TCP/IP device.
Port 5555 is used by default if no port number is specified.
Using this command with no additional arguments
will disconnect from all connected TCP/IP devices.

- copy file/dir to device


- copy file/dir from device
- copy host->device only if changed
(-l means list but don't copy)
(see 'adb help all')
shell
- run remote shell interactively
shell <command>
- run remote shell command
emu <command>
- run emulator console command
logcat [ <filter-spec> ] - View device log
forward <local> <remote> - forward socket connections
forward specs are one of:
tcp:<port>
localabstract:<unix domain socket name>
localreserved:<unix domain socket name>
localfilesystem:<unix domain socket name>
dev:<character device name>
jdwp:<process pid> (remote only)
jdwp
- list PIDs of processes hosting a JDWP transport

6 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

adb install [-l] [-r] [-s] [--algo <algorithm name> --key <hex-encoded key> --iv <hex-encoded iv>] <file>
- push this package file to the device and install it
('-l' means forward-lock the app)
('-r' means reinstall the app, keeping its data)
('-s' means install on SD card instead of internal storage)
('--algo', '--key', and '--iv' mean the file is encrypted already)
adb uninstall [-k] <package> - remove this app package from the device
('-k' means keep the data and cache directories)
adb bugreport

- return all information from the device


that should be included in a bug report.

adb backup [-f <file>] [-apk|-noapk] [-shared|-noshared] [-all] [-system|-nosystem] [<packages...>]


- write an archive of the device's data to <file>.
If no -f option is supplied then the data is written
to "backup.ab" in the current directory.
(-apk|-noapk enable/disable backup of the .apks themselves
in the archive; the default is noapk.)
(-shared|-noshared enable/disable backup of the device's
shared storage / SD card contents; the default is noshared.)
(-all means to back up all installed applications)
(-system|-nosystem toggles whether -all automatically includes
system applications; the default is to include system apps)
(<packages...> is the list of applications to be backed up. If
the -all or -shared flags are passed, then the package
list is optional. Applications explicitly given on the
command line will be included even if -nosystem would
ordinarily cause them to be omitted.)
adb restore <file>

- restore device contents from the <file> backup archive

adb help
adb version

- show this help message


- show version num

scripting:
adb wait-for-device
- block until device is online
adb start-server
- ensure that there is a server running
adb kill-server
- kill the server if it is running
adb get-state
- prints: offline | bootloader | device
adb get-serialno
- prints: <serial-number>
adb status-window
- continuously print device status for a specified device
adb remount
- remounts the /system partition on the device read-write
adb reboot [bootloader|recovery] - reboots the device, optionally into the bootloader or recovery program
adb reboot-bootloader
- reboots the device into the bootloader
adb root
- restarts the adbd daemon with root permissions
adb usb
- restarts the adbd daemon listening on USB
adb tcpip <port>
- restarts the adbd daemon listening on TCP on the specified port
networking:
adb ppp <tty> [parameters]
- Run PPP over USB.
Note: you should not automatically start a PPP connection.
<tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1
[parameters] - Eg. defaultroute debug dump local notty usepeerdns
adb sync notes: adb sync [ <directory> ]
<localdir> can be interpreted in several ways:
- If <directory> is not specified, both /system and /data partitions will be updated.
- If it is "system" or "data", only the corresponding partition is updated.
environmental variables:
ADB_TRACE
ANDROID_SERIAL
ANDROID_LOG_TAGS

- Print debug information. A comma separated list of the following values


1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp
- The serial number to connect to. -s takes priority over this if given.
- When used with the logcat option, only these debug tags are printed.

Especialmente interesante es el shell que nos proporciona el adb, donde podemos ejecutar
ciertos comandos de un shell UNIX as como utilidades como sqlite3 para acceder a bases de datos
SQLite, o screenrecord (API >=19 ~ v.4.4), para grabar en video la actividad de la pantalla.
En el caso de disponer de varios dispositivos conectados simultneamente (virtuales o no),
una llamada directa a adb shell nos informa de que hay varios dispositivos conectados. En ese caso
debemos proceder de la siguiente manera:
7 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

C:\...\> adb devices


List of devices attached
04c3ae8ddc5bd06a
device
192.168.56.101:5555
device
C:\...\>adb -s 192.168.56.101:5555 shell
shell@android:/ #

Otra posibilidad sera asignar el serial del dispositivo a la variable de entorno


ANDROID_SERIAL, y as ejecutar el adb shell sin problemas.

C:\...\>set ANDROID_SERIAL=192.168.56.101:5555
C:\...\>adb shell
shell@android:/ #

Para trabajar directamente con el dispositivo fsico hay que recordar habilitar el Modo
depuracin USB. Si con adb devices en vez de aparecernos el dispositivo como device aparece
unauthorized, hay que aceptar en el dispositivo la huella digital (clave RSA) para autorizar a ese
ordenador a acceder al dispositivo

Acceso desde lnea de comandos sqlite3


Desde el shell del adb podemos acceder a sqlite3, el programa de lnea de comandos para
crear y gestionar bases de datos en SQLite.
Lo primero es localizar la base de datos para acceder a ella a travs de la utilidad. En
nuestro caso estar situada en un emulador Android, por lo que debemos conectar primero el adb.

C:\>adb connect 192.168.56.101


* daemon not running. starting it now on port 5037 *
* daemon started successfully *
connected to 192.168.56.101:5555

Una vez tenemos acceso al emulador nos moveremos hasta la localizacin de la base de
datos y la abriremos con la utilidad sqlite3, pasndole su nombre como parmetro.

C:\>adb shell
shell@android:/ $ su
su
Test prop
no androVM.su.bypass prop -> su access rights managed by the SuperUser app
shell@android:/ # cd data/data/com.empresa.app /databases
cd data/data/com.empresa.app /databases

8 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

shell@android:/data/data/com.empresa.app/databases # sqlite3 bd.sqlite


SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

Tenga especial cuidado al escribir el nombre de la base de datos pues es case sensitive, y de
no coincidir con la existente, se creara una vaca.
Podemos escribir comandos precedidos por un punto (.comando) o SQL terminado en ;

sqlite> .help
.help
.backup ?DB? FILE
.bail ON|OFF
.databases
.dump ?TABLE? ...
.echo ON|OFF
.exit
.explain ?ON|OFF?
.header(s) ON|OFF
.help
.import FILE TABLE
.indices ?TABLE?
.log FILE|off
.mode MODE ?TABLE?

.nullvalue STRING
.output FILENAME
.output stdout
.prompt MAIN CONTINUE
.quit
.read FILENAME
.restore ?DB? FILE
.schema ?TABLE?
.separator STRING
.show
.stats ON|OFF
.tables ?TABLE?
.timeout MS
.vfsname ?AUX?
.width NUM1 NUM2 ...
.timer ON|OFF
sqlite>

Backup DB (default "main") to FILE


Stop after hitting an error. Default OFF
List names and files of attached databases
Dump the database in an SQL text format
If TABLE specified, only dump tables matching
LIKE pattern TABLE.
Turn command echo on or off
Exit this program
Turn output mode suitable for EXPLAIN on or off.
With no args, it turns EXPLAIN on.
Turn display of headers on or off
Show this message
Import data from FILE into TABLE
Show names of all indices
If TABLE specified, only show indices for tables
matching LIKE pattern TABLE.
Turn logging on or off. FILE can be stderr/stdout
Set output mode where MODE is one of:
csv
Comma-separated values
column
Left-aligned columns. (See .width)
html
HTML <table> code
insert
SQL insert statements for TABLE
line
One value per line
list
Values delimited by .separator string
tabs
Tab-separated values
tcl
TCL list elements
Print STRING in place of NULL values
Send output to FILENAME
Send output to the screen
Replace the standard prompts
Exit this program
Execute SQL in FILENAME
Restore content of DB (default "main") from FILE
Show the CREATE statements
If TABLE specified, only show tables matching
LIKE pattern TABLE.
Change separator used by output mode and .import
Show the current values for various settings
Turn stats on or off
List names of tables
If TABLE specified, only list tables matching
LIKE pattern TABLE.
Try opening locked tables for MS milliseconds
Print the name of the VFS stack
Set column widths for "column" mode
Turn the CPU timer measurement on or off

9 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Manifiesto
Toda aplicacin Android necesita un archivo denominado AndroidManifest.xml en su
carpeta raz. La estructura general y los elementos (sin atributos ni contenido) se pueden observar
a continuacin:
<manifest>
<uses-permission />

permisos que necesita la aplicacin y que el usuario debe aprobar en


la instalacin

<permission />
<permission-tree />
<permission-group />
<instrumentation />
<uses-sdk />

permite indicar la compatibilidad de la aplicacin con las distintas


versiones del sistema, indicando el API (n entero) de la versin
mnima, mxima y aquella para la que fu desarrollada.

<uses-configuration />

Indica qu caractersticas hardware/software necesita la aplicacin,


para evitar instalarla en un dispositivo que carezca de ellas (ciertos
tipos de teclado, de pantalla ...)

<uses-feature />

Informa de los elementos hardware/software de los que depende la


aplicacin, aunque algunos pueden ser opcionales. Se incluir un
elemento por caracterstica (ver tabla adjunta)

<supports-screens />

Permite indicar qu tamaos de pantalla soporta la aplicacin


(pequeas, normales, grandes, extra-grandes o lmitar por dp's)

<compatible-screens />

Indica las combinaciones de tamao y densidad para las que la


aplicacin es compatible. Android no lee nunca esta informacin sino
que es para filtrar aplicaciones para el usuario. Se recomienda no
usarla y utilizar mejor el elemento anterior.

<supports-gl-texture />
<application>

Declara que la aplicacin soporta un formato de compresin de una


textura de OpenGL
La declaracin de la aplicacin y sus componentes

<activity>

Un elemento por cada actividad de la aplicacin

<intent-filter>
<action />
<category />
<data />
</intent-filter>
<meta-data />
</activity>

Indica los tipos de intents a los que puede responder

<activity-alias>
<intent-filter>
...
</intent-filter>
<meta-data />
</activity-alias>
<service>
<intent-filter>
...
</intent-filter>
<meta-data/>
</service>

La declaracin de un servicio, componentes sin interfaz


que ejecutan en segundo plano alguna tarea.

10 de 75

IES Chan do Monte DAM2 Android


<receiver>
<intent-filter>
...
</intent-filter>
<meta-data />
</receiver>

david@edu.xunta.es
Declara un BroadCastReceiver, el cual permite a la aplicacin recibir
intents que son enviados en modo difusin (broadcast)

<provider>

Declara un ContentProvider en nuestra aplicacin, lo cual


proporciona un acceso estructurado a nuestros datos.

<grant-uri-permission />
<meta-data />
<path-permission />
</provider>
<uses-library />

Especifica una librera que necesita nuestra aplicacin.

</application>
</manifest>

Lista de caractersticas hardware. Incluir una en cada <uses-feature>


Audio

android.hardware.audio.low_latency

Bluetooth

android.hardware.bluetooth

Camera

android.hardware.camera
android.hardware.camera.autofocus
android.hardware.camera.flash
android.hardware.camera.front
android.hardware.camera.any

Location

android.hardware.location
android.hardware.location.network
android.hardware.location.gps

Microphone

android.hardware.microphone

NFC

android.hardware.nfc

Sensors

android.hardware.sensor.accelerometer
android.hardware.sensor.barometer
android.hardware.sensor.compass
android.hardware.sensor.gyroscope
android.hardware.sensor.light
android.hardware.sensor.proximity

Screen

android.hardware.screen.landscape
android.hardware.screen.portrait

Telephony

android.hardware.telephony
android.hardware.telephony.cdma
android.hardware.telephony.gsm

Television

android.hardware.type.television

Touchscreen

android.hardware.faketouch
android.hardware.faketouch.multitouch.distinct
android.hardware.faketouch.multitouch.jazzhand
android.hardware.touchscreen
android.hardware.touchscreen.multitouch
android.hardware.touchscreen.multitouch.distinct
android.hardware.touchscreen.multitouch.jazzhand

USB

android.hardware.usb.host
android.hardware.usb.accessory

Wifi

android.hardware.wifi

Ver http://developer.android.com/guide/topics/manifest/manifest-intro.html
11 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Proceso de construccin de una aplicacin


El proceso de construccin de una aplicacin comienza con la compilacin y
empaquetamiento de todos los componentes necesarios:
El cdigo fuente .class se compila a archivos .dex (Dalvik byte code en el futuro ART )
Los recursos (res/...) compilados en resources.arsc
Archivos de recursos no compilados (p.e. imgenes)
Una versin binaria del AndroidManifest.xml

Para ejecutar una aplicacin en un dispositivo/emulador debe estar firmada. Cuando


estamos desarrollando la aplicacin, las herramientas que utilizamos la firman por defecto en
modo debug, utilizando una contrasea pblicamente conocida. Cuando queramos distribuir la
aplicacin (Google Play, etc.) entonces ser cuando firmemos la aplicacin con nuestra clave
privada.
En la siguiente figura se puede ver en detalle todo el proceso de construccin del APK:
La herramienta aapt (Android Asset Packaging Tool) lee el manifiesto y los archivos de
recursos para compilar todo y generar el archivo R.java, que contiene referencias que
podemos utilizar para identificar dichos recursos.
La herramienta aidl convierte interfaces .aidl en interfaces Java. Los interfaces AIDL
(Android Interface Description Language) son similares a otros IDL, usados para definir el
interfaz que utilizarn un cliente y un servicio para comunicarse utilizando IPC (InterProcess
Communication). En Android slo sern necesarios si estamos desarrollando una
aplicacin que necesita crear hilos para dar servicio a potenciales aplicaciones cliente.
Se compila todo el cdigo Java (src/...), junto con R.java y los .aidl, y se generan los .class
La herramienta dex convierte los .class (y .class externos) a .dex (Dalvik byte code o ART).
La herramienta apkbuilder empaqueta en un .apk todo lo generado anteriormente
(recursos compilados, recursos no compilados y .dex).
12 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Se firma la aplicacin (en modo debug o en modo release).


Si se firma en modo release, entonces se utiliza la herramienta zipalign para alinear el .apk
(optimiza la memoria RAM usada cuando la aplicacin corre en un dispositivo, alinea los
recursos no comprimidos como imagenes o raws de manera adecuada para su mejor
acceso desde el cdigo). Esta aplicacin debe utilizarse siempre despus de firmar la
aplicacin y justo antes de distribuir la aplicacin, pues en otro caso, el firmado del archivo
deshara el alineamiento.

Ver http://developer.android.com/tools/building
13 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Programando para distintas pantallas


A la hora de programar aplicaciones debemos tener en cuenta que existen gran cantidad de
dispositivos, cada uno con sus caractersticas especficas de tamao, resolucin, etc. Depende de
nosotros que nuestra aplicacin pueda ajustarse de la mejor manera posible a todas las posibles
combinaciones.

Conceptos
Tamao de pantalla
Tamao fsico, medido como la diagonal de la pantalla.
Para simplificar, Android agrupa todas las posibles pantallas existentes en cuatro grupos:
small (pequea), normal (normal), large (grande) y xlarge (extra-grande)
Densidad de pantalla
La cantidad de pixels en una cierta rea fsica de la pantalla. Suele medirse en puntos por
pulgada (dpi dots per inch).
Para simplificar, Android agrupa todas las posible densidades existentes en cuatro grupos:
ldpi (baja), mdpi (media), hdpi (alta) y xhdpi (extra-alta)
Orientacin
La orientacin de la pantalla desde el punto de vista del usuario. Puede ser vertical o modo
retrato (portrait) y horizontal o modo paisaje (landscape), y por lo general hay que tener en cuenta
que es posible que pueda cambiar en tiempo de ejecucin.
Resolucin
El nmero total de pixels fsicos de la pantalla. Las aplicaciones no deben trabajar
directamente con este dato sino con tamaos de pantalla y densidades.

14 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Unidades
Pixels (px)
Un punto fsico de la pantalla. Es dependiente de la densidad pues su tamao vara con
esta. No se debera utilizar.
Pulgadas (in) Milimetros (mm) Puntos (pt = 1/72in)
Medidas independientes de la densidad, pues siempre ocupan lo mismo. No se deberan
utilizar salvo que necesitemos tamaos exactos en la pantalla.
Density-independent pixel (dp)
Un pixel virtual utilizado para definir dimensiones y posiciones en el layout de una manera
independiente de la densidad del dispositivo.
Un dp es equivalente a un pixel fsico en una pantalla 160dpi, la cual est considerada como
la pantalla de una densidad media. El sistema gestiona automticamente en tiempo de ejecucin
la conversin a pixels basndose en la densidad de la pantalla del dispositivo, con la siguiente
frmula: px = dp * (dpi / 160).
Por ejemplo, en una pantalla de 240 dpi, 1 dp son 1.5 pixels.
Siempre se debera usar dp's como unidades cuando se define el interfaz de la aplicacin
para asegurar una correcta visin en diferentes densidades.
Scale-independent pixel (sp)
Es similar al dp pero escalado por el tamao de texto preferido por el usuario. Se debe usar
al definir tamaos de texto pero nunca para tamaos de layouts.

15 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Consejos
Declarar los tamaos de pantalla que permite nuestra aplicacin
Se puede hacer incluyendo en el manifiesto el elemento <supports-screens>
<supports-screens android:resizeable=["true"| "false"]
android:smallScreens=["true" | "false"]
android:normalScreens=["true" | "false"]
android:largeScreens=["true" | "false"]
android:xlargeScreens=["true" | "false"]
android:anyDensity=["true" | "false"]
android:requiresSmallestWidthDp="integer"
android:compatibleWidthLimitDp="integer"
android:largestWidthLimitDp="integer"/>

Proporcionar diferentes interfaces para los distintos tipos de pantalla


Android siempre intenta redimensionar el interfaz a la pantalla actual, pero no siempre
queda bien. Esto se explica en la seccin de calificadores, en un epgrafe posterior.
Proporcionar diferentes versiones de un archivos de imgen para las diferentes densidades
Igual que en el punto anterior, aunque Android redimensiona las imgenes, no siempre
obtenemos un resultado ptimo, por lo que deberemos preparar distintas imgenes para distintas
densidades. De nuevo en este caso utilizaremos calificadores para asociar cada imagen a su
densidad.
Ver http://developer.android.com/guide/practices/screens_support.html
Ver http://developer.android.com/training/multiscreen/screendensities.html

16 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Recursos
Los recursos se deben almacenar siempre de manera independiente del cdigo en
subcarpetas de la carpeta res/ de la carpeta principal del proyecto.

Acceso
El acceso a dichos recursos puede hacerse desde el cdigo fuente y desde archivos XML.
Desde el cdigo se hace llamando a los mtodos adecuados y utilizando los IDs numricos
automticamente generados en la clase R. Esta clase no se debe tocar nunca manualmente pues la
herramienta aapt la regenera automticamente con cada compilacin del cdigo.
Ver http://developer.android.com/guide/topics/resources/accessing-resources.html

Tipos de recursos y calificadores de configuracin


Android soporta distintas configuraciones que permiten controlar cmo el sistema
selecciona distintos recursos alternativos basndose en las caractersticas del dispositivo.
Los calificadores de configuracin son cadenas que se pueden aadir a una carpeta de
recurso del proyecto, y especifican la configuracin para el cual los recursos de dicha carpeta han
sido diseados.
Se utilizarn de la siguiente manera:
1. Se crea una carpeta dentro de la carpeta res/ del proyecto denominada
<nombreCarpetaRecurso>-<calificadores>
<nombreCarpetaRecurso> es uno de los nombres indicado en la tabla adjunta.
<calificadores> sern uno o ms calificadores separados por guiones. De incluir varios
hay que tener en cuenta su orden, que ser el mismo que en la tabla adjunta.
2. Guardar los recursos especficos de esa configuracin dentro de la carpeta. Los nombres de
dichos archivos sern exactamente los mismos que en la carpeta de recursos por defecto
para que el sistema, dado un nico nombre, elija el adecuado en el momento preciso y en
tiempo de ejecucin.

17 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Nombres de las
carpetas de recursos

Tipo de recurso

drawable

Archivos de imagen (.png .jpg .gif) o archivos XML de bitmaps, figuras, etc.

layout

Archivos XML de interfaces para las aplicaciones

menu

Archivos XML que definnen los mens de la aplicacin

raw

Archivos que se almacenan en sus propios formatos RAW

values

Archivos XML que contienen valores como cadenas, nmeros, etc.

animator, anim, color, xml, ... Archivos XML que almacenan otros tipos de informacin

Los calificadores a combinar con el nombre de la carpeta se pueden agrupar en funcin de


distintos criterios:
Caractersticas

Calificadores

Cdigo del pas y cdigo del operador

mcc310 (U.S.), mcc208-mnc00 (Francia con Orange)

Idioma (ISO 639-1) y regin(r) (ISO 3166-1-alpha-2)

es, en, en-rUS, fr-rFR, fr-rCA, pt,

Direccin del interfaz

ldrtl (layout-direction-right-to-left) paises rabes


ldltr (layout-direction-left-to-right)

Asegurar un mnimo de anchura (smallestWidth)


independiente de la orientacin

sw320dp, sw600dp, sw720dp

Asegura anchura, eligiendo sin pasarse la ms cercana


Dependiente de la orientacin

w720dp, w1024dp

Idem anterior con la altura

h720dp, h1024dp

Tamao

small, normal, large, xlarge

Aspecto (ratio de la pantalla)

long, notlong

Orientacin

land, port

Interfaz

car, desk, television, appliance(servidor sin display)

Modo nocturno

night, notnight

Densidad (dpi)

ldpi, mdpi, tvdpi, hdpi, xhdpi, nodpi

Interfaz tctil

notouch, finger

Tipo de teclado

keysexposed, keyshidden, keyssoft

Mtodo de introduccin de texto

nokeys, qwerty, 12key

N del API de la versin

v3, v4, v7

Ver http://developer.android.com/guide/topics/resources/providing-resources.html

18 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Actividades

java.lang.Object

android.content.Context

android.content.ContextWrapper

android.view.ContextThemeWrapper

android.app.Activity

Ciclo de vida

Ver tabla en http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle


19 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Interfaces
java.lang.Object

android.view.View

android.widget.ViewGroup

android.widget.RelativeLayout

android.widget.LinearLayout

android.widget.AbsoluteLayout

android.widget.FrameLayout

android.widget.GridLayout

android.widget.ImageView

android.widget.ImageButton

android.widget.ProgressBar

android.widget.TextView

android.widget.EditText

android.widget.Button

android.widget.CompoundButton

android.widget.CheckBox

android.widget.ToggleButton

View y ViewGroup
La clase View representa el bloque bsico de construccin para los componentes del
interfaz. Es la clase base para todo tipo de widgets usados para crear interfaces interactivos con el
usuario.
La clase ViewGroup es (hereda) un tipo especial de View que puede contener otros Views
(o otros ViewGroup's, que son View's...). Es la clase base de todo tipo de Layouts y contenedores
de Views.
Todo view debe tener un id, por si accedemos desde cdigo o para referenciarlo
desde el propio XML (RelativeLayout)

Layouts
Creacin por cdigo y por archivo XML
Tipos: Linearlayout, RelativeLayout, etc.
El RelativeLayout es el mejor view group para disear un interfaz debido a que elimina la
necesidad de tener varios view group's anidados, manteniendo una jerarqua plana que mejora
mucho el rendimiento. Siempre ser mucho mejor utilizar un RelativeLayout que varios
LinearLayout anidados.

Cdigo de manejo del interfaz


setContentView para asignar el interfaz a una actividad
findviewById (despus del setContentView) para buscar un view en la interfaz asignada
20 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Implementacin del clic de un botn

Existen un par de opciones, aunque todas se basan en implementar el siguiente interfaz.

interface android.view.View.OnClickListener {
public abstract void onClick (View v);
}

donde v es el View en el que se ha hecho clic.


La primera opcin es asignar en el XML de la definicin del interfaz (layout) el atributo
android:onclick al nombre del mtodo que implementa dicha funcin.

XML

.java

<Button

android:onClick="unMtodo"

/>

public void unMtodo(View v) {

Es la manera ms sencilla, aunque hay que tocar en dos archivos y la asociacin entre el listener y
el mtodo podra dar problemas en algn caso si hay fragments en el cdigo. Se recomienda la
siguiente opcin, implementada totalmente en el cdigo.
La segunda opcin es crear un objeto que implemente dicho interfaz y pasrselo como parmetro
al mtodo public void setOnClickListener (View.OnClickListener l) , que se encarga de registrar el
callback que ser invocado cuando en este view se haga clic.

OnClickListener ocl = new OnClickListener() {


@Override
public void onClick(View v) {

}
};
botn.setOnClickListener(ocl);

21 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Aunque se suele hacer todo directamente en un solo paso:


@Override
protected void onCreate(Bundle savedInstanceState) {
...

botn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
unMtodo(v);
}
});

private void unMtodo(View v) {

22 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Notificaciones
Toast
Toast es una clase que nos permite mostrar mensajes en pantalla un determinado tiempo y
luego desaparecen. La manera ms rpida de crearlos es utilizar el mtodo esttico makeText, al
que se le pasa el contexto, el mensaje y la duracin. El objeto Toast devuelto puede utilizarse
directamente para llamar al mtodo show() para que se muestre en pantalla.
Toast.makeText(this,Mensaje, Toast.LENGTH_SHORT).show() ;

Si deseamos personalizar el interfaz del layout y su comportamiento, debemos instanciar


un objeto de la clase como en el siguiente ejemplo. Aqu hemos construido un layout propio
( R.layout.miToast) que inflamos y posteriormente accedemos a sus view's para personalizarlos.
Toast t = new Toast(this);
View layout = getLayoutInflater().inflate(R.layout.miToast, (ViewGroup)findViewById(R.id.toast));
TextView tvMensaje = (TextView)layout.findViewById(R.id.mensaje);
tvMensaje.setText("Mensaje");
t.setDuration(Toast.LENGTH_LONG);
t.setView(layout);
t.show();

Snackbar
Snackbar es una clase proporcionada con Material Design, y que nos permite ms
posibilidades que la anterior:
- Puede contener un botn de accin (deshacer, por ejemplo)
- Puede aparecer y desaperece automticamente (~ Toast) o quedar indefinidamente
- Se puede descartar con un desplazamiento lateral por parte del usuario (utilizando un
CoordinatorLayout)
- Se integra mejor en el interfaz, apareciendo en la parte inferior e integrandose en el UI
Para que aparezca en pantalla llamaremos al mtodo esttico make que necesita tres
parmetros:
- view: referencia a una vista que permita encontrar un contenedor para alojar nuestro layout
- text/resId: texto o un entero con el identificador del recurso de cadena para mostrar en pantalla
- duration: Snackbar.LENGTH_INDEFINITE, LENGTH_LONG o LENGTH_SHORT
Utilizando encadenamiento de mtodos, antes de llamar al mtodo show(), podemos
llamar a otros mtodos estticos como setActionTextoColor y .setAction para establecer una accin
asociada.
23 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Intents
Un intent es bsicamente una descripcin abstracta de alguna operacin que queremos
realizar. Son mensajes que nos permiten invocar componentes o actividades (internos a nuestra
aplicacin, o externos). Se podra considerar como el puente de unin, en tiempo de ejecucin,
entre distintas actividades.
Un intent necesita como mnimo dos parmetros:

action: El tipo de accin que queremos ejecutar.

ACTION_MAIN
ACTION_VIEW
ACTION_ATTACH_DATA
ACTION_EDIT
ACTION_PICK

ACTION_CHOOSER
ACTION_GET_CONTENT
ACTION_DIAL
ACTION_CALL
ACTION_SEND

ACTION_SENDTO
ACTION_ANSWER
ACTION_INSERT
ACTION_DELETE
ACTION_RUN

ACTION_SYNC
ACTION_PICK_ACTIVITY
ACTION_SEARCH
ACTION_WEB_SEARCH
ACTION_FACTORY_TEST

data: Los datos sobre los que queremos operar, expresados como un Uri
aunque posteriormente se le puede aadir informacin como:

category: aade informacin adicional al tipo de accin a ejecutar


type: especifica el tipo MIME de los datos
component: especifica el nombre de la clase del componente a usar por el intent
extras: es un Bundle con informacin adicional que utilizar la actividad llamada

Los intents pueden ser implcitos, donde simplemente decimos la operacin que queremos
realizar. El sistema buscar los componentes registrados para dicha accin (a travs de sus intentfilter) y, de haber ms de uno, nos dejar elegirlo.
A continuacin mostramos ejemplos para que nos permita ver una pgina web en el
navegador, enviar un correo desde el cliente de correo que elijamos, etc.

Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.ieschandomonte.edu.es"));


startActivity(i);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_EMAIL, "cursoandroid@gmail.com");
intent.putExtra(Intent.EXTRA_SUBJECT, "Asunto");
intent.putExtra(Intent.EXTRA_TEXT, "Texto del correo");
startActivity(Intent.createChooser(intent, "Enviar correo"));
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:986883305"));
startActivity(intent);

24 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Ver aqu multitud de ejemplos.


Los intents pueden tambin ser explcitos, donde identificaremos el componente que
vamos a invocar usando su atributo .class
Intent intent = new Intent(this, miOtraActividad.class);
intent.putExtra(clave1, valor1);
intent.putExtra(clave2, valor2);
startActivity(intent);
// en el cdigo de miOtraActividad accederemos a los datos
Bundle extras = getIntent().getExtras();
if(extras!=null) {
String valor1 = extras.getString(clave1);

Cuando queremos obtener un resultado de una actividad que hemos iniciado, deberemos
llamar al mtodo startActivityForResult y sobreescribir onActivityResult

public void startActivityForResult (Intent intent, int requestCode)


public void startActivityForResult (Intent intent, int requestCode, Bundle options)

donde requestCode es un entero que nos ser devuelto en el mtodo onActivityResult para
distinguir qu actividad es la que ha finalizado, y options es un Bundle con opciones para indicar
cmo se debe iniciar la actividad.
protected void onActivityResult (int requestCode, int resultCode, Intent data)

donde requestCode es el cdigo proporcionado en el mtodo startActivityForResult, resultCode es


el cdigo proporcionado en el mtodo setResult de la actividad que ha finalizado, y data que es un
Intent en cuyos extras nos pueden venir datos de vuelta de la actividad.
public final void setResult (int resultCode)

es el mtodo que deber deber invocar en algn momento la actividad antes de cerrarse. Su valor
se recupera en el mtod onActivityResult. A menudo se utilizan las constantes RESULT_OK o
RESULT_CANCEL.

25 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Almacenamiento
Dependiendo de las necesidades especficas de cada aplicacin, podremos elegir distintas
opciones de almacenamiento.

Preferencias compartidas (Shared Preferences)


La clase SharedPreferences permite a la aplicacin almacenar en un archivo pares clavevalor de tipos primitivos de datos (boolean, float, int, long y string).
Si nuestra actividad utiliza un nico archivo de preferencias, accederemos a l a travs del
mtodo siguiente, donde mode especifica combinaciones de permisos de acceso:
public SharedPreferences getPreferences (int mode)

Es un mtodo de Activity, y no le pasamos el nombre del archivo que tiene que leer porque, al ser
nico, utiliza el nombre de la actividad.
El atributo mode nos permite establecer el acceso:

MODE_PRIVATE
MODE_WORLD_READABLE
MODE_WORLD_WRITEABLE

Preferencias accesibles slo para el usuario.


Modo lectura para otros usuarios.
Escritura para todo el mundo.

Si, en cambio, utilizamos varios archivos de preferencias, usaremos otro mtodo donde
adems indicaremos el nombre que identifica al archivo que nos interesa.
public abstract SharedPreferences getSharedPreferences (String name, int mode) // mt. Context

A los valores ya conocidos del atributo mode podemos aadirle:


MODE_MULTI_PROCESS

Permite que varios procesos puedan modificar el mismo archivo

Para leer valores utilizaremos mtodos como getBoolean() o getString().

26 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Para escribir valores realizaremos tres pasos:


1. Llamar al mtodo edit() para obtener un SharedPreferences.Editor
2. Utilizar mtodos del editor como putBoolean() o putString()
3. Llamar al mtodo public abstract boolean commit () para confirmar los cambios.
Los mtodos get... tienen dos parmetros: el primero es la clave perteneciente al valor que
queremos obtener. El segundo es el valor que nos devolver la funcin en el caso de que no exista
dicha clave. En el caso de que la clave exista pero el valor no sea del tipo adecuado, se lanza una
ClassCastException.
Los mtodos put de escritura devuelven el mismo objeto editor que utilizan, por lo que
son encadenables ver Method chaining

Pantalla de preferencias

Si necesitamos una actividad para editar preferencias, ver la clase PreferenceActivity. Esta
clase, obsoleta a partir de la versin 3.0, (ver PreferenceFragment) nos permite crear de manera
automtica una pantalla (tpica de Ajustes...) a partir de un archivo XML donde definimos su
estructura.

public class Ajustes extends PreferenceActivity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.layout.ajustes);
}

<?xml version="1.0" encoding="utf-8"?>


<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="Preferences" >
<PreferenceCategory android:title="Pedidos" >
<ListPreference
android:defaultValue="1"
android:entries="@array/descOrderBy"
android:entryValues="@array/orderBy"
android:key="orderByClientes"
android:summary="Seleccione el orden para mostrar clientes"
android:title="Orden" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pedidosEncadenados"
android:summary="Debe presionar Atrs para volver"
android:title="Encadenar pedidos" />
</PreferenceCategory>

27 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

<PreferenceCategory android:title="Artculos" >


<CheckBoxPreference
android:defaultValue="false"
android:key="mostrarDescatalogados"
android:summary="Mostrar artculos descatalogados"
android:title="Descatalogados" />
</PreferenceCategory>
<PreferenceCategory android:title="Resumen" >
<EditTextPreference
android:dialogTitle="Introduzca su correo"
android:key="correo"
android:summary="Envo del resumen de ventas"
android:title="Correo electrnico" />
</PreferenceCategory>
<Preference
android:summary="Ir a la web de la empresa"
android:title="Ms informacin" >
<intent
android:action="android.intent.action.VIEW"
android:data="http://www.google.com/" />
</Preference>
</PreferenceScreen>

Los arrays de cadenas de valores y de descripcin de la lista (ListPreference) estn


almacenados en el archivo strings.xml de la siguiente manera:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">xAPP</string>
<string name=
<string-array name="descOrderBy">
<item>N de pedidos</item>
<item>Alfabtico</item>
</string-array>
<string-array name="orderBy">
<item>NumPedidos</item>
<item>Apellidos</item>
</string-array>
</resources>

Y el archivo que almacena las preferencias es el archivo por defecto de la actividad, al que
podemos acceder con el mtodo getPreferences() y gestionar igual que cualquier otro archivo de
preferencias. Su posible contenido se puede visualizar a continuacin:
shell@android:/data/data/com.empresa.app/shared_prefs # cat com.empresa.app_preferences.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="orderByClientes">NumPedidos</string>
<string name="correo">correoImpresora@empresa.com</string>
<boolean name="pedidosEncadenados" value="true" />
<boolean name="mostrarDescatalogados" value="false" />
</map>

28 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Almacenamiento interno
Permite almacenar archivos en la memoria interna del dispositivo. Por defecto, estos
archivos son privados para las aplicaciones que los crean, y no estn accesibles para el resto de
aplicaciones. Este tipo de archivos se eliminan cuando se desinstala la aplicacin.
Ver ms aqu

Almacenamiento externo
Todos los dispositivos Android disponen una memoria externa compartida. Esta puede ser
extrable (tarjeta SD, p.e.) o estar interna sin poder extraerse. Los archivos que se almacenen en
dicha memoria son accesibles en lectura y escritura.
Para tener acceso a dicha memoria, nuestra aplicacin deber obtener permisos de lectura
y/o de escritura en el manifiesto.
<manifest>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

29 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

A partir de la versin 4.4, estos permisos no son necesarios si dichos archivos son privados
a nuestra aplicacin. De esta manera se podra utilizar el atributo maxSdkVersion para que el
permiso se solicitara slo cuando sea necesario.
<manifest>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
</manifest>

Ver ms aqu

Almacenamiento a travs de la red


En el caso de tener acceso a la red, podremos utilizar las clases existentes en los paquetes
java.net.* y android.net.* para almacenar y recuperar datos a travs de servicios web existentes.

Base de datos
Android proporciona soporte completo a las bases de datos SQLite,un gestor de bases de
datos extremadamente ligero. En el siguiente epgrafe se puede ver en detalle.

30 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Gestor de base de datos SQLite


Caractersticas bsicas

Cdigo libre.

Utilizado por grandes programas.

Los datos se almacenan en un nico archivo.

Escrito en ANSI-C, se puede obtener su cdigo en un nico archivo para unirlo a nuestro
proyecto. La versin completa ocupa menos de 500K, y mucho menos excluyendo partes
opcionales.

Soportado en todas las plataformas existentes: Windows, Unix (Linux, Mac OS-X, iOS,
Android), etc.

La aplicacin no se comunica con un gestor externo. El SQLite se une directamente a


nuestro cdigo, por lo que slo se producen llamadas entre funciones de nuestro propio
programa.

Soporta transacciones, es decir, que un conjunto de instrucciones se comporte como una


transaccin: ACID (Atomicity, Consistency, Isolation, Durability)

Soporta SQL, pero no el estndar SQL-92 completo. Por ejemplo, no soporta RIGHT OUTER
JOIN ni FULL OUTER JOIN, etc. La integridad referencial (FOREIGN KEY) la soporta a partir de
la versin 3.6.19, aunque viene desactivada por defecto. S soporta TRIGGERS, por lo que
en versiones anteriores se puede implementar.

SQLite utiliza asignacin de tipos dinmica, es decir, en todo momento se asignan los tipos
en funcin de los valores utilizados. De hecho, aunque se establezca en el CREATE TABLE,
los tipos de datos no se establecen para cada columna de la tabla, sino que para cada valor
se le asignar un tipo TEXT, INTEGER, REAL, BLOB o tambin pueden ser NULL.

No necesita administracin ni configuracin. Existe, en todo caso, una utilidad de lnea de


comandos denominada sqlite3 que permite acceder al esquema y a los datos de una base
de datos.

Desde cdigo, el esquema de la base de datos se puede consultar en una tabla especial
denominada sqlite_master. Con un simple SELECT se puede acceder a los objetos
existentes: tablas, ndices, etc. El campo type nos informa del tipo de objeto y, obviamente,
se trata de una tabla de slo lectura mantenida por el sistema.

Existen multitud de aplicaciones con un entorno visual para manejar las bases de datos
SQLite (p.e. SQLite Administrator), e incluso extensiones para los navegadores ms
utilizados (p.e. SQLiteManager Firefox add-on).

31 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Creacin y acceso a la base de datos


Por defecto la base de datos se almacena en la memoria interna privada a la aplicacin, en
concreto en la carpeta data/data/nombreAplicacin/databases/nombreBD. Tambin se puede
almacenar en otras ubicaciones, por ejemplo en la memoria externa, para permitir el acceso a
otras aplicaciones y usuarios:

String rutaBD = Environment.getExternalStorageDirectory()


+ File.separator + CARPETA_BD + File.separator + NOMBRE_BD;

El mtodo recomendado para crear una nueva base de datos es creando una subclase de
SQLiteOpenHelper, cuyo constructor necesita cuatro parmetros:
- Context context: El contexto de la aplicacin, para crear y usar la base de datos
- String name: El nombre de la base de datos, o null para una base de datos en memoria
- SQLiteDatabase.CursorFactory factory: usado para crear cursores null los crea por defecto
- int version: Comenzando en 1, es la versin de la base de datos con la que debera trabajar el
cdigo actual . Si la base de datos encontrada es menor, se llamar a onUpgrade. Si es mayor, se
llamar a onDowngrade.
En nuestra nueva clase habr que sobreescribir el mtodo onCreate(SQLiteDatabase db),
donde se ejecutar el SQL para crear las tablas necesarias. Este mtodo slo es llamado cuando la
base de datos no existe.
Es importante recordar las limitaciones existentes en los tipos de datos en SQLite Version 3.
En concreto, tiene un tipado dinmico muy flexible (lo cual no tiene porque ser algo bueno...), es
decir, cualquier columna puede almacenar cualquier tipo de valor (excepto una columna INTEGER
PRIMARY KEY). De hecho, se puede probar como ejercicio la invencin de un nuevo tipo de datos
para una columna y veremos que todo funciona bien e incluso el comando .schema del shell nos
informar adcuadamente de nuestra nueva invencin.
Otro mtodo que debemos sobreescribir es onUpgrade (SQLiteDatabase db, int oldVersion,
int newVersion), que nos informa de la versin de la base de datos encontrada para que la
actualicemos a la versin que necesita la aplicacin. Igualmente existe un mtodo onDowngrade
(SQLiteDatabase db, int oldVersion, int newVersion), aunque su ejecucin se realizar en
situaciones muy especiales.

32 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

La ocurrencia de dicha subclase nos proporciona los mtodos getReadableDatabase() y


getWritableDatabase(), que nos devolvern un objeto de tipo SQLiteDatabase, que representa la
base de datos sobre la que realizaremos las operaciones necesarias. Tenga en cuenta que hasta
que llame a estos mtodos, no se crea fsicamente la base de datos.
Otra manera de crear y/o abrir la base de datos, prescindiendo de la clase anteriormente
citada es hacerlo directamente con alguno de los siguiente mtodos (existen ms sobrecargas):

public static SQLiteDatabase openDatabase (String path, SQLiteDatabase.CursorFactory factory, int flags)
public static SQLiteDatabase openOrCreateDatabase (String path, SQLiteDatabase.CursorFactory factory)

Al realizar el esquema de la base de datos, en concreto los nombres de los campos, es


buena idea llamar _id a la clave primaria, pues muchas de las funciones existentes en la librera de
acceso a la base de datos asumen este nombre.

Operaciones con la base de datos


Una vez disponemos del objeto SQLiteDatabase disponemos de los mtodos insert, update
y delete para las operaciones de modificacin de la base de datos, y un mtodo query para acceder
a los datos. Dichos mtodos disponen de multitud de parmetros adecuadas a su funcionamiento.
public long insert (String table, String nullColumnHack, ContentValues values)
public int update (String table, ContentValues values, String whereClause, String[] whereArgs)
public int delete (String table, String whereClause, String[] whereArgs)
public Cursor query ( String table, String[] columns, String selection, String[] selectionArgs,
String groupBy, String having, String orderBy, String limit)
Tambin disponemos de dos mtodos directos si tenemos construido el SQL en el cdigo:
public Cursor rawQuery (String sql, String[] selectionArgs)

SQL que devuelve datos (cursor)

public void execSQL (String sql)

SQL que no devuelva datos.

Finalmente cerraremos la base de datos con el mtodo close().

33 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Recuperacin de datos a travs de cursores


Un cursor es un mecanismo para recuperar y moverse a travs de un conjunto de datos de
una consulta a una base de datos. Dicho acceso se realizar registro a registro, navegando
utilizando los mtodos existentes. Por lo general obtendremos un cursor de la llamada a alguno de
los mtodos de recuperacin de datos vistos con anterioridad.
Los mtodos ms importantes de un cursor son los siguientes:
De posicin:
public abstract int getPosition ()
public abstract boolean isBeforeFirst|First|Last|AfterLast ()

De navegacin:
public abstract boolean moveToFirst|Last|Previous|Next ()
public abstract boolean moveToPosition (int position)
public abstract boolean move (int offset)

De extraccin:
public abstract String|float|int|byte[]|etc getString|Float|Int|Blob|etc (int columnIndex)

De esquema:
public
public
public
public
public

abstract
abstract
abstract
abstract
abstract

int getColumnIndex (String columnName)


int getColumnCount ()
String getColumnName (int columnIndex)
String[] getColumnNames ()
int getCount ()

Por ejemplo, para recuperar datos de un cursor, podemos hacer:


Cursor filas= bd.rawQuery("SELECT codigo, nombre FROM Categorias", null);
while(filas.moveToNext()) {
long codigo = filas.getLong(filas.getColumnIndex("codigo"));
String nombre = filas.getString(filas.getColumnIndex("nombre"));

// o filas.getLong(0);
// o filas.getString(1);

En el caso de que queramos hacer algo distinto cuando no hay datos:


Cursor filas= bd.rawQuery("SELECT codigo, nombre FROM Categorias", null);
if (filas.moveToFirst()) {
do {
long codigo = filas.getLong(filas.getColumnIndex("codigo"));
String nombre = filas.getString(filas.getColumnIndex("nombre"));

}
while(filas.moveToNext())
}
else {
// NO HAY FILAS
}

// o filas.getLong(0);
// o filas.getString(1);

34 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Listas

Poblar la lista desde el cdigo


Mtodo en absoluto recomendado, adems de por el nulo diseo de nuestra aplicacin,
entre otras cosas porque los datos estn en el cdigo y necesitariamos una edicin y compilacin
para modificarlos si fuera necesario.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Spinner lista = (Spinner)findViewById(R.id.spinner1);
final String[] datos = {"Elem1","Elem2","Elem3","Elem4","Elem5"};
ArrayAdapter<String> adaptador = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, datos);
lista.setAdapter(adaptador);
lista.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// int pos = lista.getSelectedItemPosition(); ( == position)
String str = (String) lista.getItemAtPosition(pos);
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();
}

});

@Override
public void onNothingSelected(AdapterView<?> parent)
{
// el adaptador no tiene datos...
}

Poblar la lista desde un recurso string-array


<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="items">
<item>elemento 1</item>
<item>elemento 2</item>
<item>elemento 3</item>
<item>elemento 4</item>
<item> elemento 5</item>
</string-array>
</resources>

final Spinner lista = (Spinner)findViewById(R.id.spinner1);


SpinnerAdapter adaptador=ArrayAdapter.createFromResource(this,R.array.items,android.R.layout.simple_list_item_1);
lista.setAdapter(adaptador);

35 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Poblar un Spinner desde clases propias

public class Categoria {


private long id;
private String nombre;
public Categoria(long id, String nombre) {
this.id=id;
this.nombre=nombre;
}
public long getId() {
return id;
}
@Override
public String toString() {
return nombre;
}
}
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Spinner lista = (Spinner)findViewById(R.id.spinner1);
lista.setAdapter(getAdaptador());
// Usar setOnItemClickListener con Spinner puede dar problemas...
lista.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
SpinnerAdapter adaptador = lista.getAdapter();
Categoria categoria = (Categoria) adaptador.getItem(position);
String str = categoria.toString() + " (" + categoria.getId() + ")";
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}

@Override
public void onNothingSelected(AdapterView<?> arg0) {
// adaptador sin datos
});

private SpinnerAdapter getAdaptador() {


ArrayList<Categoria> listaCategorias = new ArrayList<Categoria>();
AsistenteBD asistenteBD = new AsistenteBD(this, "factu", 1);
SQLiteDatabase bd = asistenteBD.getReadableDatabase();
Cursor filas= bd.rawQuery("SELECT codigo, nombre FROM Categorias", null);
int posCodigo = filas.getColumnIndex("codigo");
int posNombre = filas.getColumnIndex("nombre");
while(filas.moveToNext()) {
long codigo = filas.getLong(posCodigo);
String nombre = filas.getString(posNombre);
listaCategorias.add(new Categoria(codigo,nombre));
}
bd.close();
return new ArrayAdapter<Categoria>(this,android.R.layout.simple_spinner_item,listaCategorias);

}
}

36 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Poblar un ListActivity desde clases propias


Similar al ejemplo anterior, pero utilizando directamente una ListActivity, es decir, una
Activity que nicamente contiene una lista como interfaz. Por ello:
- Eliminamos setContentView y todas las referencias al Spinner
- Utilizamos ListAdapter en vez de SpinnerAdapter, y lo asignamos con setListAdapter
- No confundir los mtodos getAdaptador() donde CREAMOS el adaptador y l.getAdapter() donde
OBTENEMOS el adaptador
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setListAdapter(getAdaptador());
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
ListAdapter adaptador = l.getAdapter();
Categoria categoria = (Categoria) adaptador.getItem(position);
String str = categoria.toString() + " (" + categoria.getId() + ")";
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}

private ListAdapter getAdaptador() {


ArrayList<Categoria> listaCategorias = new ArrayList<Categoria>();
AsistenteBD asistenteBD = new AsistenteBD(this, "factu", 1);
SQLiteDatabase bd = asistenteBD.getReadableDatabase();
Cursor filas= bd.rawQuery("SELECT codigo, nombre FROM Categorias", null);
int posCodigo = filas.getColumnIndex("codigo");
int posNombre = filas.getColumnIndex("nombre");
while(filas.moveToNext()) {
long codigo = filas.getLong(posCodigo);
String nombre = filas.getString(posNombre);
listaCategorias.add(new Categoria(codigo,nombre));
}
filas.close();
bd.close();
return new ArrayAdapter<Categoria>(this,android.R.layout.simple_list_item_1,listaCategorias);
}

Personalizar una lista estndar


En vez de utilizar el ArrayAdapter anterior vamos a crear nuestro propio adaptador, en
donde controlaremos la creacin de cada uno de los layouts sobreescribiendo el mtodo getView.
En este primer ejemplo utilizaremos un layout del sistema (de android.R.layout...) por lo que slo
podremos modificar las propiedades estndar de este layout o del nico TextView que contiene,
no podremos aadir nuevos View's.
En este caso cambiaremos el color de fondo del layout cuando se trate de un cliente VIP.

37 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

class AdaptadorClientes extends ArrayAdapter<miniCliente> {


private Context contexto;
private ArrayList<miniCliente> datos;
public AdaptadorClientes(Context contexto, ArrayList<miniCliente> datos) {
super(contexto, android.R.layout.simple_list_item_1, datos);
this.contexto = contexto;
this.datos = datos;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View layoutFila = super.getView(position, convertView, parent);
miniCliente cliente = (miniCliente) datos.get(position);
TextView tvNombre = (TextView) layoutFila.findViewById(android.R.id.text1);
tvNombre.setTextColor(ColorStateList.valueOf(cliente.isVIP()?Color.BLUE:Color.BLACK);
layoutFila.setBackgroundColor(cliente.isVIP()?Color.YELLOW:Color.WHITE);
return layoutFila;
}
}
// Los siguientes mtodos pertenecen a la activity
// La clase anterior con el adaptador puede ser externa o interna a la propia activity
public void poblarLista() {
listaClientes.setAdapter(new AdaptadorClientes(this, getDatosClientes()));
}
public ArrayList<miniCliente> getDatosClientes() {
ArrayList<miniCliente> datosClientes = new ArrayList<>();
SQLiteDatabase bd = new AsistenteBD(this).getReadableDatabase();
Cursor filas = bd.rawQuery("SELECT * FROM clientes ORDER BY apellidos, nombre",
null);
int colIndexCodCliente = filas.getColumnIndex("codCliente");
int colIndexNombre = filas.getColumnIndex("nombre");
int colIndexApellidos = filas.getColumnIndex("apellidos");
int colIndexVIP = filas.getColumnIndex("VIP");
while(filas.moveToNext()) {
int codCliente = filas.getInt(colIndexCodCliente);
String nombre = filas.getString(colIndexNombre);
String apellidos = filas.getString(colIndexApellidos);
boolean VIP = filas.getInt(colIndexVIP)==1;
datosClientes.add(new miniCliente(codCliente,nombre,apellidos,VIP));
}
bd.close();
return datosClientes;
}

Fijarse que estamos utilizando la llamada a super.getView(...) por lo que este mtodo ya
nos devuelve el layout que debemos utilizar. Solamente debemos 'personalizarlo' en funcin de los
datos correspondientes a la posicin indicada. En este caso, la asignacin de las propiedades debe
realizarse a todos los View's puesto que la llamada al getView del padre est optimizada y
posiblemente reutilice layouts que ya no son necesarios (convertView), pero cuyas propiedades
han sido modificadas anteriormente y ahora deben ser reasignadas en su totalidad.

38 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Personalizando el interfaz de la lista SIN optimizacin


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="@+id/foto"
android:layout_width="48dp"
android:layout_height="48dp" />

<TextView
android:id="@+id/nombre"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toEndOf="@id/foto"
android:layout_toStartOf="@+id/mute"
android:gravity="left|center_vertical"
android:padding="5dp" />
<ImageView
android:id="@+id/mute"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:padding="10dp" />
</RelativeLayout>

public class Contacto {


private long id;
private String nombre;
public Contacto(long id, String nombre) {
this.id=id;
this.nombre=nombre;
}
public long getId() { return id; }
@Override public String toString() { return nombre; }
public boolean isMute() { return ((id==3)||(id==6)||(id==8)||(id==11)); } // algunos al azar
}
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Adaptador adaptador = new Adaptador (this, getDatos());
this.setListAdapter(adaptador);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
ListAdapter adaptador = l.getAdapter();
Contacto contacto = (Contacto) adaptador.getItem(position);
String str = contacto.toString() + " (" + contacto.getId() + ")";
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}

private ArrayList<Contacto> getDatos() {


ArrayList<Contacto> listaContactos = new ArrayList<Contacto>();
AsistenteBD asistenteBD = new AsistenteBD(this, "Contactos", 1);
SQLiteDatabase bd = asistenteBD.getReadableDatabase();
Cursor filas= bd.rawQuery("SELECT codigo, nombre FROM Contactos", null);
int colIndexCodigo=filas.getColumnIndex("codigo");
int colIndexNombre=filas.getColumnIndex("nombre");
while(filas.moveToNext()) {
long codigo = filas.getLong(colIndexCodigo);
String nombre = filas.getString(codIndexNombre);
listaContactos .add(new Contacto(codigo,nombre));
}
bd.close();
return listaContactos ;
}

39 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

class Adaptador extends ArrayAdapter<Contacto> {


private Context contexto;
private ArrayList<Contacto> datos;
public Adaptador(Context contexto, ArrayList<Contacto> datos) {
super(contexto, R.layout.filacontacto,datos);
this.contexto=contexto;
this.datos=datos;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// RECORDAR QUE ESTE CDIGO EST MAL PROGRAMADO POR SU PROBLEMA DE RENDIMIENTO
Contacto contacto = (Contacto)datos.get(position);
View layoutFila = LayoutInflater.from(contexto).inflate(R.layout.filacontacto,
ImageView foto = (ImageView) layoutFila.findViewById(R.id.foto);
TextView tvNombre = (TextView) layoutFila.findViewById(R.id.nombre);
ImageView mute = (ImageView) layoutFila.findViewById(R.id.mute);

null);

tvNombre.setText(contacto.toString());
// Asignamos la foto al ImageView (ver ms adelante)
if (contacto.isMute())
mute.setImageResource(R.drawable.mute);
else
mute.setImageDrawable(null);

Personalizando el interfaz de la lista CON optimizacin


En el siguiente cdigo se implementan dos optimizaciones bsicas:

Reutilizar layouts creados anteriormente y que hay que destruir porque al desplazar la lista
pierden visibilidad. Estos layouts pueden reutilizarse cambiando el contenido de sus View's,
evitando as operaciones muy costosas como crear los objetos en memoria representrados
en sus respectivos archivos XML (inflado).
Utilizar el 'patrn' ViewHolder, almacenando una estructura de fcil acceso y extensible en
su elemento Tag almacenando informacin como las referencias a los Views, evitando
llamadas a mtodos muy costosos como findViewById.

class Adaptador extends ArrayAdapter<Contacto> {


public static class ViewHolder {
ImageView foto, mute;
TextView tvNombre;
}
private Context contexto;
private ArrayList<Contacto> datos;
public Adaptador(Context contexto, ArrayList<Contacto> datos) {
super(contexto, R.layout.filacontacto,datos);
this.contexto=contexto;
this.datos=datos;
}

40 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
Contacto contacto = (Contacto)datos.get(position);
View layoutFila = convertView;
if(convertView==null) {
layoutFila = LayoutInflater.from(contexto).inflate(R.layout.filacontacto,
viewHolder = new ViewHolder();
viewHolder.foto
= (ImageView) layoutFila.findViewById(R.id.foto);
viewHolder.tvNombre
= (TextView) layoutFila.findViewById(R.id.nombre);
viewHolder.mute
= (ImageView) layoutFila.findViewById(R.id.mute);
layoutFila.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) convertView.getTag();
String strNombre = contacto.getId() + ".jpg";
// Creamos una carpeta fotos en la carpeta /data/data/paquete/files/
String strRuta = contexto.getFilesDir().getAbsolutePath() + "/fotos/" +
File ruta = new File(strRuta);
if (ruta.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(strRuta);
viewHolder.foto.setImageBitmap(bitmap);
}
else
viewHolder.foto.setImageResource(R.drawable.contacto);

null);

strNombre;

viewHolder.tvNombre.setText(contacto.toString());
if (contacto.isMute())
viewHolder.mute.setImageResource(R.drawable.mute);
else
viewHolder.mute.setImageDrawable(null);
}

return layoutFila;

41 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Ejemplo Mantenimiento de Clientes

AsistenteBD.java
import
import
import
import

android.content.ContentValues;
android.content.Context;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteOpenHelper;

public class AsistenteBD extends SQLiteOpenHelper {


private static final String NOMBRE_BD = "clientes.db";
private static final int VERSION_BD = 1;
public AsistenteBD(Context context) {
super(context, NOMBRE_BD, null, VERSION_BD);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sqlCreateProvincias =
"CREATE TABLE provincias(codProvincia INTEGER PRIMARY KEY AUTOINCREMENT,nombre TEXT)";
String sqlCreateClientes= "CREATE TABLE clientes(codCliente INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT,apellidos TEXT,NIF TEXT,codProvincia INTEGER,VIP INTEGER)";
db.execSQL(sqlCreateProvincias);
db.execSQL(sqlCreateClientes);
ContentValues cv = new ContentValues();
cv.put("nombre", "A Corua");
db.insert("provincias", null, cv);
cv.put("nombre", "Lugo");
db.insert("provincias", null, cv);
cv.put("nombre", "Ourense");
db.insert("provincias", null, cv);
cv.put("nombre", "Pontevedra");
db.insert("provincias", null, cv);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}

ListaClientesActivity.java
import
import
import
import
import
import
import
import
import
import
import
import
import

android.content.Intent;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.support.v7.app.AppCompatActivity;
android.os.Bundle;
android.view.Menu;
android.view.MenuItem;
android.view.View;
android.widget.AdapterView;
android.widget.ArrayAdapter;
android.widget.ListView;
android.widget.Toast;
java.util.ArrayList;

public class ListaClientesActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {


final int REQUEST_CODE_NUEVO_CLIENTE = 1;
final int REQUEST_CODE_EDITAR_CLIENTE = 2;
ListView listaClientes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//region Interface
setContentView(R.layout.activity_lista_clientes);
listaClientes = (ListView)findViewById(R.id.listaClientes);
//endregion
poblarLista();
listaClientes.setOnItemClickListener(this);
}

42 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

public void poblarLista() {


listaClientes.setAdapter(
new ArrayAdapter<miniCliente>(this,android.R.layout.simple_list_item_1, getDatosClientes()));
}
public ArrayList<miniCliente> getDatosClientes() {
ArrayList<miniCliente> datosClientes = new ArrayList<>();
SQLiteDatabase bd = new AsistenteBD(this).getReadableDatabase();
Cursor filas = bd.rawQuery("SELECT * FROM clientes ORDER BY apellidos, nombre", null);
int colIndexCodCliente = filas.getColumnIndex("codCliente");
int colIndexNombre = filas.getColumnIndex("nombre");
int colIndexApellidos = filas.getColumnIndex("apellidos");
int colIndexVIP = filas.getColumnIndex("VIP");
while(filas.moveToNext()) {
int codCliente = filas.getInt(colIndexCodCliente);
String nombre = filas.getString(colIndexNombre);
String apellidos = filas.getString(colIndexApellidos);
boolean VIP = filas.getInt(colIndexVIP)==1;
datosClientes.add(new miniCliente(codCliente,nombre,apellidos,VIP));
}
bd.close();
return datosClientes;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
miniCliente cliente = (miniCliente)parent.getItemAtPosition(position);
Intent intent = new Intent(this,ClienteActivity.class);
intent.putExtra("codCliente", cliente.getCodCliente());
startActivityForResult(intent, REQUEST_CODE_EDITAR_CLIENTE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/* switch(requestCode)
{
case REQUEST_CODE_NUEVO_CLIENTE:
switch(resultCode)
{
case RESULT_OK:
Toast.makeText(this, "Insercin realizada", Toast.LENGTH_LONG).show();
poblarLista();
break;
case RESULT_CANCELED:
Toast.makeText(this, "Insercin cancelada", Toast.LENGTH_LONG).show();
break;
}
break;
case REQUEST_CODE_EDITAR_CLIENTE:
switch(resultCode)
{
case RESULT_OK:
Toast.makeText(this, "Edicin realizada", Toast.LENGTH_LONG).show();
poblarLista();
break;
case RESULT_CANCELED:
Toast.makeText(this, "Edicin cancelada", Toast.LENGTH_LONG).show();
break;
}
break;
}*/
String mensaje= (requestCode==REQUEST_CODE_NUEVO_CLIENTE?"Insercin ":"Edicin ") +
(resultCode==RESULT_OK?"realizada":"cancelada");
Toast.makeText(this,mensaje,Toast.LENGTH_SHORT).show();
if(resultCode==RESULT_OK) poblarLista();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_lista_clientes, menu); return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(item.getItemId()) {
case R.id.action_settings:
return true;
case R.id.action_nuevoCliente:
Intent i = new Intent(this,ClienteActivity.class);
startActivityForResult(i, REQUEST_CODE_NUEVO_CLIENTE);
return true;
}
return super.onOptionsItemSelected(item);
}

43 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

miniCliente.java
public class miniCliente {
int codCliente;
String nombre, apellidos;
boolean VIP;
public miniCliente(int codCliente,String nombre,String apellidos, boolean VIP) {
this.codCliente=codCliente;
this.nombre=nombre;
this.apellidos=apellidos;
this.VIP=VIP;
}

public int getCodCliente () { return codCliente; }


public boolean isVIP() { return VIP; }
@Override
public String toString() { return apellidos + ", " + nombre; }

activity_cliente.xml
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="gal.amenedo.mantenimientodeclientes.ClienteActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etNombre"
android:hint="@string/nombre"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etApellidos"
android:hint="@string/apellidos"
android:layout_below="@+id/etNombre"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etNIF"
android:hint="@string/NIF"
android:layout_below="@+id/etApellidos"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/listaProvincias"
android:layout_below="@+id/etNIF"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/VIP"
android:id="@+id/chkVIP"
android:layout_below="@+id/listaProvincias"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/guardar"
android:id="@+id/bGuardar"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

44 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

ClienteActivity.java
public class ClienteActivity extends AppCompatActivity implements View.OnClickListener {
boolean esNuevoCliente;
int codCliente;
SQLiteDatabase bd;
EditText etNombre, etApellidos, etNIF;
Spinner listaProvincias;
CheckBox chkVIP;
Button bGuardar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bd = new AsistenteBD(this).getReadableDatabase();
codCliente = getIntent().getIntExtra("codCliente", -1);
esNuevoCliente = (codCliente == -1);
setResult(RESULT_CANCELED);
//region View's
setContentView(R.layout.activity_cliente);
etNombre = (EditText) findViewById(R.id.etNombre);
etApellidos = (EditText) findViewById(R.id.etApellidos);
etNIF = (EditText) findViewById(R.id.etNIF);
listaProvincias=(Spinner)findViewById(R.id.listaProvincias);
chkVIP = (CheckBox) findViewById(R.id.chkVIP);
bGuardar = (Button) findViewById(R.id.bGuardar);
//endregion
bGuardar.setOnClickListener(this);
setTitle(getString(esNuevoCliente ? R.string.nuevoCliente : R.string.editarCliente));
bGuardar.setText(getString(esNuevoCliente ? R.string.insertar : R.string.guardar));
int codProvincia=-1; //codProvincia virtual correspondiente al 1 elemento vaco de la lista de provincias
if (!esNuevoCliente) {
Cursor filas = bd.rawQuery("SELECT * FROM Clientes WHERE codCliente=?", new String[]{codCliente + ""});
int colIndexNombre = filas.getColumnIndex("nombre");
int colIndexApellidos = filas.getColumnIndex("apellidos");
int colIndexNIF = filas.getColumnIndex("NIF");
int colIndexCodProvincia = filas.getColumnIndex("codProvincia");
int colIndexVIP = filas.getColumnIndex("VIP");
if (filas.moveToFirst()) {
String nombre = filas.getString(colIndexNombre);
String apellidos = filas.getString(colIndexApellidos);
String NIF = filas.getString(colIndexNIF);
codProvincia = filas.getInt(colIndexCodProvincia);
boolean VIP = filas.getInt(colIndexVIP) == 1;
etNombre.setText(nombre);
etApellidos.setText(apellidos);
etNIF.setText(NIF);
// la provincia seleccionada se establece despus de poblar la lista para aprovechar el bucle
chkVIP.setChecked(VIP);

}
filas.close();

//region Lista de Provincias


int posProvinciaSeleccionada=0, posActual=0;
ArrayList<Provincia> alProvincias = new ArrayList<>();
alProvincias.add(new Provincia(-1,"")); // primer elemento en blanco. Comprobar su cdigo.
Cursor filas = bd.rawQuery("SELECT * FROM Provincias ORDER BY nombre", null);
int colIndexCodProvincia = filas.getColumnIndex("codProvincia");
int colIndexNombre = filas.getColumnIndex("nombre");
while (filas.moveToNext()) {
posActual++;
int codProvinciaBD = filas.getInt(colIndexCodProvincia);
if(codProvinciaBD==codProvincia) posProvinciaSeleccionada=posActual;
String nombreBD = filas.getString(colIndexNombre);
alProvincias.add(new Provincia(codProvinciaBD,nombreBD));
}
filas.close();
listaProvincias.setAdapter(new ArrayAdapter<Provincia>(this,android.R.layout.simple_list_item_1,alProvincias));
listaProvincias.setSelection(posProvinciaSeleccionada);
//endregion
}

45 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

@Override
public void onClick(View v) { // Guardar
String nombre=etNombre.getText().toString().trim();
String apellidos=etApellidos.getText().toString().trim();
String NIF=etNIF.getText().toString().trim();
int codProvincia=((Provincia)listaProvincias.getSelectedItem()).getCodProvincia();
Object objCodProvincia=(codProvincia==-1)?null:codProvincia;
//TODO: if(hayErroresEnDatos) { Toast - focus - return }
if(esNuevoCliente) {
String sql = "INSERT INTO clientes (nombre,apellidos,NIF,codProvincia,VIP) VALUES (?,?,?,?,?)";
Object[] valores = {nombre,apellidos,NIF,objCodProvincia,chkVIP.isChecked()?1:0};
bd.execSQL(sql,valores);
}
else {
String sql = "UPDATE Clientes SET nombre=?,apellidos=?,NIF=?,codProvincia=?,VIP=? WHERE codCliente=?";
Object[] valores = {nombre,apellidos,NIF,objCodProvincia,chkVIP.isChecked()?1:0, codCliente};
bd.execSQL(sql,valores);
}
setResult(RESULT_OK);
finish();
}

46 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Fragmentos
Un fragmento es un tipo de componente que encapsula un cdigo y (casi siempre) su
interfaz de usuario, y que puede ser reutilizado por distintas actividades. De hecho, un fragmento
siempre se utilizar en el contexto de una actividad, no puede utilizarse de forma independiente.
Se puede considerar que es una seccin de una actividad con su propio ciclo de vida, que
recibe sus propios eventos y que se pueden reutilizar por distintas actividades, aadiendose o
eliminandose incluso de manera dinmica.
La interaccin con los fragmentos se realiza a travs de Activity.getFragmentManager() y
Fragment.getFragmentManager(). Hay que tener tambin en cuenta que los Fragmentos no
heredan de Context, por lo que usaremos el mtodo getActivity() para obtener la actividad que lo
contiene en la ejecucin actual.

Soporte para versiones antiguas


En el caso de que el API mnimo de nuestra aplicacin sea menor que 11, tendremos que
utilizar la Support Library proporcionada por el propio Google (android-support-v4.jar)
En este caso tendremos que utilizar la clase FragmentActivity (en vez de Activity) para crear
actividades que utilicen fragmentos, y el gestor de fragmentos se obtendr con la funcin
getSupportFragmentManager();
Si nuestro API mnimo es 11 o mayor, podremos utilizar tranquilamente las clases
getFragmentManager(), Activity, Fragment y sus subclases.
Dichas versiones podemos consultarlas en el Manifiesto en la siguiente entrada:
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="18"
/>

Ciclo de vida
Los fragmentos tienen su propio ciclo de vida, aunque ste siempre depende de su
actividad asociada. Es decir, si una actividad est parada, ninguno de sus fragmentos pueden estar
activos, y si una actividad es destruida, tambin se destruirn sus fragmentos.

47 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Los mtodos ms importantes que son llamados al crear un fragmento son los siguientes:
onAttach(Activity) .- llamado cuando el fragmento se asocia con la actividad
onCreate(Bundle) .- llamado para realizar la creacin inicial del fragmento
View onCreateView(LayoutInflater, ViewGroup, Bundle) .- Se usa para definir su layout,
es decir, debe crear y devolver la vista jerrquica (el View) asociada al fragmento.
Normalmente se infla y se devuelve el layout deseado a travs del bombn pasado
como primer parmetro, y como segundo parmetro se pasa el ViewGroup para que sea
el padre del view generado.
onActivityCreated(Bundle) .- avisa al fragmento de que su actividad se ha creado (ha
finalizado su Activity.onCreate())
onViewStateRestored(Bundle) .- avisa al fragmento de que el estado de su vista ha sido
restaurado.
onStart() .- hace el fragmento visible al usuario (igual que su actividad)
onResume() .- el fragmento interacta con el usuario (igual que su actividad)

Cuando un fragmento no se usa, se llamar a los siguientes mtodos:


onPause() .- el fragmento deja de interactuar con el usuario
onStop() .- el fragmento deja de ser visible al usuario
onDestroyView() .- permite al fragmento liberar recursos asociados a sus vistas
onDestroy() .- se llama para indicar que se destruye el fragmento
onDetach() .- se llama justo antes de que el fragmento deja de asociarse a la actividad

Gestin esttica
La creacin bsica de fragmentos se realiza creando su clase java (que herede de Fragment
o alguna de sus subclases), creando su layout (si dispone de l, habitualmente s) y finalmente
inflndolo en su evento onCreateView.
Para utilizarlo necesitamos enlazarlo en los layouts en los que queramos que aparezca a
travs del elemento <fragment>
<fragment android:name="paquete.nombre.clase.fragmento"
android:id="@+id/id_fragmento"

...

android:layout_width="0dp"
android:layout_height="match_parent"
/>

48 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Gestin dinmica
Cuando se aade un fragmento estticamente a una actividad, declarndolo con
<fragment> en su layout, este no se podr eliminar en tiempo de ejecucin. Si queremos
intercambiar fragmentos de una actividad dinmicamente tendremos que crear utilizar
FragmentTransaction, que proporciona el API para aadir, eliminar y reemplazar fragmentos.
Si queremos aadir un fragmento a una actividad, siempre es necesario tener un View
preparado para contener a dicho fragmento. Una buena idea es utilizar un FrameLayout, que es el
contenedor ms sencillo.
Usando una nica FragmentTransaction podemos realizar mltiples operaciones con
fragmentos, como por ejemplo las siguientes:

add (int containerViewId, Fragment fragment)

remove (Fragment fragment)

replace (int containerViewId, Fragment fragment)

Al final realizaremos un .commit() para que los cambios surtan efecto.

Comunicacin con otros fragmentos


Con el objetivo de reutilizar los fragmentos, se deberan disear de manera que fueran
totalmente autocontenidos, componentes modulares que definen su propio comportamiento y no
dependen de otros mdulos externos que no siempre estarn accesibles.
Como a menudo deseamos que ante una situacin que ocurre en un fragmento ocurra algo
en otro fragmento distinto, podemos caer en la trampa de, a travs del gestor de fragmentos,
acceder directamente y realizar el cambio. Esto no debemos permitirlo pues no tendramos un
fragmento diseado con el objetivo anteriormente comentado. Dos fragmentos nunca deberan
comunicarse directamente. Para conseguir una independencia total, la comunicacin entre
fragmentos se realizar siempre a travs de la actividad que los contiene.
Para conseguirlo utilizaremos una solucin bien conocida, que incluso se estudia como un
patrn denominado Inyeccin de dependencias, una forma de Inversin de control (IoC). Se basa
en el uso de callbacks retrollamadas ( el principio de Hollywood).
Vea la implementacin en la solucin correcta del ejercicio de los fragmentos port/land.
Ver http://developer.android.com/training/basics/fragments/communicating.html
Ver http://www.javaworld.com/article/2077462/learn-java/java-tip-10--implement-callback-routines-in-java.html

49 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Ejemplo: Fragmento para visualizar un importe con dos monedas


public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FrgMoneda frgPrecioCoste=(FrgMoneda) getFragmentManager().findFragmentById(R.id.frgPrecioCoste);

frgPrecioCoste.setValue2(6f);

}
<RelativeLayout 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: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="com.example.etmoneda.MainActivity" >
<fragment
android:id="@+id/frgPrecioCoste"
android:name="com.example.etmoneda.FrgMoneda"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
/>
<fragment
android:id="@+id/frgPrecioVenta"
android:name="com.example.etmoneda.FrgMoneda"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/frgPrecioCoste"
/>
</RelativeLayout>
public class FrgMoneda extends Fragment {
static final float CAMBIO = 1.5f;
EditText et1,et2;
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frg_moneda, container);
et1 = (EditText) view.findViewById(R.id.et1);
et2 = (EditText) view.findViewById(R.id.et2);
TextWatcher tw1 = new TextWatcher() {
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) { actualizar(et1,et2,CAMBIO); }
};
TextWatcher tw2 = new TextWatcher() {
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) { actualizar(et2, et1,1/CAMBIO); }

};
et1.addTextChangedListener(tw1); et1.setTag(tw1);
et2.addTextChangedListener(tw2); et2.setTag(tw2);
return view;

50 de 75

IES Chan do Monte DAM2 Android

public
public
public
public

void setValue1(float
void setValue2(float
String getValue1() {
String getValue2() {

david@edu.xunta.es

f) { et1.setText(""+f); }
f) { et2.setText(""+f); }
return et1.getText().toString(); }
return et2.getText().toString(); }

private void actualizar(EditText et1, EditText et2, float cambio) {


String str = et1.getText().toString();
String resultado;

float f;
try {
f = Float.parseFloat(str);
resultado = "" + f*cambio;
}
catch(NumberFormatException ex) {
resultado = "***";
}
TextWatcher tw = (TextWatcher) et2.getTag();
et2.removeTextChangedListener(tw);
// para que no salte su TextWatcher (==bucle infinito)
et2.setText(resultado);
et2.addTextChangedListener(tw);
// restauramos su TextWatcher

}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
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="com.example.etmoneda.MainActivity" >
<EditText
android:id="@+id/et1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:ems="10" >
<requestFocus />
</EditText>
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<EditText
android:id="@+id/et2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:ems="10" >
</EditText>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="$" />
</LinearLayout>

51 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Ejemplo INCORRECTO de fragmentos: Maestro/Detalle en port/land


Incorrecto aunque funciona: no separa la lgica de los fragmentos, no implementa callbacks. FrgLista depende de FrgDetalle. Mal.

Principal.java
import android.os.Bundle;
import android.app.Activity;
public class Principal extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

layout/activity_main.xml
<RelativeLayout 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: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=".Principal" >
<fragment
android:id="@+id/frgLista"
android:name="com.example.fragmentesqueleto.FrgLista"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
</RelativeLayout>

52 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

layout-land/activity_main.xml
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="horizontal"
android:baselineAligned="false"
tools:context=".Principal" >
<fragment
android:id="@+id/frgLista"
android:name="com.example.fragmentesqueleto.FrgLista"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<fragment
android:id="@+id/frgDetalleLandscape"
android:name="com.example.fragmentesqueleto.FrgDetalle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
</LinearLayout>

FrgLista.java
import
import
import
import
import
import

android.app.ListFragment;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.ArrayAdapter;
android.widget.ListView;

public class FrgLista extends ListFragment {


// Como es un ListFragment nos evitamos hacer un layout propio
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

String[] elementos = new String[] {"perico", "pepito", "pablito"};


ArrayAdapter<String> adaptador = new ArrayAdapter<String>
(this.getActivity(), android.R.layout.simple_list_item_1, elementos);
setListAdapter(adaptador);

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
FrgDetalle frgDetalle = (FrgDetalle)
getFragmentManager().findFragmentById(R.id.frgDetalleLandscape);
if(frgDetalle!=null && frgDetalle.isInLayout()) {
frgDetalle.setKey(position);
}
else {
Intent intent = new Intent(this.getActivity(), DetalleActivity.class);
intent.putExtra("posicion", position);
startActivity(intent);
}
}
}

53 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

DetalleActivity.java
import android.os.Bundle;
import android.app.Activity;
public class DetalleActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalle);
Bundle extras = getIntent().getExtras();
//if(extras!=null) // debera serlo siempre ...
int posicion = extras.getInt("posicion");
FrgDetalle frgDetalle = (FrgDetalle)
getFragmentManager().findFragmentById(R.id.frgDetallePortrait);
frgDetalle.setKey(posicion);
}

layout/activity_detalle.xml
<RelativeLayout 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: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=".DetalleActivity" >
<fragment
android:id="@+id/frgDetallePortrait"
android:name="com.example.fragmentesqueleto.FrgDetalle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
</RelativeLayout>

FrgDetalle.java
import
import
import
import
import
import

android.app.Fragment;
android.os.Bundle;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.TextView;

public class FrgDetalle extends Fragment {


@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

return inflater.inflate(R.layout.frg_detalle, container);

public void setKey(int position) {


TextView tvPosition = (TextView) getView().findViewById(R.id.tvPosition);
tvPosition.setText("Pos: " + position);
}

54 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

layout/frg_detalle.xml
<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/tvPosition"
android:layout_width="107dp"
android:layout_height="wrap_content"
/>
</LinearLayout>

Ejemplo CORRECTO de fragmentos: Maestro/Detalle en port/land


La implementacin correcta del ejercicio se basa en mejorar la implementacin del
fragmento que gestiona la lista. Se puede observar en el ejemplo anterior que en su cdigo se
encuentran referencias al fragmento FrgDetalle, lo cual es inaceptable. Una cosa es hacer clic en un
elemento de la lista y otra distinta es lo que la actividad que contiene ese fragmento quiere hacer
en ese momento con esa informacin.
A continuacin est el nuevo cdigo de las clases involucradas. El resto del ejemplo no ha
cambiado.
FrgLista.java
import
import
import
import
import
import

android.app.Activity;
android.app.ListFragment;
android.os.Bundle;
android.view.View;
android.widget.ArrayAdapter;
android.widget.ListView;

public class FrgLista extends ListFragment {


// Como es un ListFragment nos evitamos hacer un layout propio
IClicEnLista avisarA;
public interface IClicEnLista {
public void clicEnLista(int posicion);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] elementos = new String[] {"perico", "pepito", "pablito"};
ArrayAdapter<String> adaptador = new ArrayAdapter<String>
(this.getActivity(), android.R.layout.simple_list_item_1, elementos);
setListAdapter(adaptador);
}
@Override
55 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

public void onAttach(Activity activity) {


super.onAttach(activity);
try {
avisarA = (IClicEnLista) activity;
}
catch (ClassCastException ex) {
String str=activity.toString() + " tiene que implementar FrgLista.IClicEnLista";

throw new ClassCastException(str);

}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if(avisarA != null)
avisarA.clicEnLista(position);
else
throw new IllegalStateException("No hay nadie esperando el clic en la lista");

Principal.java
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
public class Principal extends Activity implements FrgLista.IClicEnLista {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void clicEnLista(int posicion) {
FrgDetalle frgDetalle=(FrgDetalle) getFragmentManager().
findFragmentById(R.id.frgDetalleLandscape);
if(frgDetalle!=null && frgDetalle.isInLayout()) {
frgDetalle.setKey(posicion);
}
else {
Intent intent = new Intent(this, DetalleActivity.class);
intent.putExtra("posicion", posicion);
startActivity(intent);
}
}
}

56 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Hilos con AsyncTask


La clase AsyncTask facilita la realizacin de operaciones sencillas en un hilo separado
mientras permite publicar resultados (por lo general, visualizar su progreso) en el hilo del UI sin
tener que crear y controlar de manera explcita hilos a travs de la clase Thread.
Se suele utilizar para operaciones sencillas, de pocos segundos, como cargar datos o
conectar con una cierta ubicacin remota. Para operaciones ms costosas en tiempo es mejor
recurrir a los APIs proporcionados por el paquete java.util.concurrent.
De echo, a partir de la versin HONEYCOMB (API 11) no se pueden ejecutar varios
AsyncTask en paralelo pues era fcil caer en errores de programacin difcilmente detectables, y
por ello se ejecutan en serie. Para ejecutarlos en paralelo es necesario llamar al mtodo
executeOnExecutor si es un API superior al indicado:
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB

Para utilizarla debemos crear un objeto de una clase que la herede, y llamar al mtodo
execute, que tiene tres parmetros (tres tipos genricos):

Params.- el tipo de los parmetros enviados a la tarea para su ejecucin

Progress.- el tipo de las unidades de progreso publicadas durante el procesamiento, y que


necesitaremos para actualizar la UI

Result.- el tipo del resultado del procesamiento devuelto al acabar la tarea


private class BajarArchivos extends AsyncTask<URL, Integer, Long> {

}
new BajarArchivos().execute(url1, url2, url3);

Existen cuatro pasos bsicos en la ejecucin de la tarea, reflejados en cuatro mtodos que
suelen ser sobreescritos para configurarla adecuadamente. Todos estos mtodos tienen acceso al
UI excepto el segundo (doInBackground) que se ejecuta en un hilo aparte y adems es de obligada
sobreescritura (abstract).

void onPreExecute() .- se invoca antes de que se empiece a ejecutar la tarea. Aqu se suele
inicializar la tarea y se muestra una barra de progreso en el UI.

abstract Result doInBackground(Params... params) .- Se llama a continuacin del anterior y


57 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

aqu es donde incluiremos el cdigo que deseamos ejecutar en un hilo separado. A este
mtodo se le pasa un nmero variable de parmetros del tipo Param (el que se hay
indicado en su creacin). Al acabar el mtodo se devolver el resultado de la operacin (del
tipo Result tambin indicado en su creacin), dato al que se acceder en el mtodo
onPostExecute. En este mtodo se suele llamar al mtodo publishProgress(Progress...) para
publicar una o ms unidades de progreso, unidades que se pasan automticamente al UI
Thread a travs del mtodo onProgressUpdate(Progress...).

void onProgressUpdate(Progress... values) .- se invoca en el hilo del UI despus de una


llamada a publishProgress(Progress...) y se actualiza el UI para indicar el progreso.

void onPostExecute(Result result) .- se invoca en el hilo del UI al acabar el proceso


pasndole el resultado de la tarea.

Si en algn momento se llama al mtodo cancel(true), las siguientes llamadas a


isCancelled() devolvern true, por lo que debemos testear est funcin durante el proceso de la
tarea (dentro del doInBackground, dentro del bucle principal si lo hay), para acabarla en cuanto
podamos en el caso de que se haya cancelado. Una vez acaba este mtodo, saltar el evento
onCancelled(Result) en vez del onPostExecute(Result).
En el siguiente ejemplo veremos una implementacin con una ProgressDialog. Obviamente
se puede acceder a una barra de progreso incrustada directamente en el layout (UI) con lo que el
ejemplo sera todava ms sencillo.

import
import
import
import
import
import
import
import
import
import

java.net.MalformedURLException;
java.net.URL;
android.os.AsyncTask;
android.os.Bundle;
android.app.Activity;
android.app.ProgressDialog;
android.content.DialogInterface;
android.content.DialogInterface.OnCancelListener;
android.view.View;
android.widget.Toast;

public class MainActivity extends Activity {


ProgressDialog dialogo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void clic(View v) {
dialogo = new ProgressDialog(this);
dialogo.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // o STYLE_SPINNER
dialogo.setMessage("Bajando...");
dialogo.setCancelable(true);
dialogo.setMax(5);
URL urlElPais=null, urlElMundo=null, urlLaVoz=null,urlFaro=null, urlDiario=null;
try {
urlElPais = new URL("www.elpais.es");
urlElMundo = new URL("www.elmundo.es");

58 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

urlLaVoz = new URL("www.lavozdegalicia.es");


urlFaro = new URL("www.farodevigo.es");
urlDiario = new URL("www.diariodepontevedra.es");
} catch (MalformedURLException e) {

new BajarPaginas().execute(urlElPais,urlElMundo,urlLaVoz,urlFaro,urlDiario);
}
private class BajarPaginas extends AsyncTask<URL, Integer, Integer> {
@Override
protected void onPreExecute() {
dialogo.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
cancel(true);
}
});
dialogo.setProgress(0);
dialogo.show();
}
@Override
protected Integer doInBackground(URL... params) {
int i=0;
for(; i<params.length; i++) {
publishProgress(i);
// simulamos bajar la pgina URL
try { Thread.sleep(1000); } catch (InterruptedException e) {}
if(isCancelled()) break;
}
return i;
}
@Override
protected void onProgressUpdate(Integer... values) {
dialogo.setProgress(values[0]);
}
@Override
protected void onPostExecute(Integer result) {
dialogo.dismiss();
Toast.makeText(MainActivity.this,"Bajados " + result + " archivos", Toast.LENGTH_LONG).show();
}
@Override
protected void onCancelled(Integer result) {
dialogo.dismiss();
Toast.makeText(MainActivity.this, "Tarea cancelada por el usuario.\n" +
"Bajados hasta el momento " + result + " archivos", Toast.LENGTH_LONG).show();
}
}
}

59 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Google Services Google Maps Android API v2


Gracias al API de Mapas proporcionado por Google podemos usarlos en nuestras
aplicaciones con todas las posibilidades que nos ofrecen:

mapas 3D

mapas terrestres, satlite, hbridos, interiores

mapas basados en vectores para mayor velocidad y/o anchos de banda reducidos

transiciones animadas

uso de marcadores personales para indicar localizaciones especiales en el mapa

dibujar en el mapa figuras e imgenes en capas

controlar la vista del mapa a travs de zooms, rotaciones, gestos.

etc.

Licencia de uso del servicio


El uso de las APIs de Google Maps es totalmente gratuito siempre y cuando se cumplan
ciertas condiciones.
Existe tambin las llamadas API de Google Maps for Businness que amplan las
caractersticas ofertadas inicialmente.
Las diferencias bsicamente se refieren a la cantidad y periodicidad de las solicitudes
realizadas por los clientes, as como mejores resoluciones, escalas, etc. Tambin hay que destacar
que si se utiliza el API gratuito, los mapas proporcionados deben estar disponibles para el usuario
final de forma pblica y gratuita.
Ver ms detalles en https://developers.google.com/maps/licensing?hl=es
Ver los TOS (Terms of Service) en https://developers.google.com/maps/terms
Tambin se pueden resolver muchas dudas en el FAQ https://developers.google.com/maps/faq?hl=es

Instalacin del servicio

60 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

El API de Google Maps para Android es parte de Google Play Services.


La
librera
se
instala
en
la
services/samples/maps desde el SDK-Manager.

carpeta

<android-sdk>/extras/google-play-

Una vez instalado el SDK de los servicios de Google Play, y slo en el caso de que queramos
probar la aplicacin en un emulador, debemos:

Instalar desde el SDK-Manager (si no lo estn ya) las Google APIs en las versiones que
queramos de Android (4.22 API 17 o mayor).

Crear un AVD cuya plataforma destino sea dicha Google API, para probar ah la aplicacin.

A continuacin debemos copiar la librera de los servicios de google play, situada en la


61 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

carpeta <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/ al
workspace. Si estamos usando Eclipse podemos usar File > Import > Android > Existing Android
Code into Workspace (chequeando la casilla de copiar). En Propiedades > Android del proyecto
recien importado, si la casilla de verificacin IsLibrary no est seleccionada, hacerlo.
Finalmente, debemos referenciar esta librera desde nuestra aplicacin de mapas. Si
estamos usando Eclipse podemos ir a Properties del proyecto > Categora Android > y aadimos la
librera.

Tambin es necesario incluir el servicio de Google Play en el manifiesto. Concretamente


dentro del elemento <application> incluiremos:
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

Obtencin de una clave del API


Para que funcione nuestro servicio debemos registrar el proyecto en la Consola de Google
API para obtener una clave. Dicha consola nos pedir dos datos: el certificado firmado de nuestra
aplicacin y el nombre del paquete. La clave que nos proporciona es gratuita y para un n ilimitado
de usuarios y, como hemos comentado, est unida a un par certificado/paquete.

62 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Obtencin del certificado de nuestra aplicacin

El certificado de nuestra aplicacin es un SHA-1. Para mostrarlo tenemos dos opciones: la


primera es usar la aplicacin keytool (carpeta bin de java: %JAVA_HOME%\bin) con el parmetro -v.
Hay que fijarse que nuestra aplicacin puede tener dos certificados: debug y release.
En nuestro caso estamos usando el certificado debug (que estar almacenado en el archivo
debug.keystore) por lo que haremos lo siguiente:
keytool

-list -v -keystore "%USERPROFILE%\.android\debug.keystore"


-alias androiddebugkey -storepass android -keypass android

donde %USERPROFILE% suele ser C:\Users\nombreUsuario


La salida de la herramienta keytool nos visualizar la clave SHA1 (20 bytes separados por :)
C:\Users\_\.android>dir debug.keystore
El volumen de la unidad C es Acer
El nmero de serie del volumen es: 5653-D23F
Directorio de C:\Users\_\.android
05/07/2011

23:31
1.267 debug.keystore
1 archivos
1.267 bytes
0 dirs 44.290.076.672 bytes libres

C:\Users\_\.android>keytool

-list -v -keystore debug.keystore -alias androiddebugkey


-storepass android -keypass android

Nombre de alias: androiddebugkey


Fecha de creacin: 06-jul-2011
Tipo de entrada: PrivateKeyEntry
Longitud de la cadena de certificado: 1
Certificado[1]:
Propietario: CN=Android Debug, O=Android, C=US
Emisor: CN=Android Debug, O=Android, C=US
Nmero de serie: 4e1390b0
Vlido desde: Wed Jul 06 00:31:12 CEST 2011 hasta: Fri Jun 28 00:31:12 CEST 2041
Huellas digitales del certificado:
MD5: B7:7E:F9:D8:74:39:57:20:45:83:FB:EE:99:8E:CC:5F
SHA1: F3:1E:01:88:DE:E2:DD:2B:CA:E7:E2:A6:51:06:87:AF:AB:F8:0A:71
Nombre del algoritmo de firma: SHA1withRSA
Versin: 3

La segunda opcin es, si utilizamos Eclipse, acceder a la opcin Windows > Preferences y
acceder a la categora Android > Build.

63 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Crear un proyecto en la Consola de Google API / Google Developers Console

Ir a https://code.google.com/apis/console https://cloud.google.com/console y crear (si no


est previamente creado) un nuevo proyecto.
En Services / APIs % auth >APIs podremos ver una lista de todos los APIs y servicios
existentes. Activaremos (ON) el servicio de Google Maps Android API v2.
En API Access > Create new Android Key / Credentials > Create New Key > Android Key >
Introducimos la cadena con la huella SHA1, un punto y coma y el paquete de nuestra aplicacin:
SHA1;nombre.paquete. De esta manera obtendremos nuestra API key.
Aadir la API key al manifiesto

Justo antes de cerrar el elemento <application> incluiremos:

64 de 75

IES Chan do Monte DAM2 Android


<meta-data
</application>

david@edu.xunta.es

android:name="com.google.android.maps.v2.API_KEY"
android:value="laAPIKeyObtenida"/>

Otros ajustes en el manifiesto


Adems de las dos modificaciones del manifiesto comentadas anteriormente son
necesarios algunos permisos ms:
<uses-permission
<uses-permission
<uses-permission
<uses-permission

android:name="android.permission.INTERNET"/>
android:name="android.permission.ACCESS_NETWORK_STATE"/>
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

Opcionalmente, dependiendo de si hace uso de localizacin WiFi/antenas y/o GPS):


<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Y finalmente, debido a que los mapas se renderizan utilizando OpenGL ES v2 (OpenGL for
Embedded Systems: variante simplificada de OpenGL para dispositivos integrados), indicaremos,
dentro del elemento <manifest>:
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>

65 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Mapas
Para probar la aplicacin podemos utilizar un MapView o aadir el siguiente MapFragment
a un layout:
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.google.android.gms.maps.MapFragment"/>

y accederemos al mapa utilizando la funcin getMap() del MapFragment:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MapFragment frgMap = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
GoogleMap map = frgMap.getMap();
if (map != null) {
LatLng cdm = new LatLng(42.389741,-8.709544);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(cdm, 17));
}
}

En caso de usar la librera de soporte de fragmentos, usaremos SupportMapFragment.


Tipos

Existen cuatro tipos de mapas:

Normal: Carreteras y sus nombres, monumentos y elementos naturales.

Hbrido: El normal con la foto satlite de fondo

Satlite: Foto satlite. No se ven carreteras ni nombres.

Terrestre: Datos topogrficos con lneas y contornos y algunas carreteras.

66 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

67 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Interiores: planos de plantas

Utilizando el tipo GoogleMap.MAP_TYPE_NORMAL con valores altos de zoom, existen


ciertas localizaciones que nos permiten visualizar su plano interior.
Esta caracterstica slo se nos permite en un plano a la vez, normalmente el primero
aadido a la aplicacin. En el caso de quererlo visualizar en otro, se desactivar primero en uno y
se activar en el otro con la funcin GoogleMaps.setIndoorEnabled(boolean).
Si quisieramos visualizar un plano no disponible podramos hacer dos cosas:

Incluirlo en Google Maps para que lo utilicen todos los usuarios.

Mostrar el plano como GroundOverlay o TileOverlay (slo para nuestros usuarios)

Estado inicial

Los valores iniciales del mapa nos permiten:

definir la cmara (localizacin, zoom, soporte e inclinacin

tipo de mapa

visibilidad de los botones de zoom y brjula

qu tipos de gestos permitimos al usuario para manipular la cmara


Podremos establecerlos en el diseo del layout:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment"
map:cameraBearing="112.5"
map:cameraTargetLat="-33.796923"
map:cameraTargetLng="150.922433"
map:cameraTilt="30"
map:cameraZoom="13"
map:mapType="normal"
map:uiCompass="false"
map:uiRotateGestures="true"
map:uiScrollGestures="false"
map:uiTiltGestures="true"
map:uiZoomControls="false"
map:uiZoomGestures="true"/>

O tambin podremos establecer los valores iniciales del mapa en ejecucin, creando un
objeto GoogleMapOptions de la siguiente manera:
68 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

GoogleMapOptions options = new GoogleMapOptions();


options.mapType(GoogleMap.MAP_TYPE_SATELLITE)
.compassEnabled(false)
.rotateGesturesEnabled(false)
.tiltGesturesEnabled(false);

y dicho objeto se pasar al constructor del mapa, bien va new MapView(Context,


GoogleMapOptions), o bien va MapFragment.newInstance(GoogleMapOptions).
Ver en https://developers.google.com/maps/documentation/android/map

Dibujando sobre el mapa

Bocadillos en https://developers.google.com/maps/documentation/android/infowindows
Figuras en https://developers.google.com/maps/documentation/android/shapes
Marcadores en https://developers.google.com/maps/documentation/android/marker
Imgenes en https://developers.google.com/maps/documentation/android/groundoverlay
y en https://developers.google.com/maps/documentation/android/tileoverlay

Cambiando la vista

Ver en https://developers.google.com/maps/documentation/android/views

Ejemplo: Marcadores y lneas


public class MainActivity extends Activity {
Marker marcador;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MapFragment frgMap = (MapFragment) getFragmentManager().findFragmentById(R.id. map);
final GoogleMap mapa = frgMap.getMap();
if (mapa == null) {
Toast.makeText(this, "Imposible acceder al mapa", Toast.LENGTH_SHORT).show();
finish(); return;
}
LatLng cdm = new LatLng(42.389741,-8.709544);
mapa.moveCamera(CameraUpdateFactory. newLatLngZoom(cdm, 17));
marcador = creaMarcador(mapa,cdm,"Chan do Monte", "Ciclos Medios y Superiores de Informtica", true);
}

private Marker creaMarcador(GoogleMap mapa, LatLng pos, String titulo, String datos, boolean arrastrable) {
return mapa.addMarker(new MarkerOptions()
.position(pos)
.title(titulo)
.snippet(datos)
.draggable(arrastrable)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)));
}

69 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Actualizando el cdigo anterior con el que aparece a continuacin, conseguimos dibujar


una ruta (bajamos el zoom para verla completamente).

LatLng cdm = new LatLng(42.389555, -8.709945);


mapa.moveCamera(CameraUpdateFactory. newLatLngZoom(cdm, 15));
marcador=creaMarcador(mapa,cdm,"Chan do Monte", "IES con Ciclos Medios y Superiores de Informtica", true);
PolylineOptions pl = new PolylineOptions()
.add(new LatLng(42.389682, -8.710419))
.add(new LatLng(42.389207, -8.709636))
.add(new LatLng(42.388150, -8.710620))
.add(new LatLng(42.387933, -8.711320))
.add(new LatLng(42.388156, -8.711768))
.add(new LatLng(42.390855, -8.712755))
.color(Color.GREEN);
mapa.addPolyline(pl);

y aadiendo este cdigo a continuacin del anterior, creamos marcadores con una pulsacin larga:
mapa.setOnMapLongClickListener(new OnMapLongClickListener() {
@Override public void onMapLongClick(LatLng posicion) {
creaMarcador(mapa, posicion, "Posicin", posicion.toString(), false).showInfoWindow();
});
70 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Localizacin
Para gestionar la localizacin tenemos dos posibilidades: utilizar el API proporcionado por
la librera android.location (clase LocationManager), o utilizar el proporcionado por Google
Location Services API, parte de Google Play Services (clase LocationClient). Esta ltima es una
librera de alto nivel, mucho ms potente que la primera, y que gestiona de manera automtica
localizaciones, movimientos, precisin, as como una gestin ptima de consumo para aprovechar
mejor la batera.
Por lo general obtener la localizacin de un dispositivo no es sencillo por diversos motivos:
la precisin que vara en cada seal, estar situado en interiores, el usuario se mueve, multitud de
fuentes distintas de localizacin (GPS Global Positioning System , antenas de telefona e incluso
redes Wi-Fi).

Paquete android.location
La clase LocationManager nos proporciona acceso a distintos proveedores de localizacin,
los cuales disponen de los listeners (LocationListener) adecuados para que nos avisen
periodicamente de las nuevas localizaciones detectadas.
La clase LocationProvider es la superclase de los distintos proveedores de localizacin
existentes:
- NETWORK_PROVIDER

necesita permiso ACCESS_COARSE_LOCATION

- GPS_PROVIDER

necesita permiso ACCESS_FINE_LOCATION (incluye al anterior)

Adems de registrar los listeners adecuados, podremos registrar alertas de proximidad,


Intent's que saltarn si las coordenadas entran en el rea definida por un punto y un radio.

public class Principal extends Activity implements LocationListener {


private EditText etLatitud,etLongitud;
private LocationManager locationManager;
private String provider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_principal);
etLatitud = (EditText)findViewById(R.id.etLatitud);
etLongitud = (EditText)findViewById(R.id.etLongitud);

71 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);


if(!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// preguntar si quiere acceder a su habilitacin. Imposible dsd cdigo
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
finish();
return;
}
Criteria criteria = new Criteria();
// criteria.set...
provider = locationManager.getBestProvider(criteria, false);
// Location location = locationManager.getLastKnownLocation(provider);
}
@Override
protected void onResume() {
super.onResume();
long minTiempo = 1000; // en milisegundos
float minDistancia = 1; // en metros
locationManager.requestLocationUpdates(provider, minTiempo, minDistancia, this);
}
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
etLatitud.setText(location.getLatitude() + "");
etLongitud.setText(location.getLongitude() + "");
}
@Override
public void onProviderDisabled(String provider) {
Toast.makeText(this, "Proveedor desactivado: " + provider, Toast.LENGTH_SHORT).show();
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(this, "Proveedor activado: " + provider, Toast.LENGTH_SHORT).show();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
}

72 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Juntndolo todo

public class MainActivity extends Activity implements LocationListener {


private LocationManager locationManager;
private String provider;
private TextView tvInfo;

private GoogleMap mapa;


private ArrayList<LatLng> listaPosiciones = new ArrayList<LatLng>();
double[] listaLatitudes,listaLongitudes;
private PolylineOptions pl = new PolylineOptions();
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
listaLatitudes = new double[listaPosiciones.size()];
listaLongitudes = new double[listaPosiciones.size()];
int i=0;
for(LatLng latLng:listaPosiciones) {
listaLatitudes[i]=latLng.latitude;
listaLongitudes[i++]=latLng.longitude;
}
outState.putDoubleArray("latitudes", listaLatitudes);
outState.putDoubleArray("longitudes", listaLongitudes);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvInfo = (TextView) findViewById(R.id.tvInfo);
bInicio = (Button) findViewById(R.id.bInicio);
bFin = (Button) findViewById(R.id.bFin);
chkVerMapa = (CheckBox) findViewById(R.id.chkVerMapa);
//*** GPS **************************
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if(!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// preguntar si quiere acceder a su habilitacin. Imposible dsd cdigo
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
finish();
return;
}
Criteria criteria = new Criteria();
// criteria.set...
provider = locationManager.getBestProvider(criteria, false);
Location location = locationManager.getLastKnownLocation(provider);
//*** Mapa **************************
MapFragment frgMap = (MapFragment) getFragmentManager().findFragmentById(R.id. map);
mapa = frgMap.getMap();
if (mapa == null) {
Toast.makeText(this, "No ha sido posible acceder al mapa", Toast.LENGTH_SHORT).show();
finish();
return;
}
if(savedInstanceState!=null) {
listaLatitudes = savedInstanceState.getDoubleArray("latitudes");

73 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

listaLongitudes = savedInstanceState.getDoubleArray("longitudes");
for(int i=0; i<listaLatitudes.length; i++) {
LatLng punto = new LatLng(listaLatitudes[i],listaLongitudes[i]);
listaPosiciones.add(punto);
pl.add(punto);
}
mapa.addPolyline(pl);
}

@Override
protected void onResume() {
super.onResume();
long minTiempo = 500; // en milisegundos
float minDistancia = 1; // en metros
locationManager.requestLocationUpdates(provider, minTiempo, minDistancia, this);
}
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(this);
}
private Marker creaMarcador(GoogleMap mapa, LatLng posicion, String titulo, String datos, boolean arrastrable) {
return mapa.addMarker(new MarkerOptions()
.position(posicion)
.title(titulo)
.snippet(datos)
.draggable(arrastrable)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)));
}
// *** Interfaz LocationListener ******************
@Override
public void onLocationChanged(Location location) {
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
listaPosiciones.add(latLng);
int numPosiciones = listaPosiciones.size();
tvInfo.setText("N de puntos: " + numPosiciones );
pl.add(latLng);
//if(chkVerMapa.isChecked())
mapa.addPolyline(pl);
if(numPosiciones==1)
mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));
}
@Override
public void onProviderDisabled(String provider) {
Toast.makeText(this, "Proveedor desactivado: " + provider, Toast.LENGTH_SHORT).show();
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(this, "Proveedor activado: " + provider, Toast.LENGTH_SHORT).show();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
}

74 de 75

IES Chan do Monte DAM2 Android

david@edu.xunta.es

Bibliografa
http://developer.android.com
http://www.vogella.com
http://www.sgoliver.net

75 de 75

Vous aimerez peut-être aussi