Académique Documents
Professionnel Documents
Culture Documents
dispositivos móviles
IFC04CM14
Cuadernillo de prácticas
Julio, 2014
Documento maquetado con TEXiS v.1.0.
Cuadernillo de prácticas
Julio, 2014
Copyright
c Pedro Pablo Gómez Martín
Índice
Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 25
Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 41
v
vi Índice
Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 66
4. Controles y layouts 67
Pr. 4.1. Interfaces de usuario como XML . . . . . . . . . . . . . . 67
En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 91
public c l a s s HolaMundo {
$ javac HolaMundo.java
$ java HolaMundo
½Hola mundo!
1
2 Capítulo 1. Repaso de Java y Eclipse
Fíjate que ahora hemos usado java (no javac) que es el programa que
contiene la implementación de la JVM. Además, no hemos especicado la
extensión.
public c l a s s Main {
$ javac Main.java
$ java Main
$ javac Fecha.java
$ java Main
Compila el chero:
$ javac AppletBasico
<!DOCTYPE html>
<html>
<head>
< t i t l e >Mi a p p l e t</ t i t l e >
</ head>
<body>
<h1>Mi a p p l e t</ h1>
<applet code=" A p p l e t B a s i c o . c l a s s "
width=" 3 0 0 "
height=" 2 0 0 ">
</ applet>
</ body>
</ html>
$ appletviewer AppletBasico.html
Para probarlo, vamos a hacer un applet que escribirá por la salida están-
dar una cadena indicando que se ha invocado a cada uno de los métodos.
Ten en cuenta que esa salida estándar normalmente no se verá si el applet
se ejecuta directamente en el navegador.
javac CicloVidaApplet.java
} // AppletConBoton
add(new java.awt.Button("Pulsame"));
Lo hemos separado en tres líneas porque más adelante haremos uso del
botón.
// C l a s e que manejará e l e v e n t o de p u l s a c i ó n d e l b o t ó n .
// Observa que e s t a c l a s e no e s p ú b l i c a ( no comienza con
// p u b l i c ) .
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
} // c l a s s M i A c t i o n L i s t e n e r
} // c l a s s BotonClaseExterna
Compila la clase. Observa que aparecen dos cheros .class, uno para
cada una de las clases.
Con la solución anterior, la clase oyente está al mismo nivel que la del
applet, cosa que conceptualmente no es demasiado limpio. Además, podría-
mos querer acceder a atributos estáticos de la clase del applet desde el oyente,
y no lo podremos hacer a no ser que sean públicos.
Para solucionar las dos cosas, Java permite la denición anidada de clases.
En inglés se conoce como top-level nested classes and interfaces. A nosotros
sólo nos interesan aquí las clases.
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
// C l a s e anidada , d e n t r o de o t r a . Observa e l s t a t i c .
static class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d ( j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos a l a t r i b u t o p r o t e g i d o de l a c l a s e a l a
// que p er t en e ce m os .
System . o u t . p r i n t l n ( m e n s a j e ) ;
}
} // c l a s s M i A c t i o n L i s t e n e r
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
} // i n i t
} // c l a s s BotonClaseAnidada
Compila la clase. Observa que aparecen dos cheros .class, pero ahora
el del listener tiene un nombre cualicado con el nombre de la clase a
la que pertenece (BotonClaseAnidada$MiActionListener.class).
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
} // c l a s s BotonClaseAnidadaNoEstatica
Compila la clase. Observa que aparecen dos cheros .class, igual que
antes.
// C l a s e l o c a l , p r i v a d a d e l método i n i t ( ) . No s e r á
// v i s i b l e f u e r a .
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos a l a t r i b u t o dinámico ( no e s t á t i c o )
// p r o t e g i d o de l a c l a s e a l a que p e r te n ec em o s .
System . o u t . p r i n t l n ( m e n s a j e ) ;
}
} // c l a s s M i A c t i o n L i s t e n e r
} // B o t o n C l a s e L o c a l 1
En principio, parece que las clases locales no aportan nada respecto a las
clases internas no estáticas anteriores. La diferencia que las hace especiales es
que pueden acceder a los atributos y variables locales del método donde se han
denido y creado, siempre que éstos sean final (constantes). Esto supone
un acercamiento a los cierres (closures ) de la programación funcional. Es
decir, si el método init() anterior tuviera una variable local (final), el
método actionPerformed() de la clase local podría acceder a él.
Esto es útil especialmente con los parámetros. Veamos un ejemplo que lo
demuestra. Para eso, el botón lo crearemos en un método nuevo que recibirá
parámetros, y lo invocaremos varias veces para poner varios botones en el
applet.
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos t a n t o a l a t r i b u t o de l a c l a s e
// e x t e r n a , como a l parámetro ( g r a c i a s a que e s
// f i n a l ) .
System . o u t . p r i n t l n ( m e n s a j e + " : " + pulsado ) ;
}// a c t i o n P e r f o r m e d
} // c l a s s M i A c t i o n L i s t e n e r
} // ponBoton
} // c l a s s B o t o n C l a s e L o c a l 2
// En l a l l a m a d a a a d d A c t i o n L i s t e n e r hacemos e l
// new y d e f i n i m o s l a c l a s e d i r e c t a m e n t e . Por
// nombre de l a c l a s e ponemos e l nombre de l a
// " s u p e r c l a s e " ( i n t e r f a z ) que vamos a
// implementar , l u e g o e l método , y cerramos
// todo .
miBoton . a d d A c t i o n L i s t e n e r (
new j a v a . awt . e v e n t . A c t i o n L i s t e n e r ( ) {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
System . o u t . p r i n t l n ( m e n s a j e + " : " +
pulsado ) ;
} // a c t i o n P e r f o r m e d
}); // Cerramos l a d e f i n i c i ó n de l a c l a s e
// y d e l método
add ( miBoton ) ;
} // ponBoton
} // c l a s s BotonClaseAnonima
// Método l l a m a d o cuando s e p u l s a e l b o t ó n d e l a p p l e t .
protected void botonPulsado ( ) {
System . o u t . p r i n t l n ( m e n s a j e ) ;
} // b o t o n P u l s a d o
} // BotonFinal
// Método l l a m a d o cuando s e p u l s a e l b o t ó n d e l a p p l e t .
protected void botonPulsado ( ) {
System . o u t . p r i n t l n ( m e n s a j e ) ;
} // b o t o n P u l s a d o
} // BotonFinal
Actualmente Android no soporta Java 8, por lo que no profundizaremos
más.
Tiene una carpeta JRE System Library donde se enumeran las libre-
rías disponibles en el JRE que se usará para lanzar la aplicación.
System.out.println("½Hola mundo!");
public c l a s s HolaMundo {
Haz doble click sobre el lateral izquierdo del código, para añadir un
punto de ruptura (breakpoint ). También puedes colocar el cursor so-
bre la línea y pulsar <Ctrl>-<Mays>-B. Verás aparecer un circulito
indicando la existencia del punto de ruptura.
Notas bibliográcas
La cantidad de documentación disponible sobre Java es inmensa. Para
comenzar, quizá un buen punto de partida sean los tutoriales proporcionados
por Oracle (http://docs.oracle.com/javase/tutorial/index.html) o, si
se preeren los libros tradicionales, la última edición del conocido Piensa en
Java de Bruce Eckel.
Los applets vivieron su explendor a nales de la década de 1990 y prin-
cipios de la del 2000. Hoy su uso es minoritario, habiendo sido desplazados
por los modos de interacción con el navegador que han proporcionado las pá-
ginas dinámicas y JavaScript (HTML5). En este capítulo nos ha interesado
principalmente su ciclo de vida, y el hecho de que se ejecutan integrados en
un navegador. Se puede encontrar más información sobre ellos por ejemplo
en http://docs.oracle.com/javase/tutorial/deployment/applet/.
También en los tutoriales de Oracle hay una sección dedicada especial-
mente a las clases anidadas (http://docs.oracle.com/javase/tutorial/
java/javaOO/nested.html).
Las expresiones lambda de Java 8 suponen un gran cambio en la losofía
de programación en Java en muchos aspectos, no sólo como una manera
más cómoda de especicar eventos para elementos del interfaz de usuario.
Aunque sus repercusiones quedan fuera del curso, para el lector curioso un
buen punto de inicio es http://www.oracle.com/technetwork/articles/
java/architect-lambdas-part1-2080972.html.
Eclipse es un IDE ampliamente utilizado por los desarrolladores de Java.
Sin embargo, en ocasiones puede ser bastante complejo, debido a su tama-
ño y versatilidad. Afortunadamente, incluye una ayuda relativamente rica,
que incluye incluso tutoriales para iniciarse en su uso. En el sitio web del
programa (http://www.eclipse.org) también se pueden encontrar algunos
tutoriales.
1
Este comando apenas es útil en este ejemplo porque estábamos terminando, y no
habría diferencia con usar cualquiera de los demás comandos. Sin embargo en situaciones
más complejas es muy práctico para relanzar la ejecución hasta el siguiente punto de
ruptura.
En el próximo capítulo. . .
En el próximo capítulo daremos nuestros primeros pasos con Android,
instalando el software necesario para desarrollar aplicaciones para él, y ana-
lizando sus herramientas principales.
para él.
Código abierto
27
28 Capítulo 2. Primeros pasos con Android
Capacidades multimedia
Para eso:
1
En esencia, es similar a una máquina virtual , pero ejecutándose sobre
un hardware emulado particular (el de los teléfonos), y sobre él el software
de Android en la versión que elijamos.
1
De hecho, el emulador de las SDK está basado en QEmu
Cámara
Sensores
Vamos a crear nuestro primer teléfono. Para eso en Windows lanza AVD
Manager en el raíz de las SDK. En GNU/Linux, usa de nuevo el programa
android del directorio tools. Ahora tendrás que pasarle un parámetro (avd),
por lo que necesitarás hacerlo desde una consola:
$ ./android avd
Vamos a crear nuestro primer AVD. Para eso, pulsa el botón New y verás
un nuevo cuadro de diálogo con varias opciones:
Skin : HVGA.
Opciones de emulación:
Scale display to real size: a partir de los datos de tamaño del monitor,
intenta que la representación del móvil tenga un tamaño real similar
al dispositivo físico que se está emulando.
Wipe user data: elimina los datos de usuario que se hayan introducido
tras la creación del AVD. Es como reformatearlo.
Ve al AVD y descuelga.
gsm list
inbound from 555666777 : active
Con gsm list observa que la segunda llamada está en estado incoming.
gsm list
inbound from 555666777 : held
inbound from 444333222 : active
Desde el propio CLI podemos hacer muchas tareas. Por ejemplo, po-
demos terminar una llamada:
Lanza Eclipse
Reinicia Eclipse.
Con el primer botón se abre el gestor de las SDK que ya conocemos. Esto
demuestra que efectivamente el ADT es un envoltorio de las SDK, invocán-
dolas desde Eclipse. Lo que hagamos ahí afectará a nuestra instalación de
las SDK, igual que si lo hubiéramos hecho lanzando la aplicación a mano por
separado.
Por último, hay un botón para crear un chero XML de Android con
información de recursos.
Notas bibliográcas
La documentación on-line de Android es muy rica y está bien escrita
(aunque en inglés):
Introducción a Android:
http://developer.android.com/about/index.html
Descripción básica de las aplicaciones disponibles en las SDK:
http://developer.android.com/tools/help/index.html
Plataformas Android y niveles de API:
http://developer.android.com/guide/appendix/api-levels.html
Android Virtual Devices :
http://developer.android.com/guide/developing/devices/index.
html
Gestión de AVDs:
http://developer.android.com/guide/developing/devices/managing-avds.
html
Características del emulador que ejecuta los AVDs:
http://developer.android.com/guide/developing/devices/emulator.
html
Uso del emulador:
http://developer.android.com/guide/developing/tools/emulator.
html
Plugin ADT (Android Development Tools ) para Eclipse:
http://developer.android.com/tools/sdk/eclipse-adt.html
En el próximo capítulo. . .
En el próximo capítulo haremos nuestras primeras aplicaciones para An-
droid. Para eso, analizaremos las actividades (Activity ) de Android, que
constituyen sus piezas fundamentales.
Resumen:
En este capítulo crearemos nuestras primeras aplicaciones para An-
droid, viendo el proceso de desarrollo y explorando las llamadas acti-
vidades (activity ) y su ciclo de vida.
43
44 Capítulo 3. Actividades: las ventanas de Android
Pulsa Next.
1
Si hiciste las prácticas del capítulo 1, es posible que ya tengas un proyecto con ese
nombre. Puedes utilizar, por ejemplo, HolaMundoAndroid en ese caso. También puedes
numerar las prácticas poniendo por ejemplo 3.01_HolaMundo para mantener el orden en
los proyectos de Eclipse.
2
Observa que la lista desplegable sólo muestra las plataformas que hayas instalado.
Pulsa Next.
3
Esta ejecución no es de depuración, por lo que no se modicará la perspectiva de
Eclipse.
package l i b r o . a z u l . pulsame ;
import a n d r o i d . app . A c t i v i t y ;
import a n d r o i d . o s . Bundle ;
/∗ ∗
∗ A c t i v i d a d de Android . R e p r e s e n t a l a ventana
∗ p r i n c i p a l de l a a p l i c a c i ó n .
∗/
public c l a s s PulsameActivity extends Activity {
/∗ ∗
∗ Método l l a m a d o cuando s e c r e a l a a c t i v i d a d .
∗/
@Override
protected void o n C r e a t e ( Bundle savedInstanceState ) {
// Llamamos a l método que estamos s o b r e e s c r i b i e n d o
// de l a c l a s e padre .
super . o n C r e a t e ( s a v e d I n s t a n c e S t a t e ) ;
} // onCreate
/∗ ∗
∗ Método l l a m a d o cuando s e p u l s a s o b r e e l b o t ó n
∗ de l a ventana . Es l l a m a d o a t r a v é s de l a c l a s e
∗ anónima d e l e v e n t o .
∗/
private void botonPulsado ( ) {
++_numVeces ;
_boton . s e t T e x t ( " P u l s a d o " + _numVeces + " veces " ) ;
} // b o t o n P u l s a d o
/∗ ∗
∗ Botón de l a ventana .
∗/
private Button _boton ;
/∗ ∗
∗ Número de v e c e s que s e ha p u l s a d o e l b o t ó n .
∗/
private int _numVeces ;
} // P u l s a m e A c t i v i t y
Los otros dos son más confusos. Cuando compilamos una aplicación ne-
cesitamos tener acceso a las clases auxiliares que vamos a utilizar, como por
ejemplo la clase padre de las actividades, android.app.Activity, de la que
hereda la única clase de nuestro programa. Si Eclipse no encuentra esa clase,
no podrá compilar el programa. Esas clases las proporciona la plataforma An-
droid contra la que estamos compilando nuestra aplicación (Compile with ).
A modo de ejemplo, si en cualquiera de los proyectos anteriores despliegas
android.jar (en la carpeta Android <version>), verás que, entre otras
muchas, está la mencionada android.app.Activity. Las versiones más re-
cientes de la plataforma contienen las clases de las versiones anteriores, junto
con otras nuevas que proporcionan funcionalidad adicional.
El punto negativo de tener los dos valores es que debemos ser cuidadosos
al desarrollar, para no utilizar características de la versión Compile with que
no estén en Minimum Required SDK. Si lo hacemos, fallará en ejecución en
el dispositivo.
5
En realidad, el nombre lint se usa para referirse a cualquier herramienta que analiza
el código fuente de cualquier lenguaje de programación buscando construcciones sospe-
chosas, normalmente haciendo análisis estático del código.
Los otros dos valores (Minimum required y target SDK ) son metadatos
del proyecto que se guardan en el chero de maniesto, AndroidManifest.xml
en el raíz del proyecto. No nos preocuparemos de este chero hasta bastante
más adelante; de momento es suciente con saber que contiene información
general sobre la aplicación. Si lo abres, encontrarás fácilmente dónde se es-
pecican ambos valores.
3. Modica los metadatos del proyecto para indicar como versión mínima
necesaria la 4 (Android 1.6).
Deshabilitar lint para que Eclipse no lo use. Para eso, nos va-
mos al menú Window - Preferences, y en Android - Lint Error
Checking deshabilitamos las dos casillas de vericación.
5. Una vez que Eclipse no pone pegas a nuestro código, pruébalo tanto en
Android 1.6 como en Android 2.3. Verás que en uno de ellos funciona,
y en el otro no. Ten en cuenta que si como Minimum Required SDK
hubiéramos puesto un número mayor, entonces no habríamos podido
probarlo en Android 1.6 por no cumplir la restricción del API mínimo.
new android.app.Fragment();
Esto crea un objeto de una clase que se incluyó por primera vez en
el nivel de API 11 (Android 3.0). Actualiza la anotación para Lint
7 y
if (android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.HONEYCOMB)
new android.app.Fragment();
6
También podemos usar la constante Build.VERSION_CODES.ECLAIR
7
Si usaste la constante, ahora será Build.VERSION_CODES.HONEYCOMB
Cuando lo hagas, conecta el móvil por USB. Si todo va bien, las SDK
de Android deberían tener acceso a él. Para comprobarlo, puedes usar una
aplicación en consola :
9
$ cd $ANDROID_SDK_PATH
$ cd platform-tools
$ ./adb devices
List of devices attached
emulator-5554 device
emulator-5556 device
D32C76402AF837AE device
En este caso, vemos los dos AVDs lanzados, y un dispositivo físico conec-
tado por USB. Si no ves tu móvil, comprueba que tienes los controladores
necesarios.
Para lanzar una aplicación sobre el móvil, basta con que en Eclipse ini-
cies una ejecución normal. Eclipse te mostrará la lista de los dispositivos
disponibles que pueden ejecutar tu programa, y es suciente con que elijas
el dispositivo físico. Verás tu aplicación en el móvil un instante después.
8
Dependiendo del móvil, podrían necesitarse controladores especícos del fabricante.
9
En la práctica 3.6 la analizaremos con más calma.
en el emulador.
/**
* Constante con el "tag" usado para registro de sucesos
* en LogCat relativos a esta actividad.
*/
private static final String TAG = "Pulsame";
$ ./adb -e logcat
Ahora verás toda la información que nos daba eclipse. Las alternativas
son brief, process, tag, raw, time, threadtime y long. Pruébalas e
identica sus diferencias.
5. Abre un shell con algún AVD. Por ejemplo, si sólo tienes uno lanzado:
$ ./adb -e shell
# <<--- shell remoto en el móvil
10. Pulsa ahora la tecla Inicio, o el botón Home , para volver al escritorio
de Android.
Fíjate que el programa que hemos hecho no tiene método main(), igual
que ocurre en los applets. De hecho, nuestra aplicación hereda de la clase
android.app.Activity. Una actividad es un componente de una aplicación,
que muestra una ventana con la que interactuar (buscar en la agenda, hacer
una foto, escribir un mensaje, mirar los mensajes entrantes). Cada actividad
tiene una ventana que, normalmente, ocupará toda la pantalla.
Vamos a hacer una nueva aplicación que nos muestre un mensaje cuando
se invoque a los métodos que controlan el ciclo de vida.
import a n d r o i d . app . A c t i v i t y ;
import a n d r o i d . o s . Bundle ;
@Override
public void o n C r e a t e ( B u n d l e s a v e d I n s t a n c e S t a t e ) {
super . o n C r e a t e ( s a v e d I n s t a n c e S t a t e ) ;
s e t C o n t e n t V i e w (R . l a y o u t . a c t i v i t y _ c i c l o _ d e _ v i d a ) ;
a n d r o i d . u t i l . Log . i (TAG, " onCreate " ) ;
}
protected void o n S t a r t ( ) {
super . o n S t a r t ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onStart " ) ;
}
protected void o n R e s t a r t ( ) {
super . o n R e s t a r t ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onRestart " ) ;
}
protected void o n P a u s e ( ) {
super . o n P a u s e ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onPause " ) ;
}
protected void o n S t o p ( ) {
super . o n S t o p ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onStop " ) ;
}
protected void o n D e s t r o y ( ) {
super . o n D e s t r o y ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onDestroy " ) ;
}
} // c l a s s C i c l o D e V i d a A c t i v i t y
1. Desde Eclipse, haz una copia del proyecto Pulsame y ponle el nombre
PulsameLocalizado. Vamos a cambiarlo para hacer uso de recursos
_boton.setText("½Púlsame!");
por:
_boton.setText(R.string.pulsameAdmiracion);
10. Vamos a crear ahora un nuevo chero de cadenas para que se utilice
cuando el móvil esté congurado con idioma inglés. Selecciona la car-
peta res/, y en el menú contextual pulsa New, Other (o pulsa Ctrl-N).
14. En el nuevo chero, que de momento no tiene cadenas, crea una nueva.
17. Añade al nuevo chero de cadenas (en inglés), las que teníamos en
español. Es más rápido utilizar la vista en XML, copiar y pegar el
contenido original y modicar lo que corresponda. Verás que hay al-
gunas cadenas que añadió el asistente que ni siquiera estamos usando
(y que de hecho aparecen en inglés). Lanza la aplicación otra vez y
comprueba que todo está en inglés, incluso el nombre de la aplicación
en el lanzador.
18. Cambia la conguración del idioma del AVD a español. Para eso, ve
a Settings, Language & keyboard (Locale & text o Language & input,
según la versión), Select language, y elige Español (España).
<plurals name="numPulsaciones">
<item quantity="one">Pulsado %d vez</item>
<item quantity="other">Pulsado %d veces</item>
</plurals>
<plurals name="numPulsaciones">
<item quantity="one">Touched %d time</item>
<item quantity="other">Touched %d times</item>
</plurals>
Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:
http://developer.android.com/training/basics/firstapp/index.
html
Herramienta lint de las SDK de Android:
http://tools.android.com/tips/lint/
Logcat, el sistema de log de Android:
http://developer.android.com/tools/debugging/debugging-log.
html
Clase para enviar mensajes de log:
http://developer.android.com/reference/android/util/Log.html
Ciclo de vida de las actividades:
http://developer.android.com/training/basics/activity-lifecycle/
http://developer.android.com/guide/components/activities.html
Documentación sobre la clase Activity:
http://developer.android.com/reference/android/app/Activity.
html
Descripción de los recursos en Android
http://developer.android.com/guide/topics/resources/overview.
html
Cualicadores de los recursos:
http://developer.android.com/guide/topics/resources/providing-resources.
html
En el próximo capítulo. . .
En el próximo capítulo veremos los controles (widgets ) y layouts más
importantes de Android, y la creación de interfaces a través de recursos.
Controles y layouts
El usuario no es más que un periférico
que teclea cuando se le envía una
petición de lectura
P. Williams
En la práctica 3.9 hemos hecho uso de recursos para las cadenas, que
ya no están cableadas en el código. Sin embargo, estamos construyendo el
interfaz de la ventana manualmente. El método onCreate() es:
67
68 Capítulo 4. Controles y layouts
} // onCreate
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/boton"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/pulsameAdmiracion"/>
setContentView(R.layout.activity_pulsame);
setContentView(_boton);
Ahora el campo _boton se está obteniendo del recurso (una vez que
ya se ha congurado la vista), en lugar de crearlo manualmente como
hacíamos antes con new. Además, ya no establecemos el contenido (lo
hace el propio layout ). Únicamente añadimos el listener.
// ...
public void onCreate(Bundle savedInstanceState) {
....
// Nos hacemos observadores de sus pulsaciones.
_boton.setOnClickListener(
new View.OnClickListener() {
} // onCreate
...
android:onClick="botonPulsado"
Ser público
Para las pruebas que vamos a realizar, abre el chero del recurso de tipo
layout activity_pulsame.xml de dicha práctica usando, esta vez sí, la vista
Graphical Layout. Eclipse nos muestra un entorno gráco para diseñar la
ventana, en lugar de tener que utilizar directamente el XML. Nos permite así
hacer cambios y ver el resultado automáticamente, sin necesidad de compilar
la aplicación y probarla en un AVD o terminal físico.
layout_width
1. Fíjate que los atributos que conocíamos con los nombres
y layout_height en el XML, están, en el editor de propiedades, dentro
de una sección Layout Parameters y se llaman, sencillamente, width y
height. Esto es debido a que son atributos heredados del contenedor
en el que está el botón, no del botón en sí mismo. En el XML tendremos
que especicar como nombre layout_*, pero en la vista gráca tenemos
que buscarlos dentro de esa sección de las propiedades.
el asistente de proyectos del ADT nos crea siempre una actividad con una
única etiqueta con el texto Hello World!
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:text="@string/hello_world" />
4. Haz las mismas pruebas sobre ella que las que hiciste con el botón.
Ten en cuenta que con las etiquetas es más complicado hacerse una
idea de qué está pasando, porque son transparentes. Cuando se dise-
ña un layout, a menudo es interesante poner fondos temporales a los
controles transparentes, para tener una visión más clara de lo que está
ocurriendo. Para eso, puedes poner en el atributo android:background
un color con el formato #RRGGBB (por ejemplo, #FF0000 para rojo).
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/editText"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:inputType="text"
android:text="@string/escribeAlgo" />
4. Haz las mismas pruebas sobre ella que las que hiciste antes.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#E0E0E0"
android:orientation="vertical" >
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#80FF80"
android:text="@string/etiqueta1" />
<TextView
android:id="@+id/text2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FF8080"
android:text="@string/etiqueta2" />
</LinearLayout>
Algunas pistas:
numeroAAdivinar = dado.nextInt(100) + 1;
Además, aquí van algunas ideas, algo más avanzadas, para mejorar el
programa:
et.setOnKeyListener(new android.view.View.OnKeyListener() {
public boolean onKey(View v,
int keyCode,
android.view.KeyEvent event) {
// Han pulsado (o soltado) una tecla...
if ((event.getAction() ==
android.view.KeyEvent.ACTION_DOWN) &&
(keyCode ==
android.view.KeyEvent.KEYCODE_ENTER)) {
// Ha sido una pulsación de "intro"
// PENDIENTE! HACER ALGO!!
return true;
}
else
return false;
} // onKey
});
Por último, ¾qué tal añadir una etiqueta que diga cuantos intentos
llevas?
Debemos tener cuidado cuando se utiliza este layout para evitar crear por
error referencias circulares que impidan la colocación de los componentes.
Además, en este caso suele ser más sencillo crear el layout directamente en
XML, porque la herramienta gráca de diseño es bastante difícil de domar.
Debe tenerse en cuenta que no hace falta jar todos los lados de
los controles, dado que aún se hará uso de los atributos layout_width y
usaríamos el código:
<TextView
android:id="@+id/tvNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignBaseline="@+id/etNombre"
android:padding="8dp"
android:text="@string/nombre"/>
<EditText
android:id="@+id/etNombre"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/tvNombre"
android:layout_alignParentTop="true"/>
Para saber en código qué opción está seleccionada, tenemos dos op-
ciones. La primera es recorrer todos los RadioButton y mirar cuál tiene
el atributo checked a cierto. La segunda, mucho más práctica, es pregun-
tarle al RadioGroup el identicador del RadioButton seleccionado (método
getCheckedRadioButtonId()).
3. Añade código al evento del botón para que escriba en la etiqueta in-
RadioGroup rg = ...;
rg.setOnCheckedChangeListener(
new RadioGroup.OnCheckedChangeListener() {
public void onCheckedChanged(RadioGroup group,
int checkedId) {
// Hacer algo...
}
}
);
Una casilla de vericación es un control tipo botón especial que tiene dos
posibles estados, marcado y desmarcado. Muestra un texto y una parte que
representa el estado. Para desarrollar su comportamiento, la clase CheckBox
hereda de Button.
Tiene los atributos android:text y android:checked que podríamos
esperar. Para saber desde código si está o no marcado, podemos usar boolean
isChecked(). Por último, para detectar los cambios de estado, tendremos
que registrarnos como observadores suyos:
CheckBox cb = ...
cb.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// Hacer algo! Sabemos quién ha sido pulsado
// y su estado
} // onCheckedChanged
} // new
); // setOnCheckedChangeListener
3. En la parte inferior, coloca una etiqueta invisible con el texto ½No! ½El
domingo no!, que se haga visible cuando se seleccione el domingo.
Crea un proyecto nuevo que muestre un botón que al ser pulsado muestre
un mensaje usando un Toast.
alert.setMessage(R.string.<id>);
alert.setPositiveButton(android.R.string.ok, null);
alert.show();
Algunos comentarios:
Vamos a probarlo.
if (result == DialogResult.Yes) {
...
}
En el próximo capítulo. . .
En el próximo capítulo nos preocuparemos por invocar a una actividad
desde otra. Esto nos llevará a los intents y al chero AndroidManifest.xml.
93
94 Capítulo 5. Intents, chero de maniesto y componentes
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="libro.azul.dosactivities"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="..."
android:targetSdkVersion="..." />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
....
</application>
</manifest>
<activity
android:name="libro.azul.dosactivities.MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="libro.azul.dosactivities.SecondaryActivity"
android:label="@string/title_activity_secondary" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Cuando Android tiene que lanzar una actividad, busca el nombre de la clase
en ese atributo, crea una nueva instancia suya y la ejecuta. En concepto, es
similar a la referencia a la clase de un applet en una página HTML.
3. Mira la salida del logcat. Verás que ha saltado una excepción del tipo
ActivityNotFoundException. Sólo las actividades registradas en el
chero de maniesto pueden ser ejecutadas, incluso aunque la clase
exista realmente. Por tanto deben declararse todas.
El signicado de:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
startActivityForResult(i, SECONDARY_ACTIVITY_TAG);
String respuesta;
EditText et = (EditText) findViewById(R.id.etRespuesta);
respuesta = et.getText().toString();
@Override
protected void onActivityResult(int requestCode,
int resultCode,
Intent data) {
String respuesta;
if ((resultCode == RESULT_CANCELED) ||
(data.getStringExtra("respuesta").equals("")))
respuesta = getResources().getString(
R.string.antipatico);
else
respuesta = data.getStringExtra("respuesta");
} // onActivityResult
El objetivo, sin embargo, es conseguir que sea igual lanzar una actividad
implementada en nuestra aplicación, que una ventana de otra.
En esta práctica vamos a hacer una nueva aplicación que será una copia
de la anterior, pero que tendrá únicamente la primera actividad, e invocará
a la segunda de la original.
por:
<activity
android:name=".SecondaryActivity"
android:label="@string/title_activity_secondary" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
$ ./adb shell
# ps
# kill <pid de dosactivities>
# kill <pid de invocacionremota>
2. Usando el adb shell, asegúrate de que ninguna está lanzada (esto po-
drás hacerlo también sobre un dispositivo físico, aunque no seas root).
Deja abierta la conexión con el shell.
Sin embargo, los intents se han diseñado para usarlos de una manera
mucho más potente llamada uso implícito, en el que no se proporciona qué
actividad debe procesar la solicitud. El objetivo es que los intents creen un
lenguaje en el sistema para que una aplicación pueda decir quiero llamar
a casa, y el sistema sepa qué actividad es capaz de ejecutar ese deseo. El
nombre intent (intención) encaja con esa idea, en el que las aplicaciones
notican su deseo de que algo ocurra.
Con esta idea en mente, los intents poseen, además del nombre de la
clase que hemos usado y los datos extra (setClassName() y putExtra())
varios campos más. Ten en cuenta que en las invocaciones implícitas nunca
se establece el nombre de la clase, y el sistema hará uso del resto de campos
para deducir qué actividad es capaz de atender la solicitud. En concreto:
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/ejecutarIntent"
android:onClick="onBotonEjecutar" />
Verás que la acción solicitada sirve para lanzar la actividad que muestra
el resumen de uso de la batería. Nos muestra una estimación del tiempo
que durará la batería, y los porcentajes de uso de batería de las diferentes
1
aplicaciones disponibles . Dado que los AVD no simulan la batería con tanta
exactitud, en este caso el resultado en el emulador será mucho más pobre.
startActivity(i);
}
1
El resultado podría variar dependiendo del teléfono concreto.
Esto nos demuestra que en algunas ocasiones Android hace uso única-
mente de la acción para decidir qué aplicación lanzar (como ocurría en el
ejemplo de las estadísticas de uso de la batería), pero a veces necesita anali-
zar también la URI. Precisamente eso es lo que ocurre aquí; como la acción
ACTION_VIEW es muy genérica, Android necesita analizar la URI y al ver que
hace referencia a mensajes cortos lanza la aplicación adecuada.
i.setAction(Intent.ACTION_VIEW);
i.setType("vnd.android-dir/mms-sms");
i.putExtra("address", "5554433");
En este sentido se debe tener en cuenta que las aplicaciones pueden crear
2
sus propias acciones para que otras las utilicen . Incluso, pueden reutilizar
las acciones existentes, y diferenciarse de las demás a través de la URI o del
tipo MIME. Por ejemplo, la aplicación Skype informa a Android de que es
capaz de procesar intents con la acción VIEW_ACTION, pero con URIs del tipo
skype:<usuario>.
La conclusión de esto es que la información más importante para una
acción irá siempre en la URI, y es el uso que se recomienda. Los datos extra
deben utilizarse para información adicional que no afecte a la elección de la
actividad destino.
i.putExtra("sms_body",
"Hola! Estoy aprendiendo Android :-)");
13. Si das hacia atrás, para cerrar la escritura del mensaje, la actividad
funciona como normalmente y dejará el mensaje en los borradores.
14. Modica la aplicación para indicar que quieres enviar texto plano.
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT,
"Moebius cruzó la carretera para " +
"llegar al mismo lado");
2
Para eso, necesitarán conocerla cadena de la acción. Fíjate que nosotros aqui hemos
estado usando las constantes denidas en la clase Intent, que serán cadenas.
i.setPackage("com.whatsapp");
i.setAction(Intent.ACTION_GET_CONTENT);
i.setType("image/*");
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setAction(Intent.ACTION_DIAL);
i.setData(Uri.parse("tel:5554433"));
<uses-permission
android:name="android.permission.CALL_PHONE"/>
11. El móvil te muestra los permisos que el .apk declara que necesita.
Verás que solicita un servicio con coste (llamar a números de teléfono
directamente). Acepta la instalación.
Desinstala la aplicación.
Borra el .apk.
Desactiva la opción Fuentes desconocidas.
2. El proyecto creado estará vacío. Crea una clase nueva con el menú File
- New - Class.
4. El código fuente que nos crea Eclipse tiene denido un método llamado
onReceive(), sin código, que es abstracto en la clase padre. Ese método
será llamado automáticamente por Android cuando ocurra un suceso de
if (i.getAction().equals(
Intent.ACTION_POWER_CONNECTED))
android.util.Log.i(TAG, "Cargador conectado");
else if (i.getAction().equals(
Intent.ACTION_POWER_DISCONNECTED))
android.util.Log.i(TAG, "Cargador desconectado");
<receiver android:name=".DeteccionEnchufado">
</receiver>
7. Nos falta denir en qué sucesos estamos interesados. Para eso tenemos
que hacer uso de un elemento <intent-filter>, que establece ltros
a los intents que deberían llegarnos. Dentro del elemento <receiver>
que acabas de crear, añade:
<intent-filter>
<action android:name=
"android.intent.action.ACTION_POWER_CONNECTED"/>
</intent-filter>
<intent-filter>
<action android:name=
"android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
Esto indica que nos interesan los mensajes relacionados con la conexión
y desconexión del cargador. Fíjate que en el código fuente utilizamos
la constante Intent.ACTION_POWER_DISCONNECTED. En el chero de
maniesto debemos usar su valor (que siempre es una cadena).
9. Para estar seguros del estado de partida del AVD, usa el interfaz en
consola y desconecta el cable de alimentación. Para eso, conéctate por
telnet con el puerto par en el que está escuchando el AVD (recuerda
que puedes saber cual es mirándolo en el título de la ventana del AVD):
11. En una ventana nueva, abre una sesión con el shell y lista los procesos.
$ ./adb shell
# ps
# logcat
power ac on
OK
Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:
Archivo de maniesto
http://developer.android.com/guide/topics/manifest/manifest-intro.
html
http://developer.android.com/guide/components/intents-filters.html
https://developer.android.com/guide/components/intents-common.html
Firma de aplicaciones
http://developer.android.com/tools/publishing/app-signing.html
http://developer.android.com/reference/android/content/BroadcastReceiver.
html
En el próximo capítulo. . .
En el próximo capítulo veremos algunas de las posibilidades para guardar
y recuperar datos en Android.
Acceso a datos
640 KB deberían ser sucientes para
todo el mundo
Erróneamente atribuída a Bill Gates
persistentes.
El primer modo de almacenar datos que vamos a ver puede que, incluso,
ni siquiera deba ser categorizado como tal, dado que es el propio sistema
quien hace la mayor parte del trabajo.
119
120 Capítulo 6. Acceso a datos
1. Haz una copia de la práctica del ciclo de vida (práctica 3.8) y llámala
CicloDeVidaSaveInstance.
2. Modica el código del método onCreate para que muestre el contenido
del parámetro:
@Override
public void onSaveInstanceState(Bundle outInstance) {
} // onSaveInstanceState
return numPulsados;
} // getEtiquetaBoton
// Incrementamos el contador...
++_numVeces;
} // botonPulsado
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pulsame);
if (savedInstanceState != null) {
_numVeces = savedInstanceState.getInt(
STATE_NUM_VECES);
if (_numVeces != 0) {
Button b = (Button) findViewById(R.id.boton);
b.setText(getEtiquetaBoton());
}
} // if (savedInstanceState != null)
} // onCreate
1. Haz una copia del proyecto con el juego Adivina mi número. Llámalo
AdivinaMiNumeroEstable.
2. Lánzalo. Haz un par de intentos de adivinar el número.
6. Para conseguir todo esto, vamos a seguir algunos atajos. Vamos a guar-
dar:
if (savedInstanceState == null) {
// Estamos iniciándonos desde cero.
// Comenzamos una partida.
reiniciaPartida();
}
else {
// Tenemos que reconstruirnos desde el bundle.
_numElegido = savedInstanceState.getInt(
STATE_NUM_ELEGIDO);
_numIntentos = savedInstanceState.getInt(
STATE_NUM_INTENTOS);
if (_numElegido == -1)
// La partida está terminada.
partidaAcabada();
else {
// La partida está a mitad. Ponemos el texto de la
// etiqueta superior.
TextView tv =
tv = (TextView)findViewById(R.id.etiquetaSuperior);
tv.setText(
savedInstanceState.getString(STATE_MENSAJE));
// Actualizamos la etiqueta del número de intentos
actualizarIntentos();
}
}
SharedPreferences preferencias;
Una vez conseguido el objeto, obtener los atributos que contiene es similar
a como lo haríamos con un bundle :
SharedPreferences.Editor editor;
editor = preferencias.edit();
editor.putInt(<atributo>, <valor>);
...
editor.commit();
Para esta práctica vamos a hacer una actividad nueva que simulará un
cuadro de diálogo de selección de opciones. Mostrará varios botones de radio,
y el usuario deberá elegir una de las opciones que ofrecen. Tendrá un botón
de Aceptar que, al ser pulsado, guardará la selección, de modo que la próxima
vez que se lance la actividad aparecerá seleccionada.
preferencias = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor;
editor = preferencias.edit();
RadioGroup rg;
rg = (RadioGroup) findViewById(R.id.preferenciasMelodia);
editor.putInt(PREFERENCIA_MELODIA,
rg.getCheckedRadioButtonId());
editor.commit();
finish();
} // onAceptar
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences preferencias;
preferencias = getPreferences(Context.MODE_PRIVATE);
int id = preferencias.getInt(PREFERENCIA_MELODIA,
R.id.silencio);
RadioButton rb;
rb = (RadioButton) findViewById(id);
rb.setChecked(true);
} // onCreate
11. Haz un cat del chero para ver la estructura del XML. Comproba-
rás que dentro está la entrada para nuestro atributo. El valor es el
identicador del botón de radio.
12. Si utilizas ls -l, podrás ver los permisos del chero. Comprueba que
el usuario y el grupo tienen acceso de lectura y de escritura. Ambos
son el usuario particular que Android ha asociado a nuestra aplicación
(app_xx).
InputStream is = getResources().openRawResource(R.raw.<id>);
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioGroup
android:id="@+id/rgOpciones"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical">
</RadioGroup>
<Button
android:id="@+id/bAceptar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@android:string/ok"
android:onClick="onAceptar"/>
</LinearLayout>
</ScrollView>
5. Abre el chero recién creado, y mete varias líneas de texto con lo que
serían las melodías disponibles.
Silencio
Andrómeda
Basic Bell
Cassiopeia
Chime
A cricket chirps
Crossing walk
Cuisine
Down hill
Emotive sensation
Eridani
Faint
Happy synth
Illuminator
Lira
RadioGroup rg;
rg = (RadioGroup) findViewById(R.id.rgOpciones);
InputStream is;
is = getResources().openRawResource(R.raw.melodias);
Scanner sc = new Scanner(is);
while (sc.hasNextLine()) {
String melodia = sc.nextLine();
RadioButton rb;
rb = new RadioButton(this);
rb.setText(melodia);
rg.addView(rb);
}
((RadioButton)rg.getChildAt(0)).setChecked(true);
} // onCreate
Ten en cuenta que esta aplicación es sólo un ejemplo para probar la carga
desde cheros de recursos crudos. La utilidad que pretende tener (hacer las
veces de preferencias del teléfono para elegir una melodía) requeriría todavía
bastante más trabajo de programación.
que nos devuelve una instancia de una clase tradicional de Java que
representa un stream de un chero. Como antes, podremos utilizar clases
auxiliares para recubrirlo y que nos facilite la escritura.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp">
<TextView
android:id="@+id/tvNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignBaseline="@+id/etNombre"
android:layout_marginRight="10dp"
android:text="@string/introduceNombre" />
<EditText
android:id="@+id/etNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvNombre"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:inputType="text"/>
<TextView
android:id="@+id/tvVisitantes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/etNombre"
android:paddingTop="20dp"
android:text="@string/visitantesAnteriores"/>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/tvVisitantes">
<TextView
android:id="@+id/etVisitantes"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="top"/>
</ScrollView>
</RelativeLayout>
EditText et;
et = (EditText) findViewById(R.id.etNombre);
et.setOnKeyListener(
new android.view.View.OnKeyListener() {
public boolean onKey(View v,
int keyCode,
android.view.KeyEvent event) {
// Han pulsado una tecla...
if ((event.getAction() ==
android.view.KeyEvent.ACTION_DOWN) &&
(keyCode ==
android.view.KeyEvent.KEYCODE_ENTER)) {
// Ha sido "intro"
onNuevoNombre();
return true;
}
return false;
}
});
EditText et;
et = (EditText) findViewById(R.id.etNombre);
String nuevoNombre;
nuevoNombre = et.getText().toString();
if (nuevoNombre.trim().equals("")) {
muestraMensaje(R.string.errorNombre);
return;
}
try {
FileOutputStream fos;
fos = openFileOutput(NOMBRE_FICHERO,
Context.MODE_PRIVATE |
Context.MODE_APPEND);
java.io.OutputStreamWriter out;
out = new OutputStreamWriter(fos);
out.write(nuevoNombre + "\n");
out.close();
muestraMensaje(R.string.bienvenido);
actualizarVisitantes();
}
catch (Exception e) {
muestraMensaje(R.string.errorRegistro);
}
InputMethodManager imm;
imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(et.getWindowToken(), 0);
et.requestFocus();
et.setText("");
} // onNuevoNombre
try {
String visitante;
FileInputStream fis;
fis = openFileInput(NOMBRE_FICHERO);
Scanner scanner = new Scanner(fis);
if (scanner.hasNextLine()) {
visitante = scanner.nextLine();
strBuild.append(visitante);
}
while (scanner.hasNextLine()) {
visitante = scanner.nextLine();
strBuild.append("\n" + visitante);
} // while
scanner.close();
tv.setText(strBuild.toString());
} // try
catch (Exception e) {
} // actualizarVisitantes
Toast t;
String mensaje;
mensaje = getResources().getString(id);
t = Toast.makeText(this, mensaje, Toast.LENGTH_LONG);
t.show();
} // muestraMensaje
10. Abre y cierra la aplicación varias veces, y constata que los nombres
anteriores se mantienen.
$ ./avd shell
Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:
http://developer.android.com/training/basics/activity-lifecycle/
recreating.html
Entrada/salida en Java
http://docs.oracle.com/javase/tutorial/essential/io/
http://developer.android.com/guide/topics/resources/providing-resources.
html
http://developer.android.com/guide/topics/data/data-storage.html
http://developer.android.com/reference/android/os/Environment.html
En el próximo capítulo. . .
En el próximo capítulo veremos el acceso a algunos de los sensores y
hardware especíco habituales en los dispositivos móviles.
ejemplo de grácos.
Vibrator vibrator;
vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
141
142 Capítulo 7. Sensores y otro hardware
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vibracion);
} // onCreate
Ten en cuenta que esto solicitará la vibración del móvil también cuando
esté activa la actividad y se gire. ¾Cómo lo evitarías?
1
3. Prueba la aplicación . Fallará.
<uses-permission android:name="android.permission.VIBRATE"/>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
1
Como podrás suponer, el emulador de AVD no emula la vibración, por lo que tendrás
que usar un dispositivo físico.
android:padding="10dp">
<TextView
android:id="@+id/tvCancion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_alignBaseline="@+id/bReproducir"
android:layout_alignParentLeft="true"
android:text="@string/queCancionEs"/>
<Button
android:id="@+id/bReproducir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvCancion"
android:layout_alignParentRight="true"
android:padding="8dp"
android:text="@string/reproducir"
android:onClick="onReproducir"
tools:context=".Vibracion" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bReproducir"
android:layout_above="@+id/bAceptar">
<RadioGroup
android:id="@+id/rgOpciones"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<RadioButton
android:id="@+id/ParaElisa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ParaElisa"
android:checked="true"/>
<RadioButton
android:id="@+id/champions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/champions"/>
<RadioButton
android:id="@+id/cumple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cumple"/>
<RadioButton
android:id="@+id/wakaWaka"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wakaWaka"/>
<RadioButton
android:id="@+id/yellowSubmarine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/yellowSubmarine"/>
</RadioGroup>
</ScrollView>
<Button
android:id="@+id/bAceptar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="onAceptar"
android:text="@android:string/ok"/>
</RelativeLayout>
_vibrator.cancel();
_vibrator.vibrate(patron, -1);
} // onReproducir
super.onPause();
_vibrator.cancel();
} // onPause
_vibrator.cancel();
RadioGroup rg;
rg = (RadioGroup) findViewById(R.id.rgOpciones);
int idRadioButton;
idRadioButton = rg.getCheckedRadioButtonId();
int idString;
if (idRadioButton == R.id.cumple)
idString = R.string.acertaste;
else
idString = R.string.fallaste;
} // onAceptar
Los dispositivos móviles van equipados con una creciente cantidad de sen-
sores que les permiten captar información del entorno a través de diferentes
magnitudes físicas.
SensorManager sm;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensores);
SensorManager sm;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
java.util.List<Sensor> listaSensores;
listaSensores = sm.getSensorList(Sensor.TYPE_ALL);
TextView tv;
tv = (TextView) findViewById(R.id.sensores);
tv.setText(sb.toString());
} // onCreate
m
Acelerómetro, gravedad y aceleración lineal, medidos en
s2
Detección de proximidad
donde:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/tvX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_above="@+id/tvY"
android:padding="8dp"
android:text="@string/x" />
<TextView
android:id="@+id/tvValorX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvX"
android:layout_alignBaseline="@+id/tvX"
android:padding="8dp" />
<TextView
android:id="@+id/tvY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:padding="8dp"
android:text="@string/y" />
<TextView
android:id="@+id/tvValorY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvY"
android:layout_alignBaseline="@+id/tvY"
android:padding="8dp" />
<TextView
android:id="@+id/tvZ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/tvY"
android:padding="8dp"
android:text="@string/z" />
<TextView
android:id="@+id/tvValorZ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvZ"
android:layout_alignBaseline="@+id/tvZ"
android:padding="8dp"/>
<TextView
android:id="@+id/tvPrecision"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@id/tvZ"
android:layout_alignParentBottom="true"
android:padding="8dp"
android:text="@string/precision" />
<TextView
android:id="@+id/tvValorPrecision"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvPrecision"
android:layout_alignBaseline="@+id/tvPrecision"
android:padding="8dp"/>
</RelativeLayout>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_acelerometro);
Vamos a ver un ejemplo muy simple que demuestra esta última posibili-
dad, para crear una actividad en la que aparecen barras de colores alternos.
Paint _brocha1;
Paint _brocha2;
7. Ejecuta el programa.
2. Igual que antes, tendremos una clase interna que heredará de View.
Como atributos guardará una lista estática (con un array y un con-
tador) que mantendrá las posiciones de los dedos (conseguidas en el
evento onTouchEvent()) y que serán usados en onDraw() para pintar
los círculos. Además, tendremos 10 brochas, una para cada potencial
dedo, cada una de un color. En el constructor lo inicializaremos todo:
int r = 0;
int g = 0xFF;
int b = 0;
for (int i = 0; i < 10; ++i) {
_dedos[i] = new PointF();
_brochas[i] = new Paint();
_brochas[i].setColor(0xFF000000 +
(r << 16) + (g << 8) + b);
_brochas[i].setStyle(Paint.Style.FILL);
r += 20;
g -= 20;
b += 20;
} // for
} // Constructor
} // class MultitouchView
if (event.getAction() == MotionEvent.ACTION_UP)
_numDedos = 0;
else {
_numDedos = event.getPointerCount();
if (_numDedos > 10)
_numDedos = 10;
for (int i = 0; i < _numDedos; ++i) {
_dedos[i].x = event.getX(i);
_dedos[i].y = event.getY(i);
}
}
invalidate();
return true;
}
} // onDraw
Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:
http://developer.android.com/reference/android/os/Vibrator.html
http://developer.android.com/reference/android/hardware/SensorManager.
html
http://developer.android.com/reference/android/hardware/SensorEvent.
html
http://developer.android.com/guide/topics/graphics/2d-graphics.html
El próximo capítulo. . .
... ½½te toca escribirlo a ti!! Ha llegado la hora de que experimentes con
todas las posibilidades que proporciona Android. Se quedan en el tintero
muchas características interesantes. Conamos que el curso te haya servido
como impulso para encontrarlas :-)