dentro dentro de de la la lista de preferencias del fichero reslxmllpreferencias.xml. res/xml/preferencias.xml.
2.
android: title el el valor "Modo "Modo multijugador". multijugador". Asígnale al parámetro android:
3.
Crea tres elementos dentro de esta lista: lista: Activar Activar multijugador, multijugador, Máximo Máximo de de jugadores y Tipo de conexión. Para este ultimo han han de de poder poder escogerse escogerse los valores: valores: 8/uetooth, Bluetooth, Wi-Fi e Internet.
Otra opción para organizar las preferencias preferencias consiste consiste en en agruparlas agruparlas por por categorías. Con esta opción se visualizarán en en la la misma misma pantalla, pantalla, pero pero separadas separadas por grupos. Has de seguir el siguiente esquema: c PreferenceScreen> cCheckBoxPreference /> cEditTextPreference > android:Citle-"Modo multijugador"> /> c«CheckBoxPreference Chec kBoxPreference -.../> cEditTextPreference .../>
A continuación se representa la forma en la la que que Android Android muestra muestra las las categorías: categorías:
108
Actividades e e Intenciones
Reproducir música
Sí reprociuce oí&acs ds fc:1d<) fcodo
:~f:' fi:
Tipo de gráficos
•
tos gráficos Jdf!(CJS Ss fícogí l::!la frr:pres~r;tCDúfl í pres«níí non d~ de los
S~ ~~<:OJ?.
Número Numero de Fragmentos :;u&HO~ esoícs t~ 01:(::; x )"=! ::.11\•ldt J~tt: oUe En cuantos divide ti!'! un astei alele
(n
Activar modo multijugaei muitijugac. •| Máximo de Jugadores jugadores Tipo de conexión
é
a
1.
Práctica: Organizando preferencias (//). (II). Modifica la práctica anterior para que en lugar de de mostrar mostrar las las propiedades en dos pantallas, las muestre en una sola, tal y como se se muestra en la imagen anterior.
3.5.2. Como se almacenan las preferencias de usuario Si modificas un valor de una preferencia, este quedará almacenado almacenado de de forma forma dispositivo. Para conseguir esta persistencia persistencia Android Android utiliza utiliza un un permanente en el dispositivo. la carpeta carpeta fichero xml que se almacena en el sistema de ficheros. Se utiliza la de esta esta carpeta, shared_prefs para almacenar los ficheros de preferencias. Dentro de se utiliza utiliza el el fichero fichero para almacenar las preferencias de usuario se nombre,. del .paquete_preferences.xml, donde hay que remplazar nombre . paquete_preferences.xml, remplazar nombre . paquete por el paquete que contiene la aplicación. Esto nombre.. del del.paquete Esto significa significa que solo puede haber unas preferencias de usuario por aplicación. Como Como se se de preferencia, preferencia, pero pero aa estudiará en el capítulo 9 pueden haber otros ficheros de directamente por por diferencia de las preferencias de usuario no pueden ser editadas directamente el usuario sino que hay que acceder a ellas por código.
Ejercicio paso a paso: Donde se almacenan las preferencias de usuario. de crear: crear: Veamos donde se han almacenado las preferencias que acabamos de
1.
Para navegar por el sistema de ficheros de un dispositivo (tanto virtual virtual como como real) accede al menú Windows > Show View > Others Others...... >> Android >File > File
Expl. Expl.
109
El gran libro de Android Androíd 2.
Busca el siguiente siguiente fichero: fichero: /data/data/org.examples.asteroide/shared_prefsl /data/data/org. examples. asteroide/sharedjprefs/ org. examples. ex ampies, asteroide asteroide_preferences. xml. _preferences.xml.
IH Probíem s © Consolé Qp Emú lato j Cl LogCat \ Q Resourc s Cls i Mame
Expl .
: Píopertt i
Si1e St2e , [)ate Date
i>
2010-03-02 2010-03-0Z 2010-06-04 2010·0641 2010-06-07 2010-06-07 2010-06-07 2010-06-07 2010 ·06·09 2010-06-09
ip.co.omfonsoft.openwnn yg» org.example.Shape.Drawñble 1• ¡B. org.exampl-e.Shape_Drawable a ~ org org.example.asteroides ..e:xampl·e.a.steroides. !> ¡B. Iíb m?5 Mb j WS shared_prefs íM org.e3e8fnpl«.asfeeroi d€S_preferen ces.xml p org.exsmpíe.locationtest ~
®¡;. ¡p.co.cmron>oft.openwnn
Irme Time 08:32 0832
15;22 17«4 17;04 17:04 14.-01 14:01 266 2010-06-05 1431 2010-03-10 2010 ·G3 ·10 06:56 06:56
<111
15o2Z 1H l4
Sesrch i Q Des ices ^ B l á •§ i - ^ Info **■ P r:rm~s.icns Jnfo Permi&sions .l. drwxr-xr-x d:rwxr-xr-x drwxr-xr-x ctrvvxr-xr-x drwxr-xr-x d:rVt'Xr-xr-x drwxr-xr-x drwxr-xr-x df1NXrWX--X drwxrwx—x íül -rw-rw—-d~v,;r-xr-x drvvxr-xr-x -
3.
Pulsa el botón del del disquete disquete para para descargar descargar elel fichero. fichero.
4.
Tiene que que ser ser similar similara: a: Visualiza su contenido. Tiene 11
11
3.5.3. Accediendo aa los los valores valores de de las las preferencias preferencias necesario acceder acceder aa los los valores valores de de las laspreferencias preferenciaspara paraalterar alterar Por supuesto, será necesario el funcionamiento de nuestra nuestra aplicación. aplicación. El El siguiente siguiente ejemplo ejemplo nos nos muestra muestracomo como realizarlo: publ ic void mostrarPrefe re ncias() { public mostrarPreferenciasí){ edPreferences( SharedPreferences pref pref == getShar getSharedPreferences< "org. example . asteroides.___ preferences", PFI.IVll.TE) ; "org.example.asteroides preferences", lvJC>DE_ MQDE_PRIVATE); Stri.ng ca : "" +-f- pre f ..getBoolean("música" getBoolean("musi.ca", ,true ) String s"' s = "músi "música: pref true)
.getSt.rin<:J ( "graf i cos ", "? " ) ; +", gráficos: "+ " + pref pref.getString("gráficos; Toas t . makf:Tex t: ( thi s , s, t . LEi"iJGTH_ SJIORT) . show { ) ¡ Toast.¡nakeText(this, s, Toas Toast.LENGTH_SHORT).show();
} La función comienza creando creando el el objeto objeto pref pref de de lala clase clase SharedPrefer·ences SharedPref erencesyy le asigna un fichero de preferencias, preferencias, abriéndolo abriéndolo en en modo modo privado privado (solo (solo nuestra nuestra acceso). A A continuación continuación crea crea elel string string ss yy leleasigna asignalos losvalores valores aplicación tiene acceso). de dos de las () yy las preferencias. preferencias. Se Se utilizan utilizan los los métodos métodos pret. pref getBoolean .getBoolean() pret. get.Stri ng ( l, que pref.get.str.ingo, que disponen disponen de de dos dos parámetros: parámetros: elel valor valor de de key key que que queremos buscar (("música" "mu,3ica " yy "graLcos" } yy el "gráficos") el valor valor asignado asignado por por defecto defectoen en caso de no encontrar esta esta key key.. Finalmente se visualiza visualiza el el resultado resultado utilizando utilizando lala clase clase Toast. Toast. Los Los tres tres parámetros indicados son son el el contexto contexto (nuestra (nuestra actividad}, actividad), elel string stringaamostrar mostraryyelel tiempo que se estará mostrando mostrando esta esta información. información.
110
Actividades ee Intenciones Intenciones
m ■ Ejercicio paso aa paso: paso: Accediendo Accediendo aa los los valores valores de de las las preferencias. preferencias. 1.
anterior en en la la clase clase Asteroides. Asteroides. Copia la función anterior
2.
un botón. botón. Por Por ejemplo, ejemplo, Jugar. Jugar. Asígnala a un
3.
también el el resto resto preferencias preferencias que que hayas hayas introducido. introducido. Visualiza también
S—T Preguntas de repaso yy reflexión: reflexión : Menús Menús yy preferencias. preferencias.
3.6. Añadiendo una una lista lista de de puntaciones puntaciones en en Asteroides Asteroides 3.6. Añadiendo Muchos videojuegos permiten permiten recordar recordar las las puntuaciones puntuaciones de de partidas partidas anteriores, anteriores,de de jugador puede puede tratar tratar de de superar superar su su propio propio récord récord oo mejorar mejorar elel de de esta forma, forma, un jugador otros jugadores. jugadores. En el capítulo 99 estudiaremos estudiaremos varios varios métodos métodos para para que que esta esta información información se se almacene permanentemente permanentemente en en el el sistema. sistema. En En elel capítulo capítulo 10 1Oestudiaremos estudiaremos como como podemos compartirlo utilizando utilizando Internet. Internet. En En este este capítulo capítulo nos nos centraremos centraremos en en representar esta lista de de puntuaciones puntuaciones de de forma forma atractiva atractiva utilizando utilizandolalavista vistaListview. Listview. Vamos a intentar que que el el mecanismo mecanismo de de acceso acceso aa esta esta lista lista de de puntuaciones puntuaciones sea lo más independiente independiente posible posible del del método método final final escogido. escogido. Con Con este este propósito, propósito, vamos a definir la interfaz interfaz AimacenPuntuaciones. AlmacenPunt.u ac i ones.
Ejercicio paso aa paso: paso: El interfaz interfaz AimacenPuntuaciones. AlmacenPuntuaciones. 1.
aplicación Asteroides. Asteroides. Abre la aplicación
2.
Pulsa con el el botón botón derecho derecho sobre sobre lala carpeta carpeta de de código código (srclcom.example.asteroides) selecciona New New >>Interface. (src/com.example.asteroides) yy selecciona Interface.
3, 3.
En el campo Ñame Name introduce introduce AimacenPuntuaciones AlmacenPuntuaciones yy pulsa pulsa Finish. Finish.
4, 4.
Introduce el código que Introduce que se se muestra muestra aa continuación: continuación: public interface interface AimacenPuntuaciones AlmacenPuntuaciones {{ public void v o íd guardarPuntuacióníint guardarPuntuacion( int puntos,String ptm tos , St.ring nombre,long notnbre, long fecha); fecha); public publ ic Vector Vector listaPuntuaciones(int l istaPuntuac i o n es{ ínt cantidad); cantidad);
}
111
El gran libro de Android
j) á>
<:-:::asüssss*' Nota sobre Java: La interfaz inlerfaz es una clase abstracta pura, es decir una clase donde se indican los métodos pero no se imple implementa menta ninguno (en este caso se dice que los métodos son abstractos). Permite al programador de la clase establecer la estructura de esta (nombres de métodos, sus parámetros yy tipos que retorna, pero no el código de cada método). Una interfaz también puede contener constantes, es decir campos de tipo static yy final.
Las diferentes clases que definamos para almacenar puntuaciones han de implementar esta interfaz. interfaz. Como ves tiene dos métodos. métodos. El primero para guardar la puntuación de una partida, con los parámetros puntuación obtenida, obtenida, nombre del jugador y fecha de la partida. partida. La segunda es para obtener una lista de puntuaciones previamente almacenadas. almacenadas. El parámetro cantidad indica el número máximo de puntuaciones que ha de devolver. 5. 6.
Veamos a continuación una clase que utiliza esta interfaz. interfaz. Para ello crea en el proyecto la clase AlmacenPuntuacionesArray.
6.
Introduce el siguiente código: código; public class AlntacenPuntuacionesArray PúmacenPuntuacionesArray implements AlvnacenPuntuacionesf Alrr.-'lcenPuntuaciones {
private VectorcString> puntuaciones; p unt uaciones; prívate Vector public AlmacenPuntuacionesArray;) AlmacenPuntuacione sArray() {{ puntuaciones -~ new Vector(); VectorcString>(); puntuaciones.add( " J.2 3 000 Pepito Pepi to Domingez"); Domj_nge z''i; puntuaciones.add("123000 ppuntuaciones,add("111000 u nt uac i o n es.add( " lllOOO Pedro Martínez"}; Marti n ez•); puntuaciones.add("OllOOO Pérez " i; puntuaciones.add("011000 Paco Pérez");
} l"üOverride i c void gua rdarPuntuacion( int puntos, ©Override publ public guardarPuntuacioníint String nombre, nombre , loag long fecha)
{
puntu aciones.add(O, punto puntuaciones.add(0, puntoss +t· "" "" + nombre);
} @üvex:dck VectorcString> listaPuntuaciones
return
puntuaciones ; puntuaciones;
} } Esta clase almacena la lista de puntuaciones en un vector de String. String. Tiene el inconveniente de que al tratarse de una variable local, cada vez que se cierre la aplicación se perderán penderán las puntuaciones. puntuaciones. El constructor inicializa el array e introduce tres valores. valores. La idea es que aunque todavía no esté programado el juego y no podamos jugar, jugar, tengamos ya algunas puntuaciones para poder representar reoresentar una lista. lista. El método
112
Actividades e e Intenciones guardarPunt.uacion guardarPuntuacion () (> se limita a insertar en la la primera primera posición posición del del array un String con los puntos y el nombre. La La fecha no es es almacenada. almacenada. El método listaPuntuaciones lístaPuntuaciones () o devuelve el vector de de String String entero, entero, sin tener en cuenta el parámetro cantidad que debería limitar limitar el el número número de Strings devueltos. devueltos.
7.
En la actividad Asteroides tendrás que declarar una variable variable para para almacenar las puntuaciones: puntuaciones; public static AlmacenPuntuaciones aalmacén lmacen == new AlmacenPuntuacionesArrayí); AlmacenPuntuacionesP.rray () ;
J,
<""7'":;::::r Nota sobre Java: El modificador static permite compartir compartir el el valor valor de de una una variable entre todos los objetos de la clase. Es decir, decir, aunque aunque se se creen creen varios varios objetos, objetos, solo existirá una única variable almac2n almacén compartida por todos todos los los objetos. objetos. El El fuera de de la la clase. clase. Por Por lo lo modificador public permite acceder a la variable desde fuera Para acceder acceder aa esta esta variable variable tanto, no será necesario crear métodos getters yy setters. Para de un un punto yy el el no tendremos más que escribir el nombre de la clase seguida de nombre de la variable. Es decir Asteroides. almacen almacén.
8.
puntuaciones obtenidas, obtenidas, Para que los jugadores puedan ver las últimas puntuaciones modifica el cuarto botón del layout main. xml para que main.xmi que en lugar lugar del del texto texto modifica los los ficheros ficheros "Salir" se visualice "Puntuaciones". Para ello modifica res/values/strings. También sería interesante que que cambiaras el el fichero fichero res/values ·-en/ strings. res/values-en/strings.
9.
botón para para que que llame llame al al método: método; Modifica el escuchador asociado al cuarto botón public void lanzarPunr.uaci.ones (Vi.ew view) {{ lanzarPuntuaciones(View Intent ii = Intent( this , Puntuaciones .class); - new Intent(this, Puntuaciones.class); startActivity(i);
} 10. De momento no te permitirá ejecutar la aplicación. Hasta que que en en el el siguiente siguiente no será posible. apartado no creemos la actividad Puntuaciones no 3.7. 3. 7. La vista ListView Listview visualiza una lista deslizable verticalmente de de varios varios elementos, elementos, Una vista ListView Layout .Su .Su utilización utilización es es algo algo donde cada elemento puede definirse como un Layout en la la siguiente siguiente figura: figura: compleja, pero muy potente. Un ejemplo lo podemos ver en Welcome 10 Voicln The Voicín De hace 190 días Vo-cfn Hntdquartm Necesitas que te ayude? Support De hace 173 días k Volcin Headauarters, Barceiona, Spam I lose l. ^P>
De hace 93 dtas
113
El gran libro de Android Listview los siguientes siguientes cuatro cuatro pasos: pasos: El uso de un Li s tView conlleva los •
Diseñar un Layout que lo contenga contenga al al LListview. i stvie~".
•
que se se repetirá repetirá en en la la lista. lista. Diseñar un Layout individual que
•
Implementar una actividad que lo lo visualice visualice el el Layout Layout con con el el ListView. Listview.
•
de los los Layouts Layouts individuales individuales según según nuestros nuestrosdatos. datos. Personalizar cada una de
Veamos estos pasos con más detalle: detalle: s tView dentro Para utilizar un Li Listview dentro de de un un Layout Layout has has de de usar usar lala siguiente siguiente estructura: estructura:
< FrameLayout>
android : id=''@andrvid: .id;'.l.ist N android:id="Sandroid:id/list"
... . ~ . /> / >
/ >
Donde tenemos un FrameLayout FrameLayout que que me me permite permite visualizar visualizar dos dos posibles posibles elementos, uno u otro, pero no no los los dos dos simultáneamente. simultáneamente. El El primero primero es es elel L.istView Listview que se visualizará cuando haya haya algún algún elemento elemento en en la la lista. lista. El El segundo segundo puede puede ser ser cualquier tipo de vista y se se visualizará visualizará cuando cuando no no existan existan elementos elementos en en lala lista. lista. ElEl sistema controla la visibilidad visibilidad de de forma forma automática, automática, solo solo has has de de tener tener cuidado cuidado de de identificar cada uno de los elementos elementos con con el el valor valor exacto exacto que que se se muestra. muestra.
NOTA: Recuerda que para crear nuevos nuevos identificadores identificadores debes debes utilizar utilizar la la expresión expresión "@ ". El "@ ·+.icl/nornhre_.i t i d/nonthre^J elent:i den t i f.icador ti csdor". El carácter carácter @ @ significa significa que que se se trata trata de de un un identificador de recurso (es (es decir decir se se definirá definirá en en la la clase clase Rjava). R.java). El El carácter carácter ++ significa que el recurso ha de de ser ser creado creado en en este este momento. momento. En En este este caso caso hemos hemos utilizado identificadores que que ya ya han han sido sido definidos definidos en en el el sistema sistema (es (es decir decir @andro.id: Sandroid: significa que es un un recurso recurso definido definido en en la la clase clase android.Rjava). android.R.java). Una vez creado el Layout Layout que que contiene contiene el el ListView Listview tendremos tendremos que que visualizarlo en una actividad. Para Para este este propósito propósito utilizaremos utilizaremos un un tipo tipo de de actividad actividad especial, ListActivity. También tendremos que indicar indicar al al sistema sistema cada cada uno uno de de los los Layaouts Layaouts individuales individuales que contendrá el Listview. Esto (). Esto lo lo haremos haremos llamando llamando al al método método set:ListAdapter setListAdapterO. Existen varias alternativas con con diferentes diferentes grados grados de de dificultad. dificultad. Para Para una una mejor mejor comprensión iremos mostrando tres ejemplos ( l, de ejemplos de de uso uso de de setListAdapter setListAdapterO, de más más sencillo a más complejo. fc®© 16 3S SiíD 16:32 ©ES
114
Actividades e Intenciones Las capturas anteriores muestran muestran los los tres tres ListView ListView que que vamos vamos construir. construir. El El de de la izquierda se limita a mostrar una una lista lista de de Strings. Strings. El El del del centro centro visualiza visualiza una una lista lista de un Layout diseñado por por nosotros. nosotros. Aunqu,e Aunque este este Layout Layout tiene tiene varios varios componentes (una imagen y dos dos textos), textos), solo solo cambiamos cambiamos uno uno de de los los textos. textos. En En elel último ejemplo cambiaremos también también la la imagen imagen de de cada cada elemento. elemento.
3.7.1. Un ListViewque ListView que visualiza una una lista lista de de Strings Strings
Ejercicio paso a paso: Un LístView ListView que que visualiza visualiza una una lista lista de de Strings. 1.
El Layout que utilizaremos en en Asteroides Asteroides para para mostrar mostrar las las puntuaciones puntuaciones se llamará puntuaciones. xml. En puntuaciones.xmi. En el el se se incluye incluye una una vista vista ListView. Listview. Crea el Layout con el siguiente código: código: aandroid:layout_height="fill_parent"> n droid:text.Size= "1 0pt " " Í>
android:: layout layout__width= "fill_jparent" android ....width= "f i ll__parent " android:layout_ height=" Odip " android:layout_height="Odip" android:layout_ weight=" 1"> android:layout_weight="1"> android :drawSelectorOnTop= " .fa.lse" Í>
a.ndroid: id= " @a.nd.r-oi.ci: .id/em¡Jty " android:id="©android;id/empty" android:layout_width= android: layout_width= ""fill_parent" fi..U .....Parent" android : layout_height= "" f.i.l.I ...].)a.rent" android:layout_height= fi 1 .¡.....parent"
android: text = "No > androiol:text= "No hay puntuac:i.ones puntuaciones"" 1/> <íLinearLayout>
115
El gran libro de Android 2.
crear la la actividad actividad Puntuactones Puntuaciones para para visualizar visualizar elel Necesitamos ahora crear Crea una una nueva nueva clase clase en en tu tu proyecto proyecto ee introduce introduce elel Layout anterior. Crea siguiente código: ppublic ubl i c cla ss Puntuaci.oneE class Puntuaciones extends extends List:Activity ListActivity {{ public void onCreate (EundJ.e .,¡¡override SOverride void onCreate(Bundle savedinst.anceState) savedlnstanceState) super . oncr·eate ( savedi ns tanceState) ; super.onCreate(savedlnstanceState); setContentVtew(R.layout.puntua c iones); setContentView(R.layout.puntuaciones); setListAdapter(new this , setLis'cAdapter (new ArrayAdapter( ArrayAdapter íthis, androi.d. R. layout. s.ímp.le ___ .l. .i st _____ .?~ tt:mt ___ .1. android.R. layout. siinple_list_item_l,
{
1
.l\sterotdes. alma e en . .1. t st.aPunt.uac iones (.J. O) ) ) ; Asteroides.almacén.listaPuntuaciones(10)i);
} } que vaya vaya aa visualizar visualizar un un ListView Listview ha ha de de heredar heredar de de Toda actividad que Li.stActi.vi ty. Además, ha stAdapter() para ListActivity. ha de de llamar llamar alal método método setLi. setListAdapterO para indicar la lista de elementos elementos aa visualizar. visualizar. Este Este método método funciona funcionade dediferente diferente manera según los parámetros parámetros que que se se indiquen indiquen (ver (versobrecarga sobrecargaen enJava). Java). En En utilizado una una de de las las posibilidades posibilidades más más sencillas, sencillas, usar usarcomo como el ejemplo se ha utilizado de la la clase Clase ArrayAdapter. ArrayAdapter. Un Un ArrayAdapter ArrayAdapter actúa actúa parámetro un objeto de un ListView ListView yy el el suministrador suministrador de de información, información, en en este este como puente entre un de la la lista lista se se define define como como un un Strings Strings de de un un array. array. ElEl caso, cada elemento de constructor de esta clase clase tiene tiene tres tres parámetros: parámetros: El El primer primer parámetro parámetroes esun un cont.ext context con información sobre sobre elel entorno entorno de de lala aplicación. aplicación. Utilizaremos Utilizaremos como contexto la la misma misma actividad actividad que que hace hace lala llamada. llamada. ElEl segundo segundo parámetro es un Layout, Layout, utilizado utilizado para pa'a representar representarcada cadaelemento elementode delalalista. lista. en lugar lugar de de definir definir uno uno nuevo, nuevo, utilizaremos utilizaremos una unaya yadefinido definido En este ejemplo, en El último último parámetro parámetro es es un un array array con con los los strings strings aa mostrar. mostrar. en el sistema. El istaPu.ntuaciones (O ¡ que Para ello, llamamos al al método método 1listaPuntuaciones que nos nos devuelve devuelve esta lista del objeto estático estático al.macen almacén de de lala clase ClaseAsteroides. Asteroides. 3. 3.
Recuerda que que toda toda nueva nueva actividad actividad ha ha de de ser ser registrada registrada en en Android1'1anifest AndroidManif est... xnü xml..
4.
Prueba si funcionan funcionan las las modificaciones modificaciones introducidas. introducidas.
3. 7 .2. Un ListView que 3.7.2. que visualiza visualiza Layouts Lsyouts personalizados personalizados Vamos a personalizar el el Listview Listview anterior anterior para para que que cada cada elemento elementode delalalista listasea sea Layouf definido por por nosotros. nosotros. Para Para ello ello sigue sigue los los siguientes siguientespasos: pasos: un Layout
®
1. 1.
Ejercicio Ejercicio paso paso aa paso: paso: Un Un ListView ListViewque que visualiza visualiza Layouts Layouts personalízados. personalizados. Remplaza la clase clase anterior anterior por: por: public xtends ListActivity public class Puntuaciones Puntuaciones eextends ListActivity {{ @(rverride oíd oonCreate(Bundle n CreateíBundle savedlnstanceState) •SOverride public vvoid savedlnstanceState) super .onCreate(savedlnsta nceState);; super.onCreate(savedlnstanceState) setContentView (R. J.ayout .puntuac.iones); setContentView(R,layout.puntuaciones);
116
{
Actividades e Intenciones setListAdapter( new P.rrayhdapter ( this , ArrayAdapter{this, layout . element o_ lista, R. layout. elemento__lÍ8ta, R. id. titulo, R.id.título. Asteroides.almacen.listaPuntuaciones(lO))i; Asteroides.almacén.listaPuntuaciones(10)));
} } Como hemos explicado, la la clase clase ArrayAdapter ArrayAdapter permite permite insertar los datos desde desde un un array array de de stri.ng string en en nuestro nuestro Listview. Listview. En En este ejemplo se utiliza un un constructor constructor con con cuatro cuatro parámetros: parámetros: thia: this: es el contexto, con con información información sobre sobre el el entorno entorno de delalaaplicación. aplicación. R . layout. el emento_ list a : es r.layout.eieinento_iieta: es una una referencia referencia de de recurso recurso aa lala vista vista
que será utilizada repetidas repetidas veces veces para para formar formar lala lista. lista. Se Se define define a continuación. R . id. titulo: identifica r.id. identifica un un id id de de la la vista vista anterior anterior que que ha ha de de ser ser un un TextView. Textview. Su texto será será reemplazado reemplazado por por el el que que se se indica indica en en elel siguiente parámetro. Aste:núdes.a J.macen. listaPuntuaciones(lO): vector Asteroides.almacén.listaPuntuaciones{10): vector de de String String que serán serán visualizados visualizados en en cada cada uno uno de de los los con los textos que Text view. Esta lista Textview. lista es es obtenida obtenida accediendo accediendo aa lala clase clase Asteroides a su variable variable estática estática almacen almacén llamando llamando aa su su método listaPuntuaciones lista.Puntuaciones () {).. 2.
de definir definir el el Layout Layout que que representará representará cada cada uno uno de de los los Ahora hemos de Crea el el fichero fichero res/Layoutlelemento_lista.xml res/Layout/elementoJista.xml con con elementos de la lista. Crea el siguiente código: android:layout_height="?android:attr/listPreferr:editemHeight" > android:src="®drawable/asteroide2"/> : id="®+id/titulo" /> <
117
El gran libro de Android android: t.ext="Otro Texto" Tex.to" android:layout_toRightOf= "@id/icono" android:Iayout_toRightOf="®id/icono" android:layout_below="®id/titul.o" and roí d: 1 ayout_be.l ow= " ® id /1 i tul o " android:layout_alignParentBottom=" true" android:layout_alignParentBottom="true" android:gravity= "center"/> android:gravity="center"/> <:/RelativeLayout>
Este Layout representa una imagen a la izquierda con dos textos a la superior. Para combinar estos derecha, uno de mayor tamaño en la parte superior. elementos se ha escogido un Relat iveLayout, iveLayout. , donde el ancho se establece a partir de un parámetro de configuración del sistema ?android : attr/listPrefen·edltemHeight . El primer elemento que ?android:attr/listPreferreditemHeight. contiene es un imageview I rnageviev" alineado a la izquierda. izquierda. Su alto es la misma que el contenedor (f ( fill~oarent) iii_parent) mientras que el ancho se establece con el mismo parámetro que el alto del contenedor. Por lo tanto la imagen será cuadrada. 3.
Las imágenes utilizadas en la aplicación Asteroides puedes descargarlas de www.androidcurso.com. Copia el fichero asteriode2.png a la carpeta res/drawable. resldrawable.
4.
resultado. Ejecuta la aplicación y verifica el resultado.
3.7.3. Un ListView con nuestro propio adaptador En el ejercicio anterior hemos visto como podíamos asociar un Layout definido por Listview y personalizar uno de sus textos. Si queremos algo más nosotros al Lístvíew adaptable, como cambiar las fotos o cambiar varios textos, textos, tendremos que escribir nuestro propio adaptador de List.Vi.ew Llstview extendiendo la clase Base.Adapter. BaseAdapter. En esta clase habrá que sobrescribir los siguientes cuatro métodos: métodos: View getview;int getView (int position, posit.ion, View convertView, convert.View, V.lewGroup ViewGroup parent) Vlew
Este método ha de construir un nuevo objeto view con el Layout correspondiente a la posición position y devolverlo. devolverlo. Opcionalmente podemos partir de una vista base convertView para generar más rápido las vistas. vistas. El último parámetro corresponde al padre al que la vista va a ser añadida. añadida. getCount () int getCount()
Devuelve el número de elementos de la lista. (int position) Object getitem getltem{int
Devuelve el elemento en una determinada posición de la lista. getitemid(int position) long getltemldíint
Devuelve el identificador de fila de una determinada posición de la lista. lista. Veamos un ejemplo: ejemplo:
Ejercicio paso a paso: Un ListView LístVíew con nuestro propio adaptador. adaptador. 118
Actividades e e Intenciones 1.
Crea la !a clase MiAdaptador.java MiAdaptadorjava en el proyecto con el siguiente código: código: public class !VliAdaptador BaseAdapter { MiAdaptador extends BaseAdapcer private final Activity actividad; prívate private final Vector<;String> VectorcString> lista; prívate public MiAdaptador(Activity actividad, actividad. Vector lista) {{ super(); super() ; this .actividad = = actividad; this.actividad this.lista = = lista;
}
} @Override getitem(int argO) {{ ©Override public Object getltem(int return lista.elementAt(argO); l ista .elementAt(argO);
} ®Override public long getltemld(int getitemid(int. position) {{ ©Override return position; }
}
119
El gran libro de Android En el constructor de la clase se indica la actividad donde se ejecutará y la lista de datos a visualizar. visualizar. El método más importante de esta clase es getview ( l el cual tiene que construir los diferentes Layouts que serán getviewí) añadidos en la lista. lista. Comenzamos construyendo un objeto view a partir del código xml definido en e/emento_lista.xml. elementojista.xml. Este trabajo se realiza por Luego, se modifica el texto de uno medio de la clase Layoutinfiater. Layoutinflater. Luego, arrsy que se pasó en el constructor. de los Textview Text.View según el array constructor. Finalmente, se obtiene un número al azar (Math.roundO) (rvra th. round (} ) y se asigna uno de los tres gráficos de forma aleatoria. aleatoria.
2. 2.
Puntuac i ones la llamada al constructor de Remplaza en la clase Puntuaciones ArrayAdapter ArrayAdapter por: por: set.ListAdapter( new MiAdaptador(this, setListAdapter{new MiAdaptador{this. Asteroi.des .almacen .listaPuntuaciones(J.O))); Asteroides.almacén.1istaPuntuaciones(10))!¡
Ejecuta la aplicación y verifica el resultado. resultado.
NOTA: En algunos casos el adaptador ha de trabajar con listas muy grandes o estas listas han de ser creadas desde un servidor. En estos casos es mejor ir solicitando la información información a medida que se va representando. representando. Un ejemplo se muestra en la aplicación ApiDemos Ap.i.Demos descrita en el capítulo 1, en la actividad: com . example.android.apis.view.L i st13 com.example.android.apis.view.Listl3
3.7.4. 3.7.4. Detectar una pulsación sobre un elemento de la lista Un Listview puede tener diferentes componentes que nos permitan interaccionar usuario. Por ejemplo, ejemplo, cada elemento definido en getviewí) con el usuario. getview () puede tener botones para diferentes acciones. acciones. Hay un tipo de interacción muy sencilla de definir. definir. La clase ListActivity tiene un método que es invocado cada vez que se pulsa sobre un elemento de la lista. utilizarlo. lista. El siguiente ejercicio ilustra cómo utilizarlo.
Ejercicio paso a paso: Detectar una pulsación sobre un elemento de la lista.
5* 1.
Añade el siguiente método a la clase Puntuaciones.java: Puntuaciones.java: @Override onListitemClick(ListView listview, l istVi e w, ®Override protected void onListlteraClick(Listview posi.U.on, long id) { V.iew view, v.i.ew, int position, View super .onLi stitemCl ick(listView, view, vi.ew , position, position , id); super.onListltemClick(listview, Object o == getListAdapter getListAdapter() () .getitem(position); .getltemíposition) ,Toast.makeT■ext {this, "Selección: Toast.makeText.( t his , "Selecc ión: " + Integer.toString(position) Integer.toSt.ring(posi.tion) o.toString() ,Toast.LENGTH_LONG) . show(); + " - " + o,toString(),Toast.LENGTHLONG).show();
} 2.
120
Ejecuta la aplicación y verifica el resultado. resultado.
Actividades ee Intenciones Intenciones
3.8. Las Intenciones Intenciones 3.8. Las Una intención representa representa la la voluntad voluntad de de realizar realizar alguna alguna acción acción oo tarea; tarea; como como realizar una llamada de de teléfono teléfono oo visualizar visualizar una una página página web. web. Una Una intención intención nos nos permite lanzar lanzar una actividad actividad oo servicio servicio de de nuestra nuestra aplicación aplicación oo de de una una aplicación aplicación diferente. tipos de de intenciones: intenciones: diferente. Existen dos tipos •
Intenciones explícitas: explícitas: se se indica indica exactamente exactamente elel componente componente aa lanzar. lanzar. Intenciones típica es es la la de de irir ejecutando ejecutando los los diferentes diferentes componentes componentes Su utilización típica Asteroides internos de una aplicación. aplicación. Por Por ejemplo, ejemplo, desde desde la la actividad actividad Asteroides lanzamos AcercaDe por por medio medio de de una una intención intención explícita. explicita.
•
Intenciones implícitas: pueden pueden solicitar solicitar tareas tareas abstractas, abstractas, como como "quiero "quiero Intenciones implícitas: un mensaje". mensaje". Además Además las las intenciones intenciones se se tomar una foto" foto" o "quiero "quiero enviar enviar un de ejecución, ejecución, de de forma forma que que el el sistema sistema mirará mirará cuantas cuantas resuelven en tiempo de aplicaciones han han registrado la la posibilidad posibilidad de de ejecutar ejecutar ese ese tipo tipo de de actividad. actividad. al usuario usuario lala actividad actividad que que Si encuentra varias el el sistema sistema puede puede preguntar preguntar al utilizar. prefiere utilizar.
Además, ha estudiado estudiado en en el el apartado apartado Comunicación Comunicación entre entreactividades actividadeslas las Además, como se ha intenciones ofrecen un servicio servicio de de paso paso de de mensajes mensajes que que permite permite interconectar interconectardatos datos En concreto concreto se se utilizan utilizan intenciones intenciones cada cada vez vezque quequeramos: queramos: entre actividades. actividades. En •
Lanzar una actividad actividad (startActivity (startActi vi ty {)). () ).
•
servicio (startservice (stanservice ()). () ). Lanzar un servicio
•
Lanzar un anuncio anuncio de de tipo tipo broadcast broadcast (sendBroadcast (sendBroadcast (;). () ).
• Comunicarnos con un un servicio servicio (bindservíce (bindService ()). () ). En muchas ocasiones ocasiones una una intención intención no no será será inicializada inicializada por por lala aplicación, aplicación,sisino no ejemplo, cuando cuando pedimos pedimos visualizar visualizar una una página página Web. Web. En por el sistema, por ejemplo, En otras otras ocasiones será será necesario necesario que que la la aplicación aplicación inicialice inicialice su su propia propia intención. intención. Para Paraello ello objeto de de la la clase clase Intent. lntent. se creará un objeto Cuando se crea una una Intención Intención (es (es decir, decir, se se instancia instancia un un objeto objeto de de tipo tipo intent) Intent) esta contiene información información de de interés interés para para que que el el sistema sistema trate trate adecuadamente adecuadamente lala intención o para el el componente componente que que recibe recibe la la intención. intención. Podemos Podemos destacar destacar lala siguiente información: información: Nombre del componente: componente: Identificamos Identificamos el el componente componente que que queremos queremos lanzar lanzarcon con intención. Hay la intención. Hay que que utilizar utilizar el el nombre nombre de de clase clase totalmente totalmente cualificado cualificado (org. example. asteroides. AcercaDe) que que queremos queremos lanzar. lanzar. El El nombre nombre del del (org.exampie.asteroides.AcercaDe) componente es opcional. opcional. En En caso caso de de no no indicarse indicarse se se utilizará utilizará otra otra información información de la intención para para obtener obtener el el componente componente aa lanzar. lanzar.AA este este tipo tipo de de intenciones intenciones se les conocía como intenciones intenciones explícitas. explicitas. Acción: Una cadena de de caracteres caracteres donde donde indicamos indicamos lala acción acción aa realizar realizar (o (o en en caso de un Receptor de anuncios anuncios (Broadcast (Broadcast receiver) receiver) la la acción acción que quetuvo tuvo lugar lugar yy queremos reportar). reportar). La La clase clase intent Intent define define una una serie serie de de constantes constantes para para acciones genéricas genéricas que que son son listadas listadas aa continuación. continuación. No No obstante, obstante, además además de de estas podemos definir definir nuevas nuevas acciones: acciones:
121
El gran libro de Android
Constante
Componente a Acción lanzar
ACTION AC i ION CALL
Actividad
Inicializa Iníciaiiza una llamada de teléfono. teléfono.
ACTION EDIT
Actividad
Visualiza datos para que el usuario los edite. edite.
ACTION ACTION
~~AIN
MAIN
Actividad
Arranca como actividad principal de una tarea. tarea, (sin datos de entrada y sin devolver datos)
l>.CTION ACTION SYNC
Actividad
Sincroniza datos en un servidor con los datos de un dispositivo móvil.
ACTION- BATTERY- LOW
Receptor de anuncios
Advertencia de bacteria baja.
ACTION HEADSET plug PLUG action headset
Receptor de anuncios
Los auriculares han sido conectados o desconectados.
ACTION- SCREEN SCREEN- ON ON ACTION
Receptor de anuncios
La pantalla es activada. activada.
ACTION action_timezone_changed - TU.1EZONE- CHANGED
Receptor de anuncios
Se cambia ¡a la selección de zona horaria. horaria.
Tabla 2: Acciones estándar de las Intenciones. Intenciones.
También puedes definir tus propias acciones. acciones. En este caso has de indicar el paquete de tu aplicación como prefijo. prefijo. Por ejemplo: ejemplo: org.exampie.asteroides. org. example. asteroides. ~1UESTRA PUNTUACIONES. MUESTRA_PONTCJAC IOKES. Datos: Referencia a los datos con los que trabajaremos. trabajaremos. Hay que expresar estos datos por medio de una URI (el mismo concepto ampliamente utilizado en Internet). tel:963228525, http://www.androidcurso.com, Internet). Ejemplos de URis URls son: son: tel:963228525. content://call log/calls .. . En muchos casos resulta importante saber el tipo de content://cali loa/calis... datos con el que se trabaja. trabaja. Con este propósito se indica el tipo MIME asociado decir, se utiliza el mismo mecanismo que en Internet. Ejemplos de a la URI, URI, es decir, text/xml , image/jpeg, image/jpeg , audio/mp3 , . .. tipos MIME son text/xml, audio/mp3,... Categoría: Complementa a la acción. acción. Indica información adicional sobre el tipo de lanzado. El número de categorías puede ser componente que ha de ser lanzado. arbitrariamente ampliado. ampliado. No obstante, obstante, en la clase Intent mtent se definen una serie de categorías genéricas que podemos utilizar.
122 122
Constante
Significado
CATEGORY BROWSABLE
La actividad lanzada puede ser con seguridad invocada por el navegador para mostrar los datos referenciados por un enlace - por ejemplo, ejemplo, una imagen o un mensaje de correo electrónico. electrónico.
Actividades e e Intenciones Constante
Significado
CATEGORY GADGET CATEGORY_GADGET
La actividad puede ser embebida dentro de de otra actividad actividad que aloja gadgets.
CA1'EGORY HOI·1E CATEGORY_HOME
La actividad muestra la pantalla de inicio, inicio, la la primera primera pantalla que ve el usuario cuando el dispositivo dispositivo está está encendido o cuando la tecla HOME es presionada.
CATEGORY LAUNCHER
La actividad puede ser la actividad inicial de de una una tarea tarea yy se muestra en el lanzador de aplicaciones de de nivel nivel superior.
CATEGORY category PREFERENCE preference
es un un panel panel de de preferencias. preferencias. La actividad aa lanzar es
Tabla 3: Categorías estándar de las Intenciones.
Una categoría suele utilizarse junto con con una acción acción para para aportar aportar información información adicional. Por ejemplo, indicaremos ACTION_t-1AIN agtion_ main aa las las actividades actividades que que pueden pueden utilizarse como puntos de entrada de una aplicación. aplicación. Indicaremos Indicaremos además además CATEGORY_LAUNCHER category launcher en la actividad que que será escogida escogida por por defecto defecto para para ser ser lanzada en primer lugar. Extras: Información adicional que será recibida por por el el componente componente lanzado. lanzado. Está Está Estas colecciones colecciones de de valores valores formada por un conjunto de pares variable/valor. Estas se almacenan en un objeto de la clase Bundle. Bundie. Su Su utilización utilización ha ha sido sido descrita descrita en la sección Comunicación entre actividades. actividades. Recordemos Recordemos cómo cómo se se introducían estos valores en un Intent. mtent. intent.putExtra("usuario", intent.putExtra{"usuario", "Pepito Perez"l; Perez"); intent . putExtra("edad", 27); intent.putExtra{"edad", 27);
nuevas actividades aprendimos aprendimos aa lanzar lanzar una una En el apartado Creación de nuevas constructor Intent intent (Context (Context contexto, contexto, actividad de forma explícita utilizando el constructor ciass clase) . Por ejemplo, para para lanzar lanzar la la actividad actividad AcercaDe Acercaoe escribíamos: escribíamos: Class Intent intent = this , AcercaDe. class ); = new Intent( Intentíthis, AcercaDe.class); startActivity(intent); startActivity{intent);
podemos usar usar el el constructor constructor Para lanzar una actividad de forma explícita podemos Intent (String n, uri ÍString actio action, Uri uri). uri). Por ejemplo: ejemplo:
= new Intent(Intent.ACTION_DIAL, Intent(Intent.ACTION_DIAL, Intent intent = URI.pa:rse("t.el:962849347"); URI.parse ("tel:96284S347") ; startActivity(intent); startActivity(intent) ;
startActivityForResuit o si si esperamos esperamos que que la la También se puede utilizar star tAc tivi t yForResult () actividad nos devuelva datos.
Ejercicio paso a paso: paso; Uso de intenciones implícita. implícita. 1.
Crea un nuevo proyecto con nombre Intenciones. Intenciones.
123
El gran libro de Android 2.
El Layout de la la actividad actividad inicial inicial ha ha de de estar estar formado formado por por cinco cincobotones, botones, tal y como se muestra aa continuación: continuación:
abrir jágkia Web trinad»de Teletono Goigfe
man Jar Correo i 3.
actividad principal principal ee incorpora incorpora los los siguientes siguientesmétodos: métodos: Abre la actividad public void pgi·ieb pgWeb (Víew ÍView ··.riew) view) {{ Intent intent: (lnt:en t .ACTION_VIEvi, intent ~- new new lntent IntentÍIntent.ACriON__VIEW, Ud . . parse ( "ht tp: //wvvw . androidcurso. com/") ) ; Uri.parse("http://www.androidcurso.com/")) startActivity(intent); startActlvity(intent);
} public void llamadaTelefono(View llaraadaTelefono(View view) view) {{
Intent Intent int.ent intent == new new Intent Intent (Intent..ACTION_C.l.\I_,L, (Intent .ACTIOK__CALL, Uri. . parse("te1.:962849347")); Uri.parse{"tel:962849347")); startActivity(intent); startAct ivity(intent);
public void googleMaps (View view) googieMaps(View view) { Intent (Intent . ACTION_VIEI'l, Intent int.ent intent == new new Intent Intent(Intent.ACTIQK_VIEW, Uri. p arse ( "9eo: 41. 656313, ··O. 877351")) ; Uri.parse("geo:41.656313,-0.877351"));
startAct.ivity(intent); startActivity(intent);
} public void void tomarFoto(View toraarFoto(View view) view) {{ Intent intent "android. media.. act ion. Il'
startActivity(intent); startActivity(intent);
mandarCorreo(View public void mandarCorreo( View vview) iew) {{ Int.ent nt ~= new (Intent . .ACTION_SF.ND) ; Intent inte intent new Intent Intent(Intent.ACTION_SFM)); intent . set:'I'ype("text/plain"); intent.setType í"text/plain"); intent .. putExtra (Inte nt. EXTRA....SSüBJECT, L13JECT, "asunto"); intent.putExtra(Intent.EXTRA "asunto"); intent. putExtra ( Intent. EXTRA____TEXT, ; intent.putExtra(Intent.EXTRA TEXT, "texto "texto del del correo") correo"); intent . putExtra(Int.ent.EXTHA_ Et
124
Actividades e e Intenciones 4.
onclick de cada uno de los botones al método Asocia el atributo onciick correspondiente, correspondiente.
5. 6.
Si ejecutas esta aplicación en un emulador es muy posible que el botón mandar Correo o Google Maps no funcione. funcione. La razón es que no hay ninguna aplicación instalada en el emulador que sea capaz de realizar este tipo de acciones. problemas, Abre el AVD A VD Manager y acciones. Si tienes estos problemas, crea un dispositivo virtual con Google API. Estos dispositivos incorporan Google, como la además de las API de Android, algunas de las API de Google, de Google Maps (Estas API se estudiarán más adelante). adelante).
6.
Ejecuta la aplicación en un terminal real. Observa como el botón mandar Correo te permite seleccionar entre diferentes aplicaciones con esta funcionalidad. funcionalidad.
0 Email
JP;: Mensajes A WhatsApp WhatsApp ~~
'S' •
Wi-Fi
Este resultado puede variar en función de las aplicaciones instaladas.
NOTA; NOTA: En el capítulo 7 se estudiará el tema de la seguridad. seguridad Aprenderás como has de solicitar el permiso adecuado si quieres que tu aplicación llame por teléfono o acceda a Internet. Internet. Cuando estas acciones no las realizas directamente, si no que las pides a través de una intención, no es tu aplicación quien las realiza y por tanto no has de pedir estos permisos.
adicionales; Tabla con Intenciones que podemos Recursos adicionales: utilizar de aplicaciones Google. Google. Aplicación
Browser
URI URÍ
http ://d/recc/or)_ web http://dirección_ https:lldirección _ web h\Xps\lldlrección_web 1111 "" (cadena vacía) http ://dirección_ web http:lldirección_ https:lldirección_ web https :lldirección_web
Acción VIEW WEB_
SEARCH
—
Resultado
Abre una ventana de navegador con una URL. Abre el fichero en la ubicación indicada navegador. en el navegador.
125
El gran libro de Android Androíd Aplicación
URI
tel.número teléfono tel:mímero
Dialer
Resultado
Acción CALL
Realiza una llamada de teléfono. teléfono. Los números validos se definen en IETF RFC 3966. Entre estos se incluye: incluye: tel:2125551212 y tel:(212)5551212. Necesitamos el premiso android. pe:nnission. C.Z\.LL_PHONE android.permission.CALL_PHONE
te\:número_teléfono tel :número_teléfono voicemail: voicemail:
DIAL
Introduce un número sin realizar la llamada.
Google Maps
geo:latitud,longitud geo.latitud,longitud geo:lat,long?z=zoom geo:O,O?q=dirección geo:0,07g=dlrección geo:O,O?q=búsqueda geo.0,0?g=búsqueda
VIEW
Abre la aplicación Google Maps para una localización determinada. determinada. El campo z especifica el nivel de zoom.
Google Streetview
google.streetview: cbll=latitud,longitud& cb\\=latitudJongitud& cbp=1 ,yaw„pltch,zoom& ,yaw, pitch,zoom& mz=mapZoom
VIEW
Abre la aplicación Street View para la ubicación dada. dada. El esquema de URI se basa en la sintaxis que utiliza Google Maps. Maps. Solo el campo cb/1 cbll es obligatorio. obligatorio.
llegar a
a
a
Práctica Práctica:: Uso de intenciones implícitas. implícitas. 1.
Crea nuevos botones en la aplicación del ejercicio anterior y experimenta anterior. A con otro tipo de acciones y URL. Puedes consultar la tabla anterior. continuación tienes algunas propuestas: propuestas:
2.
WEB_ SEARCH. ¿Encuentras alguna diferencia? Compara las acciones VIEW view y web_search.
3.
CALL y dial. DIAL. ¿Encuentras alguna diferencia? Compara las acciones call
4.
Experimenta con Google Streetview
3.8.1. La etiqueta Cuando creamos una nueva actividade, servicio o receptor broadcast podemos ai sistema que tipo de intenciones implícitas pueden ser resueltas con informar al nuestro componente. componente. Para conseguir esto utilizaremos un filtro de intenciones de AndroidManifest.xml. mediante la etiqueta AndroldManifest.xml. Cuando desarrollamos una aplicación lo habitual es utilizar intenciones explicitas, explícitas, que son resueltas utilizando el nombre de la clase. clase. Por lo tanto, tanto, si vamos a llamar a nuestro componente de forma explicita, explícita, no tiene sentido crear un filtro de intenciones. intenciones.
e
Enlaces de interés: Aprender más sobre intenciones. intenciones. •
Deve/opers - Reference: C/ass lntent: Android Developers Class Intent:
http://developer.android.com/reference/android/content/lntent.html http://developer.android.com/reference/android/contenUintent.html
126
•
lntent Filters: Android Developers - Dev Gide: lntents Intents and Intent
•
http://developer.android.com/guide/topics/intents/intents-filters.html http://developer.android.com/quide/topics/intents/intents-fiiters html
CAPÍTULO CAPÍTULO 4. 4.
Gráficos Gráficos en en Android Android
Android nos proporciona, aa través través de de su su API API gráfica, gráfica, una una potente potente yyvariada variadacolección colección de funciones que pueden pueden cubrir cubrir prácticamente prácticamente cualquier cualquier necesidad necesidad gráfica gráfica de de una una aplicación. Podemos destacar destacar la la manipulación manipulación de de imágenes, imágenes, gráficos gráficos vectoriales, vectoriales, animaciones, trabajo con con texto texto oo gráficos gráficos en en 3D. 3D.
introducen algunas algunas de de las las características características más más significativas significativas En este capítulo se introducen del API gráfico de Android. Nos Nos centraremos centraremos en en el el estudio estudio de de las las clases clases utilizadas utilizadas para el desarrollo de de gráficos gráficos en en 2D. 2D. En En el el capítulo capítulo 22 se se describió describiócómo cómose se utilizaban utilizaban las vistas como elementos elementos constructivos constructivos para para el el diseño diseño del del interfaz interfaz de de usuario. usuario. Disponíamos de una amplia amplia paleta paleta de de vistas. vistas. No No obstante, obstante, en en muchas muchas ocasiones ocasionesva va a ser interesante diseñar diseñar nuestras nuestras propias propias vistas. vistas. En En este este capítulo capítulo veremos veremos cómo cómo hacerlo. hacerlo. Trataremos de aplicar lo lo aprendido aprendido en en un un ejemplo ejemplo concreto, concreto, lala representación representaciónde de gráficos en Asteroides. Asteroides. Se Se utilizarán utilizarán dos dos técnicas técnicas alternativas, alternativas, los los gráficos gráficos en en mapa mapa de bits y en formato formato vectorial. vectorial. Al Al final final del del capítulo capítulo se se describen describen las las herramientas herramientas disponibles en Android Android para para realizar realizar animaciones. animaciones. En En concreto concreto se se describirán describirán las las las animaciones animaciones de de propiedades. propiedades. Por Por supuesto, supuesto, resultaría resultaría animaciones Tween yy las todas sus sus funciones funciones para para gráficos, gráficos, por por lolo que que se se recomienda recomienda alal imposible abarcar todas lector que consulte la la documentación documentación de de Android Android para para obtener obtener una una descripción descripción pormenorizada.
Objetivos: •
Enumerar las distintas APIS APIS gráficas gráficas para para 2D 2D yy 3D 3D disponibles disponiblesen en Android. Android.
•
las principales principales clases clases para para gráficos gráficos en en 2D 2D Describir cómo se utilizan las (canvas, (Canvas, Paint y Path}. Path).
•
Introducir la clase Drawable Drawabie yy utilizar utilizar muchos muchos de de sus sus descendientes descendientes (BitmapDrawable, GradienDrawable, GradienDrawable, etc.). etc.).
127
El gran libro de Android aplicaciones. Estudiar cómo crear nuevas vistas yy utilizarlas en distintas aplicaciones.
•
• Aplicar gran parte de lo aprendido en un ejemplo concreto: Asteroides. Asteroides. Describir las herramientas de Android para crear animaciones.
•
4.1. 4.1. Clases Clases para para gráficos gráficos en en Android Android Antes de empezar a trabajar con gráficos conviene familiarizarse con con las clases clases que que nos van a permitir crear y manipular gráficos en Android. A A continuación, continuación, se se introducen algunas de estas clases:
--
,.,.....
... "..,,.., iim Poli[MedÍa]: AP/s para gráficos en Android. •Wür Poli[Media]:
4.1.1 4.1.1.. Canvas canvas representa una superficie donde podemos dibujar. dibujar. Dispone Dispone de de una una La clase canvaB texto, etc. etc. Para Para serie de métodos que nos permiten representar líneas, círculos, texto, Pain t ) donde definiremos dibujar en un canvas necesitaremos un pincel ((Paint) definiremos el el color, color, el el grosor de trazo, trazo, la transparencia, etc. También podremos definir definir una una matriz matriz de de 3x3 3x3 (Matri x ) que nos permitirá transformar coordenadas aplicando (Matrix) aplicando una una translación, translación, escala o rotación rotación.. Otra opción consiste en definir definir un área conocida conocida como como Clip, Clip, de de forma que los métodos de dibujo afecten solo a esta área. Veamos, a continuación, algunos métodos de la clase canvas canvas.. No No se trata trata de un un listado exhaustivo y muchos de estos métodos pueden trabajar trabajar con con otros otros parámetros, por lo tanto se recomienda consultar la documentación del SDK SDK para para una información más detallada. Para dibujar figuras geométricas: geométricas: ddrawCircle(float r awCirclel float ex, float cy, floa t rad i o , Paint float radio, Paint pincel pincel)) drawüval(Rect.F drawOval(RectF ovalo, Paint. Paint pincel) drawRect(RectF l) drawRect. (SectF rectl rect, Paint pince pincel) drawPoint ( float xx,, float yy,, Pain drawPoint(float Paintt pincel) drawPointBi float[S)] pts, Paint drawPoints(float Paint pincel)
Para dibujar líneas y arcos: drawLine( float iniX, i Y, float drawLine(float inix, float in iniY, float finX, finX, float float finY, finY, Paint. pincel) drawLines ( float [] punt.os, drawLines(float[] puntos, Paint pincel) ddrawArc(RectF rawl-1.rc (Rec t F ovalo loat iiniAnglulo, n i An glulo, float n glulo, ovalo,, ffloat float aanglulo, boolean usarCentro, Paint pincel) ddrawPath(Path r awPath(Path ttrazo, razo, Paint pincel pincel))
Para dibujar texto: drawText(String texto, float float x, float y, Paint Paint pincel)
128
Gráficos en Android Androíd drawTextOnPath(String texto, Path Path trazo, trazo, float float desplazamHor, desplazamHor, float desplazamVert. desplazamVert, Paint. Paint pincel) pincel) drawPosText(String texto, float[] float [] posicion, posición, Paint Paint pincel) pincel)
Para rellenar todo el canvas (a no no ser ser que que se se haya haya definido definido un un Clip): Clip): drawColor(int color) drawARGB(int drawARGBÍint alfa, int rojo, rojo, int int verde, verde, int int azul) azul) drawPaint(Paint pincel)
Para dibujar imagines: drawBitmap(Bitmap drawBitmap(Bitraap bitmap, Matrix matriz, matriz, Paint Paint pincel) pincel)
Si definimos un Clip, solo se dibujará en en el el área área indicada: indicada: boolean clipRect(RectF rectangulo) rectángulo) boolean clipRegion(Region clipRegion(Región region) región) boolean clipPath(Path trazo) ti-azo)
Definir una matriz de transformación (Matrix) (Matrix) nos nos permitirá permitirá transformar transformar coordenadas aplicando una translación, escala escala oo rotación: rotación: settvlatrix(fvlatrix setMatrix(Matrix matriz) Matrix getMatrix() getMatrixí) concat(Matrix matriz) translate(float transíate(float despazX, despazX, float float despazY) despazY) scale(float escalaX, float float escalaY) escalaY) rotate(float grados, float float centnJX, centroX, float float centroY) centroY) skew(float despazx, despazX, float float despazY) despazY)
Para averiguar el tamaño del canvas: int int
getEeight() getHeight!) getWidth () getWidthO
Poli[ Media]:|: La clase canvas en Androíd. PolílMedia Android.
Creación de una una vista vista personalizada. personalizada. Ejercicio paso a paso: Creación donde se se crea crea una una vista vista que que es es dibujada dibujada A continuación, se muestra un ejemplo donde por código por medio de un canvas. 1.
con nombre nombre EjemploGraficos. EjemploGraficos. El El nombre nombre del del Crea un nuevo proyecto con example.ejemplograficos. paquete debe ser org. example . ej emplograf ices.
2.
Remplaza el código de la actividad por: por: public clasa class EjemploGraficosActivity EjemploGraficosActivity extenda extends Activity Activity {
129
El gran libro de Android ©Override public void onCreate(Bundle savedlnstanceState) ssuper.onCreate(savedlnstanceState); uper. onC:ceate ( sav·edlnstances tate} ; setContentView( n e w EjemploView( this )); setContentView (new Ej eruploView {this) !;
publ ic class class EjemploView extends extend s View {{ public ppublic ub lic EjemploView (Context contexc) context) { super (context); super(context); } @Overr:ide ©Override void protected vo i d onDraw(Canvas canvas) {{ //Dibujar aquí í/Dibujar
Comienza con la creación de una Activity, pero en este caso, caso, el objeto view que asociamos a la actividad mediante el método setcontentView setcontentview () o no está definido mediante XML. Por el contrario, contrario, es creado mediante Ej emp l oView. código, código, a partir del constructor de la clase EjemploView. La clase EjemploView extiende view, view, modificando solo el método onDraw o ( l responsable de dibujar la vista. vista. A lo largo del capítulo, capítulo, iremos viendo ejemplos de código que pueden ser escritos en este método. método. 3.
Si ejecutas la aplicación no se observará nada. nada. Aprenderemos a dibujar en esta canvas utilizando el objeto Pa i nt descrito en la siguiente sección. sección. Paint
~)
......_-~' Nota sobre Java; Java: Siempre que en un ejemplo aparezca un error indicándote que no se puede resolver un tipo, pulsa Ctrl-Shift-0 Ctrl-Shift-O para añadir los impar/ import de forma automática. automática.
4.1.2. 4.1.2. Paint Como acabamos de ver, ver, la mayoría de los métodos de la clase canvas utilizan un parámetro de tipo Pa.i.nt. Paint. Esta clase nos permite definir el color, color, estilo o grosor del trazado de un gráfico vectorial.
_...
-
, .,_,,~
•
130
Poli[Media]: Androíd. Poli Medía : La clase dase Paint en Android.
Gráficos en Android
Ejercicio paso a paso: paso: Uso de la clase Paint. Paint. 1.
Escribe dentro de onDraw del ejercicio anterior el código siguiente: siguiente: Pa i nt pincel pincel= Pai nt() ; Paint = new Faintí); ppincel.setColor(Color.BLCffi) incel.setColor (C'ol o r .ELUE) ; ppincel.setStrokeWidth(8); inc el . s e t St r okeWidth (B) ; pinc e l. s e t Styl e(Style . STROKE) ; pincel.setStyle(Style.STROKE); canvas.drawCirc l e(150 , 150, 1100, 00 , pincel); canvas.drawCircle(150,
-~t
<;,"';":::::. Nota sobre Java: mpor t el sistema te Java: Si pulsas Ctrl-SIIift-0 Ctrl-Shift-O para añadir los iimport advertirá que la clase style S tyl e está definida en dos paquetes diferentes: !;;hoose type to to smport import ¿hoose type
Page 1 of 1
^ :• andrcid.graphlcs^Paf nt.Styte fl. alJ9wi~-graeh!~l'aintS~~. F
G raphics.PathDashPathEffectStyle © ' aandrcid.g ndrc id .graphics.PathDashPathEffectStyle
j
Te preguntará cuál de los dos quieres importar. importar. No suele resultar complicado resolver este problema si analizas el contexto en el que estás trabajando. trabaj ando. En nuestro caso estamos utilizando la clase Pa in r: por lo que la primera opción es la adecuada. Paint Si alguna vez te equivocas en esta selección, lo normal es que aparezcan errores en el código. En este caso, debes deshacer y seleccionar la otra opción.
2.
resultado. Ejecuta la aplicación para ver el resultado.
Eclipse, prueba otros valores 3. Aprovechando la opción de autocompletar de Eclipse, para color. Color. Y Style. style.
4.
Prueba otros métodos de dibujo, drawLi n e () ( 1 o drawpoint drawPoint (). () . dibujo, como drawLine
4.1.2.1. Definición de colores bits. Estos bits son divididos Android representa los colores utilizando enteros de 32 bits. alfa, rojo, en 4 campos de 8 bits: alfa, rojo, verde y azul (ARGB, (ARGB, usando las iniciales en inglés). inglés). Al estar formado cada componente por 8 bits, bits, podrá tomar 256 valores diferentes. diferentes. rojo, verde y azul son utilizados para definir el color, color, mientras Los componentes rojo, que el componente alfa define el grado de transparencia con respecto al fondo. Un valor de 255 significa un color opaco y, y, a medida que vayamos reduciendo el valor, el dibujo se irá haciendo transparente. transparente. Para definir un color tenemos las siguientes opciones: opciones:
131 131
El gran libro de Android int color; color = Color. BUJE'; Color.BLUE; //Azul opaco opaco //Azul color = Color.argb(l27, Color.argb(127, O, 0, 255, 255, 0); 0); //Verde //Verde transparente transparente color· = Ox7FOOFFOO; //Verde color 0x7F00FF00; //Verde transparente transparente .getColor(R.color.color_Círculo) ; color = getResources() getResources{).getColor(R.color.color__Circulo); una adecuada adecuada separación separación entre entre programación programación yy diseño, diseño, se se Para conseguir una la última última opción. opción. Es Es decir, decir, no no definir definir los loscolores coloresdirectamente directamenteen en recomienda utilizar la el fichero fichero de de recursos recursos res/val res/vaiues/colors.xmi: código, sino utilizar el u es/ colors. xml: vers i on = "l.O'' "utf-8"?> # "coior_circuio">#7fffff00
observar los los colores colores han han de de definirse definirse en en elel fichero fichero col coiors.xmi Como puedes observar ors . xml mediante sus componentes componentes ARGB ARGB en en hexadecimal. hexadecimal.
•—
Ejercicio paso paso aa paso: paso: Definición Definición de de colores. colores.
1.
Modifica el ejercicio ejercicio anterior, anterior, añadiendo añadiendo alal final final de de onDraw OnDraw elel código código siguiente: siguiente: pincel.setColor(Color l27,255,0,0)); pincel. setColor (Color..argb( arg£){127, 255, 0, 0) ) ; canvas.drawCircle(lSO, cativas.drawCircle(150, 250, 250, 100, 100, pincel); pincel);
2.
el color color rojo rojo seleccionado seleccionado es es mezclado mezclado con con elel color color de de Observa cómo el fondo. fondo. Prueba otros valores valores de de alfa. alfa
3.
Reemplaza la primera primera línea línea que que acabas acabas de de introducir introducirpor: por: píncel.setColor(Ox7FFFOOOO); pincel.setColor(0x7FPF0000) ; Observa cómo el el resultado resultado es es idéntico. idéntico.
4.
el fichero fichero res/values/colors res/vaiues/coiors. .xmi un nuevo nuevo color colorutilizando utilizandoelel Define en el xml un siguiente código: version= "1.0" encoding="utf~-· 88"?>
#7f > # 7fffff00 ■ccolor name=" name= "color_circulo f f f f 00 o:::resoul~ ces>
5.
Modifica el ejemplo ejemplo anterior anterior para para que que se se utilice utilice este este color colordefinido definidoen en los los recursos: recursos: pincel.setColor(getResources() . getColor(R.color.color_circulo)); pincel.setColor(getResources().getColor(R.color.color_circulo));
132
Gráficos en Android
4.1.3. Path La clase Path permite definir un trazado trazado aa partir partir de de segmentos segmentos de de línea línea yy curvas. curvas. Una vez definido puede ser dibujado con drawPath (Path, Paint.). con canvas. canvas.drawPath(Path, Paint). Un Un Path path también puede ser utilizado para para dibujar dibujar un un texto texto sobre sobre el el trazado trazado marcado. marcado.
Ejercicio paso a paso: Uso Uso de de la clase clase Path. Path. 1.
Remplaza dentro de onDraw del ejercicio ejercicio anterior anterior el el código código siguiente: siguiente: Path trazo == new Path(); Pathí); t.razo.addCircle(l50, trazo.addCircle(150, 150, 150, 100, 100, Direct.ion.CCW); Direction.CCW) ; canvas. drawColor (Color. ¡.¡HITE); canvas.drawColor(Color.WHITE)¡ Paint pincel == new Paint(l; Paint O; pincel . setColor(Color pincel. setColor (Color.. BLUE); SLCÍB) ; pincel . setStrokeWidth(B); pincel.setStrokeWidth{8); pincel.setStyle{Style.STROKE); pincel.setStyle(Style.STROKE) ; canvas.drawPath(trazo, pincel); pincel.setStrokeWidth(l); pincel.setStrokeWidth(1); pincel.setStyle(Style.F.TLL); pincel.setStyle(Style.FILL); pincel.setTextSize(20); pincel. setTypeface (Typeface . 8ANS_8E'RJF) ; pincel.setTypeface(Typeface.SANS_SERIF); canvas.drawTextOnPath("Desarrollo de de aplicaciones aplicaciones para para móviles con Android", Android", trazo, trazo, 10, 10, 40, 40, pincel); pincel);
2.
El resultado obtenido ha de ser:
\~e:>UdP
0"
0° ·
'21
ti/)
o
í'lo 0
V ..., V~
\ ,¡., 1 % ~
iJ.J
~
a
3
o< .-·
~
\"o
))-1 /Jet ro id ^ürcud 3.
Modifica en la segunda línea ion. ce¡.;¡ (contrario linea el parámetro parámetro Direct Direction.ccw (contrario aa las las agujas del reloj) por Direct ion. cw (a Direction.cw (a favor favor de de las las agujas agujas del del reloj) reloj).. Observa el resultado.
133
El gran libro de Android 4.
Modifica los parámetros significado. comprendas su significado.
de
canvas. canvas . dravrextc-nPath dra1>."fextOnPath í) {) hasta
que
5.
¿Podrías dibujar el texto a favor de las agujas del reloj, pero por fuera del círculo?
Ejercicio paso a paso: paso; Un ejemplo de Path más complejo. 1. 1.
Remplaza las dos primeras líneas del ejemplo anterior por: Path trazo =~ new Path(); PathO ; trazo . moveTo(50, 100); trazo.moveTo{50, trazo.cubicTo(60,70, 150,90, 200,110); trazo.lineTo{300,200); trazo.lineTo(300,200);
2. 2.
El resultado obtenido debe ser similar a: a:
^esarroj/o^
El trazo comienza desplazándose a las coordenadas (50,100). (50,1 00). Luego introduce una curva cúbica o Bézier hasta la coordenada (200, 11 0). Una (200,110). control, el primero (60,70) permite curva Bézier introduce dos puntos de control, controlar cómo arranca la dirección del comienzo de la curva y el segundo manera, (150,90) la dirección del final de la curva. Funciona de la siguiente manera, si trazas una recta desde el comienzo de la curva (50, 100) hasta el primer (50,100) punto de control (60,70), la curva se trazará tangencialmente a esta recta. recta. Finalmente, (200, 11 O) hasta Finalmente, se añade una línea desde las coordenadas (200,110) (300,200). (60,70)
.l 0,1 00)
(150,90) ¿200,110)
(300,200) r(300,200)
134
Gráficos en Android 3.
¿Por qué aparece una separación entre la "p" y la la "1"? "I"? ¿Qué ¿Qué dos dos parámetros del siguiente gráfico tendríamos que modificar para para que esta separación no estuviera? (Solución: (150,90) => (150,65}) (150,65))
4.1.4. Drawable La clase Drawable es una abstracción que representa "algo que que puede puede ser ser dibujado". dibujado". Esta clase se extiende para definir gran variedad de de objetos objetos gráficos gráficos más más específicos. específicos. Muchos de ellos pueden ser definidos como recursos usando usando ficheros ficheros XML. Entre ellos tenemos los siguientes:
BitmapDrawable: Imagen basada en un fichero gráfico (PNG o JPG). Etiqueta Etiqueta XML XML .
ShapeDrawable: Permite realizar un gráfico a partir de primitivas primitivas vectoriales, vectoriales, como como .. . ) o trazados ((path). Path). No formas básicas (círculos, cuadrados, cuadrados,...) No puede ser ser definido definido mediante un fichero XML. LayerDrawable: LayerDrawable: Contiene un array de Drawable que son visualizados según según el el orden del array. El índice mayor del array es el que se representa representa encima. encima. Cada Cada Drawable puede situarse en una posición determinada. Etiqueta XML list>..
StatelistDrawable: StateListDrawable Similar al anterior pero ahora podemos usar usar una una máscara de de bits y podemos seleccionar los objetos visibles. Etiqueta XML . .
botones o fondos. fondos. GradienDrawable: Degradado de color que puede ser usado en botones TransitionDrawable: Una extensión de LayerDrawabJ..es LayerDrawabies que permite un efecto de de fundido entre la primera y la segunda capa. Para iniciar la transición hay hay que que ( int tiempo). Para visualizar la llamar a startTransition startTransítioníint la primera primera capa hay hay que llamar a resetTransitionO resetTransi t ion () .. Etiqueta XML . . AnimationDrawable: AnímationDrawable: Permite crear animaciones frame a trame frame a partir partir de de una una serie serie Dra-...;able. Etiqueta XML de objetos Drawable.
clase orawable, Drawable, oo uno uno de de sus sus También puede ser interesante que uses la clase descendientes, como base para crear tus propias clases gráficas. Además de ser dibujada, la clase Drawable proporciona proporciona una una serie de de No todo todo mecanismos genéricos que permiten indicar cómo ha de ser dibujada. No orawable ha de implementar todos los mecanismos. Veamos los más importantes: Drawable importantes:
rectángulo donde donde ha de de ser ser El método setBounds(xi,yi,x2,y2) setBounds (xl, yl, x2, y2) permite indicar el rectángulo dibujado. Todo Drawable debe respetar el tamaño solicitado por por el cliente, es decir, decir, dibujado. debe permitir el escalado. Podemos consultar el tamaño preferido preferido de de un un orawable Drawable mediante los métodos getlntrinsicHeight (} {) y getintrinsicWidth getlntrinsicWidth (). (). getPadding(Rect) los márgenes márgenes El método getPadding (Rect) nos proporciona información sobre los recomendados para representar contenidos. Por ejemplo, un orawabJ.e Drawable que que intente ser un marco para un botón, debe devolver los los márgenes márgenes correctos correctos para para localizar las etiquetas, u otros contenidos, en el interior del botón. botón.
135
El gran libro de Android setstate (int (int [J[] li permite permite indicar indicar alal DrawabJ.e Drawabieen enqué quéestado estadoha hade deser ser El método setState dibujado, dibujado, por ejemplo ejemplo "con "con foco", foco", "seleccionado", "seleccionado", etc. etc. Algunos Algunos Drawable Drawabie cambiarán su representación representación según según este este estado. estado. El método setLeve l (intl permite setLevel(int) permite implementar implernentar un un controlador controlador sencillo sencillo para para indicar cómo se representará e . Por representará el el Drawab1 Drawabie. Por ejemplo, ejemplo, un un nivel nivel puede puede ser ser interpretado como una una batería batería de de niveles niveles oo un un nivel nivel de de progreso. progreso. Algunos Algunos Drawable Drawabie modificarán la la imagen imagen basándose basándose en en elel nivel. nivel.
Drawabie puede realizar realizar animaciones animaciones al al ser ser llamado llamado desde desde elel cliente cliente aa su su Un Drawable Drawabie.Caiiback. Todo cliente cliente debe debe implementar implernentar esta esta interfaz interfaz (vía (vía método Drawable . caUba.ck. Todo setCa1lback íDrawable. cal l back) ). El setcaiiback (Drawabie.Caiiback)). El sistema sistema nos nos facilita facilita realizar realizaresta estatarea tareade de de setBackg:r-oundDrawable setBackgroundDrawable(í Drawable) Drawabie) eeImageView. ImageView. forma sencilla a través de
Poli[Media]: PoiS[Med¡a]: La clase clase drawable drawabie en en Androíd. Android. alternativas para para crear crear una una instancia instancia de de Drawable. Drawabie. Puedes Puedes Existen varias alternativas de un un fichero fichero de de imagen imagen almacenado almacenado en en los los recursos recursosdel delproyecto, proyecto, crearla a partir de partir del del diseño diseño basado basado en en XML XMLoo puedes puedescrearla crearlaaapartir partir también puedes crearla aa partir de código. Veamos con más detalle algunas algunas de de las las subclases subclases de deDra·.vable. Drawabie.
4.1.4.1. BitmapDrawab/e BitmapDrawable La forma más sencilla de de añadir añadir gráficos gráficos aa tu tu aplicación aplicación es es incluirlos incluirlosen en lalacarpeta carpeta res/drawable del proyecto. proyecto. El El SDK SDK de de Android Android soporta soporta los los formatos formatos PNG, PNG, JPG JPGyy GIF. GIF. El formato aconsejado aconsejado es es PNG, PNG, aunque aunque sisi elel tipo tipo de de gráfico gráficoasí asílolorecomienda, recomienda, también puedes utilizar JPG. JPG. El El formato formato GIF GIF está está desaconsejado. desaconsejado. Cada gráfico de esta esta carpeta carpeta es es asociado asociado aa un un ID ID de de recurso. recurso. Por Porejemplo, ejemplo,para para el fichero mi . png se ___ imagen. Este mi __ imagen imagen.png se creará creará el el ID ID mi mi_imagen. Este ID ID tete permitirá permitirá hacer hacer referencia al gráfico desde desde el el código código oo desde desde XML. XML.
Ejercicio paso a paso: paso: Dibujar Dibujar un un BítmapDrawable BitmapDrawable de de los los recursos. Busca en Internet un un fichero fichero gráfico gráfico en en codificación codificación png png oo jpg jpg (los (los formatos formatos gráficos usados por por defecto defecto en en Android). Android). 1.
Renombra el fichero fichero para para que que se se llame llame mi_ímagen.png mijmagen.pngoo mi_ímagen.jpg mi_imagen.jpgyy arrastra a res/drawab/e. res/drawable.
2.
Declara la variable ernploView del variable milmagen mi imagen en en lala clase clase Ej Ejempioview del ejercicio ejercicio anterior: private prívate Drawable Drawabie milmagen; miImagen;
136
Gráficos en Android 3.
Escribe las siguientes tres líneas dentro dentro del constructor constructor de esta esta clase: clase: Resources res = context.getResources(); res.getDrawable(R.drawable.mi_imagen);; milmagen ~= res.getDrawable(R.drawable.ini_iínageíj¡ miimagen.setBounds(30,30,200,200) miImagen.setBounds(30,30,200,200);¡
4.
Escribe la siguiente línea en el método onDraw: mi Imagen. draw ( canvas) ¡ miImagen.draw(canvas);
4.1.4.2. GradienDrawable También podemos definir en XML otro tipo de Drawables como GradienDrawable. Por ejemplo, degradado desde el color color blanco blanco ejemplo, el siguiente fichero define un degradado ( FFF'FF'F') a azul (ogooff): ( OOOOFF): (ffffff) xmlns: android= "htt[-): //sc.he.rnas. a.ndroi.d. com/alJk/.res/andro.id ">
android:startColor="#FF'F'F'F'F" android:startColor="#FFFFFF" android:endColor="#OOO OF'F' " android:endColor="iOOOOFF" android:angle="2 70 " /> android:angle="270"
Este tipo de objetos gráficos es utilizado con frecuencia como fondo de de botones botones la dirección dirección del del degradado. degradado. Solo Solo se se o de pantalla. El parámetro angle indica la O, 90, 180 180 y 270. 270. permiten los ángulos 0,
Ejercicio paso a paso: Definir un GradienDrawable. 1.
Asteroides. Abre el proyecto Asteroides.
2.
Crea el siguiente fichero res/drawable/degradado.xml. resldrawableldegradado.xml:
com,lapk/rc:::s/andro:i.d '> xmlns android;angle="270" >
3.
4.
1
de una una vista, para conseguir Introduce la siguiente línea en el constructor de que este drawable sea utilizado como fondo. setBackgroundResource(H..drawable.degradado); setBackgroundResource ÍR.drawable.degradado); Resulta más conveniente definir el fondo de de una una vista vista en su su Layout Layout en en XML en lugar de hacerlo por código. código. Comenta la línea línea introducida introducida en en el punto anterior ee introduce el siguiente atributo en el Layout main.xml. main.xml. android: back.ground= background= "©drawable/degradado "®drawable/ degradado''"
137
El gran libro de Android
4.1.4.3. TransitionDrawable TransitionDrawable Un TransitionDrawable es una una extensión extensión de de LayerDrawablef> Layerorawables que que permite permite un un efecto efecto de fundido entre la primera primera yy la la segunda segunda capa. capa. Para Para iniciar iniciar lala transición transición hay hay que que llamar a startTransition.(int startTransition(iat tiempo) tiempo!.. Para Para volver volver aa visualizar visualizar lala primera primera capa capa hay que llamar a reset.Transit.ion resetTranai t ion ()()..
Ejercicio paso a paso: paso; Definir Definir un un TransítionDrawable. TransitionDrawable. 1.
Crea un nuevo proyecto proyecto con con nombre nombre Transition. Transition.
2.
recursos en en res/drawableltransicion.xml res/drawable/transicion.xmlcon con elelcódigo: código: Crea el siguiente recursos <.?xn1l .1.0"" encoding="utf-B"?> 11
com/apk/res/android" > - >
- android:drawable="@drawable/asteroide3"/>
3.
Remplaza en la actividad actividad el el método método oncreate oncreate por porelel siguiente siguientecódigo: código: ®Override ©Override public void onCreate(Bundle onCreate(Bundle savedinstanceStatei savedlnstanceState) super. onCreate(savedinstanceState); super.onCreate(savedlnstanceState); ImageView image this );; iniage = new ImageView( IraageView (this) setContentVie•.;(image); setContentView(image); 'I'.r:ansitionDrawable n ~- ('I'ransitionDrawable) TransitionDrawable transitio tx-ansition (TransitionDrawable) getResources () . getDrawable (R. drav1abl e . transicion ) ; getResources().getDrawable(R.drawable.transición); image . set imageDrawable(transition); image.setlmageDrawable(transition); transition.startTransition(2000); transition.startTransition(2000);
4.
{
} Si todo funciona funciona correctamente, correctamente, verás verás cómo cómo lala llamada llamada aa transition. startTransition (2000) provoca que transition.startTransition(2000)provoca que lala primera primera imagen imagen se se transforme, a lo largo de de dos dos segundos, segundos, en en la la segunda segunda imagen. imagen.
4.1.4.4. ShapeDrawable ShapeDrawable dinámicamente un un gráfico gráfico mediante mediante primitivas primitivas vectoriales, vectoriales, una una Cuando quieras crear dinámicamente utilizar ShapeDrawable. ShapeDrawable. Esta Esta clase clase permite permite dibujar dibujargráficos gráficos buena opción puede ser utilizar básicas. Un Un ShapeDrawable ShapeDrawable es es una una extensión extensión de de Drawable, Drawable, por porlolo a partir de formas básicas. tanto, puedes utilizar todo todo lo lo que que permite permite Drawable. Drawable.
paso: Definir Definir un un ShapeDrawable. ShapeDrawable. Ejercicio paso a paso: Veamos un ejemplo sobre cómo cómo utilizar utilizar un un objeto objeto shapeDrawable ShapeDrawable para para crear crear una vista a medida.
138
Gráficos en Android 1.
Abre el proyecto EjemploGraficos. EjemploGraficos.
2.
En la clase Ej emploVíew declara Ejempioview declara la la siguiente siguiente variable: variable: pri vate ShapeDr·awable Imagen; prívate ShapeDrawable mi milmagen;
3.
Añade las siguientes tres tres líneas líneas dentro dentro del del constructor constructor de de esta esta clase: clase: míimagen milmagen == new ShapeDrawable(new ShapeDrawablefnew OvalShape()); OvalShape()); miimagen.getPaint() .setColor(OxffOOOOff); milmagen.getPaint().setColor(OxffOOOOff); míima gen.setBounds(lO, 10, milmagen.setBounds(10, 10, 310, 310, 60); 60);
En el constructor, un objeto ShapeDrawable ShapeDrawable es es definido definido como como un un óvalo. óvalo. Se Se le asigna un color y se definen sus sus fronteras. fronteras. 4.
Escribe la siguiente línea línea en en el el método método onoraw: onDraw: míimagen.draw(canvas); miImagen.draw{canvas);
5.
Ejecuta la aplicación yy observa el el resultado. resultado.
Toda nueva vista creada va va aa poder poder ser ser reutilizada reutilizada de de forma forma directa directa por por cualquier programa. Incluso Incluso podrá podrá ser ser usada usada desde desde un un Layout Layout en en XML. XML. No No tienes tienes emploview en más que escribir la clase Ej Ejempioview en un un fichero fichero independiente independiente para para que que lala clase sea visible y utilizar la la siguiente siguiente etiqueta etiqueta en en tu tu r..ayout Layout cuando cuando quieras quieras dibujar dibujar un óvalo. óvalo, android: layout_height="wrap__cont.ent" />
de hacerlo. hacerlo. En el siguiente apartado trataremos de
4.1.4.5. AnimationDrawable Android nos proporciona varios mecanismos mecanismos para para crear crear animaciones animaciones gráficas. gráficas. Una Una estas animaciones animaciones pueden pueden ser ser creadas creadas en en ficheros ficheros XML. XML. En En ventaja a destacar es que estas una de de las las animaciones animaciones más más sencillas, sencillas, las las creadas creadas aa partir partir de de este apartado veremos una utilizaremos la la clase claseAnímatíonDra1vable. AnimationDrawable. una serie de fotogramas. Para ello utilizaremos
Ejercicio paso a paso: Uso Uso de de AnímatíonDrawable. AnimationDrawable. 1.
con nombre nombre Animacion. Animación. Crea un nuevo proyecto con
2.
Crea el siguiente recurso recurso resldrawab/e!animacion.xml. res/drawable/animacion.xml. androíd : oneshot= "false "> - />
139
El gran libro de Android -
a ndroid :duration= "2 00" /> android:duration="200"
3.
Copia los ficheros misi/1.png, misiH.png, misi/2.png misil2.png y misi/3.png misiIS.png a la carpeta res! res/ drawable. drawable. Los encontrarás en www.androidcurso.com.
4.
por: Remplaza el código de la actividad por: public class AnimacionActivity Activity {{ puhlic Aniltk"'!cionActivity extends .Act.ivit.y AnimationDrawable animacion; animación; @Override puhlic void onCreate(Bundle savedinst.anceSt.ate) SOverride public savedlnstanceState) {{ super.onCreate(savedlnstanceState); super . onCreat.e (savedinst:anceState); animacion (JI.nimationDrawable)getResources() .getDrawable( animación = (AnimationDrawable)getResources().getDrawable{ R.drawable.animación)¡; R.drawable.animacion) Ima geView vista == new ImageVie'\o;( this ); ImageView imageView(this); vista. setBackgroundColor (Color. VJHITE); v/HITE) ; vista.setBackgroundColor(Color. vista.setima9eDrawable(animacionl; vista.setlmageDrawable(animación); vista.setOnClickListenex(new vista.setOnClickListener(new OnClickListener(l OnClickListener{) { puhlic onClick:(View view) { public void onClick(View animacion.st.art.(); animación.start();
} })
;
set.Cont.ent.View{vist.a); setContentView(vista);
animación En este ejemplo se comienza declarando un objeto an i macion de la clase Animat.ionDrawabJ.e. AnimationDrawable. Se inicializa utilizando el fichero XML incluido en los mageview para ser representada recursos. recursos. Se crea una nueva vista de la clase Iimageview animación. Finalmente, por la actividad y se pone como imagen de esta vista anü1acion. se crea un escuchador de evento onclick onciick para que cuando se pulse sobre la
vista se ponga en marcha la animación. animación.
Preguntas de repaso y reflexión: Las clases para gráficos en Android. Android.
4.2. 4.2. Creación Creación de de una una vista vista en en un un fichero fichero independiente independiente Como hemos visto en los ejemplos anteriores, para poder dibujar en Android hemos emploView, descendiente de View. Esta clase tenido que crear una nueva clase Ej Ejemploview, icosActivlty por lo que solo podía ser utilizada EjemploGraficosActivit.y era creada dentro de EjempioGraf desde esta clase. clase. Va a resultar mucho más interesante crear esta clase en un fichero independiente. independiente. De esta forma, forma, podremos utilizarla desde cualquier parte de
140
Gráficos en Android nuestro proyecto, proyecto, o desde otros proyectos. proyectos. Incluso Incluso estará estará disponible disponible en en la la paleta paleta de de ejercicio, te te permite permite verificar verificar este este concepto. concepto. vistas del editor visual. El siguiente ejercicio,
Ejercicio paso aa paso: paso: Creación de de una una nueva nueva Vista Vista independiente. eraploview En este ejercicio vamos vamos aa poner la la clase clase ej E jempl ovi ew en en un un fichero fichero independiente para que pueda ser utilizada utilizada desde desde cualquier cualquier parte. parte. 1.
EjemploGraficos. Abre el proyecto EjemploG ra fieos.
2.
En la clase EjemploGraficosActivity Ejempl oGraf icosActivity selecciona selecciona todo todo el el texto texto correscorresla clase Ejemploview. pondiente a la definición de la Ej e mplovi ew.
3.
derecho sobre sobre src/org.example/ejemplografico srclorg.example/ejemp/ografico yy Pulsa con el botón derecho New/C/ass... selecciona la opción New/Class...
4. Introduce como nombre de la clase clase Ejemploview. Ej empl o Vi e w. 5.
el portapapeles. portapapeles. Pega el texto que has puesto en el
6.
que, al ejecutar la aplicación, aplicación, el el resultado resultado es es idéntico idéntico al al obtenido obtenido Verifica que, en la sección anterior. anterior.
En este apartado aprovechamos para para introducir introducir otros otros aspectos aspectos que que tendrás tendrás que que tener en cuenta para crear nuevas vistas. vistas. Cuando Cuando quieras quieras crear crear una una nueva nueva vista, vista, v i e w, escribir escribir un un constructor constructor yy como como mínimo mínimo tendrás que extender la clase view, sobreescribir los métodos onSizeChangedO onSi zeChanged () yy onDrawO. o n Dr aw (). Es Es decir, decir, tendrás tendrás que que esquema: seguir el siguiente esquema: ppublic ublic class c las s MiVista extends e x t ends View View {{ publ i c MiVista(Context context, con text , AttributeSet At t r ibuteS e t attrs) a ttrs) {{ public super (con text, attrs); a tt r s ); super(context, //Inicializa la vista //Ojo: Aún no se conocen sus dimensiones } @Clve:n:ide tected void vo i d onSizeChanged(int o nSizeChange d (int ancho, ancho, int i n t alto, alto, ©Override pro protected int a nch o_anteri or, int alto_anterior){ al t o_anterior){ int ancho_anterior, //Te informan del ancho y el alto
} ©Override @()verTíde protected p r otected void v oid onDraw(Canvas o nDraw(Canvas canvas) c a nvas) {{ í/Dibuja. aguí aquí la vista //Dibuja } } Observa cómo el constructor utilizado tiene tiene dos dos parámetros: parámetros: El El primero primero de de tipo tipo Cont.ext te permitirá acceder al al contexto contexto de de aplicación, aplicación, por por ejemplo ejemplo para para utilizar utilizar Context El segundo, segundo, de de tipo tipo AttributeSet, Attributesec , te te permitirá permitirá acceder acceder recursos de esta aplicación. El a los atributos de esta vista, vista, cuando sea creada creada desde desde XML. XML. El El constructor constructor es es el el
141
El gran libro de Android vista, pero, lugar adecuado para crear todos los componentes de tu vista, pero, cuidado, cuidado, en este punto todavía no se conoce las dimensiones que tendrá. Android realiza un proceso de varias pasadas para determinar el ancho y alto de cada vista dentro de un Layout. Cuando finalmente ha establecido las dimensiones de una vista llamará a su método onsizeGangedO. onsizeGanged () . Nos indicará como parámetros el ancho y alto asignado. asignado. En caso de tratarse de un reajuste de tamaños, tamaños, por ejemplo si una de las vistas del Layout desaparece y el resto tiene que ocupar su espacio, se nos pasará el ancho y alto anterior. anterior. Si es la primera vez que se llama al espacio, método estos parámetros valdrán O. 0. El último método que siempre tendrás que reescribir es onDrawO. onDraw () . Es aquí donde tendrás que dibujar la vista. vista.
1.
Ejercicio paso a paso: Una Vista que pueda ser diseñada desdeXML. desde XML
Vamos a modificar la vista anterior para que pueda ser utilizada usando un diseño en XML: 1. 1.
Ej empl.oView. para que coincida con el Modifica el código de la clase Ejemploview. siguiente (en negrita se resaltan las diferencias): diferencias): ppublic ublic class cla ss EjemploView Ejempl.oView extends exten ds View prívate ShapeDrawable ShapeDrawabl.e miImagen; miimagen;
{
public EjemploView(Context EjempJ.oView(Contex t context, contex t , AttributeSet attrs) attr s) super (context., attrs); attr s ); super{context, miimagen ShapeDrawable (new OvalShape{)); OvaJ.Shape () ) ; miImagen == new ShapeDrawable(new mi i magen .getPaint{) .setColor(OxffOOOOff); milmagen.getPainc().setColor(OxffOOOOff);
{
} ®Override p r otected void onSi z eChanged(int anc ho , int alto, ©Override protected onSizeChanged(int ancho, int ancho anc h o_ ant erior, iint n t alto_ l{ anterior, alto anterior anterior){ miimagen .setBounds (O , 0, O, anch o, alto); milmagen,setBounds(0, ancho,
}} 010vet.·:ci.d (Canvas canvas) ©Overridee protected void on.Draw onDraw(Canvas mi Imagen. clraw ( canvas) ; miImagen.draw(canvas);
{
} } La primera diferencia es la utilización de un constructor con el parámetro At trHmteset. Este constructor es imprescindible si quieres poder definir la AttributeSet. vista en XML. También se ha añadido el método onsizechanged, para que el óvalo se dibuje siempre ajustado al tamaño de la vista. vista. 2.
142
Abre el fichero main.xml en modo Graphical Graphicat Layout. Layout. En la paleta de vistas dentro de la última sección, sección, observa cómo se ha añadido la nueva vista: vista:
Gráficos en Android f2& & Library Views t_ Custom Si
QJ El EjemploView EjempioView 3.
Arrastra dos elementos de este tipo al Layout.
4.
vistas, tal y Modifica las propiedades que definen el ancho y alto de estas vistas, continuación: como se muestra a continuación: <,org. example exampJ.e.. e ejj emplograf empl.ogra.ficos. emploV.i ew android:layout_height="lOOpx"
android: id= ""!? @-r- id/ej i d/ejf::iirp.7.oV.i<.:·)h'2" e¡np 1 o Vi e w2" android: layout_width= "L.i .l..I_parent: " f.:i 11 _pa.rent " android: layout_ height= "t'ill_paren<:" //> > android:layout_height="fill_parent"
¡SOTA: "wrap_contentNuestra N OTA: No utilices el valor "r,.,TaP .....ccmtent ". Nuestra vista no ha sido programada sop ortar este tipo de valor. para soportar 5.
Ejecuta la aplicación. aplicación. El resultado ha de ser similar a: a:
Helio word
4.3. Creando Creando la la actividad actividad principal principal de de Asteroides Asteroides 4.3. Una vez que conocemos los rudimentos que nos permiten utilizar gráficos en Android vamos a aplicarlos a nuestro ejemplo.
143
El gran libro de Android Androld
Ejercicio paso a paso: Creando la actividad principal de Asteroides. Lo primero que necesitamos es crear una actividad que controle la pantalla del juego propiamente dicho. A esta actividad la llamaremos ,;uego. Juego. 1. 1.
Asteroides. Abre el proyecto Asteroides.
2.
Lo primero que necesitamos es crear una actividad que controle la pantalla del juego propiamente dicho. Crea la clase Juego ,Juego con el siguiente código. código. publ i c class c l ass Juego extends Ac tiv i ty {{ public Activity @Ovcrride o n Create (Bundle savedlnstanceState) savedins tanceState ) ®Override public void onCreate(Bundle super.onCreate(savedinstanceState super.onCreate(savedlnstanceState)) ; setContentView(R.layo ut .juego);; setContentView(R.layout.juego)
{
} } 3.
Necesitaremos un Layout para la pantalla del juego. Crea el fichero res/layout/juego. x ml con el siguiente contenido: contenido: res/layout/ juego. xmi android;layout_height="fill_parent" >
Como puedes observar, observar, cuando diseñamos un Layout en XML, no estamos limitados a escoger los elementos que tenemos en la paleta de vistas; vistas; vamos a utilizar vistas creadas por nosotros. En este ejemplo utilizaremos la vista org. example . asteroides . VistaJuego Vista(Juego que será descrita más adelante. Va a org.example.asteroides. ser esta vista la que lleve el peso de la aplicación. aplicación.
144
4.
android :bac kground asociado Observa que se ha indicado el parámetro android-.background al recurso ©drawable/fondo.
5.
De momento no podremos ejecutar la aplicación hasta haber definido la clase VistaJuego.
Gráficos en Android
4.3.1. La clase Gráfico El juego que estamos desarrollando va a desplazar muchos tipos de gráficos por pantalla: pantalla: asteroides, nave, nave, misiles, misiles, etc. etc. Dado que el comportamiento de todos estos elementos es muy similar, con el fin de reutilizar el código y mejorar su comprensión, comprensión, vamos a crear una clase que represente un gráfico a desplazar por pantalla. A esta nueva clase la llamaremos Grafico Gráfico y presentará las siguientes Drawable. características. El elemento a dibujar será representado en un objeto Drawabie. Como hemos visto, esta clase presenta una gran versatilidad, lo que nos permitirá tmapDrawable) , vectoriales (shapeDrawable), trabajar con gráficos en bitmap (Bi (BitmapDrawabie), (shapeDrawabie), gráficos con diferentes representaciones (stateListDrawable), gráficos animados (Ani mationDra•-.rable). Además, un G:r·afico (AnimationDrawable). Gráfico dispondrá de posición, velocidad de desplazamiento, ángulo de rotación y velocidad de rotación. rotación. Para finalizar, finalizar, un gráfico que salga por uno de los extremos de la pantalla reaparecerá por el extremo contrario, tal y como ocurría en el juego original origina) de Asteroides.
lÉlf 1. 1.
Ejercicio paso a paso: paso; La clase Gráfico. Gráfico.
Crea una nueva clase Grafico Gráfico en el proyecto Asteroides y copia el siguiente código. código. class Gráfico Grafico {{ private í//Imagen /Imagen que dibujaremos dj.bujaremos prívate Drawable drawable; private double posX, posY; //Posición //Velocidad desplazamiento private double incX, incY; i nt ángulo, angulo, rotacion;//Ángulo y velocidad rotación private int rotación;//Angulo y l to; private int ancho, aalto; //Dimensiones de la imagen /íPara determinar coliBión private int radioColision; //Para colisión view.ivaJ.idat.e) //Donde dibujamos el gráfico {usada (usada en view.ivalidate) private View view; /i ii Para determinar el espacio a borrar (view.ivalidate) (vi.ew. i vaU.date) public publ ic static final f ina l iint nt MAX_VELOCIDAD J.!l!X_ VELOCIDAD = 20; public Grafico(View Gráfico(View view, Drawable drawable) drawable){( this .view == view; this.view this .drawable = drawable; this.drawable drawable . getintrinsicWidth(); ancho == drawable.getlntrinsicWidthO; alto== drawable.getlntrinsicHeight drawable.getintrinsicHeight(); alto () ,radioColision = (alto+ancho)/4; (alto+ancho?/4; } public void dibujaGrafico(Canvas canvas}{ canvas){ canvas.save(); canvas.save{); int x=(int) x= (int ) {posX+ancho/2); (posX+ancho/2}; int y=( int ) (posY-ralto/2) (posY+alto/2);; y=(int) canvas.rotate(( float ) angula, ( float ) x,(float) x, (float ) y); canvas.rotate((float) ángulo,(float)
145
El gran libro de Android Androíd drawable .setBounds( ( i n t )posX, ({int)posY, i nt )posY, drawable.setBounds{íint)posX, (íint)posX+ancho, i n t )posX+ancho, {i n t )posY+alto); (int)posY+alto); drawab le.draw( canvas); drawable.draw(canvas); canvas.restore(); canvas.rescore(); int rlnval rinvaJ. = (int) /2 ++ MA.l( íint) l'1ath.hypot Math.bypot (ancho, (ancho, alto) alto)/2 MAX_____'v'ELOCIDAD VELOCIDAD;; view.invalidate(x-rinval, view.invalídate(x-rlnval, y-rinval y-rlnval,, x+rinval, x+rlnval, y+rinval}; y+rlnval);
} publ ic vo i d incrementaPos{ d o ub le factor} public void incrementaPos(double factor){{ posX+=incX posX-f- = incX * factor; factor; 1/ S.i. /,/ Si salimos de J.a la pantalla./ pantalla, corregimos corregimos pos.lcJ.on posición if(posX<-anch o/2} {posX=view.getWidth{)-ancho/2;} if{posX<-ancho/2) {posX=view.getwidth()-ancho/2;} if(posX>view . getWidth() - ancho/2) {{ posX=-ancho/2;} if(posX>view.getWidth()-ancho/2) posX=-ancho/2;} posYi-=incY * factor; factor; posY+=incY if (posY<-alto/2) {posY=view.getHeight{)-alto/2;} if(posY<-alto/2i {posY=view.getHeight()-alto/2;} if (posY>view.getHeight()-alto/2) {posY=-alto/2;} if(posY>view.getHeight()-alto/2! {posY=-alto/2;} angulo += +'= rotacion t or; í//Actualizamos /ActuaJ.izamon ángu}_o ángulo rotación * fac factor; ángulo
publ ic double distancia(Graf ico g) public distancia(Gráfico g) {{ Math.hypotSposX-g.posY, posy-g.posX); hypoi: (posX - g. pos Y, posy - g. posX) ; rretum e turn Math.
} ublic boolean s ion(Grafico g) ppublic boolean verificaColi verificaColision(Gráfico g) { (distancia(g) (radioColision+g.radioColision) rreturn e turn (distancia (g) < (radioColision+g. radioColision)};
2.
4)
Al principio de la clase clase hemos hemos definido definido varios varios campos campos con con elel modificador modificador prívate. Vamos a necesitar acceder aa estos estos campos campos desde desde fuera fuera de de lala clase, por lo que resulta necesario necesario declarar declarar los los métodos métodos get get yy set set correspondientes. Vamos a realizar realizar esta esta tarea tarea de de forma forma automática automática Eclipse. Sitúa Sitúa el el cursor cursor al al final final de de lala clase clase utilizando una herramienta de Eclipse. (a última llave) llave) y pulsa pulsa con con el el botón botón derecho. derecho. Selecciona Selecciona en en (justo antes de la el menú desplegable Source/Generate ... En Source/Generate Getters Getters and and Setters Setters... En lala todos los los campos campos (botón (botón Sellect Sellect Al!) Alí) yy pulsa pulsa OK. OK. ventana de diálogo marca todos Eclipse hará el trabajo por nosotros. nosotros.
..:;,.-z:;::::.' Nota sobre Java: En el el tutorial tutorial Java Java Esencial Esencial 1/ Encapsulamiento Encapsulamiento yy visibilidad visibilidad puedes aprender más sobre los los métodos métodos gel get yy set. set. Lo Lo encontrarás encontrarás en en www.androidcurso.com.
4.3.2. La clase VistaJuego Pasemos a describir la creación creación de de VistaJuego VistaJuego que, que, como como hemos hemos indicado, indicado, es es lala del juego. juego En En una una primera primera versión versión solo solo se se responsable de la ejecución del representarán los asteroides de de forma forma estática: estática;
146
Gráficos en Android
—
Ejercicio paso a paso: La clase VistaJuego. VistaJuego.
1.
Crea una nueva clase vistaJuego VistaJuego en el proyecto Asteroides y copia el siguiente código: código: public class VistaJuego Vist.aJuego extends View {{ //// í//i lili prívate prívate prívate
ASTEROIDES ¡¡lili ////// Vect.or Asteroides; /! Vector // Vector con los Astero.-ides Asteroides ínt numAst.eroides = 55;; //Número int numñsteroides // Número in.-iciaJ. inicial de asteroides ínt int numFragmentos ~= 33;; !! // Fragmentos en que se divide
public VistaJuego(Cont.ext. VistaJuego(Context context, AttributeSet attrs) { super( contex t, a.ttrs); super(context, attrs); Drawable drawableNave, drawableAsteroide, drawableMisil; drawablel•steroide . getResources () . getDrawable ( drawableAsteroide == context context.getResources().getDrawable( R.drawable. asteroidel); R.drawable.asteroidel); Asteroides == new Vector(); VeGtor(}; for (int O; ii < numAsteroides; i++} íint ii =~ 0; !•!•+) { Grafico Grafico( thís , drawableAsteroide); Gráfico asteroide = = new Gráfico(this, asteroide.setincY(Mat.h.random() asteroide.setlncY{Math.random() * 4 - 2); 2) ; asteroide.setincX(Mat.h 4 - 2); asteroide. setlncX (Math..random() randotn() **4-2); asteroide.setAngulo((ínt} h .random() * 360}); asteroide.setAngulo((int) (Mat (Math.random{) 360)); as t eroide.setRotacion(( ínt ) (Math.random() * 8 asteroide.setRotacion((int) 8 -- 4}); 4)); Asteroides.add(asteruide); Asteroides.add(asteroide);
} @Override ínt ancho, int alto, ©Override protected voíd void onSizeChanged( onSizeChanged(int int ancho_anter, alto_anter) {{ ancho_anter , int alto_anter} super. onSizeChanged(ancho, alto, ancho_anter, alt.o_anter}; super.onSizeChanged(ancho, alto_anter); // Una vez que conocemos nuestro ancho yy alt.o. alto, for (Grafico (Gráfico asteroide: Asteroides) {{ asteroide.setPosX(Math.random()* asteroide.setPosX(Math.random<)*
(ancho-asteroide.getAnchoO));
{ancho···asteroide . get~.ncho())};
asteroide.set.PosY(Math.random(}* asteroide.setPosY(Math.random()* (alto-asteroide.getAlto())); (alto-asteroide.getAltoO));
} } @Ove:rridE.o ©Override protected void onDraw(Canvas canvas) super .onDraw(canvas}; super.onDraw ícanvas); (Gráfico asteroide: Asteroides) { for (Grafico
{
asteroide.dibujaGrafico(canvas);
} } } 147
El gran libro de Android Como ves se han han declarado declarado tres tres métodos. métodos. En En elel constructor constructor creamos creamos los los asteroides e inicializamos inicializamos su su velocidad, velocidad, ángulo ángulo yy rotación. rotación. Sin Sin embargo, embargo, resulta imposible iniciar iniciar su su posición, posición, dado dado que que no no conocemos conocemoselel alto altoyyancho ancho de la pantalla. Esta Esta información información será será conocida conocida cuando cuando se se llame llame aa oonSizeChanged n sizecha nged () ().. Observa Observa cómo cómo en en esta esta función función los los asteroides asteroides están están situados de forma forma aleatoria. aleatoria. El El último último método, método, onDraw onDraw{)(),, es eselelmás másimportante importante de la clase vview, i e w, dado dado que que es es el el responsable responsable de dedibujar dibujarlalavista. vista. 2.
Hemos creado una una vista vista personalizada. personalizada. No No tendría tendría demasiado demasiado sentido, sentido, pero podrá ser utilizada utilizada en en cualquier cualquier otra otra aplicación aplicación que que desarrolles. desarrolles. uego. x ml yy observa Visualiza el Layout jjuego.xmi observa cómo cómo lala nueva nueva vista vista se se integra integra perfectamente en el el entorno entorno de de desarrollo. desarrollo.
»n ▼Jlccaíe ^ jn heme |L] ünearlayout @ Vista Juego (or<
3.
Registra la la actividad actividad Juego Juego en en AndroidManifest.xml. AndroidManifest.xml.
4.
Ejecuta la aplicación. Has Has de de ver ver cinco cinco asteroides asteroides repartidos repartidosalalazar azarpor porlala pantalla: pantalla:
4.3.3. Introduciendo la taJuego la nave nave en en Vi VistaJuego El siguiente paso consiste consiste en en dibujar dibujar lala nave nave que que controlará controlará elel usuario usuariopara paradestruir destruir los asteroides.
Práctica: Introduciendo Introduciendo la la nave nave en en VistaJuego VistaJuego 1.
Declara las siguientes variables variables al al comienzo comienzo de de lalaclase clasevistaJuego: VistaJuego: // ///Í
NAVE
/////í
private ave ; /! prívate Grafico Gráfico nnave; // Gráfico Gráfico de de la la nave nave private prívate int giroNave giroNave;; /! // Incremento Incremento de de dirección dirección private prívate float float aceleracionNave aceleracionNave;; // // aumento aumento de de velocidad velocidad /! Iacremento e
148
Gráficos en Android prívate statíc F'iiSO_GlRO_NiiVE = static final ínt int PASO_GIRG_NAVE = S; 5; prívate statíc Sf; static final float PliSO_ACELEF&!CION_l'JlitiE PASO_ACELERAC!ON_NAVE == O. 0.5f;
Algunas de estas variables serán utilizadas en el siguiente capítulo. capítulo. 2.
En el constructor de la clase instancia la variable drawableNave drawableNave de forma similar a como se ha hecho en dr·awableJl.steroide. drawabieAsteroide.
3.
Inicializa también en el constructor la variable nave de la siguiente forma: nave
-= new Grafico(thís, Gráfico(this, drawableNave);
4.
En el método onsicechange () (; posiciona la nave justo en el centro de la vista.
5. 6.
En el método onDraw { l dibuja la nave en el canvas onDrawO canvas..
G. 6.
Ejecuta la aplicación. aplicación. La nave ha de aparecer centrada:
7. 7.
Si cuando situamos los asteroides, alguno coincide con la posición de la nave, el jugador no tendrá ninguna opción de sobrevivir. Sería Seria más nave, interesante asegurarnos de que al posicionar los asteroides estos se encuentran a una distancia adecuada de la nave y, en caso contrario, tratar de obtener otra posición. Para conseguirlo puedes utilizar el siguiente código en sustitución de las dos líneas asteroide. setPosx ((...) ... ) y asteroide.setPosY(_) asteroide . setPosY (...).. do {{ asteroide.setPosX(Math.random()*(anchoasteroide.getAncho())); asteroide.setPosX(Math.random()*(ancho-asteroide.getAncho())); asteroide.setPosY(Math.randomO *(alto-asteroide.getAlto())); asteroide.setPosY(Math.random()*(altoasteroide.getAlto())); (ancho+alto)/5); }} while(asteroide.distancia(nave) while(asteroide.distancia(nave ) << (ancho+alto )/5 );
I-
. .. Ejercicio paso a paso: Evitando que VistaJuego cambie su
representación con el dispositivo en horizontal yy en vertical. 1.
Ejecuta la aplicación. aplicación.
149
El gran libro de Android 2.
orientación la la pantalla pantalla del del dispositivo. dispositivo. En En elel emulador emulador se se Cambia de orientación la tecla Ctri-F11 Ctrl-F11.. consigue pulsando la
3.
Observa cómo cada cada vez vez se se reinicializa reinicializa lala vista, vista, regenerando regenerando los los asteroides. Esto nos nos impediría impediría jugar jugar de de forma forma adecuada. adecuada. Para Para solucionarlo solucionarlo asteroides. edita AndroidManifet.xml. AndroidManífet.xml. En En la la lengüeta lengüeta Application Application selecciona selecciona lala actividad Juego. En los los parámetros parámetros de de la la derecha derecha selecciona selecciona en en Screen Screen orientation: landscape.
4.
nuevo la la aplicación. aplicación. Observa Observa cómo cómo lala actividad actividad Juego Juego será será Ejecuta de nuevo siempre representada en en modo modo horizontal horizontal,, de de forma forma independiente independiente aa lala posición del teléfono.
5.
las propiedades propiedades de de la la actividad actividad Juego. Juego. En En Theme Theme Abre de nuevo las Selecciona android: sLyle/'I'heme. NoTi tleBar. F'ullscree n. selecciona el Valor valor @ «android:style/Theme.NoTitleBar.Pullscreen. Este tema visualizará la la vista vista ocupando ocupando toda toda lala pantalla, pantalla, sin sin lala barra barra de de notificaciones ni el nombre nombre de de la la aplicación. aplicación.
6.
Si en Theme ... yy seleccionas Theme pulsas pulsas el el botón botón Browse Browse... seleccionas elel botón botón circular circular puedes ver ver una una lista lista de de estilos estilos definidos definidos en en elelsistema. sistema. System Resources puedes
NOTA. En algunas instalaciones esta esta lista lista puede puede que que no no tetefuncione. funcione. 7.
Ejecuta la aplicación en en un un terminal terminal real real yy verifica verifica elel resultado. resultado.
NOTA: En un emulador si si cambias cambias la la orientación orientación (Crti-Fll) (Crtl-Fll) esta esta cambiará cambiará igualmente. Se trata de de un un error error de de simulación, simulación, al al no no soportar soportaresta estaconfiguración. configuración.
4.4. 4.4. Representación Representación de de gráficos gráficos vectoriales vectoriales en en Asteroides Asteroides La versión original del juego Asteroides Asteroides se se ejecutaba ejecutaba sobre sobreordenadores ordenadorescon conescasa escasa potencia gráfica. Tal yy como como puedes puedes ver ver aa continuación, continuación, nuestra nuestra nave nave se se representaba con un un simple triángulo. triángulo.
150
Gráficos en Androíd Android Dado que cuando hemos hemos diseñado diseñado la la clase clase Gráfico, Grafico, la la representación representación del del mismo la hemos hemos delegado delegado en en un un objeto objeto Drawabie, orawable, va va aa resultar resultar muy muy fácil fácil cambiar cambiar los gráficos de de la la aplicación aplicación para para que que tengan tengan una una apariencia apariencia más más retro. retro.Simplemente Simplemente utilizando subclase de de Drawabie, Drawable, ShapeDrawable, ShapeDra\oTable, en en lugar lugar de de BitmapDrawable, BitmapDrawable, Utilizando la subclase para cambiar la forma de de dibujar dibujar los los gráficos. gráficos.
Ejercicio paso aa paso: paso: Representación Representación vectorial vectorial de de los /os Asteroides. Asteroides. 1.
Abre la clase vistaJuego. Vista,Juego.
2.
En el constructor remplaza la la línea: línea: drawableAst.ero.ide drawableAsteroide
context. getResources í) . getDrawable ( =~ context.getResourcesO.getDrawable{ R.drawable.asteroidel); R.drawabie.asteroidel); por el siguiente código: código: SharedPreferences contex.t.getSharedPreferences( SharedPreferences pref = context.getSharedPreferences{ "org. ex.ample. asteroides_preferences", Context.MODE_PRIVATE); Context .!1DDE:_PR.Tv:ZlTE) ; "org.example.asteroidesjpreferences", if (pref.getString("gráficos" (pref.getString("graficos",, "0' "0")),equals("0")) .equals("O")) { Path pathAscer-oide pathP..steroide == new new PathO; Path (); pathi\steroide. moveTo ( ( float float ) 0.0) o. 0); patiiAsteroide. float)) 0.3, ((float) path.Asteroide .lineTo { ( float float ) 0.0) O. O); pathAsteroide.lineTo float)) 0.6, ((float) pathJI.steroide .lineTo { ( float o. 3); pathAsteroide.lineTo float)) 0.6, {float (float)) 0.3) pathP..steroide .lineTo o. 2); patiiAsteroide. lineTo ( ( float float)) 0.8, (float)) 0.2) o. 8' {float pathAster·oide .lineTo ( ( float l. o, ((float) float ) 0.4) o. 4); pathAsteroide.lineTo float)) 1.0, path.Asteroide . lineTo ( ( float float ) 0.6) pathAsteroide.lineTo float)) 0.8, ((float) o. 6); path.As teroide .lineTo ( ( fl oat ) 0.9, ( float ) o. 9); pathAsteroide.lineTo float) (float) 0.9) o. 9' path.Asteroide. l ineTo { ( float ( float ) 1.0) l. O); pathAsteroide.lineTo float)) 0.8, (float) path.Asteroide.lineTo{( float ) 0.4, ( float ) 1.0); 0.4, (float) l . O); pathAsteroide.lineTo((float) pathAsteroide.lineTo path.Asteroide .lineTo ( ( float ) 0.0, {float ) 0.6); 0.0, (float) o. 6); pathAsteroide.lineTo((float) pathAsteroide.lineTo path.Asteroide .lineTo ( ( float ) 0.0, 0.0, (float) ( float ) 0.2); pathAsteroide.lineTo pathAsteroide.lineTo((float) o. 2); path.Asteroide.lineTo(( float ) 0.3, 0.3, (float) ( float ) 0.0); 0 . 0); pathAsteroide.lineTo pathAsteroide.lineTo{(float) n ew ShapeDrawable ShapeDrawable{( ShapeDrawable dAsteroi.de == new n e w Pat.hShape Pat.hShape(pathAsteroide, 1, new (pathAsteroide, 1, 1, 11)); D); dAsteroide.getPaint() . set.Color(Color.WHITE); dAsteroide.getPaint{).setColor(Color.WHITE); dAsteroide.getPaint() set.Style(.Style.STROKE); dAsteroide. getPaint () .. set.Style (Style. STROKE) ; dAsteroide.setintrinsicWidth(SO); dAsteroide.setlntrinsicWidth(50); dAsteroide.setintrinsicHeight(50); dAsteroide.setlntrinsicHeight(50); drawableAstero.ide dAsteroide; drawableAsteroide -= dAsteroide; setBackgroundColor(Color.BDACK); setBackgroundColor(Color.BLACK); } else {( drawableAsteroide drawableAsteroide = context.getResources().getDrawable( contex.t. getResources () . getD:r:·awable ( R.drawable.asteroidel); R.drawabie.asteroidel) ;
Lo primero que hace hace este código código es es consultar consultar en en las las preferencias preferencias para para ver ver si el usuario usuario ha ha escogido escogido gráficos gráficos vectoriales. vectoriales. En En caso caso negativo, negativo, se se
151
El gran libro de Android realizará la misma inicialización de drawabieAsteroide drawableAsr.er,:Jide que teníamos antes. afirmativo, comenzaremos creando la variable antes. En caso afirmativo, pathAstex·oide pathAsceroide de la clase Path. En este objeto se introducen todas las órdenes de dibujo necesarias para dibujar un asteroide. asteroide. Luego se crea la variable dAsteroide de la clase ShapeDrawable shapeDrawabie para crear un drawable a partir del path. path. Los últimos dos parámetros (..., (... , 1. i, 1) i) significan el valor de escala aplicado al eje x y al eje y. y. Luego se debe indicar el color y el estilo drawable. Finalmente del pincel y el alto y ancho por defecto del drawable. asignamos el Objeto objeto creado drawableAsteroide. Creado a drawabieAsteroide.
3.
Ejecuta la aplicación y selecciona el tipo de gráficos adecuado en las preferencias. preferencias.
0O
Práctica: Práctica; Representación vectorial de la nave. nave. Como habrás comprobado en el ejercicio anterior, anterior, la nave se representa siempre utilizando un fichero png. png. En esta práctica has de intentar que también pueda representarse vectorialmente. vectorialmente. 1.
Crea un nuevo objeto de la clase Path para representar la nave dentro de la sección if i f introducida en el ejercicio anterior. anterior. Como puedes ver en la ilustración siguiente, triángulo. Como el ángulo de siguiente, debe ser un simple triángulo. rotación inicial es cero, cero, la nave debe mirar a la derecha. derecha.
(O,O¡:>:x (0,0L_^
►
x
(1,0.5) ^>(1,0.5) (0,1 (0.1 )
tr yy
2.
Crea un nuevo shapeDrawabie ShapeDrawabl e a partir de Path Pa t h anterior. anterior. Unas dimensiones adecuadas para que la nave pueden ser 20 de ancho y 15 de alto. alto.
3.
Inicializa la variable drawableNave drawable Nave de forma adecuada.
4.5. Animaciones 4.5. Animaciones El entorno de programación Android incorpora tres mecanismos para crear animaciones en nuestras aplicaciones: aplicaciones: •
La clase Animati.o nDraw·able: Permite crear drawables que reproducen Animar,ionDrawable: una animación fotograma a fotograma. fotograma. Se ha descrito su uso en la sección de Drawables. Drawables.
• Animaciones Tween: Tween: Permiten crear efectos de translación, translación, rotación rotación,, zoom y alfa a cualquiera vista de nuestra aplicación. aplicación. 152
Gráficos en Android • Animaciones de propiedades: Nuevo mecanismo incorporado incorporado en en Android Android 3.0, Permite animar cualquier propiedad de de cualquier objeto, objeto, sea sea una una 3.0. vista o no. no. Además, modifica el objeto en en sí, sí, no no solamente solamente cambia cambia su su representación en pantalla como ocurre en un animación animación Tween. Tween. Las siguientes secciones describen con más detalle estos estos tipos tipos de de animación. animación.
4.5.1. Animaciones Tween Una "animación tween" puede realizar series de de transformaciones transformaciones simples simples {posición, (posición, tamaño, tamaño, rotación y transparencia) en el contenido de de un un objeto objeto View. View, Por Por ejemplo, ejemplo, si si tienes un objeto Textview puedes moverlo, moverlo, rotarlo, rotarlo, aumentarlo, aumentarlo, disminuirlo disminuirlo oo cambiarle la transparencia al texto. La secuencia de órdenes que define la "animación tween" tween" puede puede estar estar escrita escrita mediante xml o código, pero es recomendable recomendable el fichero fichero xml, xml, al al ser ser mas mas legible, legible, reutilizable e intercambiable. Las instrucciones de la animación definen las transformaciones transformaciones que que quieres quieres que que ocurran, cuándo ocurrirán y cuánto tiempo tiempo tardarán tardarán en en completarse. completarse. Las Las ocurran, secuenciales oo simultáneas. simultáneas. Cada Cada tipo tipo de de transformaciones pueden ser secuenciales parámetros específicos, yy también también existen existen unos unos transformación tiene unos parámetros parámetros comunes a todas las transformaciones, como como el el tiempo tiempo que que durarán durarán yy el el tiempo de inicio. inicio.
al directorio directorio res/animl res/anim/ en en El fichero XML que define a la animación debe pertenecer al un único único elemento elemento raíz: raíz: este este debe debe ser ser tu proyecto Android. El archivo debe tener solo un , , , ,, oo elemento elemento uno de los siguientes: , (además de de otro otro ). ). Por Por defecto, defecto, que puede contener grupos de estos elementos (además todas las instrucciones de animación ocurren a partir partir del del instante instante inicial. inicial. Si Si quieres quieres que que una animación comience más tarde debes especificar el el atributo atributo startoffset. startof fset.
Sr
Tween Ejercicio paso a paso: Creación de una animación Tween para animar para animar una una vista. vista.
1.
Crea un nuevo proyecto con con nombre AnimacionTween.
2.
res/anim/tweenanimation.xml yy pega pega el el siguiente siguiente código: código: Crea el fichero res/animltweenanimation.xml /J
153
El gran libro de Android android:f android; f romDegrees= rornDegrees= "0 "0"" android:toDegrees = "360 " android:toDegrees="360" android:pivotX=" 50% " android:pivotX="50%" android:pivot.Y=""50%"/> 50% "/> android:pivotY= tartOffset = "4000" android:startoffset="4000"
android:duration= "2000"
aruir~id: du rat i on=" 2000 "
android:fromXDelta= android ;froitiXDelta= "O" "0" android ta= ""0" O" android:: f:r·omYDel froíTiYDelta= android:toXDelta=" 50"" android:toXDelta= "50 android:toYDelta="100 android:toYDelta= "100 " /> />
android:duration=''2 0(l0 '' android:duration="2000" android: fr orrLZI.lpha= "1 f ronnAlpha= "i "" android:toAlpha="O" android:toAlpha= "0" /> />
3.
Abre el fichero res!Layoutlmain. xml yy añade res/Layout/main.xml añade el el siguiente siguiente atributo atributo aa lala vista vista de tipo TextView:
android:id="@+id/textView" android:id="@+id/textview" 4.
Abre la actividad del del proyecto proyecto yy añade añade las las líneas líneas marcadas marcadas en en negrita negrita alal método OnCreate (). @OverridE: n CreatE: (Bundle savedinst.anceState) ©Override public void oonCreate(Bundle savedlnstanceState) {{ super. onCreate{savedinstanceState); super.onCreate(savedlnstanceState);
setCorlten tView(R.layclut. main); setContentView(R.layout.main); TextView texto = (TextView) (TextView) findViewByid(R.id.textView); findViewByld(R.id.textview) ¡ Animation animacion animación = AnimationUtils.loadAnímation(this, AnimationOtlis.loadAnimation(this, R.anim . anímacion);; R.anim.animación) texto.startAnimation(animacion); texto.startAnimation(animación);
5.
}} Ejecuta la aplicación.
Como podrás ver, el Textview Textview comienza comienza haciéndose haciéndose más más pequeño pequeño (etiqueta ), te >) y, ), después rota rota sobre sobre sí sí mismo mismo (etiqueta (etiqueta ) y, finalmente, se desplaza (etiqueta «transíate?)) aa la la vez vez que que se se hace hace >). Al transparente (etiqueta ). Al finalizar finalizar la la animación, animación, vuelve vuelve aa su su posición y estado inicial, sin importar importar dónde dónde ni ni cómo cómo haya haya acabado. acabado.
lili
Recursos adicionales: Lista Lista de de animaciones tween y sus atributos atributos
etiquetas etiquetas
de de
Los siguientes atributos son aplicables aplicables aa todas todas las las transformaciones: transformaciones: startoffse startof fsett -- Instante inicial inicial de de la la transformación transformación en en milisegundos. milisegundos. duration -- Duración de la transformación transformación en en milisegundos. milisegundos.
154
/as las
Gráficos en Android animación. - Número de repeticiones adicionales de la animación, repea.tcount repeatcount -
interpoiator -- En lugar de realizar una transformación lineal se aplica algún interpolator son: interpolación. Alguno de los valores posibles son; tipo de interpolación. accelerate_interpolator, accelerate_decelerate_interpolator, acce?Lerate_iriterpo.lator, accelerate_decel.erate_i.nterpolator, anticipate_interpolator, anticipate_overshoot_interpolator, antici.pate_interpolator, decelerate__ interpolator,/ bounce interpolator, cycle interpolator, decelerate_intexpolator cycle...interpolator, bounce__inter:polator, oversh oot_ interpolator linear_interpolator, linear_ int.erpolator, overshoot_interpolator
Lista de las transformaciones con sus atributos específicos: específicos: - Desplaza la vista. vista.
X. toXDelta -- Valor inicial y final del desplazamiento en eje X. f romXDeita, toXDeita trornXDelta, Y. - Valor inicial y final del desplazamiento en eje Y. toYDelt.a fromYDelt.a, fromYDeita, toYDeita
- Rota la vista. vista. te>
giro. Este quedará fijo pivotx, - Punto sobre el que se realizará el giro. pi vot.Y vot.x, pivotY pi pantalla. en la pantalla. vista. -Cambia Cambia el tamaño de la vista. toxscale -- Valor inicial y final para la escala del eje X fromxscale fromxscale,, toxscaie
(0.5=50%, 1=100%)
f romYScale, toYScale -Valor - Valor inicial y final para la escala del eje Y. fromYScale, zoom. Este quedará pivotx, pivotY pivotY -- Punto sobre el que se realizará el zoom. fijo en la pantalla. pantalla. vista. - Cambia la opacidad de la vista. -Valor toAlpha fromAlpha f romAipha,, toAipha Valor inicial y final de la opacidad.
Práctica: Introduciendo animaciones en Asteroides En esta práctica has de conseguir que los diferentes elementos del Layout inicial otro, con diferentes efectos. de Asteroides vayan apareciendo, apareciendo, uno tras otro, 1.
Asteroides. Abre el proyecto Asteroides.
2.
Crea una nueva animación con nombre giro_con_zoom.xml. Debe durar 1 dos segundos y de forma simultánea debe hacer un zoom de escala 3 a 1 yy un giro de dos vueltas (720°). El punto de anclaje de la rotación y el vista. zoom ha de ser el centro de la vista.
3.
título. Selecciona el Layout main.xml y pon un id al Textview correspondiente al título.
155
El gran libro de Android 4. 4.
inicial de de Asteroides, Asteroides, crea crea un un objeto objeto correspondiente correspondienteaa este este En la actividad inicial Textview la animación animación anterior. anterior. TextVi ew y aplícale la
5. 5.
Crea una nueva animación animación con con nombre nombre aparecer.xml. aparecer.xml. Debe Debe comenzar comenzaraa los losdos dos segundos, segundos, durar un segundo segundo y modificar modificar el el valor valor de de alpha alpha de de O 0 hasta hasta 1.1.
6. 6.
Aplica esta animación al al primer primer botón. botón.
7.
Crea una nueva animación animación con con nombre nombre desplazamiento_derecha.xml. desplazamientojderecha.xml. Debe Debe segundos, durar durar un un segundo segundo yy modificar modificar elel valor valor de de comenzar a los tres segundos, desplazamiento x de 400 hasta hasta O 0.. Prueba Prueba también también algún algún tipo tipo de de interpolación. interpolación.
8.
Aplica esta animación al al segundo segundo botón. botón.
9.
Si dispones de tiempo, tiempo, crea crea dos dos nuevas nuevas animaciones animaciones aa tu tu gusto gustoyy aplícalas aplícalas al tercer y cuarto botón.
giro_con_zoom.xml al al botón botón "Acerca "Acerca de" de" cuando cuando sea sea 10. Aplica la animación giro_con_zoom.xml pulsado. caDe , la pulsado. Observa cómo al al lanzar lanzar la la nueva nueva actividad actividad Acer AcercaDe, la actividad actividad principal continúa ejecutándose.
4.5.2. Animaciones de de propiedade propiedades de Android Android (nivel (nivel de de API API 11 11), se ha ha incorporado incorporado un un nuevo nuevo A partir de la versión 3.0 de ), se tipo de animaciones. A diferencia de de las las animaciones animaciones Tween Tween que que solo solo es es aplicable aplicable aa vistas, vistas, una animación de de propiedades propiedades puede puede animar animar cualquier cualquier tipo tipo de de objetos. objetos. Además, no está restringido aa las las cuatro cuatro transformaciones transformaciones antes antes vistas, vistas, podemos podemos animar cualquier propiedad del del objeto. objeto. Por Por ejemplo, ejemplo, podemos podemos hacer hacer una una animación animación el color color de de fondo fondo de de una una vista. vista. que cambie progresivamente el respecto aa las las animaciones animaciones Tween, Tween, es es que que estas estas solo solo Otra diferencia, con respecto modifican la forma en que la la vista vista es es representada, representada, pero pero no no sus sus propiedades. propiedades. Por Por ejemplo, si aplicas una animación animación Tween Tween para para que que un un texto texto se se desplace desplace por por lala pantalla, se visualizará correctamente, correctamente, pero pero al al acabar acabar lala animación animación elel texto texto estará estará en el lugar inicial. Lo que te te obligará obligará aa implementar implementar tu tu propia propia lógica lógica para para manejar manejar En una una animación animación de de propiedades propiedades estará estará cambiando cambiando elel este cambio de posición. En cómo se se representa. representa. objeto en sí, no solamente cómo Desventajas de las animaciones Tween: Tween: • Solo podemos animar objetos i ev:. objetos de de la la clase clase vview. • Está limitado a estas estas cuatro cuatro transformaciones transformaciones yy no no puede puede aplicarse aplicarse aa otros otros el color color de de fondo. fondo. aspectos como cambiar el forma en en que que la la vista vista es es representada, representada, pero pero no no sus sus • Solo modifica la forma propiedades en sí. Desventajas de las animaciones de de propiedades: propiedades: • Solo disponible aa partir partir de de la la versión versión 3.0. 3.0. En En la la actualidad actualidad existen existen muy muy pocos dispositivos que la la soporten. soporten para inicializarse inicializarse yy hay hay que que escribir escribir más más código. código. • Requiere más tiempo para Para aprender más sobre sobre este este tipo tipo de de animación animación puedes puedes leer leer lala siguiente siguiente sección de Android Developers: Propertv Propertv Animation. Animation
156
CAPÍTULO CAPÍTULO 5. 5.
Entradas Entradas en en Android: Android: teclado, teclado, pantalla pantalla táctil táctil y y sensores sensores
La forma más habitual para para interactuar interactuar con con un un ordenador ordenador es es elel teclado teclado yy elel ratón. ratón. Por desgracia, estos dispositivos dispositivos de de entrada entrada no no existen, existen, oo están están muy muy limitados, limitados, en en los nuevos nuevos móviles móviles permiten permiten nuevas nuevasformas formasde de un teléfono móvil. Afortunadamente, los interacción con el usuario, usuario, por por lo lo que que el el diseño diseño de de nuestras nuestras aplicaciones aplicaciones ha ha de de adaptarse a estas nuevas nuevas formas formas de de interacción. interacción. AA lolo largo largo de de este este capítulo capítulo se se estudiarán diferentes alternativas alternativas para para recoger recoger las las acciones acciones que que los los usuarios usuarios realizan sobre la aplicación.
del manejo manejo de de eventos eventos en en Android, Android, comenzaremos comenzaremos con con Tras una visión general del tradicional, el el teclado, teclado, luego luego estudiaremos estudiaremos los los eventos eventos de de lala el dispositivo más tradicional, finalizaremos con con los los sensores. sensores. Estos Estos tres tres mecanismos mecanismos de de pantalla táctil, y finalizaremos aplicados al al manejo manejo de de nuestra nuestra nave nave en en lala aplicación aplicación Asteroides. Asteroides. interacción serán aplicados se tocarán tocarán otros otros aspectos aspectos importantes, importantes, como como elel manejo manejo de de hilos hilos De forma adicional se de ejecución (threads) (threads) yy las las gestures. gestares.
i Objetivos: Objetivos; •
alternativas para para manejar manejar los los eventos eventosde deusuario usuarioen en Mostrar las distintas alternativas Android. Android. se manejan manejan los los eventos eventos del del teclado. teclado. • Describir cómo se • Aprender a interaccionar con con la la pantalla pantalla táctil. táctil. son las las Gestures Gestares yy como como pueden pueden ayudarte ayudarteen en eleldiseño diseñodel del • Descubrir qué son interfaz de usuario. disponibles en en muchos muchos terminales terminalesAndroid Android yy • Enumerar los sensores disponibles aprender a utilizarlos. de hilos hilos de de ejecución ejecución (Thread}. (Thread). • Describir el uso de • Seguir mejorando la aplicación aplicación Asteroides. Asteroides.
157
El gran libro de Android
5.1. 5.1. Manejando Manejando eventos eventos de de usuario usuario Android captura los distintos distintos eventos eventos de de usuario usuariode deforma forma homogénea homogéneayyse selos lospasa pasaaalala clase encargada de recogerlos. recogerlos. Por Por lo lo general, general, va va aa ser ser un un objeto objeto tipo tipo view viewelelque que recogerá estos eventos eventos por por medio medio de de dos dos técnicas técnicas alternativas. alternativas. Los Losescuchadores escuchadoresde de Listener) yy los los manejadores manejadores de de eventos eventos(Event {EventHandler). Handler). eventos (Event Listener)
5.1.1. Escuchador de de eventos eventos de eventos eventos oo Event Event Listener Listener es es una una interfaz interfaz de de lala clase clase view viewque que Un Escuchador de callback que que ha ha de de ser ser registrado. legistrado. Cada Cada Escuchador Escuchadorde deEventos Eventos contiene un método callback método cal/back, callback, que que será será llamado llamado por por Android Android cuando cuando se se produzca produzca lala tiene solo un método Tenemos los los siguientes siguientes escuchadores escuchadoresde deeventos: eventos: acción correspondiente. Tenemos onCl ick ( l onClick() Método de la interfaz OnCJ.i.ckListener. Se interfaz View. view.OnClickLlatener. Se llama llama cuando cuando elel usuario usuario selecciona un elemento. Se Se puede puede utilizar utilizar cualquier cualquier medio mediocomo como lalapantalla pantalla táctil, las teclas de de navegación navegación oo el el trackba/1. trackball. onLongClick () onLongClick() Método de la interfaz View. OnLongClicki,istener. Se view.OnLongClickiástener. Se llama llama cuando cuando elel usuario selecciona un un elemento elemento durante durante más más de de un un segundo. segundo. onFocusChange () onFocusChange() Método de la interfaz OnFocusChangeListener. Se interfaz View. view.onFocusChangeListener. Se llama llama cuando cuando elel usuario navega dentro dentro oo fuera fuera de de un un elemento. elemento. onKey() onKey () Método de la interfaz interfaz View.OnKeyLístener. view.onKeyListener. Se Se llama llama cuando cuandose sepulsa pulsaoose se suelta una tecla del del dispositivo. dispositivo. onTouch(l onTouch() interfaz View. view.onTouchListener. Se llama llama cuando cuando se se pulsa pulsaoo Método de la interfaz OnTouchLiBtener. Se se suelta o se desplaza en en la la pantalla pantalla táctil. táctil. onCreateContextJvlenu() onCreateContextMenu()
Método de la interfaz . OnCreateContextMenuLiBtener. Se interfaz View View.OnCreateContextMenuListener. Se llama llama cuando se crea un un menú menú de de contexto. contexto. Existen dos alternativas alternativas para para crear crear un un escuchador escuchador de de evento. evento. La La primera primera es es crear un objeto anónimo por : por ejemplo ejemplo de de lala clase clase onclickListener onciickListener()(): prote c t ed void protected void onCreate(Bundle onCreate(Bundle savedValues) savedValues) {{ Butt.on Button boton boton == (Button)findViewByid(R.id.boton); (Button)findViewByld(R.id.boton); boton.BetOnClickListener( e w OnClickListener() boton.setOnClickListener( nnaw OnciickListener() { public vo i d onClick(View void onClick(View v) v) {
!/ e al.izar // Acciones aa rrealizar J } 158
Entradas en Android: teclado, pantalla táctil y sensores La segunda alternativa consiste en implementar la interfaz onclickListener onciickListener (l. Esta como parte de tu clase y recoger los eventos en el método onclick onciicko. alternativa es la recomendada por Android, Android, al tener menos gasto de memoria. memoria. A continuación, se muestra un ejemplo: ejemplo: publ ic class Ejemplo extends Actívity ementa OnClickListener{ public Activity impl implements OnClickLlstener{ protected voi d onCreate(Bundle savedValues) {{ void Button boton == (Button)findViewByid(R.id.boton); ÍButton)findViewByldíR.id.boton); boton.setOnClickListener( this ); boton.setOnClickListener(this};
} public void onClick (View v) {{ onClickíView /! Acc:ic>nes aa realizar ti Acciones
} }
5.1.2. Manejadores Manejadoras de eventos Si estás creando un descendiente de la clase View, Wew/, podrás utilizar varios métodos (cal/back) (callback) directamente usados como manejadores manejadoras de eventos por defecto (Event {Event Handlers) Handlers).. En esta lista se incluye: incluye: onKeyDovm int keyCode, KeyEvent KeyEven t e) Llamado cuando una tecla es pulsada. onKeyüown 1(int onKeyup onKeyüp (in (intt keyCode, KeyEvent e)
Cuando una tecla deja de ser pulsada.
onTrackball.Event onTrackbailEvent (Notion.Event (MotionEvent me)
Llamado cuando se mueve el trackball. trackball.
onTouchEvent (MotionEvent me)
Cuando se pulsa en la pantalla táctil.
onF'ocusChanged( boolean obtengoFoco, iint nt dirección, direccion, Rect onFocusChanged(boolean prevRectanguloFoco) Llamado cuando cambia el foco. prevRectanguioFoco)
Es la forma más sencilla, dado que no hace falta usar una interfaz, ni registrar el callback. Como en nuestro ejemplo estamos creando JuegoView, Juegoview, que es un método cal/back. descendiente de view, podremos utilizar directamente manejadores de evento. evento.
5.2. 5.2. El El teclado teclado físico, siempre es Aunque cada vez existen menos terminales Android con teclado físico, interesante aprender a gestionar los eventos procedentes del teclado. Su manejo se ilustra en el siguiente ejercicio. ejercicio.
Ejercicio paso a paso: Manejo de la nave con el teclado. teclado. Veamos cómo podemos utilizar un manejador de eventos de teclado para maniobrar la nave de Asteroides: Asteroides:
159
El gran libro de Android Android 1.
Abre el proyecto Asteroides. Asteroides.
2.
Inserta este Inserta este código en en la la clase clase vistaJuego. VistaJuego. ©Override
@Qver~c t de
public boolean onKeyDown(int onKeyDown( int codigoTecla, c odigoT'ecla, KeyEvent KeyEvent evento) evento) super. onKeyDown( codigo'l'ecla , evento); evento); super.onKeyDown(codigoTecla,
{{
// /1 Suponemos que vamos aa procesar procesar la la pulsación pulsación boolean procesada p rocesada = true; true; switch (codigoTecla) (codigoT'ecla) {( case KeyEvent.KEYCODE_DPAD_UP; KeyEvent:. KEYCODE'_DF'AD_UP: ace leracionNave == ++PAS'O_ACELE'RACTON_NA1/E; aceleracionNave PASO_ACELERACION_NAVE; break; case KeyEvent.KEYCODE_DPAD_LEFT: KeyEvent. KEYCODE_DPl\D_LEF'-T: ggiroNave i roNave == -PASO_GIRO_NAVE; - PAS'O_G.IRO_NA VE'; break; case KeyEvent.KEYCODE_DPAD_RIGHT: KeyEvent. KEYCODE_DPAD_RIGH'I': giroNave = + PASO__GIRO_NA Pi1S'O_GllW_NAVE VE;; break; case KeyEvent.KEYCODE_DPAD_CENTER: KeyEvent . KEYCODE... DPAD.... CEN-TER: case KeyEvent. KeyEven t . KEYCODE__ENTEP.: KEYCODE__ ENTER: Actival'-lisil () ; ActivaMisil(); break; default:
í/// Si estamos aquí, no no hay hay pulsación pulsación que qut:, nos nos interese interese procesada = false; break;
} return procesada; p r ocesada;
} 3.
pulse una una tecla tecla se se realizará realizará una una llamada llamada alal método método Cada vez que se pulse onKeyoown () con los siguientes siguientes parámetros; parámetros: El El primero primero es es un un entero entero que que onKeyDown o identifica el código nos identifica código de de la la tecla tecla pulsada. pulsada. El El segundo segundo es es de de lala clase clase KeyEven permite obtener obtener información información adicional adicional sobre sobre elel evento evento KeyEventt y nos permite como, ejemplo, cuándo cuándo se se produjo. produjo. Este Este método método ha ha de de devolver devolver un un como, por ejemplo, valor booleano, booleano, verdadero, verdadero, si si consideramos consideramos que que la la pulsación pulsación ha ha sido sido procesada por nuestro nuestro código, código, yy falso, falso, si si queremos queremos que que otro otro manejador manejadorde de evento, al nuestro, nuestro, reciba reciba la la pulsación. pulsación. evento, siguiente al
4.
Antes de ponerlo ponerlo en en marcha marcha comenta comenta la la llamada llamada aa ActivaMisil(), Ae t i va Misil ( J , dado dado que esta función aún aún no no está está implementada. implementada.
5.
correctamente. Verifica si funciona correctamente.
NOTA: Para poder poder recoger eventos eventos de de teclado teclado desde desde una una vista vista es es necesario necesarioque que esta esta tenga el foco foco y para para que que esto esto sea sea posible posible verifica verifica que que tiene tiene lala propiedad propiedad Focusab.l.!::= " tt rué r·uf.-? ". focusable=
160
sensores táctil yysensores pantalla táctil teclado, pantalla Android: teclado, Entradas en Android; 0 0
teclado. el teclado. con el nave con la nave Práctica: Práctica; Manejo de la
una pulsamos una Cuando pulsamos satisfactoria. Cuando forma satisfactoria. de forma funciona de no funciona anterior no ejercicio anterior El ejercicio pararla. ElEl de pararla. manera de hay manera no hay ya no pero ya girar pero pone aa girar se pone nave se la nave girar la tecla para girar no perono tecla,pero una tecla, pulsa una se pulsa cuando se activa cuando se activa solo se onKeyDown solo eventos onKeyDown de eventos manejador de cuando se suelta. atienda aa nave atienda que lala nave para que o nKeyup para eventos onKeyUp de eventos manejador de el manejador Trata de escribir el código: siguiente código: del siguiente partir del Puedes partir correcta. Puedes forma correcta. las órdenes de forma ®Override ©Override evento) { KeyEvent evento) codigoTecla, KeyEvent onKeyUp(int codigoTecla, public boolean onKeyUp(int evento); super.onKeyUp(codigoTecla, onKeyUp(codigoTecla, evento); super. pulsación la pulsación procesar la vamos aa procesar í! // Suponemos que vamos true; boolean procesada == true;
return procesada; procesada; }
continuación: muestra aa continuación: se muestra ejercicio se al ejercicio solución al posible solución Una posible Solución: Una ®Overri.de ©Override evento) { KeyEvent evento) codigoTecla , KeyEvent onKeyUp(int codigoTecla, public boolean onKeyUp(int evento); onKeyUp(codigoTecla, evento); super. super.onKeyUp(codigoTecla, pulsactón. la pulsación procesar la vamos aa procesar lí // Suponemos que vamos true; procesada == true; boolean procesada (codigoTecla ) {{ switch (codigoTecla) KeyEvent. KEYCODE_DPllD_UP: case KeyEvent.KEYCODE_DPAD_UP: O; aceleracionNave aceleracionNave == 0; break; KeyEvent. KEYCODE_DPliD_LEFT : case KeyEvent.KEYCODE_DFAD_LEFT: KeyEvent. KEYCODE_DPllD_RIGHT : case KeyEvent.KEYCODE_DPAD_RIGHT: O; giroNave == 0; break; default: interese nos interese que nos pulsación que hay pulsación no hay aquí, no estarnos aquí, // Si estamos procesada == false; break;
} procesada; return procesada;
}
161
El gran libro de Android 5.3. La La pantalla pantalla táctil 5.3. táctil Los teléfonos Android suelen incorporar una pantalla táctil, táctil, que es utilizada como dispositivo principal de entrada. entrada. El uso más importante de la pantalla táctil es como sustituto del ratón de un ordenador de sobremesa. sobremesa. De esta forma podemos seleccionar, arrastrar y soltar cualquier elemento de la pantalla de forma sencilla. sencilla. No seleccionar, obstante el uso de este dispositivo no acaba aquí. Suele utilizarse en sustitución del físico. También puede ser teclado en aquellos dispositivos que no disponen de teclado físico. utilizada como entrada de un videojuego, como se verá en este apartado. apartado. Otra alternativa para usar la pantalla táctil consiste en el uso de gestures gestares snportado soportado a partir 1.6. Las gestares gestures serán estudiadas en el siguiente punto. Otro abanico de del SDK 1.6. multi-touch, soportado a partir del SDK 2.0. nuevas posibilidades se abre con el multi-touch, 2.0.
El manejo básico de la pantalla táctil pasa por definir el método onTouchEvent en una clase víew clases). view (o implementar la interfaz onTouchListener onTouchi.ü;tener en otras clases). Este método nos devolverá en un parámetro, t. parámetro, un objeto de la clase t"loti.onEven MotionEvent. Los métodos más interesantes de la clase NotionEvent MotionEvent se indican a continuación:
getAction ({)l getAct.ion
Tipo de acción realizada. realizada. En API level 1 1 puede ser: ser: actiom down, ACTION_ DOl·IN,
ACTION__...f•IOVE, MOVE, ACTION ___UP UPOO ACTION ___CANCEL. CANCEL.
9etx ( l, getY o::;etY () ( l Posición de la pulsación. getx {), pulsación. getDownTime o ( l Tiempo en ms en que el usuario presionó, presionó, por primera vez, en una
cadena de eventos de posición. posición. 9etEventTime () Tiempo en ms del evento actual. getEventTime getPression ( l Estima la presión de la pulsación. getPressioní) pulsación. El valor O 0 es el mínimo, el valor 11 representa una pulsación normal. normal
<;;etsize getsize () Valor escalado en O 0 y 11 que estima el grosor de la pulsación. pulsación.
A partir del API level 5 estos métodos pueden indicar como parámetro un índice de puntero para decirle al sistema sobre cuál de los distintos punteros estamos consultando.
I; Ejercicio paso a paso: paso: Uso de la pantalla táctil. En este ejercicio se mostrará cómo podemos capturar los eventos procedentes de la pantalla táctil. También se aprovechará para repasar otros conceptos como: como: Creación de Layouts y herramientas de revisión de código en Eclipse. Eclipse.
162
1.
Crea un nuevo proyecto con nombre Pantalla Tactil. PantallaTactil.
2.
Modifica el Layout main.xml para que tenga una apariencia similar a la siguiente. siguiente. De esta forma practicarás la creación de Layouts. Layouts. A la derecha se muestra la estructura de vistas que contiene. contiene.
Entradas en Android: teclado, pantalla táctil y sensores
¡e Outline K5 \ O ~i~e~~i~~t,~ Properties: 5 ~~~Android Emulato ] 'g~··o;rtii~~---~<~~ ~?¡~ ~~~~~!~:i íS.JUnearlayout S üneaítayout All TextViewEntrada -- "Pulsa "Pulsa sobre esta vista" ¡;;;;¡ scroiiViewl H scrollViewl Ah Ab TextViewSalida TextViewSalida ··• "Pulsaciones-'· "Puisadooes " 3.
Una posible solución se muestra a continuación: encoding= "u'cf-8"?>
xmlns: e¡.): //.~·;cJJe.mas. xralns : android= "he "htt:p: //schemas. android. android. corn/apk/res/andr:oid" com/apk/res/android"
android:layout_width="fiil_parenc* android: layout_width= "fill_parenc" android:layout_height="fill_parent" android: layout_height= " Lill_par-ent: " android:orientation="ver-l:ical" android:orientarion="vertical" > <'I'ext::View ■cTextView android : id="®+id/TextViewEntrada" android:id="®+ id/TextViewEnt rada" android:layout_width="fill_parent" android:layout__width="fill_parent" android: layout___height="Odp" android:layou^ height="Odp" android: layout___.weight=" 1" android:layout weight="1" android:text="Pulsa sobre esta vista" vista" android:gravity= '1 center'1 android:gravity="canter"
android:background="#OOOOFF" androi d:backg round="SOOOOFF" android android:: layout_margin="2mm" layout_margin= " 2rnm" android:textSize="10pt"/> android:textSize="lOpt" />
> android:text="Pulsaciones:"/>
163
El gran libro de Android ..
4.
siguientes dos dos líneas líneas alal final final del del método métodooncreate oncreate1){): Introduce las siguientes : TextView Text.V. i.ew
entrada - (TextView) (TextView) findViewByid(R.layout. findViewByld(R.layout. TextV.i TextVi ewE.ntr.·ada) ewEn t ra d¿i) ;; entrada.setOnTouchListener( this ); entrada.setOnTouchListener(this); entrada~
5.
Pulsa Shift-Ctr/-0 Shiñ-Ctrl-O para para añadir añadir los los imports. imports.
6.
cómo el el método método setOnTouchListener setOnTouchListener está está marcado marcado como como Observa cómo erróneo. Si pones el el cursor cursor encima, encima, te te indicará indicará que que elel parámetro parámetrode deeste este método {this} (this) es es de de la la clase clase PantallaTactilActivity PantallaTactilActivity yy es es necesario necesario que sea de tipo OnTouchListener. OnTouchListener.
7.
el error error te te mostrará mostrará una una lista lista de de posibles posibles soluciones. soluciones. Para evitar el Selecciona la última última "Lef "Lef 'PantallaTact.ilAct.i.vit.y' 'PantallaTactilActivity' impJ.ement implement 'OnTouchListener' 'OnTouchListener'"" de de esta esta forma forma implementaremos implementaremos este este interfaz interfaz yy podrá ser ser considerada considerada de de este este tipo. tipo. La La declaración declaración de de lala nuestra clase podrá clase cambiará a: a: public class Pantalla'l'actilActivity PantallaTactilActivity extends extends Activity Activity implementa OnTouchListener OnTouchListener
{
8.
Se ha solucionado solucionado el el problema problema anterior, anterior, pero pero ha ha aparecido aparecidootro. otro. Ahora, Ahora, lala PantallaTactilActivity PantallaTactilActivity está está marcada marcada como como errónea. errónea. ElEl problema problema consiste en que estamos estamos diciendo diciendo que que implementamos implementamos elel interfaz interfaz OnTouchListener pero pero no no hemos hemos implementado implementado ninguno ninguno de de los los métodos métodos de este interfaz.
9.
Para evitar el el error error selecciona selecciona en en lala lista lista de de posibles posibles soluciones: soluciones: "Add "Add un .i mpl.emented mpi ernent ed methods" rnethods" de de esta esta forma forma se se añadirán añadirán todos todos los los métodos métodos necesarios de este interfaz. interfaz. La La declaración declaración de de lala clase clasecambiará camoiaráa:a: ®Ove.rride ©Override public public boolean onTouch(View onTouch(View argO, argO, MotionEvent MotionEvent argl) argl) {{ !//í TODO Auto -- generat:ed method Auto-generated inethod st:ub stub return false; false;
}
110. O. Reemplaza el nombre nombre de de los los parámetros parámetros por por otros otros más más expresivos. expresivos. Por Por ejemplo: ejemplo: argO a rgO por por vista vista yy argl argl por por evento. evento.
este método método ha ha de de devolver devolver un un parámetro. parámetro. Actualmente Actualmentees es 11. Observa como este false, que significa significa que que no no nos nos hemos hemos hecho hecho cargo cargo de de lala pulsación, pulsación, elel sistema seguirá pasando pasando este este evento evento aa otras otras vistas. vistas. En En este este caso caso elel LinearLayout que contiene contiene la la vista. vista. Cámbialo Cámbialo aa true, true, para paraque queelelsistema sistema no siga propagando este este evento. evento.
12. Reemplaza la 1 TODO la línea línea "/ "// todo Auto--9enerated Auto-generated method method stub" stub"por: por: TextView salida (R . id. Texi:Vierv8alida) ; salida == (TextView) (TextView} findViewByid findViewBylcUR.id.TextViewSalida); salida.append( e v e nto.toString( ) +"\n" ); salida.append(evento.toString{)+"\n");
164
Entradas en Android: teclado, pantalla pantalla táctil táctil yy sensores sensores 13. Ejecuta el proyecto yy verifica el el resultado. resultado. 14. acction-o acction=O significa que se ha ha pulsado pulsado sobre sobre la la pantalla, pantalla, acction=i acction=l significa que se ha soltado y acction=2 acction=2 que que se se está está desplazando desplazando el el dedo dedo (estos corresponden con las constantes tres valores corresponden con las constantes I•1otionEvent.. ACTION_DO\rlN, MotionEvent . ACTION_UP Mot ionEvent.ACTIONJDOWN, Mot ionEvent.ACTION_UP yy MotionEvent .AC'I'ION_MOVE) . MotionEvent.ACTIONMOVE). 15. Modifica el proyecto para que que cuando cuando el el móvil móvil se se ponga ponga en en apaisado apaisado el el Layout que se visualice sea:
16. Verifica el resultado en un un dispositivo dispositivo real. 17. No todas las pantallas táctiles táctiles soportan soportan los los métodos métodos getPressionOy get.Pression () y getsize o. getsize (l . Prueba si tu terminal lo lo soporta soporta y, y, en en tal tal caso, caso, observa observa el el rango de valores que obtienes. el ejercicio ejercicio que que acabas acabas de de 18. Conéctate a www.androidcurso.com www.androidcurso.com yy localiza localiza el realizar. Comprueba los valores obtenidos obtenidos con otros otros terminales terminales yy comparte comparte realizar. los valores obtenidos por tu terminal. terminal.
5.3.1. Manejo de la pantalla táctil con con multi-touch multi-touch Las pantallas táctiles más modernas modernas tienen tienen la la posibilidad posibilidad de de indicar indicar la la posición posición de de mismo tiempo. tiempo. Para Para averiguar averiguar si si el el dispositivo dispositivo varios punteros sobre la pantalla pantalla a un mismo tiene esta capacidad puedes utilizar utilizar el siguiente siguiente código: código:
boolean multiTouch boolean mul tiTouch == getPackageManager().hasSystemFeature( getPackageManager() .hasSystemFeature( PackageManager.FEATURE_TOUCHSCRESN_MULTITOUCH); Packagef'.lanager. FEA'I'URE_TOUCHSCREEN_I
El gran libro de Android Veamos una lista: lista: ACTION_DOWN - Se pulsa en la pantalla sin haber otro puntero activo. action_down ACTION____uP - Se deja de presionar el último puntero activo. action up ACTION_ r-10VE action move
- Cualquiera de los punteros activos se desplaza. desplaza. -
- Se cancela un gesture. gesture,
ACTION_CJI.NCEL act i on_cancel ACTION_OUTSIDE act i on_out s i de
--
El puntero se sale de la vista.
ACTION_POINTER_nowN action_pot.nter_down ACTION_ POINTER_UP -action_pointer_up
Se pulsa un nuevo puntero distinto al primero. primero.
Se deja de presionar un puntero pero no es el último. último.
Ejercicio paso a paso: Uso de la pantalla táctil multi-touch. multi-touch. 1. 1.
Ejecuta el ejercicio anterior en un dispositivo real con capacidad de multitouch (si no dispones de uno te será imposible realizar este ejercicio). ejercicio).
2. 2.
Pulsa simultáneamente con dos dedos en la pantalla: Si lo haces, sin desplazar los dedos, recibirás 4 eventos. Los dos primeros por las pulsaciones de cada dedo y los dos siguientes cuando se levanten. El resultado puede simular al siguiente: siguiente:
'ulsaciones: MotlonEver:( (-119,0 y-237.0 pressi 1 0.32156864 ^6=0,13333334} VlotionEvent(4050b730 | ,'--91,0 pressure-0.380- 9922 size=Q.20000002} V!otionEvent{4050b730 3Ct.ion=262 | X-2B7.0 p91.0 pressure-0,380: 922 S si?e=-0s,20000002} VlotionEvent|4050b730 action=1 | x=1l9,0y=237.0 3ressure=0.32156864 s i 3.
Como puedes ver, cuando hay más de un puntero en pantalla la acción resulta compleja de interpretar. interpretar. Veremos cómo hacerlo a continuación. continuación.
4.
En primer lugar asegúrate que tu proyecto utiliza las APis APIs 2.0 o superior. superior. Para ello utiliza la opción de menú Project/Properties/Android Project!Properties!Android y selecciona en Project Build Target la opción Android 2.0. 2.0.
5.
o n Touch (): Remplaza la siguiente línea del método onTouch sal ida. append (evento toSt.r.i ng () 4-"\n") + '' \n") ; (evento.. toString
por: por: String acciones[] « = { "ACTIONJDOWN", " ACT I ON__DOWN" ,
"ACTION_UP" , "ACTIONJJP", "ACTION_OUTSIDE 11 , "ACTION_OUTSIDE", "ACTION__POINTER_DOl'IN" "ACTION_ POINTER_ UP " }};; "ACTIOK POINTERJDOWN",, "ACTION__POINTER_UP" accion -■=- evento. evento.getAction( acción getAction ());,* codigoAccion -;';; acción accion & & HotionEvent.ACTION_MASK; Mot ionEvent . .l1CT1"01\j'__M.f':..SK ¡ "ACTION_t-10VE ", "ACTION__MOVE",
iint nt iint nt
166
"ACTION_CANCEL ", "ACTION_CANCEL",
Entradas en Android: teclado, pantalla táctil y sensores salida.append(acciones[codigoAccion]); salida.append(acciones[codigoAccion)); for (int ii =O; = 0; ii < evento.getPointerCount(); i++) {{ salida.append(" puntero-." puntero:" i-+ evento.getPointerld(i) evento.getPointerid(i) + salida.appendC ''" x:'' evento.getX(i) + "'' y:" y:'' +i· evento.getY(i)); x:" + evento.getx(i) } salida . append("\n"); salida.append("\n"); return true true;;
6.
Para visualizar cada posible acción hemos creado un array con sus nombres. A continuación, accion. continuación, averiguamos la acción en la variable acción. Esta nueva acción la ha podido hacer cualquier puntero de los activos o uno nuevo. A partir de la versión 2,0, 2.0, en esta variable se codifica simultáneamente el código de la acción (8 bits menos significativos) e índice de puntero que la ocasiona (siguientes 8 bits). bits). Para obtener esta información por separado puedes utilizar el siguiente código; código: int codigoAccion codi goAccion = acción accion & Mot MotionEvent _NllSK; ionEvent..llCTION ACTJQW_í®SJí; int iPuntero == (acción (accion & 1:<. MotionEvent.ACTION_FO!NTER_ID_í-lASK) MotionEvent .ACTION.... POINTER__ Ip_)1ASK) >> MotionEvent. J·í CTION_POIN'I'EF(_ID_SHIFT; MocionEvenc.ACTION_POINTER_ID_SHIFT;
7.
A partir de la versión 2.2 (API level 8) las dos últimas constantes quedan obsoletas y se definen otras dos equivalentes cuyos nombres son más adecuados: adecuados: int iPuntero iPunte:nl = (acción (accion & . il.C'l'.ION_POINTE'R_INDE'X_l>'diSK) & fvlotionEvent MotionEvent.ACTION__POINTER_INDEX__MASK) >> ACTI01'J___ POINTEF\_Il\TDE..'!{_511IFT; >> MotionEvent. MotionEvent.ACTION_POINTER_INDEX__SHIFT;
8.
Una vez obtenido el código de la acción mostramos su nombre en la vista salida. Luego hacemos un bucle para mostrar información de todos los activos. El método getPointerCount punteros activos. getPointercount () nos permite averiguar su número. Vamos a recorrer los punteros activos con la variable i. Al principio de este apartado vimos una serie de métodos para averiguar () , getsize getsi ze (),...). () , ... ). A partir de la versión información sobre el puntero {getx (getx o, 2.0, 2.0, estos métodos siguen dando información sobre el primer puntero pulsó, ahora también disponemos de los mismos métodos activo que se pulsó, (getx(i), getsize(i),...) pero indicando un índice de puntero {getX(i), getsize(i), ... ) para averiguar información del resto de punteros.
9.
El método getPointeriddnt getPointerid (int índice) indicel nos permite averiguar el identificador del puntero. puntero. No hay que confundir el índice de puntero con su identificador. El índice se asigna en función del orden en que fueron pulsados. El índice cero siempre es el más antiguo. antiguo. El índice de un puntero decrece a medida que los punteros anteriores a él dejan de estar activos. Por el contrario, activos. contrario, el identificador de un puntero es asignado cuando se crea y permanece constante durante toda su vida. Nos será muy útil para seguir la pista de un determinado puntero. El método id) ffindPoínterindex indPointerindex (int (i n t id l nos permite averiguar el índice de un puntero a partir de su identificador. identificador.
110. O. Ejecuta de nuevo el proyecto y vuelve a pulsar con dos dedos. dedos. El resultado ha de ser similar al siguiente: siguiente:
167
Androíd El gran libro de Android p!! +' '1
2c-/)
f,¡
:t)r\
) .2 ",::
.'\(Tll''1\ Dt" ~ }N ¡;
UP '"
lf"tr:
!J);
'.~
:
'~
'J¡
~~~~ t K l<~l;'vÍ',
.j )L' ~~.
..::\C..-Ic.}r\
:~o
~
1 ;:., \: 1
¡ 1r
,(
e \' ~ 1 . e
ur. . t~J_ u-~ p•..rP('ll) o1
1 < L.) •'t ~:rJr .. P¡l, (>-!!1 1 ''(1
c1 \,
se
ttr'iÍ'Y ~~ e)'
.
e ,_ ~ ;L ~}
1~e
r;¡
!_,
v -'~):)o
r
11. Prueba con otras combinaciones de pulsaciones e investiga la relación entre el índice y el id de puntero. puntero. que, además, 12. Modifica el programa para que, además, se muestre en cada evento, evento, el índice de puntero que lo ocasionó. ocasionó.
5.3.2. talla táctil 5.3.2. Manejo de la nave con la pa pantalla
Ejercicio paso a paso: paso: Manejo de la nave con la pantalla táctil. táctil. Veamos cómo podemos utilizar un manejador de eventos de la pantalla táctil para maniobrar la nave de Asteroides. Asteroides. El código que se muestra permite manejar la nave de la siguiente forma; forma: un desplazamiento del dedo horizontal hace girar la nave, nave, un desplazamiento vertical produce una aceleración y, y, si al soltar la pulsación no hay movimiento, movimiento, se provoca un disparo. disparo. 1.
Abre el proyecto Asteroides. Asteroides.
2.
Inserta este código en la clase vistajuego. vis t aJuego.
prívate float mX=0 private mX=O,, raY^O mY=O;; private ean disparo= fa l s e; prívate bool boolean dieparo-false; @Over:cide ©Override
event.)) {{ ppublie ubl i c boolean onTouchEvent (MotionEvent evenr. super,onTouchEvent(event); super .onTouchEvent(event); floa e v e nt.ge tXI); floatt x == event.getx(); float y = = event.getY!); e v e nt.ge tYI); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: MotionEvent. AC7'TON_ DOVIW : disparo~ t rue ; disparo=true;
break; brea k; case I\>fotionEvent . 11C'TTON_l~10 VE : MotionEvent.ACTION_MOVE:
float dx = Math. a l)s(x - m.X); Math.abs(x mX); ffloat loat dy = ~ Math.abs(y Ma th. a h s (y - mYi; mY); if (dy<6 && dx:>6) dx>6){{ g.iroNave f•lath.roun d ( (x - mX) 1 giroNave = Math.round((x / 2); disparo == false; false ; } else iiff (dx<6 && dy>6){ acelerac.ionNave = Math.round((mY t
168
Entradas en Android; Android: teclado, teclado, pantalla pantalla táctil táctil yysensores sensores } break ; break; ccase ase MotionEvent ..l!CTION_UP: ACTJOW_f7P: O; giroNave == 0; aceleracionNave = 0; O; iif f (disparo){ (disparo) { Actival>lisil () ; ActivaMisil{>; } break ; break; } mX=X; mY~y;; mX=xmY=y r eturn true; return true ; } 3.
globales mx Las variables globales mx yy mY mY van van aa ser ser utilizadas utilizadas para para recordar recordar las las último evento. evento. Comparándolas Comparándolas con coordenadas del último con las las coordenadas coordenadas actuales (x, y) y) podremos podremos verificar verificar sisi se se trata trata de de un un desplazamiento desplazamiento horizontal o vertical. vertical. Por Por otra otra parte, parte, la la variable variable disparo disparo es es activada activada cada cada vez que comienza una una pulsación pulsación (action_down). (ACTION_DOWN) . Si Si esta esta pulsación pulsación es es continuada con un un desplazamiento desplazamiento horizontal horizontal oo vertical, vertical , disparo disparo es es desactivado. Si por el el contrario, contrario, se se levanta levanta elel dedo dedo (actionjjp) (ACTION_UP) sin sin haberse producido estos estos desplazamientos, desplazamientos, disparo disparo no no estará estará desactivado desactivado l,cti vaMisil O. y se llamará aa ActivaMisil ().
4.
Antes de ponerlo ponerlo en en marcha marcha comenta comenta la la llamada llamada aa ActivaMisil Activar,lisil (), (l, dado dado que esta función aún no no está está implementada. implementada.
5.
Verifica si funciona funciona correctamente. correctamente.
6.
Modifica los parámetros parámetros de de ajuste ajuste (<6 (<6 ,>6, ,>6, /2, /2 , /25), /25 ), para para que que se se adapten adapten de forma adecuada aa tu tu terminal. terminal.
7.
En el juego original original podíamos podíamos acelerar acelerar pero pero no no desacelerar. desacelerar. Si Si queríamos queríamos detener la nave teníamos dar un giro de 180 grados y teníamos que que dar un giro de 180 grados y acelerar acelerar lolo justo. Modifica el código justo. código anterior anterior para para que que no no sea sea posible posible desacelerar. desacelerar.
5.4. 5.4. Gestures Gestures La pantalla táctil es es uno uno de de los los mecanismos mecanismos más más cómodos cómodos para para interaccionar interaccionarcon con un un teléfono Android. Android . No obstante obstante el el reducido reducido tamaño tamaño de de lala pantalla pantalla en en los los teléfonos teléfonos móviles hace que que el el diseño diseño de de la la interfaz interfaz de de usuario usuario sea sea complejo. complejo. Por Por ejemplo, ejemplo, tratar de introducir introducir las las decenas decenas de de botones botones yy menús menús que que incorporan incorporan lala mayoría mayoría de de aplicaciones de sobremesa sobremesa sería sería imposible imposible en en una una pantalla pantalla de de 33 pulgadas. pulgadas. Para Para tratar de dar nuevas alternativas alternativas en en el el diseño diseño de de interfaz interfaz de de usuario, usuario,aapartir partirdel del SDK SDK 1.6, se incorporan las gestures. gestures. Una gesture es un un movimiento movimiento pregrabado pregrabado sobre sobre lala pantalla pantalla táctil, táctil , que que lala aplicación puede reconocer. reconocer. De De esta esta forma, forma , la la aplicación aplicación podrá podrá realizar realizar acciones acciones
169
El gran libro de Android especiales en función de la gesture introducida por el usuario. Esto permite simplificar mucho una interfaz de usuario al poder reducir el número de botones. Si quieres ver un ejemplo de cómo es utilizado gestares gestures en una aplicación real, te recomendamos que bajes "Google Gesture Search" del Market. Como puedes ver en la siguiente ilustración, ilustración, esta aplicación te permite introducir una secuencia de letras o dígitos escrita directamente en la pantalla. A partir de estos caracteres realiza una búsqueda global en tu teléfono (aplicaciones, música, contactos .. .). contactos...).
11 ik
iSBO'tíf 10:29
5.4.1. Creación y uso de una librería de gestures El primer paso para usar gestures en nuestra aplicación es crear una librería que contenga algunas de ellas. Con este propósito, puedes utilizar la aplicación Gesture Builder, que está pre-instalada en la versión 1.6 del emulador.
f
170
Ejercicio paso a paso: Creación de una librería de gestures. gestures.
1. 1.
Abre un emulador con versión 1.6 y con memoria externa. externa.
2.
En el menú de programas busca el siguiente icono y abre la aplicación.
3.
Para añadir una nueva gesture a tu librería pulsa el botón "Add gesture" y realiza un trazado sobre la pantalla (por ejemplo, un visto bueno), luego "configurar"). introduce un nombre asociado a esta gesture (por ejemplo, "configurar"). El resultado se muestra a continuación: continuación:
Entradas en Android: teclado, teclado, pantalla pantalla táctil táctil yysensores sensores ~!tJ@ 9:46AM 9:46 AM
Ñame
4.
Si no te gusta como ha ha quedado quedado el el trazado, trazado, no no tienes tienes más más que que realizar realizaruno uno nuevo. El anterior anterior será será descartado. descartado.
5.
Pulsa el botón "Done" para para aceptar aceptar la la gesture. gesture. Tras Tras pulsar pulsar este este botón botón cuadro de de texto texto indicándote indicándote donde donde se se acaba acaba de de guardar guardar elel aparecerá un cuadro fichero con la librería librería de de gestures. gestures.
NOTA: Si intentas crear con con el el emulador emulador una una gesture gestureformada formadapor por varios variostrazos trazos (por (por ejemplo el símbolo "X") "X'') es es posible posible que que solo solo quede quede almacenado almacenado el el último último trazo. trazo. Para Para que ambos trazos sean sean reconocidos reconocidos en en la la misma misma gesture, gesture, has has de de introducir introducirel elsegundo segundo continuación del primero. primero. Puede Puede resultar resultar algo algo difícil, dificil, pero pero tras tras un un par par de de justo a continuación intentos lo conseguirás. conseguirás. No No te te preocupes, preocupes, introducir introducir una una gesture gesture de de varios varios trazos trazosen en en el elemulador. emulador. Concretamente, Concretamente, elel un dispositivo real no no resulta resulta tan tan complicado complicado como como en valor FaáeOffset F'adeOfOfse!:: que que indica indica el el tiempo tiempo máximo máximo en en milisegundos milisegundos problema está en el valor entre dos trazos de la misma misma gesture. gesture. Si Si al al introducir introducir dos dos trazos trazos eleltiempo tiempoentre entreellos ellos FadeOffset, se se considerará considerará que que se se han han introducido introducido dos dos gestures gestures es mayor que FaáeOffset, diferentes. Por defecto, este este valor valor es es asignado asignado aa 420 420 milisegundos. milisegundos. El El valor valor resulta resulta adecuado con un dispositivo real, real, pero pero muy muypequeño pequeñopara para el elemulador. emulador. En Enel elejemplo ejemplo descrito más adelante adelante daremos daremos un un valor valor más más alto alto aa FaáeOffset FadeOFfsE·'t: si si queremos queremos el emulador. emulador. trabajar de forma forma más más cómoda con con el
6. 5.
introducir las las gestures gestures mostradas mostradas en en lala siguiente siguiente captura. captura. Para Para Trata de introducir mejorar el porcentaje porcentaje de de reconocimientos reconocimientos correctos, correctos, puede puede ser ser interesante interesante gesture con con trazados trazados alternativos. alternativos. Esto Estose se introducir varias veces veces la la misma misma gesture consigue dándole dándole el el mismo mismo nombre nombre aa dos dos gestures. gestures.
7. I.
que una una nueva nueva gesture gesture es es introducida introducida aparece aparece una una ventana ventana de de Cada vez que texto que indica indica el el fichero fichero donde donde está está almacenando almacenanda nuestra nuestra librería. librería. Seguramente será "/sdcard/gestures". "/sdcard/gestures". Utiliza Utiliza la la vista vista File File Explorer Explorer de de Eclipse para localizar localizar este este fichero. fichero.
171
El gran libro de Android Android Qíli CU 10:36 AM acerca_de ^
acerca_de cancelar
y/
configurar
\y
configurar p|ay
j
8. 8.
liir* 1.
Add
J|
8e wd
'
j
Selecciónalo Selecciónalo yy pulsa pulsa el el botón botón «^para llpara guardarlo guardarlo en en tu tu ordenador. ordenador.
Ejercicio paso aa paso: paso: Añadiendo Añadiendo la la librería librería de de gestares gestures aa nuestra aplicación. aplicación. nuestra Para utilizar la librería librería que que acabas acabas de de guardar, guardar, crea crea un un nuevo nuevo proyecto proyectocon con los siguientes datos: Project Ñame: Name: Gestures Gestures Build Target: Androidl.6 Androidl.6 Application Ñame; Name: Gestures Gestures Package Ñame: Name: org.example.gestures or:g . exampl e . g est ures Create Activity; Activity: Gestures Gestures
2.
El siguiente paso va aa consistir consistir en en crear crear la la carpeta carpeta res/raw res/raw en en elel proyecto proyecto el fichero fichero que que has has guardado guardado (gestures) (ges t ures} en en elel ejercicio ejercicio y copiar en ella el anterior. anterior.
3. 3.
res/ layout / main. xml por por el el siguiente siguiente código: código: Remplaza res/layout/main.xmi <'.?xnll version:::::: 1'.l..O" encoding="utf-S"?> encoding='' utf-F3 "?>
android: layout_height= " Li ll_parent: "> <'l'extView cTextview android : layout_ width= "Lill_pa.r:en.t " android:layout_width="fill_parent" android : layout ___height="wrap_content" h eight= "[v·Lap_content android:layout
11
172
Entradas en Android: teclado, pantalla pantalla táctil táctil yy sensores sensores and:r·oid: gravi ty= ,, cente1:~hori zonta.I androld:gravity="center_horizontal" android: text text== "Lnt.:rod.uc(::~ "Xntrodijce una una g(:~st:ure gesture"" 11
android:textsize="8pt"
andr~ i d:textSize="Bpt"
and roid: layout _mar·gin= " } O android:layout_margin="lOdip"/> <'I'extView «TextView android:id="@-f id/salida"" android: id= "(4+id/salida andro i d: layout_width= "i'ill_pa::-ent:" android:layout_width="fill_parsnt" aandroid:layout_height= ndroi d : layout_he i g h t = "¡.¡_r-ap_conl:ent: > "wrap_content "/ '/> android:fadeOffset= "800 "/> < /' LinsarLayout >
4.
El layout anterior está está formado formado por por un un r..inearLayot LinearLayot que que contiene: contiene: un un Textview con un título, un TextView Textview para para mostrar mostrar la la salida salida del del programa programa yy un Ges t ureoverlayview que GestureOverlayview que será será utilizado utilizado para para introducir introducir los los gestures. gestures. En esta última etiqueta el el parámetro parámetro gestureStrokeType gesturestrokeType indica indica que que permitimos gestures formados formados por por varios varios trazos. trazos. El El parámetro parámetro fadeoffset fadeOffset ha sido explicado en el apartado anterior. anterior.
5.
Reemplaza el código de la la actividad actividad por: por: publ ic class Gestures public Gestures extends extends Activity Activity implementa implements OnGesturePerformedListener { private prívate GestureLibrary librería; libreria,private Textview TextView osalida; osalida; éi~Overri.de ®Override
public ptxblic void onCreate{Bundle onCreate(Bundle savedlnstanceState) savedlnstanceState) { super.onCreate(savedlnstanceState); super. onCreate(savedlnstanceState}; setContentView (R. layout. mai.n) ; setContentView(R.layout.main); librería ( this, libreria ~= GestureLibraries.fromRawl?esource GestureLibraries.fromRa wResowrce(this, R.raw.gestures); if (!libreria.load{)) (!librería.load()) { finish();
} GestureOverlayView GestureOverlayview gesturesView gesturesView == (GestureOverlayView) (GestureOverlayview) ffindViewById(R.id.gestures) indViewByld {R . id. ges!:ures) ;; .:;esturesView . addOnGest urePerformedListener{ this ); gesturesView.addOnGesturePerformedListener(this); salida = = (TextView) (Textview) findViewByld(R.id.salida); findViewByld(R.id.salida);
} public void onGesturePerformed{GestureOverlayView onGesturePerformed(GestureOverlayview ov, ov, Gesture gesture} gesture) {{ ArrayList cPredi.ction > predictions ArrayLlst predictions == libreria.recognize(gesture) libreria.recognize(gesture);;
173
El gran libro de Android sal ida. setTex.t ( '' 11 ) ;; salida.setText("") for {Frediction (Prediction prediction : predictions){ predictionsl{ salida. append (pn::diction. name;-" sal ida. appendípredict ion, ñame+ " "11 +iprediction.score+ 1'\n'1 ) ; prediction.score+"\n");
} } 6.
}) En este código se comienza declarando dos campos de la clase librería librer:ía que contendrá la librería de gestares, gestures, creada en el ejercicio anterior, anterior, y sal ida que corresponde al Textview, Text.View, donde escribiremos los resultados. resultados. salida
7.
En el constructor, tras realizar las operaciones habituales, se carga la librería de gestares gestures desde los recursos y en caso de no ser cargada finaliza la aplicación. aplicación. A continuación, continuación, asocia el GestureoverlayView Gestureoverlayview de main. xrnl al objeto gestureview gestureView y se indicará que nuestra clase será el main.xml escuchador de este elemento. elemento. Finalmente se asocia el Textview donde queremos sacar la salida al objeto salida.
8. 8.
El método onGesturePerformed se introduce para implementar la interfaz OnGesturePerformedLi.st.ener. parámetros, el OnGesturePerformedListener. Este método tiene dos parámetros, GestureoverlayView gesture yy el objeto Gestureoverlayview donde se ha introducido el gestare Gesture Gestare que ha sido introducido. introducido. El El primer paso consiste en reconocer el gesture gestare comparándolo con la lista de nuestra librería. El resultado es una gestures que considere más lista ordenada de Predictions con las gestares parecidas a la introducida. Tras borrar salida, sal ida , se recorrerán todos los elementos de esta lista mostrando el nombre del gestare gesture (prediction. ñame) name} y la puntuación de reconocimiento (prediction. score}. Resulta complicado fijar un umbral, umbral, pero una (prediction.score). puntuación inferior a 1 1 se suele considerar demasiado baja para tenerla en cuenta como predicción. predicción.
9.
obtenidas. Ejecuta la aplicación y estudia las puntuaciones obtenidas.
5.4.2. Añadiendo gestures a Asteroides En el siguiente ejercicio trataremos de aplicar lo aprendido a la aplicación Asteroides. La idea es que como una forma alternativa a usar el menú de cuatro botones que se muestra al arrancar la aplicación, aplicación, se pueda utilizar gestures. gestures.
0
1.
174
Práctica: Aí'iadiendo gestures a Asteroides. Asteroides. Práctica: Añadiendo Si el proyecto Asteroides ha sido con una versión del SDK anterior a la 1.6, tendrás que actualizarlo como mínimo a esta versión. versión. Para ello pulsa sobre el proyecto con el botón derecho y selecciona "properties", selecciona en la lista de la izquierda "Android" "Android" y marca la versión adecuada.
Entradas en Android: teclado, pantalla táctil y sensores 2.
3.
Crea la carpeta res/ raw y copia el fichero gestures res/raw gestures,, que contiene la librería creada anteriormente. Modifica
el
Layout
main.xml
para
que
disponga
de
un
GestureOverlayView.
4.
Cuando el usuario esté utilizando este Layout ha de poder introducir alguna de las cuatro gestures de la librería de forma que se ejecute la acción correspondiente.
r Solución: Los pasos a seguir para realizar el ejercicio anterior se describen a continuación: continuación: 1.
/ layo u t/main. xml el siguiente código. Cierra la Añade al principio de res res/layout/main.xml etiqueta al final del fichero. fichero,
4.
~
Añade el siguiente método:
onGesturePerformed(GestureOverlayView public void onGestur·ePerfor-med (GestureOverlayView ov, Gesture gesture) { predictions=libreria.recognize(gesture); ArrayList predictions=l ibreria .recognize(gesture); if (predictions.size()>0){ (predictions.size()>O) { String comando comando~ predict .i ons.get(O) .name; = predlctions.get(0).ñame; if (comando.equals("play")) (comando.equals("play")){{
175
El gran libro de Android lanzar·,;uego () ; lanzarJuego{); } else if (comando.equals("configurar")l{ {comando.equals("configurar")){ lanzarPreferencias(); } else if (comando.equals("acerca. (comando.equals{"acerca ...de")) de")){{ lanzar·AcercaDe () ; lanzarAcercaDe(); . equals("cancelar")) {{ } else if (comando (comando.equals("cancelar")) finish () ; finishO }
} }
5.5. 5.5. Los Los sensores sensores Bajo la denominación de sensores se se engloba un un conjunto conjunto de de dispositivos dispositivos con con los los que podremos obtener información del mundo mundo exterior exterior (en (en este este conjunto conjunto no no se se incluye la cámara, el micrófono o el GPS). Como Como se se verá verá en en este este apartado, apartado, todos todos los los sensores se manipulan de forma homogénea. homogénea. Son Son los los dispositivos dispositivos de de entrada entrada más más novedosos que incorpora Android y con con ellos ellos podremos podremos implementar implementar formas formas atractivas de interacción con el usuario. usuario. Si no dispones de un terminal físico, físico, puedes puedes instalar instalar un un software software de de emulación emulación que te permitirá realizar las las pruebas pruebas sobre sobre el el emulador, emulador, mientras mientras desde desde una una aplicación de tu PC cambias la orientación de de un un teléfono teléfono ficticio ficticio mediante mediante el el ratón. ratón. Puedes descargarte el software de de http://www.openintents.org/en/node/6, http://www.open¡ntents.orq/en/node/6. no no obstante, obstante, el proceso resulta bastante bastante laborioso. laborioso. Además, Además, tendrás tendrás que que cambiar cambiar el el código de los ejemplos para adaptarlos aa los los requerimientos requerimientos del del emulador. emulador. Por Por lo lo tanto, tanto, tendrás que realizar dos programas programas diferentes: diferentes: uno uno para para sensores sensores reales reales yy otro para el emulador.
Openlntents Sensor Símulator Simulaíor
100
Settíngs Stippofied sefisors (•'> yaw & pitch O Q mil roíl && ptcti O m<>ve mme (jl i:~"!:@:~~ pitdt O Sooket Socket .· js010 Í8010 I so. PossiWe iP adcíresses: üsíening on port 8010...
ossible IP aáóresses: Uste.ning on port 801D...
accelerometer; 0,00, -8,49,-4.90 -8,49, -4,90 accelerometer: 0,00, compass·. 13,40, compass. 13,40, -27,66,-38.45 -27,66, -38,45 orierréation. 340,00, -80,00.. -60,00,0,00 orienla.tion: 340,00, ll,OO
176
.•
El acceíerometer El compa¡ss !R] El orieotation: amntation CJ tl!.erm001eter □ tfrermctfTfeter ,-Enallled senstns·~~~:·~~--~-~ : EnaPtetí sensors ; EJ acceterometer 1 [2] ~~~~tHerometer
¡ !R] compass ! !R] orientanon
Entradas en Android: teclado, pantalla táctil y sensores Android permite acceder a los sensores internos del dispositivo a través de las clases Sensor~1anager y la interfaz SensorEventListener·, Clases Sensor, SensorEvent, SerxsorEvent, SensorManager SensorEventListener, del paquete android.hardware. android. hardware . La clase Sensor acepta ocho tipos de sensores. Aunque, los sensores disponibles varían en función del dispositivo utilizado: TY PE_ACCELER01'1E'I'EI< typeaccelerometer
type_gyroscope 'l'YPE GYROSCOPE TYPE LIGHT type light
TYPE t"íAGNETIC TYPE_MAGNETI C_FFIELD IELD
TYPE ORIENTATION type_orientation TYPE PRESSURE type_pressure
type_proximity TYPE PROXIfviiTY TYPE TEfl.lPERATURE tyfejtsmperature
Acelerómetro Sensor de giro Sensordeluz Sensor de luz Brújula Sensor de orientación Sensor de presión Sensor de proximidad Sensor de temperatura
Ejercicio paso a paso: Listar los sensores del dispositivo. No todos los dispositivos disponen de los mismos sensores. Por lo tanto, tanto, la primera tarea consiste en averiguar los sensores disponibles. disponibles. 1.
Crea un nuevo proyecto con nombre Sensores.
2.
Añade la siguiente propiedad al TextView Textview de res/layoutlmain.xml: res/layout/main.xml\ android:id="@+id/salida" android:id="@+id/sal ida"
3.
Inserta este código en la actividad principal: public class SensoresActivity extends Activity {{ private prívate TextView Textview salida;
SOverride
~'Override
public void onCreate(Bundle savedinstanceState) savedlnstanceState) { super. onCreate(savedinstanceState); super.onCreate(savedlnstanceState); setContentView( R .layout.mainl ; setContentView(R.layout.main); salida= (TextView) findViewByid(R.id.salida); salida = (Textview) findViewByld(R.id.salida); Sensorfl.lanager SensorManager sensorlv!anager sensorManager ~= (SensorManager) getSystemService getSysteraServiceí(SENSOR_SEI<\ILCE); SENSOR_SERVICE) ¡ = ser1sorManager. sensorManager. List listaSensores = getSensorList(Sensor.'I'YPE_!lLL); getSensorList (Sensor. TYPE__ALL) ¡ for (Sensor sensor: listaSensores) { for(Sensor log(sensor.getName()); log(sensor.getName{));
} } prívate void log(String logíString string) {{ + "\n" "\n!l); salida.append(string + ); } }
177
El gran libro de Android 4.
el Layout Layout de de la la actividad actividad yy obteniendo obteniendo el el El método comienza indicando el resultados. AA continuación, continuación, TextView salida, donde mostraremos los resultados. vamos a utilizar el método getsystemservíce getsystemservice para para solicitar solicitar el el sistema sistema servicios específicos. Este método pertenece aa la la clase clase context context (como (como somos Activity también somos cont.ext context)) y será será muy muy utilizado utilizado para para servicios del del sistema. sistema. Al Al indicar indicar como como acceder a gran cantidad de servicios parámetro SENSOR_SERVICE, ssnsor_service, indicamos indicamos que que queremos queremos utilizar utilizar los los sensores. Lo haremos a través del del objeto objeto senso:r·Manager. sensorManager. En En primer primer lugar lugar sensores. llamamos al método getsensorList (()1 del del objeto objeto para para que que nos nos de de J.ístasensores, listaSensores, una lista de objetos sensor. sensor. La La siguiente siguiente línea línea recorre recorre todos los elementos de esta lista para eu.rame () y para llamar llamar aa su su método método 9getNameOy mostrar el nombre de sensor.
5.
una lista lista de de los los valores valores devueltos devueltos por por el el Ejecuta el programa. Esta es una el HTC HTC Magic: Magic; código anterior ejecutándose en el AKH 976A AK8976A AK8976A AK8976A AK8976A
6.
33-axis -axis .Zi,ccelen)meter Accelerometer 3-axis Magnetic field field sensor sensor Oríentatíon sensor Orientation sensor Temperature sensor Temperatura sensor
AK8976A es una combinación de acelerómetro acelerómetro de de tres tres ejes ejes yy El AKS976A magnetómetro de tres ejes. Combinando Combinando la la lectura lectura de de los los campos campos gravitatorio y magnético terrestres proporciona proporciona también también información información de de orientación. orientación. Incluye, además, un sensor sensor interno interno de de temperatura, temperatura, útil útil para para comprobar si el móvil se está calentado demasiado. demasiado.
Como hemos visto la clase sensor sensor nos nos permite permite manipular manipular los los sensores. sensores. AA continuación, se listan los métodos métodos públicos públicos de de la la clase clase sensor: sensor: l oat geU·1aximumRange public ffloat getMaximutnRange () public String getName() getÑame()
Rango máximo en las unidades del sensor.
publíc public float getResolution ()
Nombre del sensor. Potencia (mA) usada por el sensor mientras está en uso. Resolución en las unidades del sensor. sensor.
publíc () public int get'l'ype getTypeO
Tipo genérico del sensor.
Publíc etVendor () Public String ggetVendor()
Fabricante del sensor.
publíc public int int getVersíon() getVersionO
Versión del sensor.
public ffloat l o at ggetPower() e tPower()
t inc:lination, La clase Sensorf"lanager SensorManager tiene además tres tres métodos métodos (ge (getlnclination, getOrientation y getRotat.ioni-1atr:ix), getorientation getRotationMatrix), usados usados para para calcular calcular transformaciones transformaciones de de coordenadas. coordenadas.
Ejercicio paso a paso: Acceso a los datos datos del del sensor. sensor.
178
Entradas en Android: teclado, pantalla táctil yy sensores Veamos ahora cómo obtener la la lectura lectura de cada cada uno uno de de los los sensores. sensores. 1.
Copia el siguiente código al te . al final final de de oncrea oncreate. listaSensores == senso:::·Hanage:::· sensorManager.. getSensorList ge t Sensor List (Sensor (Sensor.. ·TYPE'_OR1EN1'A'I'ION) TYPE^RlEíjTñTlON) ;¡ if (!listaSensores.isErrpty(l (ilistaSensores.isEmpty())) {{ get (O) ; Sensor o.dent.ation.Sensor orientationSensor == listaSensores. listaSensores.get(0); sensorManager. regist.eri,iste.ner (this , orientationSensor, sensorManager.registerListener(this, orientationSensor, Sen:>or14anager DE'L.4 Y_ Ul) ; } SensorManager.. SENSOR_ SENSOR_DELAY_U1);} lista.Sensores listaSensores == sensorManage.r sensoi'Manager .getSenso.rList .getSensorList (Sensor. (Sensor. ~"YPE..._ACCELE'RONE'I'E'R); TYPE,_ACCELEROMETER) ; if (!listaSensores.isEmptyO) ( ilista Senso.res.isEmpty()) {{ Sensor aceleromete.rSensor . get.(O); acelerometerSensor == listaSensores listaSensores.get(0!; sensorManager.registerListener( this , acelerorneterSensor, sensorManager. registerListener- (this, acelerometerSensor, Sensori'tanager. UI);} SensorManager. SE'NSOR_DELAY_ SENSOR_DELAY_UI) ;} l.ist.aSensores == se.nsori'lana.g er. getSenso:t"I,is t (Sensor:. 'I'YPE'_f.1AGNE1'1C_F1Ei..D) listaSensores sensorManager.getSensorList(Sensor. TYPB_MAGNBTIC_FIELD);; if ((ilistaSensores.isEmpty()) UistaSenso.res. isEmpty () i {{ Sensor rnagnet.icSensor .i .staSenso.res. get (O) ; magneticSensor == 1listaSensores.get(0); sensort•lanager. registerListener (this , rnagneticSensor, sensorManager.registerListener(this, magneticSensor, Sensor14anager. DELA Y_ UI) ;;}} SensorManager. SENSOR_ SENSOR_DELAY_UI) 1i staSensores == sensor Hanager. getSensorList. (Sensor . 'l'YPE_ 'l'EMPERAT'URE') ;; listaSensores sensorManager.getSensorList(Sensor.TYPE_TEMPERATURE) iif f ((¡listaSensores.isEmptyO) ! listaSensores. isEmpty ()) {{
Sensor t.emperatureSensor temperatursSensor == :i..ist.aSensores.get(O); listaSensores.get(0); sensorManager.registerListener( this, ternperatureSensor, sensorManager.registerListener(this, teraperatureSensor, Sensorl'lanager. SensorManager. SE'NSOR_DEIAY_UI);} SEN30R_DELAY_UI) ;}
2.
Comenzamos consultando si si disponemos de de un un sensor sensor de de orientación. orientación. Para ello solicitamos al sistema que nos nos brinde brinde todos todos los los sensores sensores de de este este getSensorList (l o.. Si la la lista lista no no está está vacía vacía obtenemos obtenemos el el tipo llamando a getsensorList Es necesario necesario registrar registrar cada cada tipo tipo de de sensor sensor por por primer elemento (el 0). Es separado para poder obtener obtener información información de de él. él. El El método método re9isterListener (()i toma como primer registerListener primer parámetro parámetro un un objeto objeto que que implemente el interface sensorEventLi.stener, sensorEventListener, veremos veremos aa continuación continuación interfaz (se indica indica this this porque porque la la clase clase que que cómo se implementa esta interfaz interfaz para para recoger recoger eventos eventos de de estamos definiendo implementará este interfaz sensores). El segundo parámetro es es el el sensor que que estamos estamos registrando. registrando. YY sensores). qué frecuencia frecuencia nos nos gustaría gustaría recibir recibir el tercero indica al sistema con qué posibles valores, valores, de de menor menor aa actualizaciones del sensor. Acepta cuatro posibles sensor_delay_normal, SENSOR_DEL.AY_UI sensor_delay_üi,, mayor frecuencia tenemos: SENSOR_DELAY_NOR1-'IAL, SENSOR_DELAY_GM1E sensor_belay_game y SENSOR_DELAY_FASTEST. sensor_delay_faste3t. Esta Esta indicación indicación Sirve sirve para para atención necesitan necesitan los los sensores, sensores, pero pero no no que el sistema estime cuánta atención garantiza una frecuencia concreta.
3.
implemente el el interface interface que que hemos hemos comentado comentado Para que nuestra clase implemente añade a la declaración de la la clase: clase: imp l ementa Sensor·EventListener implementa SensorEventListener
4.. 4
los sensores sensores tenemos tenemos que que implementar implementar dos dos Para recibir los datos de los
métOdOS métodos de la interfaz SensorEventListener: SensorEventListener; Cli>Override ©Override public voi d onAccuracyChanged(Sensor void ouAccnracyChanged(Sensor sensor, sensor, int int precision) precisión) {} {}
179
El gran libro de Android @Override OOverride public void onSensorChanged(SensorEvent onSensorChanged(Senso.r:Event evento) {{ 1 /Cada sensor serwo:r puede provocar que un thread pr·i.nc:ipal. //Cada principal pase por / /aqu.í, así a.f::í que síncrcnizamos acces,o (He //aquí, sincronizamos el acceso (se verá. vex-á más adelante) synchronized (this) ( this ) {{ switch (evento.sensor.getType()) { swi tch{evento.sensor.getType()) case Sensor. 'I'YPE ___ORIENTA'I'ION: TYPE__ORIENTATION: for (int ( int i=0 i=O ; i<3 ; i++) {{ l.og("Orientaci.ón "+evento.values[i.) log ("Ox'ientación " ++Í+": 1+"; "-i-evento, valúes fi] ));;
} break break;; case Sensor . TYPE....ACCELEROMETER: Sensor.TYPE_ACCBLEROMETER: for (int ( int i-0 i =O ; ic3 i<3 ; i++) .1++) {{ log("Acelerómetro ": "+evento.values[i] ); logí"Aceleróraetro "+i+ "+i+": "+evento.valúes[i]);
} break; case Sensor.TYPE_MAGNETIC_FIELD: Sensor. TYPE _J'IL,_GNETIC FIELD : for ((int int i=0 i=O ; ic3 i<3 ; i++) i-M-) { lo9("Magnetismo Í + ": "+evento.values log("Magnetismo "+ "+1+": "+evento.valúes [i)); [i] ) ,} break break;; default default:: for ((int int i=0 i=O ; icevento.values.length i
} } 5.
Cuando implementamos un interface interfacfí estamos obligados a implementar todos onl\.ccuracychanged no queremos sus métodos. En este caso son dos. Para onAccuracyChanged ninguna acción específica, específica, pero lo tenemos que incluir. incluir. Cuando un sensor onSensorChanged, aquí comprobaremos qué cambie se llamará al método onsensorchanged, sensor ha causado la llamada y leeremos los datos.
6.
Verifica que el programa funcione correctamente.
Cuando el evento se dispara en el método oonSensorChanged nsensorchanged comprobamos qué sensor lo ha causado y leemos los datos. Los posibles valores devueltos se indican en la documentación de la clase SensorEvent11..
11
180
http://developer.android.com/reference/androíd/hardware/SensorEvent.html http://developer.android.coni/reference/android/hardware/SensorEvent html
Entradas en Android: teclado, teclado, pantalla pantalla táctil táctil yy sensores sensores
5.5.1. Un programa programa que que muestra muestra los los sensores sensores disponibles disponibles y sus valores en en tiempo tiempo real real La aplicación realizada en en el el ejercicio ejercicio anterior anterior resulta resulta algo algo difícil difícil de de utilizar. utilizar. En En primer lugar, lugar, presupone presupone que que se se dispone dispone de de cuatro cuatro sensores, sensores,cosa cosa que que no noserá serácierta cierta en muchos dispositivos. dispositivos. Además, Además, los los datos datos de de los los sensores sensores cambian cambian demasiado demasiado rápido para poder leerlos leerlos en en un un listado. listado. El El ejercicio ejercicio mostrado mostrado aa continuación continuación es es un un resumen de los los ejemplos anteriores, anteriores, pero pero que que permite permite mostrar mostrar en en lala misma misma pantalla pantalla los valores actuales de de todos todos los los sensores sensores de de un un dispositivo. dispositivo. Además, Además, es es un un buen buen ejemplo para ilustrar ilustrar cómo cómo crear crear una una vista vista desde desde código. código.
Ejercicio paso aa paso: paso: Creación Creación de de una una vista vista desde desde código código para mostrar los datos de de los los sensores. sensores. 1.
proyecto con con nombre nombre Sensores2. Sensores2. Crea un nuevo proyecto
2.
de la la actividad actividad por por el el siguiente: siguiente: Remplaza el código de public class Sensores extends extends Activity Activity implements implementa SensorEventListener SensorEventListener { private mLinearLayout; prívate LinearLayout mLinearLayout; private aTextView [] [] [] [] ~ new new TextView TextView[20] [20] [3] [3] ;; ;; prívate TextView aTextView private mSensorManager; prívate SensorManager mSensorManager; private aSensor[] == new new Sensor Sensor[20]; prívate Sensor aSensort] [20]; private listSensors; prívate List listSensors; @Overri.de void onCreate(Bundle onCreat.e (Bundle savedlnstanceState) savedinstanceState) {{ SOverride public void super .onCreate(savedinstanceState); super.onCreate(savedlnstanceState); setContentView (R.layout .main) ; setContentView(R.layout.main); mLinearLayout == (LinearLayout) (LinearLayout) findViewByld(R.id.LinearLayout01); findVie1-1Byid (R . id. LinearLayoutOl); , mSensorManager· == (SensorManager) (Sensor·l·lana9er) getSystemService 9etSystemService{S£W.SOR_í?£'R (SElV.'JOR_SERVICE); mSensorManager /.rCS) ; listSensors == mSensorManager.getSensorList(Sensor.TYPE_ALL); mSensorManager .getSensorList (Sensor .1'YPE_ALL); int n = = 0; O; for (Sensor sensor sensor.· :: listSensors) 1istSensors) {{ aSensor[n] -- sensor; aSensorEnj TextView raTextView m'l'extView == new new TextView(this); TextView ( this ) ; mTextView.setText{sensor.getName()); mTextView.setText(sensor.getName()); mLinea~·Layout. addView (mTextView) ; mLinearLayout,addview(mTextView); nLinearLayout == new new LinearLayout(this); LinearLayout(this ); LinearLayout nLinearLayout mLinearLayout.addView(nLinearLayout); mLinearLayout.addview(nLinearLayout); O; ii << 3; 3; ±++) Í++) {{ for (int ii == 0; aTextView[n] [i] == new new TextViewíthis); TextVie\'i( this ); aTextView(n][i] aTextView[nj [i]3.setText("?"); .setText{"? "); aTextView[n] [i aTextView[n] [i] .setWidt.h(87) . setWidth(B7);; } TextView xTextView -'" new new TextView(this); TextView ( this ) ; xTextView. setText (" X: "); ") ; xTextView.setText{" nLineari.,ayout. addV:i.ew (xTextView) ;; nLinearLayout. .addview(xTextView)
181
El gran libro de Android Androíd nLi.nearLayou t .. addView (aTextView [n] [0]) [O]) ; nl.inearLayout.addview TextVi ew yText View =="~ new Tex tView((this) this );; TextView i'TextView TextView yT'ext \!íevv. setText { '' Y: y : 11"); ) ; yTextView.aetText(" nnLinearLayout,addview(yTextView); ·L inear Layout . addVie.w (y'Text:.Vie"'>~J) ¡ nLin earLayout.addView(a'I'extView[n.] [1]) ; nLinearLayout.addview{aTextView[n][1]); Text View zTextVíew this ); TextView zTextView == new TextView( TextView(this);
zzTextView.aetText{" T'ext\!ievv. setText ( 11 Z: z: "); ") ; nnLinearLayout.addview(zTextView); Linea r Layout . addView ( zTextView) ; nnLinearLayout.addview(aTextView[n][2]); LinearLayout . addView(a'I'extView[n.] [2]) ; mSen sorManager . .r:eg.i s tex: L.i s tener(thi ten er ( this sensor , mSensorManager.registerLis s,, sensor,
SensorManager SE1\fSOJ?.___ DE&1Y__UI} ; SensorManager.. SENSGR_DEL/íY_jJI) U++; n++;
} } \ü
int
accuracy) {{}} @Ove.r:x:.i de public void onSensorChanged(SensorEvent onSensorChanged (SensorEvent. event) event.) {{ SOverride syn chronized ((this) this ) { synchronízed int n n = = O; 0; for (Sensor (Sen sor sensor: iistSensors) l i stSensor s) {{ == sensox :) {{ iiff (event.sensor ( event. sensor == sensor) for (int i=O; Í
} } Tl++ n++;; } } } } 3. 3.
182
Como puedes observar esta actividad utiliza el Layout creado por defecto que, básicamente, es un LinearLayot Linea r Layot (en el código corresponde a la variable mLinearLayout) mLinearLayout ) con un TextView TextVi ew en su interior ("Helio Word ... ') . A mLinearLayout se le va a ir añadiendo una serie de vistas adicionales dispositivo. Por cada sensor se según los sensores encontrados en el dispositivo. añade; un TextView Text.Vi e w con el nombre del sensor, sensor, un LinearLayot de tipo añade: TextView con " X: ", un Textview TextView con horizontal para contener a su vez un Textview X:", el valor del sensor en el eje X, X, un TextView con"" Y ", un Textview TextView con el Textview con valor del sensor en el eje Y, TextView con el Y, un Textvie"' Textview con " Z: Z: " y un Textview valor del sensor en el eje Z. Z. Las referencias a los Textview donde se visualizarán los valores de los sensores se almacenan en el array aTextVie\..r [J [] [ J donde el primer índice identifica el número de sensor y el aTextview [] segundo la dimensión X, Z. Por otra parte, X, Y o Z. parte, los sensores encontrados J. son almacenados en el array asensor [[].
Entradas en Android: teclado, pantalla pantalla táctil táctil yy sensores sensores 4.
En el método onsensorchanged onsensorchanged ()() se se hace hace un un bucle bucle para para localizar localizar elel índice del sensor que ha ha cambiado cambiado yy se se modifican modifican los los Textview Textview correspondientes al sensor sensor con con los los valores valores leídos. leídos.
NOTA: No todos los sensores tienen tienen tres tres dimensiones. dimensiones. Por Por ejemplo, ejemplo, en en elel caso caso del del sensor de temperatura solo se se cambiará cambiará en en el el valor valorde deXX. 5.
Verifica sobre un un dispositivo dispositivo real real que que el el programa programa funciona funcionacorrectamente. correctamente.
5.5.2. Utilización de de los los sensores sensores en en Asteroides Asteroides A continuación, proponemos una una serie serie de de ejercicios ejercicios yy prácticas prácticas para para manejar manejar lala nave de Asteroides utilizando utilizando sensores. sensores.
Ejercicio paso paso aa paso: paso: Manejo Manejo de de la la nave nave con con el el sensor sensor de de orientación.
t
1.
En primer lugar, implementa implementa la la interfaz interfaz senso:cEventListener. sensorEventListener. public class VistaJue9o VistaJuego extends extends View View implementa implements SensorEventListener SensorEventListener
2.
{
En el constructor registra registra el el sensor sensor ee indica indica que que nuestro nuestro objeto objeto recogerá recogerá lala llamada callback:
SensorManager mSensorManager mSensorManager =-= (SensorManager) (SensorManager) Senso:crvranage:c context.getSystemService(Context.SENSOR_SERVICE); context. getSystemService (Context. SE!.VSOR_SERVICE); List listSensors listSensors == mSensorManager.getSensorLi mSensorManager.getSensorList( ListcSensor> st( Sensor. TYFE.._ORIE.lVTllTION) ;; Sensor.TYPE_ORIENTATION) if (!lis tSensors.isEmpty()) {{ (! listSensors.isEmpty()) Sensor orientationSensor orientationSensor
=- llistSensors.get(0); istSensors.get(O);
mSensor!Vlanager. r·egisterListener ( this, orientationSensor mSensorManager.registerListeneríthis, orientationSensor,, SensorManager. SENSOR_DEI..llY_GAME) ;; SensorManager.SENSOR_DELAY_GAME)
} 3.
siguientes dos dos métodos métodos que que implementan implementan lala interfaz interfaz Añade los siguientes SensorEventListener SensorEventListener:: ®Overr.ide onAccuracyChanged(Sensor sensor, sensor, int int accuracyl{} accuracy){} public void onAccuracyChanged(Sensor private l orinicial prívate boolean hayVa hayValorInicial private float val orinicial ; prívate valorlnicial;
== false; false;
t!{i0verride OSOverride
public void onSensorChanged(SensorEvent onSensorChanged(SensorEvent event) event) {{ valor = event. event.valúes[l]; float valor= values [l]; if (!hayValorinicial) (IhayValorlnicial){{ va l orini cial = l or; valorlnicial = va valor; hayValorinicial hayValorlnicial = true; true;
183
El gran libro de Android giroNave= (int) giroNave=(int)
(valor-valor i nicial) /3 (valor-valorlnicial)/3
;
} 4.
Prueba la aplicación. Has de tener cuidado de que el terminal esté en en una una posición cómoda al entrar en la actividad ,Juego, Juego, dado que que el movimiento de la nave se obtiene con la diferencia de la posición posición del terminal terminal con con respecto a la posición inicial.
.\Ei
a
yv « • M TSJ
Práctica; Manejo de la nave con sensor de aceleración. Práctica:
Modifica el ejemplo anterior para utilizar el sensor de aceleración en lugar lugar del del de de orientación. orientación. Gracias a la fuerza de gravedad que la (a Tierra ejerce ejerce sobre sobre el el terminal terminal podremos saber si este está horizontal. En caso de que que la la nave nave esté esté horizontal horizontal (o (o casi) no ha de girar, pero cuando el terminal se incline, incline, la la nave nave debe debe girar girar proporcionalmente a esta inclinación. Utiliza los los programas a[lteriores anteriores para para descubrir descubrir qué eje (x, y o z) es el que te interesa y el rango de valores que proporciona. que proporciona.
ª
\Ei
- v;
♦y •
¿Te cuidado cuando también
Práctica: Aceleración de la nave con sensores. animarías a controlar la aceleración de la nave con con los los sensores? sensores? Ten Ten de que no acelere con mucha facilidad, este juego resulta resulta muy muy difícil difícil la nave está en movimiento. movimiento. Puede ser una buena buena idea idea que que permitas permitas desacelerar la nave.
$
a
Práctica: Configuración de tipo de entrada en preferencias.
Todos los controles de la nave (teclado, pantalla táctil táctil yy sensores) sensores) están están activados simultáneamente. El teclado y la pantalla táctil no no interfieren interfieren cuando cuando el el usuario no quiere utilizarlos. utilizarlos. Sin embargo, la activación de de los los sensores sensores sí sí que que molestará a los usuarios que no quieran utilizar este método de de entrada. entrada. 1. 1.
Crea nuevas entradas en la configuración para activar activar oo desactivar cada cada tipo de entrada (o al menos la de los sensores).
2. 2.
Modifica el código anterior para que se desactiven las las entradas que que el el usuario no haya seleccionado. seleccionado.
5.6. 5.6. Uso Uso de de hilos hilos de de ejecució ejecución (Threads) (Threads) Cuando se lanza una nueva aplicación el sistema crea un un nuevo hilo hilo de de ejecución ejecución (thread) para esta aplicación conocida como hilo principal. Este Este hilo hilo es es muy muy importante dado que se encarga de atender los eventos eventos de de los los distintos distintos
184 184
Entradas en Android: teclado, pantalla táctil yy sensores componentes. (), onDraw (), ( l, componentes. Es decir, decir, este hilo ejecuta los métodos oncreate {), onKeyDown () . Por esta razón al hilo principal también se lo conoce como hilo del interfaz de usuario. usuario. El sistema no crea un hilo independiente cada vez que se crea un nuevo componente. decir, todas las actividades y servicios de una aplicación son componente. Es decir, ejecutados por el hilo principal. Cuando tu aplicación ha de realizar trabajo intensivo como respuesta a una interacción de usuario, hay que tener cuidado porque es posible que la aplicación no responda de forma adecuada. Por ejemplo, ejemplo, imagina que has de esperar para descargar unos datos de Internet, Internet, si lo haces en el hilo de ejecución principal este tanto, no se quedará bloqueado a la espera de que termine la descarga. Por lo tanto, podrá redibujar la vista (onDraw 0)0 () ) o atender eventos del usuario (onKeyDown ()). () ). Desde el punto de vista del usuario, usuario, se tendrá la impresión de que la aplicación se ha colgado. Mas todavía, todavía, si el hilo principal es bloqueado más de 5 segundos, segundos, el sistema mostrará un cuadro de diálogo al usuario "La aplicación no responde" para que el usuario decida si quieres esperar o detener la aplicación. aplicación. La solución en estos casos es crear un nuevo hilo de ejecución, ejecución, para que realice principal, que puede este trabajo intensivo. De esta forma no bloqueamos el hilo principal, usuario. Es decir, decir, cuando estés implementando un seguir atendiendo los eventos de usuario. método del hilo principal (empieza por on...) on...) nunca realices una acción que pueda bloquear este hilo, hilo, como cálculos largos o que requieran esperar mucho tiempo. tiempo. En estos casos hay que crear un nuevo hilo de ejecución y encomendarle esta tarea. tarea. En el siguiente apartado describiremos cómo hacerlo. hacerlo. Las herramientas del interfaz de usuario de Android han sido diseñadas para ser tanto, no se ejecutadas desde un único hilo de ejecución, el hilo principal. Por lo tanto, permite manipular el interfaz de usuario desde otros hilos de ejecución. ejecución.
5.6.1. Introduciendo movimiento en Asteroides Para que el juego cobre vida será necesario animar todos los gráficos introducidos. introducidos. En el siguiente ejercicio veremos cómo hacerlo. hacerlo.
Ejercicio paso a paso; paso: Introduciendo movimiento en Asteroides. 1.
Comienza declarando las siguientes variables en la clase vistajuego: vistaJuego: ////THREAD TIEMPO////// // lili THREAD Y TIEMPO lililí II encargado de procesar el juego /1 Thread encarGado pri vate ThreadJuego Th readJu e go thread thread= prívate = new ThreadJuego(); // /1 Cada cuanto queremos procesar cambios (ms) prívate priva te static int i n t FERrODO...PROCESO PERIODO.... PROCESO = = 50; // /1 Cuando se realizó el último proceso priv ate long ultiraoProceso ultimoProceso == 0; O; prívate
185
El gran libro de Androíd Android 2.
La animación del juego juego la la llevará llevará aa cabo cabo el el método método actualizaFisicaO actualizaFisica í) que que será ejecutado aa intervalos intervalos regulares, regulares, definidos definidos por por lala constante constante PERIODO PROCEso. Esta Esta constante constante ha ha sido sido inicializada inicializada aa 50 50 ms. ms. En En periodo proceso. ultimoProceso almacena el el instante instante en en que que se se llamó, llamó, por por última última vez, vez, aa ultimo Proceso se almacena actualizaFisica() actualizaFisica{)..
3.
método dentro dentro de de la la clase clase vistajuego: VistaJuego: Copia el siguiente método protected actualizaFisica() {{ protacted void actualizaFisicaO long ahora ahora~ Sy;>tem.currentTimeJVlillis(); = System,currentTimeMillisO;
í//1 No hagas nada nadzt si el el período perio do de de proceso proceso no no se se ha ha cumplido, cumplido. if (ultimoProceso ( ul t. imoProceso ++ PERIODO_PROCESO PERIODO PROCESO >> ahora) ahora) { return;
} // Para una ejecución ejecuc.ión en en tiempo tiempo real real. calculamos calculamos retardo rer:arclo double retardo =~ (ahora (ahora -- ultimoProceso) ultimoProceso) // PERIODO__PROCESO; PERIODO_PROCESO;
ultimoProceso == ahora; ahora; // // Para Pa ra la J.a próxima próxj_ma vez vez // Actualizamos Acr.uaJ. izamos velocidad velocida.d y y dirección dirección de de la J.a nave nave aa. partir part.j_r de de // giroNave gi.roNave y aceIeracionNave acel.eracionNav e (según (según la J.a entrada entrada del del jugador) jugador) nave. setAngulo ( ( int ) (nave.getAngulo() (nave. getAngulo () ++ giroNave giroNave ** retardo!); retardo)) ; nave.eetAngulo{(int! double nlncX nincX -~ nave.getlncXO nave.getincX() ++ aceleracionNave aceleracionNave ** Math. cos (r"lath. toRadians (nave.getAngulo())) ** retardo; retardo; Math.coa(Math.toRadians(nave.getAngulo())) double nlncY nincY == nave.getIncY() nave.getincY() ++ aceleracionNave aceleracionNave ** JVlath. sin (r"lath. toRadians (nave. getAngui.o ())) ** retardo; retar·do; Math.sin(Math.toRadians(nave.getAnguloí))) /! Actualizarnos Act:ualizamon si el el módulo módulo de de la l.a velocidad velocidad no no excede excede el el máximo máximo // if (Math.hypot (nlncX, (nincX,nincY) "''" Gx"af Grafico.getHaxVelocidad()) nlncY) <-■ ico .getMaxVelocidad () ) {{
nave.setlncX(nlncX); rtave.setinc~X(nincX); rlave.setinc=Y(nincY); nave.setlncY(nlncY); } /í Actualizamos posiciones posiciones XX ee YY // nave.incrementaPos(retardo); nave.incrementaPos(retardo) ; for (Gráfico (Grafico asteroide asteroide :: Asteroides) Asteroides) { asteroide.incrementaPos(retardo); asteroide.incrementaPos(retardo) ; } }
186
4.
continuación, este este método método será será llamado llamado de de forma forma Como veremos aa continuación, PERIODC_PROCESO continua. queremos desplazar desplazar los los gráficos gráficos cada cada periodo continua. Como queremos proceso milisegundos, verificamos si si ya ya ha ha pasado pasado este este tiempo tiempo desde desde la la última última vez vez que se ejecutó (ultimoProceso). (ultimoProceso}.
5.
posible que que el el sistema sistema esté esté ocupado ocupado yy no no nos nos haya haya Como también es posible un tiempo tiempo superior superior aa periodo_proceso, PERrono_ PRocEso, vamos vamos aa podido llamar llamar hasta hasta un calcular el factor de de retardo retardo en en función función del del tiempo tiempo adicional adicional que que haya haya pasado. Si por ejemplo, ejemplo, desde desde la la última última llamada llamada ha ha pasado pasado dos dos veces veces PErnono.PROCESO, la variable variable retardo retardo ha ha de de valer valer 2. 2. Lo Lo que que significará significará periodo proceso, la en circunstancias circunstancias que los gráficos han han de de desplazarse desplazarse el el doble doble que que en normales. De esta forma conseguiremos conseg iremos un un desplazamiento desplazamiento continuo continuo en en tiempo real.
Entradas en Android: teclado, pantalla táctil y sensores 6.
A continuación, continuación, se actualizan las variables que controlan la aceleración y cambios de dirección de la nave. Se consiguen por medio de las variables aceleracíonNave aceleradonNave y giroNave. En el siguiente capítulo, modificaremos estas variables para que el jugador pueda pilotear la nave. nave, A partir de estas variables se obtiene una nueva velocidad de la nave, descompuesta en sus componentes X e Y Y.. Si el módulo de estos componentes es mayor que la velocidad máxima permitida, no se actualizará la velocidad.
7.
Finalmente, se actualizan las posiciones de todos los gráficos (nave y asteroides) a partir de sus velocidades. Esto se consigue llamando al método incrementaPos () definido en la clase Grafico. Gráfico.
8.
Ahora necesitamos que esta función sea llamada continuamente, continuamente, para lo que utilizaremos un Thread. Thread. Crea la siguiente clase dentro de la clase VistaJuego: VistaJuego! clasa class ThreadJuego extenda extends Thread {{
©Ove rr i de
@Ovei.~r.i de
public void run() {{ true ) {{ while (!true) actuali zaFisica () ; actuallzaFisica();
} } }
9.
Introducir esta línea al final del método onsizechanged (): thread . start(}; thread.start();
run() ejecución. Este 10. Esto ocasionará que se llame al método run ( l del hilo de ejecución. método es un bucle infinito que, continuamente, llama al actualizaFisica ¡. actuaiizaFisica (().
11. Ejecuta la aplicación y observa cómo el juego cobra vida. El trabajo con hilos de ejecución es especialmente delicado. delicado. Como veremos en próximos capítulos, este código nos va a ocasionar varios quebraderos de cabeza. Un problema es que seguirá ejecutándose aunque nuestra aplicación esté en segundo plano. Veremos cómo detener el hilo de ejecución, cuando estudiemos el ciclo de vida de una actividad (NOTA: Si ejecutas el programa en el terminal real yy detectas que este funciona más lentamente, puede ser buena idea detener la aplicación) aplicación).. Un segundo problema, aparecerá cuando dos hilos de ejecución traten de acceder a la misma variable a la vez. También se resolverá más adelante. adelante.
5.7. 5. 7. Introduciendo un misil en Asteroides Para poder disparar a los asteroides va a ser necesario introducir un misil en el juego. En el siguiente ejercicio aprenderemos a hacerlo:
t Ejercicio paso a paso: Introduciendo un misil en Asteroides. Asteroides.
187
El gran libro de Android 1. 1.
vistaJuego: En primer lugar, lugar, añade las siguientes variables a la clase VistaJuego: // //// MISIL lilll/ ////// pprívate r ívate Gráfico Grafi co misil; priva te static int PASO_VELGCIDAD._MISIL P_4SO_ VE.LOC.TDAD 1"1TSIL prívate
= 12 ¡;
pprívate rivate boolean boole an nü si.lActivo == false; misilActivo prívate int ínt tiempoMisil; tiempo~1isil ;
2.
Para trabajar con gráficos vectoriales, vectoriales, puedes crear en el constructor la drav:ablet"lisil de la siguiente forma: variable drawableMisil forma: ShapeDrawable dt•lisil ShapeDr·awabl.e (new RectShape()); Rect:.-lhape ()) ; dMisil = new ShapeDrawable(new dMisi l. 9etPaint () . setColor (Color. h'HITE) ; dMisil.getPaint().setColor(Color.WHITE) dMisil.9etPaint() . setStyle(Style . STROKE) ; dMisil.getPaint().setStyle{Style.STROKE) ddMisil.setlntrinsicWidth(15); Misil . setintrinsicWidth(l5); dMisil.setlntrinsicHeight(3); dMisi l.setintrinsicHeight(3); drawab l e!Vlisil = ~ dMisil; drJiisil; drawableMisil
3.
Crea la variable drawabl.e~1isil drawableMisil para el caso en que se deseen gráficos en bitmap, utilizando el fichero misiii.png. misill . png.
4.
Inicializa el objeto misil, misi l. de forma similar a como se ha hecho en nave.
5.
En el método oonDrawf) nDraw () dibuja misil, misil , solo si lo indica la variable misillictivo. misilActivo.
6. 6.
Quita los comentarios de las llamadas a activaMlsil activaf'1isil ()
7.
En el método actuaiizaFislca actualizaFisica () (5 añade las siguientes líneas: líneas: !/Actualizamos // Actualizamos posición de misil íf (misi lActivo) {{ if (misilActivo) misi l .in cremen taPos(retardo);; misil.incrementaPos(retardo) tiempoMisil--; iif f (tiempoMisil ( tiempot"'isil < 0) O) { misilActivo = = false;
} else {{ for (int (ínt ii = = 0; O; ii < Asteroides.size(); Asteroides .size(); i++) íf (nlisil.verificaColisi o n( Asteroides . e l e mentAt(i) )) { if (misil.verificaColision(Asteroides.elementAt(i))) destruyeAsteroide( i ); destruyeAsteroide(i); break break;; } } } 8.
Añade los siguientes dos métodos: métodos: prívate void destruyeAsteroide(int i) i} Astero ides.remove(i); Asteroides.remove(i); misi.lActivo misilActivo == false;
} prívate void voíd ActivaMisil(} ActivaMisil() {{
188
{{
Entradas en Android: teclado, teclado, pantalla táctil yy sensores mi s il . setPosX (nave .getPosX () + nave.getAncho()/2-misil.getAncho()/2); nave . getAncho () /2 - misi l. getAncho () / 2 ); misil.setPosX(nave.getPosX()+ mi sil. setPosY (nave . getPosY () + nave.getAlto()/2-misil.getAlto()/2); nave . getAlto () /2 - misil .getAlto () /2) ; misil.setPosY(nave.getPosY()+ misil . setAngulo (nave .getAngulo ()) ; misil.setAngulo(nave.getAngulo()); misil. setincX(Math. cos (Math. toRadians (misi l.getAngulo() ) ) ** misil.setlncX(Math.eos(Math.toRadians(misil.getAngulo())) PASO_ VELOC.IDAD_NISIL ); PASO_VELOCIDAD_MISIL); misil. setincY (Math. sin (Math .toRadians (misil. g e tAngulo() ) ) ** misil.setlncY(Math.sin(Math.toRadians(misil.getAngulo())) PASO_VELOCIDAD_MISIL); ti e mp oMisil = (int) Math.min(this.getWidth() Math .min (this . getWid th () /1 Math.ajbst Mat h. abs ( misil. misil. tiempoMisil ge t i n cX()), thís.getHeight() this.getHe i ght() // Math.ahs(misil.getlncY())) Math. abs (misi l.getincY()) ) - 2; 2; getlncXO), rnis i lAct i vo = = true; raisilActivo
} 9.
Este último método requiere alguna explicación. explicación. Cuando se quiere quiere activar activar un nuevo misil este debe partir del centro de de la la nave. nave. Como Como las las coordenadas X, X, Y de Gráfico, Graf ico, corresponden a a la la esquina esquina superior superior del misil ha ha de de ser ser el el izquierda hay que hacer algunos ajustes. ajustes. El ángulo del módulo de de la velocidad velocidad de de la la mismo que actualmente tiene la nave. nave. El módulo PASO_VELOCIDAD_r"'ISIL. Para Para nave nos lo indica la constante constante paso_velocidad__misil. descomponerlo en sus componentes X e Y utilizamos utilizamos el coseno yy el seno. seno. que sale por un lado lado aparece aparece Dada la naturaleza del espacio del juego (lo que acabar chocando chocando contra contra la la por el otro) si disparáramos un misil este podría acabar nave. Para solucionarlo, nave. solucionarlo, vamos a dar dar un tiempo de de vida al misil misil para para la nave (tiempoMisil). impedir que pueda llegar de nuevo a la ( tiempof'-t isil ) . Para Para obtener obtener la el ancho dividido dividido la este tiempo nos quedamos con el mínimo entre el velocidad en X y el alto dividido entre entre la la velocidad en en Y. Y. Luego Luego le le restamos constante. Terminamos activando el misil. una constante.
10. Verifica que todo funciona correctamente. correctamente.
NOTA: Este código es posible posible que de algún problema problema de de acceso concurrente aa los los capítulo. datos desde dos threads diferentes. Se resolverá en el siguiente capitulo. . O
Práctica: vez. Práctica; Disparando varios misiles aa la vez. Tal y como se ha planteado en el código solo es posible posible lanzar lanzar un un misil misil cada cada vez. Si disparamos un segundo misil el primero primero desaparece. desaparece. ¿Podrías ¿Podrías modificar modificar el vez. código para que se pudieran lanzar tantos misiles misiles cómo cómo quisieras? quisieras? Consejo: Consejo: Observa como se ha resuelto para el caso de de los asteroides, asteroides, la la solución solución para para los los misiles puede ser muy similar. similar.
189
,
CAPITULO 6. CAPÍTULO 6.
Multimedia y ciclo ciclo de de vida vida Multimedia y de una actividad actividad de una
Una de las funciones más más habituales habituales de de los los modernos modernos teléfonos teléfonos móviles móviles es es su su utilización como reproductores reproductores multimedia. multimedia. Su Su uso uso más más frecuente frecuente es es como como la reproducción reproducción de de vídeos vídeos yy televisión televisión aatravés través de de Internet. Internet. reproductores MP3, para la El API de Android viene preparado preparado con con excelentes excelentes características características de de reproducción reproducción multimedia, permite la reproducción reproducción de de gran gran variedad variedad de de formatos, formatos, tanto tanto de de audio audio origen de de los los datos datos puede puede ser ser tanto tanto un un fichero fichero local local como como un un como de vídeo. El origen stream obtenido desde desde Internet. Internet. Todo Todo este este trabajo trabajo lo lo realiza realiza principalmente principalmente una una t·1ediaPlaye:r. clase MediaPlayer. Antes de comenzar la la descripción descripción de de estos estos contenidos, contenidos, comenzaremos comenzaremos elel capítulo con un aspecto aspecto de de vital vital importancia importancia en en el el desarrollo desarrollo de de aplicaciones aplicaciones en en Android, el ciclo ciclo de vida de de una una actividad. actividad. Es Es decir, decir, cómo cómo las las aplicaciones aplicaciones son son creadas, puestas en espera espera yy finalmente finalmente destruidas. destruidas.
Objetivos: •
Comprender el ciclo de de vida vida de de una una actividad actividad Android. Android.
•
eventos relacionados Utilizar de forma correcta los los diferentes diferentes eventos relacionados con con elel ciclo ciclo de vida. vida.
• •
Aprender cuándo yy cómo guardar guardar el el estado estado de de una una actividad. actividad. Repasar las facilidades multimedia multimedia disponibles disponibles en en Android, Android, qué qué formatos formatos soporta y las clases que que hemos hemos de de utilizar. utilizar. Describir la clase MediaPlayer, MediaPiayer, utilizada utilizada para para la la reproducción reproducción de de audio audio yy video. video.
• •
Dotar a la aplicación aplicación Asteroides Asteroides de de un un control control adecuado adecuado de de su su ciclo ciclo de de vida y de varios efectos efectos de de audio. audio.
191
El gran libro de Android
6.1. Ciclo de vida vida de de una una actividad actividad 6.1. Ciclo de El ciclo de vida de una aplicación Android Androides es bastante diferente del ciclo de vida de una aplicación en otros S.O., como Windows. Windows. La mayor diferencia es que, que, en Android el ciclo de vida es controlado principalmente por el sistema, sistema, en lugar de ser controlado directamente por el usuario. usuario. Una aplicación en Android va a estar formada por un conjunto de elementos básicos de visualización, conocidos como actividades. actividades. Además de varias actividades una aplicación también puede contener servicios. El ciclo de vida de los servicios será estudiado en el capítulo 8. Son las actividades las que realmente controlan el ciclo de vida de las aplicaciones, dado que el usuario no cambia de aplicación, sino de actividad. El sistema va a mantener una pila con las actividades previamente visualizadas, de forma que el usuario va a poder regresar a la actividad "atrás". anterior pulsando la tecla "atrás". Una aplicación Android corre dentro de su propio proceso Linux. Este proceso es creado para la aplicación y continuará vivo hasta que ya no sea requerido y el sistema reclame su memoria para asignársela a otra aplicación. aplicación. Una característica importante, importante, y poco usual, de Android es que la destrucción de un proceso no es controlado directamente por la aplicación. En lugar de esto, es el sistema quien determina cuando destruir el proceso, basándose en el conocimiento que tiene el sistema de las partes de la aplicación que están corriendo (actividades y servicios), acerca de qué tan importantes son para el usuario y cuánta memoria disponible hay en un determinado momento.
aplicación, el usuario vuelve a ella, Si tras eliminar el proceso de una aplicación, ella, se creará de nuevo el proceso, pero se habrá perdido el estado que tenía esta aplicación. aplicación. En estos casos, va a ser responsabilidad del programador almacenar el estado de las actividades, si queremos que cuando sea reiniciada conserve su estado. Como vemos, Android Androides es sensible al ciclo de vida de una actividad, por lo tanto necesitas comprender y manejar los eventos relacionados con el ciclo de vida si quieres crear aplicaciones estables. Una actividad en Android puede estar en uno de estos cuatro estados: estados:
Activa (Running): (Running). La actividad está encima de la pila, lo que quiere decir que es visible y tiene el foco. foco. Visible (Paused): (Paused): La actividad es visible pero no tiene el foco. Se alcanza este estado cuando pasa a activa otra actividad con alguna parte transparente o que no ocupa toda la pantalla. Cuando una actividad está tapada por completo, completo, pasa a estar parada. parada. Parada (Stopped) visible, se recomienda guardar (Stopped).: Cuando la actividad no es visible, el estado de la interfaz de usuario, usuario, preferencias, preferencias, etc.
{Destroyed). Cuando la actividad termina al invocarse el método Destruida (Destroyed): finishQ,, o es matada por el sistema Android, finish() Android, sale de la pila de actividades.
192
Multimedia y ciclo de vida de una actividad Cada vez que una actividad cambia de estado se van a producir eventos que actividad. A continuación, se podrán ser capturados por ciertos métodos de la actividad. muestra un esquema que ilustra los métodos que capturan estos eventos. Se crea la actividad
I 1 onCreate {) onCreate() l1
onStart {) () - - - - - - ,
i onResume (} onResume()
1
Activa onPause{)
Visible cmS t op() onStopO
onRest.art (> () onRestart
Parada onDest.roy() onDestroyO
Destruida
actividad. Figura 1: Ciclo de vida de una actividad. onCreate(Bundle): onCreate(Bundle): Se llama en la creación de la actividad. Se utiliza para realizar todo tipo de inicializaciones, como la creación de la interfaz de datos. Puede recibir información usuario o la inicialización de estructuras de datos. Bundie), por si se de estado de instancia (en una instancia de la clase Bundle), reanuda desde una actividad que ha sido destruida y vuelta a crear. onStart(): Nos indica que la actividad está a punto de ser mostrada al usuario.
onResume(): Se llama cuando la actividad va a comenzar a interactuar con el onResume(): usuario. usuario. Es un buen lugar para lanzar las animaciones y la música. onPause(): onPause(): Indica que la actividad está a punto de ser lanzada a segundo plano, normalmente porque otra aplicación es lanzada. Es el lugar adecuado para detener animaciones, animaciones, música o almacenar los datos que estaban en edición. edición. 193
El gran libro de Android onStop(): La actividad ya no va a ser visible para el usuario. onStop(): usuario. Ojo si hay muy poca memoria, memoria, es posible que la actividad se destruya sin llamar a este método. método. onRestart(): onRestart(): Indica que la actividad va a volver a ser representada después de haber pasado por onStop(). onStop(). onDestroy(): onDestroy(): Se llama antes de que la actividad sea totalmente destruida. destruida. Por ejemplo, ejemplo, cuando el usuario pulsa el botón o cuando se llama al método finish() finishQ.. Ojo si hay muy poca memoria, memoria, es posible que la actividad se destruya sin llamar a este método. método.
Ejercicio paso a paso: ¿Cuándo se llama a los eventos del ciclo de vida en una actividad?
ai
En este ejercicio vamos a implementar todos los métodos del ciclo de vida de la actividad principal de Asteroides y añadiremos un Toast para mostrar cuando son ejecutados. ejecutados. De esta forma comprenderemos mejor cuándo se llama a cada método. método. 1.
Abre la actividad Aste roi des del proyecto Asteroides. Asteroides. Asteroides
2.
Añade en el método oncreate oncrea te (()l el siguiente código: código: Toas t.. makeTc:.xt ( this , Toast.makeText(this,
3.
11 onCreate 11 , Toas t. . LEIVGTI-I.._.SI·fORT). show (); "onCreate", Toast.LBNGTH__SHORT).show();
Añade los siguientes métodos: métodos: @Override onStart(} ©Override protected void onStart() super.onStart(}; super.onStart();
{
Toast .mateText .makeText (this, "onStart", "onStart ", Toast. LBNGTH.,.SHORT) LENGTH....SHORT) .show(); . show();
} @Override onResume(} {{ ©Override protected void onResume() super.onResume(}; super.onResume(); Toast.makeText (this, "onResume", Toast.LENGTH_SHORT).show(); Toast .LElvGTH_E;HORT) .show(); Toast.makeText(this,
} @Override onPause(} {{ ©Override protected void onPause() Toast.makeText(this, LENG'IH_SHOR1') .show() .show(};; Toast ./naiceText (this, "onPause", Toast. LBNGTH_SHORT) super.onPause(}; super.onPause() ;
} @Override ©Override protected void onStop() {{ Toast . makeText (this, "onStop", Toast.LENGTH_SHORT).show() Toast. LEi'VG'I'H_SilOR'T) .show(};; Toast.maíceText(this, super.onStop();
} (q;Qver:üde onRestart (} ©Override protected void onRestart()
{{ super.onRestart(}; super.onRestart(); Toas t .makeText "onRestart. '', Toast. LENGT¡l_SHORT) LENGTH_ SfiOR'l')..showO show(} ; Toast. makeText (this, "onRestart",
}
194
- - - - - - -- - - - - - - - - - - - - - - - - -- -
-----------
Multimedia y y ciclo de vida de una actividad ®Override ©Override protected void onDestroy() onDestroyO { Toast.makeText(this, .show(); Toast.makeText( this, "onDestroy", TToast.LENGTH_SHORT) o ast .LENGTH_SHORT) .show(); super.onDestroy (); super.onDestroy();
} 4.
Ejecuta la aplicación y observa la secuencia de Toas t. Toast.
5.
Pulsa el botón Acerca de y luego regresa a la la actividad. actividad. Observa Observa la la secuencia de Toas t. Toast.
6.
Pulsa el botón Jugar y luego regresa a la actividad. Observa Observa la la secuencia secuencia de Toast. Toa st.
7.
Sal de la actividad y observa la secuencia de Toas t. Toast.
Ejercicio paso a paso: Aplicando eventos del ciclo de vida vida en la actividad Juego de Asteroides. Asteroides gestiona el movimiento de los objetos gráficos por medio medio de un un aplicación pasa pasa aa segundo segundo plano, plano, thread que se ejecuta continuamente. Cuando la aplicación este thread continúa ejecutándose, por lo que puede hacer que nuestro nuestro teléfono teléfono funcione más lentamente. Este problema aparece por una gestión incorrecta incorrecta del del ciclo de vida. vida. En el siguiente ejercicio veremos cómo solucionarlo: 1.
Abre el proyecto Asteroides y ejecútalo en un terminal con una una versión versión 2.3.x. 2.3.x.
2.
pulsa con con el el Pulsa el botón "Jugar" y cuando esté en mitad de la partida pulsa Las actividades actividades botón de inicio para dejar a la actividad en estado parada. Las recursos. Sin en este estado no tendrían que consumir demasiados recursos. embargo, Asteroides sí que lo hace, para verificarlos utiliza la la aplicación aplicación "Administrador de tareas" (disponible en las últimas versiones de de Android). El resultado puede ser similar al que se muestra a continuación:
195
El gran libro de Android Como puedes ver la aplicación Asteroides está consumiendo casi el 50% del uso de CPU. CPU. Evidentemente algo hemos hecho mal. No es lógico que cuando la actividad Juego esté en segundo plano se siga llamando a act.uaJizaFisj ca () actual.!saFisica o.. En este ejercicio aprenderemos a solucionarlo. solucionarlo. 3.
Juego. Incluye la siguiente variable en en la actividad juego. prívate VistaJuego Vi s t aJuego vistaJuego;
4. 4.
onc:r·eat:e añade: añade: Al final de onCreate vistaJuego
5.
indViewByid(R.id. FistaJuego); == (VistaJuego) ffindViewById(R.id.VistaJuego);
Incorpora los siguientes métodos a la actividad: actividad: @Over:ci.de pro tected void onPause() onPa use () SOverride protected super . o nPause(); super.onPause(); vistaJuego.getThread() vistaJuego.getThreadO .pausar();
{
} @()ver·:d. de protected void onResume() onResume () ®Override super.onResume(); vistaJuego.getThread() .reanu dar(); vistaJuego.getThreadO.reanudar();
{
} @Override pro tected void onDestroy() ©Override protected vistaJuego . getThread() . deten er(); vistaJuego.getThreadO.detener(); super.onDestroy();
{
} Lo que intentamos hacer con este código es poner en pausa el thread secundario, cuando la actividad deje de estar activa, activa, y reanudarlo, reanudarlo, cuando la actividad recupere el foco. foco. Además, Además, detener el thread cuando la actividad vaya a ser destruida. destruida. NOTA: realmente el thread será destruido al destruirse la actividad que lo ha posible. lanzado. lanzado. No obstante, puede resultar interesante hacerlo lo antes posible.
Observa cómo los eventos del ciclo de vida solo pueden ser escritos en un Activity, Vista,"Tuego. Activity, por lo que no sería válido hacerlo en vistaJuego.
6. 6.
vistaJu ego y busca la definición de la clase ThreadJuego y Abre la clase vistaJuego remplázala por la siguiente: siguiente: class ThreadJuego extends Thread Thread }{ prívate boolean boo lea n pausa,corriendo; pausa,corri e ndo ; public synchronized void pausar() pausa == true; true ;
}
196
{
Multimedia y ciclo de vida de una actividad public synchronized void reanudar() {{ pausa == false false;; notify (); notify(); } public void detener() {{ corriendo = = false false,-; if (pausa) reanudarí); reanudar();
} SOverride public void run() {{ corriendo =~ true; while (corriendo) { actualizaFisica(); actualizaFisica() ; synchronized (this (this)) { whi l e (pausa) { while try {{ wait () ();; } catch (Exception e) {{ } } } } } } Comenzamos declarando las variables pausa, corriendo. Estas pueden ser l. modificadas mediante los métodos pausar (} (),, reanudar (} () y detener ((). 7.
La palabra reservada synchronized impide el acceso concurrente a una sección del código. En el siguiente capítulo explicaremos su utilización. El ol se ha modificado de manera que en lugar de ser un bucle método run 1 infinito, permitimos que termine poniendo la variable corriendo a false. Luego, tras llamar a actualizaFisica (), () , se comprueba si se ha activado pausa. En tal caso, se entra en un bucle donde ponemos en espera el wait (). thread llamando al método wa i t 1 l . Este quedará bloqueado hasta que se o. Esta acción se realizará desde el método reanudar (). o. llame a notify (). () puede lanzar excepciones por lo que es obligatorio La llamada a wait wai t () ... } ca tch {{...}. ... } . escribirla dentro de un bloque try {{...} catch
8.
prívate, Dado que la variable thread es de tipo pri vate, no puede ser manipulada VistaJuego. Para poder llamar a los métodos de este desde fuera de vistajuego. objeto (pausar, reanudar) vamos a incluir un método getter. Para ello, sitúa vistajuego. Pulsa con el botón el cursor justo antes de la última llave de vist.aJuego. derecho en el código y selecciona la opción Source 1/ Generate Getters and Setters ... Marca solo el método getThread (} tal y como se muestra a Setters... getThreadO continuación: continuación:
197
El gran libro de Android
r^ ¡E ¡rr¡ "o puntuación puntuacion
"' BJ hread Üj . ?:' t thread Á
h/| o getlhteadO ~f~ 9~trhreadO j | <•" setThfead{Thtead)uego) I~H setThre.ad(ThreadJuego) i> ultimoProceso 0 U =• ultimo Proceso
Se insertará el siguiente código: código: public ThreadJuego Thread,Juego getThreadO getThread () return thread;
{
} 9. 9.
Ejecuta de nuevo la aplicación y repite repite el el segundo punto punto de de este este ejercicio. ejercicio. En este caso el resultado ha de ser similar al al siguiente:
LJL
f Descarga
F aBEiga
Aplicar :iones acti lililí Salir de 1 todo 1 3 a WhatsApp ^ RAM: 21,61MB, CPU:
" > ■' I Salir 1
jm Asteroides * RAM: 3,31MB, CPU: 0
' 1 Salir 1
ji| Música " RAM: 17,45MB, CPU;
1 Salir 1
bH
6.1.1. ¿Qué proceso se elimina? Como hemos comentado Android mantiene en memoria todos todos los los procesos procesos que que quepan aunque estos no se estén ejecutando. ejecutando. Una Una vez vez que que la la memoria memoria está está llena llena yy el usuario decide ejecutar una nueva aplicación, aplicación, el el sistema sistema ha ha de de determinar determinar qué qué ha de de ser eliminado. eliminado. Android Android ordena ordena los los proceso de los que están en ejecución ha jerárquica, asignándole asignándole aa cada cada uno uno una una determinada determinada procesos en una lista jerárquica, "importancia". basándose en los los componentes componentes de de la la "importancia". Esta lista se confecciona basándose aplicación que están corriendo (actividades (actividades yy servicios) servicios) yy el el estado estado de de estos estos componentes. componentes. Para establecer esta jerarquía de importancia importancia se se distinguen distinguen los los siguientes siguientes tipos tipos de procesos: procesos: Proceso de primer plano (Foreground process): process): Hospeda Hospeda una una actividad actividad en en la superficie de la pantalla, con la cual el el usuario usuario está interactuando interactuando (su (su método onResume () ha sido llamado). llamado). Debería haber solo solo uno uno oo unos unos pocos procesos de este tipo. Sólo serán eliminados cómo cómo último último recurso,
198
Multimedia y ciclo de vida de una actividad si es que la memoria está tan baja que ni siquiera estos procesos pueden continuar corriendo. puedencontinuarcorriendo. Proceso visible (Visible process): Hospeda una actividad que está visible en la pantalla, pero no en el primer plano (su método onPause () < J ha sido llamado). Considerado importante, no será eliminado a menos que sea necesario para mantener los procesos de primer plano. Proceso de servicio (Service process): Hospeda un servicio que ha sido inicializado con el método startserviceO. startservice
0
.0 0 _
Práctica: Aplicando eventos del ciclo de vida en la actividad inicial de Asteroides. Asteroides.
Los conceptos referentes al ciclo de vida de una actividad son imprescindibles para el desarrollo de aplicaciones estables en Android. Para reforzar estos conceptos te proponemos el siguiente ejercicio en el que vamos a reproducir una música de fondo. 1.
Abre el proyecto Asteroides. Asteroides.
2.
Busca un fichero de audio (los formatos soportados por Android se listan capítulo). Renombra este fichero a audio.xxx y cópialo a la carpeta en este capítulo). reslraw. res/raw.
NOTA: Cada vez que ejecutes el proyecto este fichero será añadido al paquete .apk. .apk Si este fichero es muy grande la aplicación también lo será, lo que ralentizará su instalación. Para agilizar la ejecución te recomendamos un fichero muy pequeño, por instalación. ejemplo un .mp3 de corta duración o un fichero MIDI (mid). (mid). Si no encuentras ninguno puedes descargar uno de la Web del curso.
199
El gran libro de Android
3.
Abre la actividad Asteroides Asr.eroides yy añade añade las las siguientes siguientes líneas líneas en en elel método método oonCreate(): nCreate () : t4ediaPlayer MediaPla.yer . c.reate·{ this, R.raw.audio); R.raw .audio) ; MedlaPlayer mp -= MediaPlayer,create(this, mp.sta:r:t(); mp.start();
4. 4.
Ejecuta el proyecto yy verifica verifica que que cuando cuando sales sales de de lala actividad actividad lala música música tiempo. sigue sonando un cierto tiempo.
5.
Utilizando Utilizando los los eventos eventos del del ciclo ciclo de de vida vida queremos queremos que que cuando cuando lala actividad actividad el audio audio deje deje de de escucharse. escucharse. Puedes Puedes utilizar utilizar los los deje de estar estar activa el métodos: métodos: mp.pause( ); mp.pause(); mp. s tart () ; mp.start{);
6. 6.
Verifica que que funciona correctamente. correctamente.
O"
Práctica: Aplicando eventos del ciclo ciclo de de vida vida en en la la actividad actividad inicial de Asteroides (II). (//).
0
1.
realizar el el ejercicio ejercicio anterior, anterior, ejecuta ejecuta la la aplicación aplicación yy pulsa pulsa en en elel botón botón Tras realizar Acerca de... de .. . La música ha ha de de detenerse. detenerse.
2.
mientras parte parte de de esta esta actividad actividad esté esté visible visible (como (como ha ha Nos interesa interesa que, mientras Es decir, decir, utilizando utilizando anterior), la la música música se se escuche. escuche. Es ocurrido en el punto anterior), los eventos del ciclo de de vida, vida, queremos queremos que que cuando cuando la la actividad actividad deje deje de de estar visible el audio deje deje de de escucharse. escucharse.
3. 3.
que funciona correctamente. correctamente. Verifica que
6.1.2. Guardando el el estado estado de de una una actividad actividad 6.1.2. Guardando Cuando el usuario usuario ha ha estado estado utilizando utilizando una una actividad, actividad, yy tras tras cambiar cambiar aa otras, otras, en memoria memoria yy continúe continúesu su primera, lo lo habitual habitual es es que que esta esta permanezca permanezca en regresa a la primera, ejecución sin alteraciones. alteraciones. Como Como hemos hemos explicado, explicado, en en situaciones situaciones de de escasez escasez de de posible que el memoria, es posible el sistema sistema haya haya eliminado eliminado el el proceso proceso que que ejecutaba ejecutaba lala actividad. caso, el el proceso proceso será será ejecutado ejecutado de de nuevo, nuevo, pero pero se se habrá habrá perdido perdido actividad. En este caso, su estado, es decir, decir, se se habrá habrá perdido perdido el el valor valor de de sus sus variables variables yy elel puntero puntero de de programa. Como consecuencia, si si el el usuario usuario estaba estaba rellenando rellenando un un cuadro cuadro de detexto textooo estaba reproduciendo un audio audio en en un un punto punto determinado determinado perderá perderá esta esta información. información . En este apartado estudiaremos estudiaremos un un mecanismo mecanismo sencillo sencillo que que nos nos proporciona proporciona Android Android para resolver este este problema. problema. actividad sensible sensible aa la la inclinación inclinación del del teléfono, teléfono, es es NOTA: Cuando se ejecuta una actividad horizontal oo en en vertical, vertical, se se presenta presenta un unproblema problemasimilar similaral al decir que puede verse en horizontal anterior. La actividad es destruida destruidayy vuelta vuelta aa construir construircon con las las nuevas nuevasdimensiones dimensionesde de llama de de nuevo nuevo al al método método onCreate onCreate{). (). Antes Antes de de que que lala pantalla y, por lo tanto, se llama resultafundamental fundamentalguardar guardarsu suestado. estado. actividad sea destruida también resulta
200
Multimedia yy ciclo de vida de una una actividad actividad Dependiendo de lo sensible sensible que que sea sea la la pérdida pérdida de de la la información información de de estado estado se se podrá actuar de de diferentes diferentes maneras. maneras. Una Una posibilidad posibilidad es, es, por por ejemplo, ejemplo, no no hacer hacer nada. el ejemplo ejemplo anterior, anterior, el el usuario usuario tendría tendría que que volver volver aa rellenar rellenar elel nada. Siguiendo con el cuadro de texto o el audio audio volvería volvería aa reproducirse reproducirse desde desde el el principio. principio. En caso de tratarse de de información información extremadamente extremadamente sensible, sensible, se se recomienda recomienda elel uso del método onPauseO en Pause () para para guardar guardar el el estado estado yy onResumeí) onResume () para para recuperarlo. recuperarlo. en un un medio medía permanente, permanente, Por supuesto, la información información sensible sensible ha ha de de almacenarse almacenarse en base de de datos. datos. Se Se trata trata del del método método más más fiable, fiable, como como vimos vimos como un fichero o una base en el ciclo de vida de una una actividad, actividad, los los métodos métodos onstop onstop()() yy onDestroy onDestroy{)() no notienen tienen la garantía de ser llamados. llamados. También tenemos una una tercera tercera posibilidad posibilidad que, que, aunque aunque no no tenga tenga una una fiabilidad fiabilidad en utilizar utilizar los los siguientes siguientes dos dos sencilla. Consiste Consiste en tan grande, resulta mucho mucho más más sencilla. métodos: métodos: onSavelnstanceState(Bundle): Se invoca invoca para para permitir permitir aa lala actividad actividad onSavelnstanceState(Bundle): Se guardar su estado. estado. Ojo, si si hay hay muy muy poca poca memoria, memoria, es es posible posible que que lala actividad se destruya destruya sin llamar llamar aa este este método. método. onRestorelnstanceState(Bundle): Se invoca invoca para para recuperar recuperar elel estado estado onRestorelnstanceState(Bundle): Se onSaveinstanceState {{). i. guardado por onSavelnstanceScate
Veamos un ejemplo de de lo lo sencillo sencillo que que resulta resulta guardar guardar la la información información de de una una variable tipo cadena de de caracteres caracteres yy entero. entero. String cadena; cadena ; int pos; pos ;
©Override
í?JOverl~ide
protected void onSavelnstanceState(Bundle onSaveinstanceState(Bundle guardarEstado) guardarEstado) { super.onSaveinstanceState(guardarEstado); super.onSavelnstanceState(guardarEstado); guardarEstado .putString (" variable", var); var ) ; guardarEstado.putString("variable", guardarEstado. putint ( "posicion", pos); pos) ; guardarEstado.putlnt("posición",
} @Ove rrtde: ©Override protected void onRestorelnstanceState(Bundle onRestoreinstanceState(Bundle recEstado) recEstado) {{ super.onRestoreinstanceState(recEstado); super.onRestorelnstanceState(recEstado); var = recEstado.getString("variable"); recEstado.getString(•variable" ); pos = recEstado.getlnt("posición"); recEstado.getint(''posici.on");
} La ventaja de usar estos estos métodos métodos es es que que el el programador programador no no debe debe buscar buscar un un método de almacenamiento almacenamiento permanente, permanente, es es el el sistema sistema quien quien hará hará este este trabajo. trabajo. El El inconveniente es que el sistema sistema no no nos nos garantiza garantiza que que sean sean llamados. llamados. En En casos casos de de extrema necesidad de memoria memoria se se podría podría destruir destruir nuestro nuestro proceso proceso sin sin llamarlos. llamarlos.
0 Vf
Práctica: estado en en Asteroides. Asteroides. Práctica: Guardando el estado 201
El gran libro de Android Androíd 1.
Ejecuta el proyecto Asteroides. Asteroides.
2. 2.
Cambia de orientación el teléfono. teléfono. Observarás como la música se reinicia cada vez que lo haces. haces.
3. 3.
Utilizando los métodos para guardar el estado de una actividad, actividad, trata de teléfono, el audio continúe en el mismo punto de que cuando se voltea el teléfono, reproducción reproducción.. Puedes utilizar los siguientes métodos: métodos; iint n t pos == mp.getCurrentPosition() mp . getCurrentPos i tion();; mp . seekTo (pos ) ; mp.seekTo(pos);
4. 4.
Verifica el resultado. resultado.
f Solución: — Solución: Una posible solución al ejercicio anterior se muestra a continuación: continuación: @Over:r:·ide ®Override
protect e d void onSaveinstanceState(Bundle protected onSavelnstanceState(Bundle estadoGuardado) estadoGuardado){{ super.onSavei nstanceStat e (estado Guardado) ; super.onSavelnstanceState(estadoGuardado) if (mp != null) nuil) {{ nt pos pos= mp.getCurrentPosition() ; iint = mp.getCurrentPosition(); estad oGuardado.put i nt ( "posicion" , pos); estadoGuardado.putlnt("posición",
} } ©Override pprotected rotected void onRestorelnstanceState(Bundle onRestoreinsta nceState(Bundle estadoGuardado){ estadoGuardado) { super.onRe s toreinstanceState(estadoGu ardad o) ; super.onRestorelnstanceState(estadoGuardado); if (estadoGuardado != null nuil && mp != null) nuil) {{ int pos = estadoGuardado.getlnt("posición"); estadoGuardado . getint("posicion" ); mp. seekTo (pos); mp.seekTo(pos);
} } Para reforzar el uso de estos métodos te recomendamos el ejercicio planteado en el apartado 6.4.1 donde se pide recordar el punto de reproducción de un vídeo. vídeo.
6.2. en Android 6.2. Utilizando Utilizando multimedia multimedia en Androíd La integración de contenido multimedia en nuestras aplicaciones resulta muy sencilla gracias a la gran variedad de facilidades que nos proporciona el API. Concretamente podemos reproducir audio y vídeo desde orígenes distintos: distintos: •
202
Desde un fichero almacenado en el dispositivo. dispositivo.
Multimedia yy ciclo de vida de una actividad •
Desde un recurso que está incrustado en el paquete de la aplicación (fichero ..apk). apk) .
•
Desde un stream que es leído desde una conexión de red. red. En este punto admite dos posibles protocolos (http: 11 y rstp: 1! ) (http:// rstp://)
También resulta sencilla la grabación de audio y vídeo, vídeo, siempre que el hardware del dispositivo lo permita. permita.
En la siguiente lista se muestran las clases de Android que nos permitirán acceder a los servicios Multimedia: Multimedia: MediaPayer: MediaPayer: Reproducción de audio/vídeo desde ficheros o streams. streams. MediaControler: MediaControler: Visualiza controles estándar para mediaPiayer mediaPlayer (pausa, (pausa, stop).
VideoView: Vista que permite la reproducción de vídeo. VideoView: vídeo. SoundPool: Maneja y reproduce una colección de recursos de audio. audio. AsyncPiayer: AsyncPlayer: Reproduce listas de audios desde un thread distinto al nuestro. nuestro. MediaRecorder: MediaRecorder; Permite grabar audio y vídeo. vídeo. AudioManager: AudioManager: Gestiona varias propiedades del sistema (volumen, tonos, etc.). (volumen, tonos, AudioTrack: AudioTrack: Reproduce un búfer de audio PCM directamente por hardware. hardware. JetPiayer: JetCreator. JetPIayer: Reproduce audio y video interactivo creado con JetCreator. Camera: Cómo utilizar la cámara para tomar fotos y video. video.
FaceDetector: Identifica la cara de la gente en un bitmap. FaceDetector: La plataforma Android soporta una gran variedad de formatos, muchos de los cuales pueden ser tanto decodificados como codificados. codificados. A continuación, continuación, mostramos una tabla con los formatos multimedia soportados. soportados. No obstante, obstante, algunos modelos de móviles pueden soportar formatos adicionales que no se incluyen en la tabla, tabla, como por ejemplo DivX. DivX. Cada desarrollador es libre de usar los formatos incluidos en el núcleo del sistema o aquellos que solo se incluyen en algunos dispositivos. Tipo
Audio
Formato
Codifica
Decodific a
AAC LC/LTP
X
X
HE-AACv1 (AAC+)
X
HE-AACv2 (enhanced AAC+)
X
AMR-NB
X
X
Detalles
Fichero soportado
Mono/estéreo con cualquier combinación estándar de frecuencias por encima de 160 Kbps y ratios de muestreo de 8 a 48kHz
3GPP ((,. 3gp) MPEG-4 ((.mp4) . mp4 ) No soporta raw AAC ((..aac) aac)
4.75 a 12.2 12,2 Kbps muestreada a @ 8kHz
3GPP ((.. 3gp)
203
El gran libro de Android Tipo
Formato
Codifica
Detalles
Fichero soportado
X
9 ratios de 6.60 Kbps Kbps aa 23.85 Kbps 3GPP ((.. 3gp) muestreada maestreada a @ @ 16kHz
X
Mono/estéreo de 88 aa frecuencia 320 Kbps, frecuencia MP3 ((.. mp3 mp3)) de muestreo constante (CBR) o variable (VBR) (VBR)
MIDI MI DI
X
MIDI 0 y 1. 1. DLS DLS v1 v1 MI DI tipo O y v2. XMF y XMF móvil. Soporte para para tonos de llamada RTTTL 1/ RTX, OT A yy OTA iMelody. ¡Melody.
Ogg Vorbis
X
FLAG FLAC
a partir 3.1 3.1
PCMIWAVE PCM/WAVE
AMR-WB
X
MP3
X
JPEG GIF
Imagen
Decodífic Decodific a
X
PNG BMP WEBP
Tipo O . mid, 0 y 11 ((.mid, ,xmf, .mxmf .rnxmf). .xmf, ). RTTTL/RTX RTTTL 1 RTX ((.rtttl, .rtttl, .. rtx}, rtx), OTA ((.ota) . ota) iMelody ((.. imy) Ogg ((.. ogg) Matroska ((.. mkv mkv a partir 44.0) .0)
mono/estereo (no (no multicanal) multícanal)
FLAC ( .flac) FLAC(.flac)
X
8 y 16 bits PCM lineal lineal (frecuencias limitadas limitadas por el hardware)
WAVE ( .wav) WAVE(.wav)
X
Base + progresivo
JPEG ( . jpg) JPEG(.jpg)
X
GIF ((.gif) .gH )
X
PNG ( . png) PNG(.png)
X
BMP ((,bmp) . bmp)
a partir 4.0 a partir 4.0
H.263
X
X
H.264 AVC H.264AVC
a partir 3.0
X
WebP ((.. webp) 3GPP ((.3gp) . 3gp ) MPEG-4 ((.mp4) . rnp4 ) Baseline Profile (BP) (BP)
3GPP ((.. 3gp) 3gp) MPEG-4 ((.mp4) . mp4 )
Video MPEG-4 SP WP8 WPS
3GPP ((.. 3gp)
X a partir 2.3.3
Streaming a partir 4.0 4.0
Tabla 1: Formatos multimedia multimedia soportados soportados en enAndroid. Android.
204
WebM ((.. webm) Matroska ((.. rnkv mkv a partir 4.0)
-
- - - - - · - - - -- -- - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - -- - - - - - -
Multimedia yy ciclo de vida de una actividad
6.3. vista VideoVíew VideoView 6.3. La La vista La forma más fácil de incluir un vídeo en tu aplicación es incluir incluir una una vista vista de de tipo tipo Videovie-.;. siguiente ejercicio. videoview. Veamos cómo hacerlo en el siguiente
Ejercicio paso a paso: Reproducir un vídeo con VideoView. 1.
siguientes datos: datos: Crea una nueva aplicación con los siguientes Project ñame: name: videoView Build Target: Android 1.5 1.5 Application ñame; name: videoView Package ñame: name: org.example.videoview org. example. videoviev,r Create Activity: videoView
2.
Remplaza el fichero ref/layout/main.xmi ref/layout/main.xml por: por:
> > 3.
Remplaza el siguiente código en la clase videoview: videoview: public class videoView extends Activity { prívate VideoView mVideoView; private
©Override public void onCreate(Bundle savedlnstanceState) savedinstanceState) {
super .onCreat.e(savedinstanceState) ; super.onCreate(savedlnstanceState); setContentView (R . layout .main) ; setContentView(R.layout.main); mVideoView =(VideoView)findViewByldíR.id.surface_view); = (VideoView) findViewByld (H.. id. surface_ v·iew) ; //de forma altemative 1 /de for:ma alternative si queremos cruer·emos un streaming usar usar 1 /mVideoView . setVideoú"RI (Uri. parse (URLstring) ) ; //mVideoView.setVideoüRI(Uri.pareeÍDRLstring))¡ mVideoView.setVideoPath("/data/video.mp4"); mVideoView . setVideoPath( " /data/video.mp4 "); mVideoView.start() mVideoView.start();¡ mVideoView.request.Focus(); mVideoView.reouestFocus{);
} } 4.
( l estamos indicando indicando un un fichero fichero local. local. En el método setvideoPath ()
205
El gran libro de Android 5. 5.
Busca un fichero de vídeo en codificación mp4. mp4. Renombra Renombra este este fichero fichero aa video.mp4. video. mp4.
6.
vídeo en en el el simulador. simulador. Para Para ello, ello, Es necesario almacenar un fichero de vídeo utiliza la opción del menú Window > Show Show View >Others... >Others ... >> Android Android >> File Explorer. Te mostrará el sistema de ficheros del del emulador. emulador.
íl Probt [ §3 Cora [ a LogC 1 p Emul D Prope | Q FHeE ¡Z 1 i Mame íM? data ^ sdcard system
B Devic ^ □ ü € 1 -Z l
í.-MSsSí
y¡
7. 7.
el botón botón donde donde aparece aparece un un teléfono teléfono con con Selecciona la carpeta data y utiliza el una flecha para almacenar un un nuevo fichero. Indica Indica el el fichero fichero vldeo.mp4. video.mp4.
8. 8.
Ejecuta la aplicación yy observa el resultado. resultado. Recuerda Recuerda que que con con Ctrl-F11 Ctri-F11 puedes cambiar la orientación. orientación. Si no no funciona, funciona, trata de de usar usar otro otro tipo tipo de de emulador. emulador.
7.14 AM
i ' IPT i. < ¡té
9. 9.
el vídeo vídeo aparezca aparezca centrado centrado yy ocupe ocupe toda toda Modifica el fichero xml para que el la pantalla del teléfono, tal y como se muestra muestra en en la la imagen imagen de de arriba. arriba.
al método método start start (>: ( ¡: 10. de la llamada llamada al 10. Añade la siguiente línea antes de mV.ideoV. i ew . setMediaController set r<:~ediaCon.t.rol J er (new MediaController Me di aContro ll er (this) (thi s)); ttiVideoView. );
De esta forma permitimos que el usuario usuario pueda pueda controlar la la reproducción reproducción del vídeo mediante el objeto MediaController. t.f;edi.aCont.roJ.le r . 11. Observa el resultado.
206
Multimedia y ciclo de vida de una una actividad actividad
6.4. 6.4. La La clase clase MediaPiayer MediaPlayer La reproducción multimedia en Android Android se se lleva lleva aa cabo cabo principalmente principalmente por por medio medio de de la clase MediaPlayer. Veremos, a continuación, continuación, las las características características más más importantes importantes de esta clase y cómo podemos sacarle provecho. provecho. Un objeto MediaPlayer va aa poder poder pasar pasar por por gran gran variedad variedad de de estados: estados; inicializados sus recursos (initialized), (initialized), preparando preparando la la reproducción reproducción (preparing), (preparing), preparado para reproducir (prepared), (preparad), reproduciendo reproduciendo (started), (started), en en pausa pausa (paused), (paused), parado (stoped), reproducción completada completada (playback (playback completed), completed), finalizado finalizado (end) (end) yy con error (error) (error).. Resulta importante importante conocer conocer en en qué qué estado estado se se encuentra encuentra dado dado que que muchos de los métodos solo pueden pueden ser ser llamados llamados desde desde ciertos ciertos estados. estados. Por Por ejemplo, no podremos ponerlo ponerlo en reproducción reproducción (método (método start starto) sino está está en en ( l ) sino estado preparado. O no no podremos podremos ponerlo ponerlo en en pausa pausa (pause (pause ()()), si está está parado. parado. Si Si ), si llamamos a un método no admitido admitido para para un un determinado determinado estado estado se se producirá producirá un un error de ejecución. La siguiente tabla permite conocer los los métodos métodos que que podemos podemos invocar invocar desde desde cada uno de los estados y cuál es es el el nuevo nuevo estado estado al al que que iremos iremos tras tras invocarlo. invocarlo. Existen dos tipos de métodos, los los que que no no están están subrayados subrayados representan representan métodos métodos llamados de forma síncrona desde nuestro nuestro código, código, mientras mientras que que los los que que están están subrayados representan métodos métodos llamados llamados de de forma forma asíncrona asincrona por por el el sistema. sistema.
Estado salida
Id le
lnitialized Initialized
setDataSource setDataSource
Estado entrada lnitialized Initialized
Preparing
prepareAsinc prepare Asme
Prepared
prepare
Preparing
Prepared Prepared
Started
Paused
Stoped
prepareAsinc prepareAsmc onPregared onPrepared
Started
seekTo seekTo start
Paused Stoped
stop
Playback Completed
prepare seekTo start
start
pause
seekTo pause
stop
stop
start
stop
onComgletion onComoletion
End Error
Playback Completed
stop seekTo
Release
release
release
release
release
release
release
release
onError
onError
onError
onError
onError
onError
onError
onError
de la la clase clase MediaPiayer. MediaPlayer. Tabla 2: Transiciones entre estados de
6.4.1. Reproducción de audio audío con con MediaPiayer MediaPlayer se va aa reproducir reproducir siempre siempre en en nuestra nuestra aplicación aplicación resulta resulta Si el audio o vídeo se interesante incluirlo en el paquete paquete .. apk apk yy tratarlo tratarlo como como un un recurso. recurso. Este Este uso uso ya ya ha ha sido ilustrado al comienzo del capítulo. capítulo. Recordemos Recordemos como como se se hacía: hacía: 1.
Crea una nueva carpeta con con nombre nombre raw raw dentro dentro de de la la carpeta carpeta res. res.
207
El gran libro de Android 2.
audio. Por ejemplo audio.mp3. audio.mp3. Arrastra a su interior el fichero de audio.
3.
Añade las siguientes líneas de código: código: MediaPlayer mp rnp = 1-'lediaPlayer. c.reat:e( this , R.raw,audio); R.r-aw.audio ) ; MediaPlayer.crea te(this, mp . s tart ( ) ; mp.start();
Si deseas parar la reproducción tendrás que utilizar el método stop{). stop (). Si, Si, a luego, start (). continuación, quieres volver a reproducirlo utiliza el método prepare () y, y, luego, \). reanudarlo. También puedes usar pause () y start {; () para ponerlo en pausa y reanudarlo. Si en lugar de un recurso prefieres reproducirlo desde un fichero utiliza el siguiente ( l . En el caso código. código. Observa cómo en este caso es necesario llamar al método prepare {). reate (). () . anterior no ha sido necesario dado que esta llamada se hace desde ecreate MeciiaPlayer rnp == new MediaPlayer{); fV1edi aPlayer () ; MediaPlayer mp rnp. setDataSource (C.ll,MINO AL FICHERO); mp.setDataSource(CAMINO mp.prepare(i;
mp.start.(); mp.start{) ;
6.5. Un Un reproductor reproductor multimedia multimedia paso paso a paso 6.5. a paso En el siguiente ejercicio vamos a profundizar en el objeto MediaP/ayer MediaPlayer por medio de ejemplo, donde trataremos de realizar un reproductor de vídeos personalizado. un ejemplo, personalizado.
Ejerc icio paso a paso: Un reproductor multimedia paso a paso. Ejercicio paso. 1. 1.
Crea una nueva aplicación con los siguientes datos: datos: Project ñame: name: VideoPlayer Build Target: Google APIs APis 1.5 BulId Target; Appli ca tion name Application ñame:: VideoPlayer Package name : org.example.videoplayer org .example.v ideopl ayer Package ñame: Create Activity: Ac tivity: VideoPlayer Min SDK Versión: Version: 3
2.
En la carpeta res/drawable res/drm.;able arrastra los cuatro ficheros de iconos: iconos: play . jpe9, pause, pause . jpeg, reset jpeg y stop.jpeg. stop_ jpeg. Los puedes encontrar play.jpeg, reset.. jpegy en www.androidcurso.com. www androidcurso.com.
3.
Remplaza el fichero resllayoutlmain. xml por: res/layout/maín.xml por:
"1.O" encoding="utf-8"?> android:orientation="horizontal"> 11
1
208
Multimedia y y ciclo de vida de una actividad < android:layout_alignFarentTop="true"> android:src="@drawable/play"/> android: src= ~~ t~dravlable/pause /> android:src="@drawahle/log"/> android: text= "/d.ata/vj_d.eo . 3Qp "1 > <"'\.T.ideoVi.ew android: id= @+.id/surfaceVi.ehr '' android:layout_below="@h id/ButonsLayout"/> android:layout_alignParentBottom= ""true"> android:text="Log:"/> 11
u
11
1
continuación: La apariencia del Layout anterior se muestra a continuación:
209
El gran libro de Android Androld m 10:00 AM
I
s
I /data/video.mp4
>r.
4.
Remplaza el código de la clase Vi d eoPlayer por: por: videoPlayer ppublic u b l ic class VideoPlayer extends e xte nds Activity implements imple men ta OnBufferingUpdateListener, OnCompletionListener, Med i aPlayer.OnPreparedListener, SurfaceHolder.Callback MediaPlayer.OnPreparedListener, pri vate MediaPlayer mediaPlayer; me d iaPlaye r ; prívate pprívate rivate SurfaceView surfaceView; surfaceView ; private sur f a c e Holder ; prívate SurfaceHolder surfaceHolder; private e d itText ; prívate EditText editText; private Irnage Button bPlay, bPlay , bbPause, Pa u se , bstop, bStop , bLog; bLog ; prívate ImageButton pprívate r i va t e TextView llogTextView; o gTe x t View ; a us e ; pprívate r ivate boolean ppause; pprívate rivate String path; p at h; pprívate rivate int i nt savePos = = 0; O; public void vo i d oonCreate(Bundle nCreate(Bundle bbundle) undle ) { ssuper.onCreate(bundle); u per .onCre ate(bundle ); setContentView (R .layout. n
2100 21
{
Multimedia yy ciclo de de vida vida de de una una actividad actividad if (mediaPlayer != != nuil) null) {{ iif f (pause) {{ mediaPlayer.start(); } else {{ playVideo(); playVideoO ;
} } }} }}); ) ; bPause = (ImageButton) findViewByld(R.id.pause); findViewByid(R.id. pause); bPause.setOnClickListener( new OnClickListener() OnClickListener() { bPause.setOnClickListener(new public void onClick(View onClick(View view) view) { iif f (mediaPlayer != ! = nuil) null) { pause == true; mediaPlayer.pause(); mediaPlayer.pause();
} }}
}}); ) ; bStop = (ImageButton) findViewById(R.id.stop); findViewByid(R.id. scop); bStop.setOnClickListener(new OnClickListener() { bStop.setOnClickListener(new OnClickListener() public void onClick(View onClick(View view) view) {{ iif f (mediaPlayer != != nuil) nuJ.l) { pause = false; mediaPlayer.stop(); mediaPlayer.stop();
} }} ) ; }}); findViewByid (R. id. logEutton ); bLog == (ImageButton) findViewById(R.id.locrButton); bLog.setOnClickListener(new OnClickListener() {{ bLog.setOnClickListener(new OnClickListener() pu.blic onClick(View view) view) {{ public void onClick(View iiff (logTextView.getVisibility()==TextView. ( logTextView. getVisibili ty () ==TextView. V'XSJñLE) VISIBLE) { logTextView. setVisibility (TextView. _INVT8IBLE) ; logTextView.setVisibility(TextView.INVISIBLE); e lse {{ } else logTextView.setVisibility(TextView. VISIBLE); logTextView.setvisibility(TextView. VISIBLE); } }} }})) i; log( log("") ; 11 " ) ;
} 5.
puedes ver, ver, la la aplicación aplicación extiende extiende lala clase clase Activity. Act ivi ty. Además, Además, Como puedes implementamos cuatro interfaces interfaces que que corresponden corresponden aa varios varios escuchadores escuchadoresde de eventos. Luego continúa continúa la la declaración declaración de de variables. variables. Las Las primeras primeras corresponden a diferentes diferentes elementos elementos de de la la aplicación aplicación yy su su significado significado resulta resulta obvio. pause nos nos indica indica sisi el el usuario usuario ha ha pulsado pulsado elel botón botón obvio. La variable pause correspondiente, la variable variable path path nos nos indica indica dónde dónde está está elel vídeo vídeo en en reproducción yy la variable variable savePos savePos almacena almacena la la posición posición de dereproducción. reproducción.
211
El gran libro de Android 6.
Añade: Añade: pprívate riva t e void playVideo playVideo í) () ttry ry {{ pause -~ false; f a lse ;
{
path == editText.getText{),toString(); editText.getText ( ) .toString(); mediaPlayer MediaPlayer(); medíaPlayer == new MediaPlayer{); mediaPl ayer.setDataSource(path); mediaPlayer.setDataSource ípath); mediaPl ayer.setDisplay(surfaceHolder); medíaPlayer.setDisplay(surfaceHolder); mediaPl ayer.prepare() ; mediaPlayer.prepare();
!/// mMediaPlayer.prepareAsync{); mMediaPlayer. prepareJ\ sync () ; Para Para atrsaming str.::aming mediaPlayer.setOnBufferingUpdateListener( thi s ); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer. setoncompletionLü;tener ( this ); mediaPlayer.setOriCompletionListeneríthis); mediaPlayer.setOnPreparedListenerl t his ); mediaPlayer.setOnPreparedListener(this);
}
mediaPlayer.seCAudioStreamType(AudioManager.STREAM,JÍUSIC); mediaPlayer . setAudioSt.reamType (AudioManager. S'I'REAM...HUSIC) ; mediaPlayer . seekTo(savePos); mediaPlayer.seekTo(savePos); ccatch atc h (Sxception (Exception e) e) {{ log(''ERR.OR: e.get.Message(i); logC'ERROR: "" + e.getMessage {) ) ;
} } 7.
continúa con con la la definición definición del del método métodoplayvideo playvideo(). () . Este Este método método El código continúa un nuevo nuevo objeto objeto se encarga de obtener obtener la la ruta ruta de de reproducción, reproducción , crear crear un r>lediaPlayer, se le le asigna asigna la la ruta ruta yy la la superficie superficie de de visualización, visualización,aa MediaPlayer, luego se continuación, se prepara En caso caso de de querer querer continuación, prepara la la reproducción reproducción del del vídeo. vídeo. En reproducir un stream desde desde la la red, red, esta esta función función puede puede tardar tardar bastante bastante tiempo, en tal caso es tiempo, es recomendable recomendable utilizar utilizar en en su su lugar lugar elel método método prepareAsync () ( l que que permite permite continuar continuar con con la la ejecución ejecución del del programa, programa, aunque sin esperar esperar aa que que el el vídeo vídeo esté esté preparado. preparado. Las Las siguientes siguientes tres tres de eventos eventos que que serán serán líneas asignan a nuestro nuestro objeto objeto varios varios escuchadores escuchadores de descritos más adelante. adelante. Tras Tras preparar preparar el el tipo tipo de de audio, audio, se se sitúa sitúa lala posición posición de reproducción a los milisegundos milisegundos indicados indicados en en la la variable variable savePos. savePos. Si Si se se trata de una nueva reproducción, reproducción, esta esta variable variable será será cero. cero.
8.
código: Añade el código: ppublic ublic void vo i d onBufferingüpdate(MediaPlayer onBufferingUpdat. e (MediaPlayer argO, argO, int i nt percent) percent)
{{ log ( " onBufferingUpdat.e percent:" percent:" ++ percent); percent) ; log("onBufferingüpdate
} public void onCompletion(MediaPlayer onCompl.etion (!'1edi.aPi.aye r argO) argO) log ( "onCompl.et.ion called"); c all.ed " ) ; loq("onCorapletion
{
} 9.
métodos anteriores anteriores implementan implementan los los interfaces interfaces onBufferingüpdate onBufferingupdate Los métodos Listener y Oncompletionhistener. oncompletionListener. El El primero primero irá irá mostrando mostrandoelel porcentaje porcentajede de el segundo segundo será será invocado invocado obtención de búfer de reproducción reproducción, mientras mientras que que el cuando el vídeo en reproducción reproducción llegue llegue al al final. final.
212
Multimedia yy ciclo de vida de una actividad 1 O. Añade el código: 10. código: _
public void onPrepax onPrepared ed (J:.1ediaPlayer (MediaPlayer mediaplaye:r) mediaplayer) { log("onPrepared called"); int mVideoVíidth mVideovlidth == mediaPXayer. mediaPlayer. getVideoWidth í) () ;,int mVideoHeight = = mediaPlayer.getVideoHeight(}; mediaPlayer.getVideoHeight( ) ; iif f (mVideoWidth != &.& mVideoHeight !" O) {{ !■- 0o && != 0) surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight); mVideoHeight ); surfaceHolder.setFixedSize(raVideoWidth, mediaPlayer.start(); mediaPlayer.start{);
} } 11. El método anterior implementa la interfaz onPreperedListener. OnPreperedListener. Es invocado una vez que el vídeo ya está preparado para su reproducción reproducción.. En este momento podemos conocer el alto y el ancho del vídeo y ponerlo en reproducción. 12. Añade el código: código: public void surfaceCreated(SurfaceHolder sux-faceCreated{SurfaceHolder holder) log("surfaceCreated called"l; called"); pplayVideo{i; layVideo();
{{
} public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) log("surfaceChanged called");
{{
} public void surfaceDestroyed(SurfaceHolder surfaceholder) log("surfaceDestroyed log{"surfaceDestroyed called");
{{
} 13. Los métodos anteriores implementan la interfaz surfaceHolder.Caiiback. surfaceHolder. Callback. Se invocarán cuando nuestra superficie de cree, cambie o se destruya. Los métodos que siguen visualización se cree, actividad: corresponden a acciones del ciclo de vida de una actividad: 14. Añade el código: ®Override SOverx'ide protected void onDestroy() onDestroyO super.onDestroy(); if (mediaPlayer !!== null) nuil) {{ mediaPlayer.release(); mediaPlayer.release() ; mediaPlayer = = nuil; null;
{
} }
15. Este método se invoca cuando la actividad va a ser destruida. destruida. Dado que recursos, resulta un objeto de la clase MediaPlayer consume muchos recursos, interesante liberarlos lo antes posible. posible.
213
El gran libro de Android 16. Añade el código: código: @Override p ublic void onPauseO o n Pause() {{ ®Override public ssuper.onPause(); uper . onPau se(); if (mediaPlayer != nuil n ull & & !pause) ¡pause) mediaPlayer. pause(); mediaPlayer.pause();
{
} } @Override p ublic void onResu me() {{ SOverride public onResume() super . onResume(); super.onResume(); if (mediaPlayer != nnuil u ll & & !pause ¡pause)) { mediaPlayer.start();
} } 17. 17. Los dos métodos anteriores se invocan cuando la actividad pasa a un segundo plano y cuando vuelve a primer plano. plano. Dado que queremos que el vídeo deje de reproducirse y continúe reproduciéndose en cada uno de estos casos, se invocan a los métodos pause {) () y start (), í) , respectivamente. respectivamente. No hay que confundir esta acción con la variable pause que lo que indica es que correspondiente. el usuario ha pulsado el botón correspondiente. 18. Añade el código: código: @Ove:n:·ide ©Override
pprotected rotected void v o id onSaveinstanceState(Bundle onSavelnstanceState(Bundle guardarEstado) super.onSaveinstanceState(guardarEstado) super.onSavelnstanceState(guardarEstado);; if (mediaPlayer !!== null) nuil) {{ iint nt pos = mediaPlayer.getCurrentPosition() mediaPlayer .getCu rrentPos ition();; gguardarEstado.putString("ruta", u a rdarEst ado.pu tStri n g (" ruta" , path); path) ; guardarEstado. putlnt ( •posicion H, pos); pos) ; guardarEstado.putlnt("posición",
{
} } ©Override protected void onRestoreinstanceState(Bundle onRestorelnstanceState(Bundle recEstado) sup er.on RestoreinstanceState (recEstado); super.onRestorelnstanceState(recEstado); iiff (recEstado != nuil) null) {{ path = recEstado.getString("ruta"); recEstado. getStrin g ( " rut.a" ) ; savePos = recEstado.getlnt("posición"); recEstado.getint("posicion" );
{{
} } 19. Cuando este sistema necesita memoria, memoria, puede decidir eliminar nuestra actividad. onsaveinstancestate para actividad. Antes de hacerlo llamará al método onSavelnstanceState darnos la oportunidad de guardar información nformación sensible. sensible. Si, Si, más adelante, adelante, el usuario vuelve a la aplicación, aplicación, esta se volverá a cargar, cargar, invocándose el método onRestorelnstanceState, onRestoreinstanceState, donde podremos restaurar el estado perdido. perdido. En nuestro caso la información a guardar son las variables path y
214
Multimedia y ciclo de vida de una actividad savePos savePos,, que representan reproduciendo. reproduciendo.
el
vídeo y
la posición que estamos
20. Ocurre el mismo proceso cuando el usuario cambia la posición del teléfono. Es decir, cuando el teléfono se voltea las actividades son destruidas y vueltas a crear, por lo que también se invocan estos métodos. 21 21.. Añade el código: private t ri n g s) {{ prívate void log(S logíStríng lXogTextView.appendis ogTe x tV i ew.append(s + "\n " ); "\n");
22. El último método es utilizado por varios escuchadores de eventos para mostrar información sobre lo que está pasando. Esta información puede visualizarse o no, utilizando el botón correspondiente. correspondiente.
6.6. 6.6. Introduciendo Introduciendo efectos efectos de de audio audio con con SoundPool SoundPool Hemos aprendido a utilizar la clase rvlediaPlayer MediaPlayer para reproducir audio y vídeo. En un primer ejercicio vamos a aprender como introducir efectos de audio en Asteroides. Como veremos esta clase no resulta adecuada para este uso. A SoundPool y se mostrará, mediante un continuación, se introducirá la clase soundPooJ. ejercicio, cómo esta sí que resulta eficiente para nuestro juego.
i gjP
Ejercicio paso a paso: Introduciendo efectos de audio con MediaPlayer en Asteroides. Asteroides. MediaP/ayer
1.
Abre el proyecto Asteroides.
2.
Copia los siguientes ficheros a la carpeta reslraw. res/raw. disparo.mp3 disparo.moS y explosion.mp3. explosión.mp3.
3.
Abre la clase vistaJuego y añade las siguientes variables: MediaPlayer mpDisparo, n1pExplosion; mpExplosion;
4.
En el constructor de la clase inicialízalas de la siguiente forma:
R.raw.disparo! mpDisparo == MediaPlayer.create(context, t-lediaPlayer.create(context, R . raw.disparo);; R.raw.explosión); mpExplosion = = MediaPlayer.createícontext, MediaPlayer . create(context, R.raw.explosion); 6. 5.
ActivaMis.il vistaJuego! Añade en el método Act ivar4:i.si1 () de Vist.aJuego: mpD i sparo.start ();; mpDisparo.start()
6.
Añade en el método dest.ruyeAst.eroide destruyeAsteroide () () de Vista,Juego: VistaJuego!
mpExplos ion .start(};; mpExplosion.start() 7.
Ejecuta el programa y verifica el resultado. ¿Qué pasa cuando disparamos o destruimos un asteroide? ¿El sonido se oye de forma inmediata?
215
El gran libro de Android La clase soundPooi soundPool maneja y reproduce de de forma forma rápida rápida recursos recursos de de audio audio en en las aplicaciones. aplicaciones. Un soundPool sound Pool es una colección de de pistas de de audio audio que que se se pueden pueden cargar cargar en en la la memoria desde un recurso (dentro de de la APK) APK) oo desde desde el el sistema sistema de de archivos. archivos. soundPool ~1ediaPlayer para para decodificar decodificar el el audio audio en en SoundPool utiliza el servicio de la clase MediaPlayer un formato crudo (PCM de16 bits), bits), lo que después después permite reproducirlo reproducirlo rápidamente rápidamente decodificarlas cada cada vez. vez. por el hardware sin tener que decodificarlas Los sonidos pueden repetirse en un bucle b ele una una cantidad establecida establecida de de veces, veces, definiendo el valor al reproducirlo, reproducirlo, o dejarse dejarse reproduciendo en en un un bucle bucle infinito infinito con con -caso, será necesario detenerlo con con el método método stop stop {). (). 1. 1. En este caso, La velocidad de reproducción también se puede puede cambiar. cambiar. El El rango rango de de velocidad velocidad 1.0 hace hace que que el el de reproducción reproducción va de 0.5 a 2.0. 2.0. Una Una tasa de de reproducción reproducción de de 1,0 original. Una Una tasa tasa de de reproducción reproducción de de 2.0 2.0 sonido se reproduzca en su frecuencia original. hace que el sonido se reproduzca al doble de de su su frecuencia frecuencia original, original, yy una una tasa tasa de de la mitad mitad de de su su frecuencia frecuencia original. original. reproducción de 0.5 hace que se reproduzca a la sound Pool hay que establecer establecer en en un un parámetro parámetro el el máximo máximo Cuando se crea un SoundPool de pistas que se pueden reproducir simultáneamente. simultáneamente. Este Este parámetro parámetro no no tiene tiene por por qué coincidir con el número de pistas pistas cargadas. cargadas. Cuando Cuando se se pone pone una una pista pista en en play (; ) hay que indicar indicar una una prioridad. prioridad. Esta Esta prioridad prioridad se se reproducción (método playO) número de de reproducciones reproducciones activas activas exceda exceda utiliza para decidir qué se hará cuando el número En este este caso, caso, se se detendrá detendrá el el flujo flujo con con el valor máximo establecido en el constructor. En la prioridad más baja, y en caso de que que haya varios, varios, se se detendrá detendrá el el más más antiguo. antiguo. En En caso de que el nuevo flujo sea el de de menor prioridad, prioridad , este este no no se se reproducirá. reproducirá.
Una lista de todos los métodos métodos de esta esta clase clase la la puedes puedes encontrar encontrar en: en: http://developer.android.com/reference/android/media/SoundPool.html http :1/developer. and ro id.com/reference/and raid/m edia/Sou nd Pool.htm 1
Ejercicio paso a paso; paso: Introduciendo efectos efectos de audio audio con con SoundPool en Asteroides. SoundPoo/ Asteroides. 1.
El proyecto Asteroides elimina todo el el código código introducido introducido aa partir partir del del punto punto 3, anterior. 3, del ejercicio anterior.
2.
vistaJuego y añade las las siguientes variables: variables: Abre la clase vistajuego
// lili /1// MULTIMEDIA lililí //!/// SoundPool soundPool; SoundPool soundPool ; int idDisparo idExplosion ; idDisparo,, idExplosion;
3.
En el constructor de la clase inicialízalas inicialízalas de de la la siguiente siguiente forma: forma: ssoundPool oundPoo l =~ nnew e w SoundPool SoundPool. ( 5, AnáioHanaíger. Aud.iol>1anager . STREAM,._MUSIC STRE.iiN....MUS.T.C ,, 0) 0);; iidDisparo d Disparo == soundPool.load{context, soundPool.load(co ntext, R.raw.disparo, 0); O); iidExplosion d Explosion == soundPool.load(context, soundPool.load(context, R.raw.explosión, R.raw.explosion, 0); O);
4.
216
En el constructor de SoundPool hay que que indicar indicar tres tres parámetros. parámetros. El El primero primero simultáneas. El El segundo segundo es es el el tipo tipo corresponde al máximo de reproducciones simultáneas.
Multimedia y ciclo de vida de una una actividad actividad de stream de audio (normalmente STREJI.l'·1_Musrc) stream musio).. El El tercero tercero es es la la calidad calidad de de reproducción, aunque actualmente no se implementa. implementa, 5. 6.
Cada una de las pistas ha ( ¡. ha de de ser ser cargada cargada mediante mediante el el método método load ioad(). Existen muchas sobrecargas para este método. método. La La versión versión utilizada utilizada en en el el ejemplo permite cargar las pistas pistas desde desde los los recursos. recursos. El El último último parámetro parámetro corresponde a la prioridad, aunque aunque actualmente actualmente no no tiene tiene ninguna ninguna utilidad. utilidad.
6.
Añade en el método l;ctivaMisil ActivaMisii () () de de VistaJuego: vis ta Juego: soundPool.play(idDisparo, l, 1, l, 1, 11,, O, 0, l); 1);
7.
El método play ( l permite playO permite reproducir reproducir una una pista. pista. Hay Hay que que indicarle indicarle elel identificador de pista; el volumen volumen para para el el canal canal izquierdo izquierdo yy derecho derecho (0.0 (0.0 aa 1.0); La prioridad; El número de repeticiones =siempre, O=solo repeticiones (-1 (-1=siempre, 0=solo una una vez, vez, ...)) y el el ratio ratio de de reproducción, reproducción, con con el el que que podremos podremos 11=repetir =repetir una vez, ... (1.0 reproducción normal, normal, rango: rango: 0.5 0.5 aa 2.0). 2.0). modificar la velocidad o pitch (1 .0 reproducción
8.
Añade en el método destruyeAsteroide destruyeAsteroide (()i de de VistaJuego: VistaJuego; soundPool.play(idExplosion, 1, 1, l, 1, O, 0, O, 0, 1); 1) ;
9.
Los parámetros utilizados para para la la explosión explosión son son similares, similares, solo solo hemos hemos menor. La La consecuencia consecuencia será será que que sisi ya ya hay hay un un introducido una prioridad menor. constructor) reproduciéndose reproduciéndose yy se se pide pide lala total de 5 pistas (ver constructor) reproducción de un nuevo disparo, el el sistema sistema detendrá detendrá la la reproducción reproducción de de la explosión más antigua, por por tener tener esta esta menos menos prioridad. prioridad.
el resultado. resultado. ¿Qué ¿Qué pasa pasa cuando cuando disparamos disparamos 10. Ejecuta el programa y verifica el ¿El sonido sonido se se oye oye de de forma forma inmediata? inmediata? o destruimos un asteroide? ¿El los parámetros parámetros del del método método play playO verifica elel ( l yy verifica 11. Modifica algunos de los resultado.
6.7. 6. 7. Grabación de audio APIs de Android disponen de de facilidades facilidades para para capturar capturar audio audio yy video, video, Las APis diferentes formatos. formatos. La La clase clase MediaReco:nier MediaRecorder te te permitiendo su codificación en diferentes esta funcionalidad funcionalidad en en tu tu aplicación. aplicación. permitirá de forma sencilla integrar esta disponen de de micrófono micrófono para para capturar capturar audio, audio, sin sin La mayoría de los dispositivos disponen sido integrada integrada en en el el emulador. emulador. Por Por lolo tanto, tanto, deberás deberás embargo esta facilidad no ha sido probar los ejercicios de este apartado apartado en en un un dispositivo dispositivo real. real. MediaRecorder dispone de de una una serie serie de de métodos métodos que que podrás podrás utilizar utilizar La clase t
constantes como como DEFuur, defult, CAI'•ICORDER, camcorder, También se pueden utilizar otras constantes VOICE .._CALL, VOICE_COMUNICATION, . .. VOICE__CALL, VOICE_COMlXNI CATION,...
setoutputFile (String (string fichero) fichero) -Nombre - Nombre del del fichero fichero de de salida. salida.
217
El gran libro de Android setOutputFormat (in (intt output output ___torma) forma) -- Formato del del fichero fichero de de salida. salida. Se Se pueden utilizar constantes de la clase MediaRecorder-. MediaRecorder. outputFormat: outputFormat; DEFl•ULT, DSFAÜLT, .1\.MRNB, AMR KB, liJ
setAudioCharmels setAudioChanneia (int (iat
((int int bbitRate) i tRate) (desde nivel nivel de de API API 8) 8) — Especificamos los bits por segundo (bps) a utilizar en en la codificación.
setAudioEncodingBitRate
setAudioSamplingRar.e {int setAudioSamplingRate (int samplj.ngRate) samplingRate) (desde nivel nivel de de API API 8) 8) — Especificamos el número de muestras por segundo segundo aa utilizar utilizar en en la la codificación. codificación. (int max_duration_ms) maxdurationms) (desde nivel nivel de de API API 3) 3) — Indicamos una duración máxima para la grabación. Tras Tras este tiempo tiempo se se detendrá.
set.MaxDurat:ion setMaxDuracion
setMaxFileSize (long max __filesize. filesize ___bytes) bytes) (desde (desde nivel nivel de de API API 3) 3) -Indicamos un tamaño máximo para el fichero. Al alcanzar alcanzar el el tamaño tamaño se se detendrá. prepar·e prepare () -- Prepara la grabación para la captura de de datos. datos. Antes de de llamarlo llamarlo
hay que configurar la grabación y después ya podemos podemos invocar invocar al al método start (l. o. st.art start () -- Comienza la captura. stop (()l -- Finaliza la captura. stop re se t. (Ol -- Reinicia el objeto como si lo reset lo acabáramos de de crear. Hay Hay que que volver volver a configurarlo. configurarlo. release () reiease O -- Libera todos los recursos utilizados utilizados de de forma forma inmediata. inmediata. Si Si no no llamas al método se liberarán cuando el objeto sea destruido. destruido. ~1ediaRecorder también dispone de métodos La clase MediaRecorder métodos que que podrás podrás utilizar utilizar para para configurar la grabación de video. Más información en: http://developer.android.com/ http://deveioper.android.com/ reference/android/media/MediaRecorder. reference/android/med ia/Med iaRecorder. html. htm I.
La siguiente tabla muestra los diferentes métodos que pueden pueden ser ser ejecutados ejecutados en en cada estado y el estado que alcanzaremos tras la llamada:
Estado salida lnitial Initial
218
l nitial
1Initiahzed nitia lized Reset
Estado Estaco entrada DataSource Prepared Confieured
Kecording
Lrror
Multimedia y ciclo de vida de una actividad
Estado salida lnitialized
lnitial initial
lnitialized Initialized
setAudioSource set setVVideoSource ideoSource
setAudioSource set V ideoSource setV ideoSource
DataSource OataSource Configured Confígured
setüutputF orrnat setOutputFormat
Prepared Prepared
Estado entrada DataSource Prepared on fi1gured eConfígured
prepare start releas llamada incorrecta Incorrecta
Error
Error
setAudioEncoder setAudioEncoder setVideoEncoder set VideoEncoder setüutputFile setOutputFile setVideoSize set ideoFrameRate setVVideoF rameRate setPreviewDisplay
Recording Recording Released
Recording
llamada incorrecta
llamada incorrecta
llamada incorrecta
llamada incorrecta
Tabla 3: Transiciones entre estados de la la clase MediaRecorder MediaRecorder
de audio audio utilizando utilizando Ejercicio paso a paso: Grabación de MediaRecorder. MediaRecorder. 1.
nombre Grabadora. Grabadora. El El nombre nombre del del paquete paquete ha ha Crea un nuevo proyecto con nombre de ser oorg rg.. example. exatnple. grabadora.
2.
código: Remplaza el Layout main.xml por el siguiente código: utf-S'"?> 0" encod encoding= /> ndro id :on Click=" detenerGrabacion "/> 1
11
11
219
El gran libro de Android androicl: layout_widt~h= "h'ra_p_co.nten t:: " android:1ayout_width="wrap_conteat"
and roid: layou t _heigh t = "vn-ap_con t:ent:" android: 1 ayout_height "wrap_cont:ent" and roid: text = "Rf..-:~p roduc.ir l' android:text="Reproducir" android: onCl ick = "re¡)r oduc.i :r "/ > android:onClick="reproducir"/>
3.
por: Remplaza el el código de la actividad por: pub lic class cl.ass GrabadoraActivity extends Activity {{ public príva t e static final String LGGJTAG LOG_'J'ilG = "Grabadora"; "Grabadora" ; prívate prívate MediaRecorder mediaRecorder; mediaRecorder ; prívate MediaPlayer mediaPlayer; med iaPlayer ; prívat prívatee static String Licher-o fichero = Environment. getExtern alStorageDi rectory () . getAbsolutePath ()+ " /audio . 3gp" ; getExternalStorageDirectory {).getAbsolutePath()+"/audio.3gp"; voi d onCreate(Bundle savedlnstanceState) savedi nstanceState) { :!<:Override pub l ic void SOverride public super . onCreate(savedinsta nceState);; super.onCreate(savedlnstanceState) setContentView (R. layout. main ) ; setContentView(R.layout.main);
} public void ggrabar(View r a bar (Vie w view) { mediaRecorder = new MediaRecorder(); Media Rec o r der (); media Recorde r . setOutputFi le( fichero ); mediaRecorder.setOutputFile(fichero); media Recorder. setAudioSou rce(MediaRecorder .AudioSource .N.TC); mediaRecorder.setAudioSource(MediaRecorder.AudioSource. MIC); mediaRecorder. setOutputFo rmat(MediaRecorder. mediaRecorder.setOutputFormat(MediaRecorder. Ou tputFormat . 'T'JiREE_G.PP) ; OutputFormat.THREE_GPP) mediaRecorder. set AudioEnc oder(MediaRecorder.AudioEnc oder . mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder. l>J.1R_ NB ); AMR_NB);
try {{ mediaRecorder .prepare ( ); mediaRecorder.prepare(); cat ch (IOException } catch (lOException e) {{ Log. ( LOG.... TAG, "Fallo en grabación"); grabación''); Log. ee(LOG__TAG,
} mediaRecorder .start(); mediaRecorder.start();
} public void detenerGrabacion(View view) view) {{ medi aRecorder .stop(); mediaRecorder.stop(); mediaRecorder.rele ase (); mediaRecorder.release();
} publ :i e void rep roduci r (Vi ew vview) i e w) {{ public reproducir(View mediaPlayer = new ne w MediaPlayer(); Me diaPl a y e r(); try {{ mediaPlayer . setDataSou rce ( f.ichero ) ; mediaPlayer.setDataSource(fichero); mediaPlayer .prep are () ; mediaPlayer.prepare(); mediaPlayer . star t (); mediaPlayer.start(); (IOExcept i on e) e ) {{ } catch (lOException
220
Multimedia yy ciclo de de vida de de una una actividad actividad Log.e( LOG___)'AG Log.e(LOG TAG,,
"Fallo en reproducción"); reproducción"); "Fallo
} } } 4.
declarando dos dos objetos objetos de de las las clases clases MediaRecorder !'
5.
continuación, se se han han introducido introducido tres tres métodos métodos que que serán serán ejecutados ejecutados alal A continuación, pulsar los botones botones de de nuestro nuestro Layout. Layout El El significado significado de de cada cada uno uno de de los los métodos que se invocan invocan acaba acaba de de ser ser explicado. explicado.
6.
en la la pestaña pestaña Permissions Permissions pulsa pulsa elel botón botón AndroidManifestxml yy en Abre AndroidManifest.xml Add ... selecciona selecciona Uses Uses Permissions. Permissions. En En el el desplegable desplegable Ñame Name indica indica Add... RECORD AUDIO. Repite este este proceso proceso para para usar usar elel permiso permiso recordaud io. Repite í-l"RITE STOP~Z\.GE. WRITE ....EXTERNAL ....STORAGE.
7.
de nuevo nuevo la la aplicación aplicación yy verifica verifica el el resultado. resultado. Ejecuta de
Ejercicio paso aa paso: paso: Grabación Grabación de de audio audio utilizando utilizando MedíaRecorder (I/) MediaRecorder (II) La aplicación anterior anterior resulta resulta algo algo confusa confusa de de utilizar. utilizar. Sería Sería más más sencillo sencillo sisi los los pudiéramos utilizar utilizar en en un un determinado determinado momento momento estuvieran estuvieran botones que no pudiéramos deshabilitados. Para conseguirlo vamos utilizar método deshabilitados. Para conseguirlo vamos aa utilizar elel método setEnabled(boolean). Veamos Veamos cómo cómo conseguirlo: conseguirlo: Button. setEnabled(boolean). 1.
Declara las siguientes siguientes tres tres variables variables al al principio principio de de lalaActividad: Actividad: private Button Button bGrabar, bGrabar , bDetener, bDetener, bReproducir; bReproducir; prívate
2.
En el método método onCreateO onCreate () inicializa inicializa estos estos tres tres botones. botones. Además, Además, comenzaremos deshabilitando deshabilitando dos dos de de los los botones botones que, que, al al principio principio de de lala aplicación, deben ser ser pulsados: pulsados: aplicación, no deben bGrabar == (Button) (Button) findViewByld(R.id.bGrabar); findViewByid(R. id.l:)Grabar); bDetener = (Button) findViewByld(R.id.-bDetener); findViewByid(R.id. bDe t: ener); bReproducir = (Button) (Button) findViewByld(R.id. findViewByid(R.id .bReproducir); bRepr·oduc.ir); bDetener.setEnabled( false);; bDetener.setEnabled(false) bReproducir.setEnabled(false); bReproducir.setEnabled(false) ;
3.
el método grabar grabar {) () añade añade el el siguiente siguiente código: código: En el bGrabar .setEnabled( false ); bGrabar.setEnabled(false); bDetener bDetener..setEnabled(true setEnabled (tr-ue));;
221
El gran libro de Android bbReproducir.setEnabled(false); Reproduc i r .setEnabled( false } ;
4.. 4
código: En el método detenerGrabacion (()i añade el siguiente código: bGr abar. setEnabled( true }; bGrabar.setEnabled(true); bbDetener.setEnabled(false); Deten er .setEnabled( false }; bReprod ucir .se tEnabled( tnle } ; bReproducir.setEnabled(true);
5.
222
resultado. Ejecuta de nuevo la aplicación y verifica el resultado.
CAPÍTULO 7. CAPÍTULO 7.
Seguridad y posicionamiento posicionamiento Seguridad y
En este capítulo abordaremos abordaremos dos dos de de los los aspectos aspectos más más novedosos novedosos de de Android: Android: lala seguridad y el API de posicionamiento. posicionamiento. El capítulo comienza estudiando estudiando los los fundamentos fundamentos del del sistema sistema de de seguridad seguridad que que incorpora Android. Android. Sin embargo, embargo, aa pesar pesar de de lo lo que que cabría cabría esperar, esperar, elel sistema sistema de de las aplicaciones aplicaciones puedan puedan realizar realizar cualquier cualquier tipo tipo de de seguridad no va a impedir impedir que que las acción con el dispositivo. dispositivo. En la segunda parte del del capítulo, capítulo, se se describe describe el el API API que que incorpora incorporaAndroid Android para para posición geográfica geográfica del del dispositivo. dispositivo. Estos Estos servicios servicios se se basan basan permitir conocer la posición GPS, pero pero también también disponemos disponemos de de novedosos novedosos servicios servicios de de principalmente en el GPS, redes Wi-Fi. Wi-Fi. AA lolo largo largo de de este este capítulo capítulo localización basados basados en en telefonía telefonía móvil móvil yy redes mostraremos una serie de de ejemplos ejemplos que que te te permitirán permitirán aprender aprender aa utilizar utilizar estas estas funciones. funciones. capítulo describiendo describiendo cómo cómo podemos podemos incorporar incorporar aa nuestra nuestra Terminaremos el capítulo aplicación servicios realizados realizados por por terceros. terceros. En En concreto concreto instalaremos instalaremos una unavista vistaque que mapa de de Google Google Maps. Maps. permite representar un mapa
~Objetivos: Objetivos:
• • • •
•
Mostrar los pilares de de la la seguridad seguridad en en Android. Android. Describir cómo Android crea un un usuario usuario Linux Linux asociado asociado aa cada cada aplicación. aplicación. esquema de de permisos permisos en en Android Android yy enumerar enumerar los los permisos permisos Describir el esquema más importantes. importantes. Mostrar cómo los permisos permisos de de Android Android pueden pueden ser ser ampliados ampliados con con usuario yy enumerar enumerar los los pasos pasos aa seguir seguirpara paracrear crear permisos definidos por por el usuario un nuevo permiso. permiso. los diferentes diferentestipos tipos Describir (as las APIs APis de de Android para para la la geolocalización geolocalización yy los de sistemas de posicionamiento posicionamiento disponibles. disponibles.
223
El gran libro de Android Android • Ver lo sencillo que que resulta resulta incorporar incorporar en en nuestra nuestra aplicación aplicación un un servicio serviciode de un tercero. En concreto, concreto, Google Google Maps. Maps. •
Seguir mejorando Asteroides Asteroides al al introducir introducir elel problema problema del del acceso acceso concurrente a datos yy cómo cómo se se puede puede solucionar solucionar elel Java Java mediante mediante lala palabra reservada synchronized. synchronized.
7.1. 7 .1. Los Los tres tres pilares pilares de de la la seguridad seguridad en en Android Android
_......
.,-,,.JII'~
lili
111 WzZ
Poli[MedÍa]: Seguridad Poli[Media]: Seguridad en en Android. Android.
La seguridad es un un aspecto aspecto clave clave de de todo todo sistema. sistema. Si Si nos nos descargáramos descargáramos una una maliciosa de de Internet Internet oo del del Play Play Store, Store, esta esta podría podría leer leer nuestra nuestra lista lista de de aplicación maliciosa contactos, averiguar nuestra nuestra posición posición GPS, GPS, mandar mandar toda toda esta esta información información por por contactos, averiguar Internet y terminar enviando enviando 50 50 mensajes mensajes SMS. SMS. En algunas plataformas, plataformas, como como Windows Windows Mobile, Mobile, estamos estamos prácticamente prácticamente tanto, los los usuarios usuarios han han de de ser ser desprotegidos ante ante este este tipo tipo de de aplicaciones. aplicaciones. Por Por lolo tanto, muy cautos antes de de instalar instalar una una aplicación. aplicación. En otras plataformas, plataformas, como como en en /OS iOS del del iPhone, iPhone, toda toda aplicación aplicación ha ha de de ser ser validada por Apple antes antes de de poder poder ser ser instalada instalada en en un un teléfono. teléfono. Esto Esto limita limita aa los los pequeños programadores programadores yy da da un un poder poder excesivo excesivo aa Apple. Apple. Se Se trata trata de de un un planteamiento totalmente totalmente contrario contrario al al software software libre. libre. esquema de de seguridad seguridad que que protege protege aa los los usuarios, usuarios, sin sin lala Android propone un esquema necesidad de imponer imponer un un sistema sistema centralizado centralizado yy controlado controlado por por una una única única empresa. empresa. La seguridad en Android se se fundamenta fundamenta en en los los siguientes siguientes tres tres pilares: pilares: primer capítulo capítulo Android Android está está basado basado en en Linux, Linux, por por lolo Como se comentó en el primer tanto, poder aprovechar aprovechar la la seguridad seguridad que que incorpora incorpora este esteSO. SO.De Deesta estaforma forma tanto, vamos a poder Android puede impedir impedir que que las las aplicaciones aplicaciones tengan tengan acceso acceso directo directo alal hardware hardware oo interfieran con recursos recursos de de otras otras aplicaciones. aplicaciones. con un un certificado certificado digital digital que que identifique identifique aa su su Toda aplicación ha ha de de ser ser firmada firmada con autor. La firma digital autor. digital también también nos nos garantiza garantiza que que elel fichero fichero de de lala aplicación aplicación no no ha ha sido modificado. modificado. Si Si se se desea desea modificar modificar la la aplicación aplicación está está tendrá tendrá que que ser serfirmada firmada de de nuevo, hacerlo el el propietario propietario de de la la clave clave privada. privada. No Noes es preciso preciso(ni (ni nuevo, y esto solo podrá hacerlo certificado digital digital sea sea firmado firmado por por una unaautoridad autoridad de decertificación. certificación. frecuente) que el certificado Si queremos que una una aplicación aplicación tenga tenga acceso acceso aa partes partes del del sistema sistema que que pueden pueden hemos de de utilizar utilizar un un modelo modelo de de permisos, permisos,de de comprometer la seguridad del del sistema sistema hemos antes de de instalar instalar lala aplicación. aplicación. forma que el usuario usuario conozca conozca los los riesgos riesgos antes más detalle detalle elel primer primeryytercer tercerpunto. punto. En los siguientes apartados apartados se se describe describe con con más El proceso de firmar una una aplicación aplicación será será descrito descrito en en el el último últimocapítulo. capítulo.
224
Seguridad yy posicionamíento posicionamiento
7 .1.1. Usuario Usuario Linux Linux yy acceso acceso aa ficheros ficheros 7.1.1. Para proteger el acceso aa recursos recursos utilizados utilizados por por otras otras aplicaciones, aplicaciones, Android Android crea crea ID) nueva nueva por por cada cada paquete paquete ((.apk) instaladoen enelel una cuenta de usuario usuario Linux Linux (user (user ID) apk) instalado sistema. usuario es es creado creado cuando cuando se se instala instala lala aplicación aplicación yy permanece permanece sistema. Este usuario constante durante toda toda su su vida vida en en el el dispositivo. dispositivo.
Cualquier dato almacenado almacenado por por la la aplicación aplicación será será asignado asignado aa su su usuario usuario Linux, Linux, por lo que normalmente normalmente no no tendrán tendrán acceso acceso otras otras aplicaciones. aplicaciones. No No obstante, obstante,cuando cuando crees un fichero puedes puedes usar usar los los modos modos modeworldreadable MODE_ WORLD_READABLE y/o y/o MODE_WORLD_WRITEABI..E para para permitir permitir que que otras otras aplicaciones aplicaciones puedan puedan leer leer oo escribir escribir mode_world_writeable en el fichero. fichero. Aunque otras otras aplicaciones aplicaciones puedan puedan escribir escribir el el fichero, fichero, elel propietario propietario siempre será el usuario usuario asignado asignado aa la la aplicación aplicación que que lo lo creó. creó. Dado que las restricciones restricciones de de seguridad seguridad se se garantizan garantizan aa nivel nivel de de proceso, proceso, elel código de dos paquetes paquetes no no puede, puede, normalmente, normalmente, ejecutarse ejecutarse en en elel mismo mismo proceso. proceso. Para ello sería necesario necesario usar usar el el mismo mismo usuario. usuario. Puedes Puedes utilizar utilizar elel atributo atributo shareduserid Android!"'anifest .xml . x ml para para asignar asignar un un mismo mismo usuario usuario Linux Linux aa sharedüserid en AndroitiManifest dos aplicaciones. Con esto esto conseguimos conseguimos que que aa efectos efectos de de seguridad seguridad ambas ambas aplicaciones sean tratadas tratadas como como una una sola. sola. Por Por razones razones de de seguridad, seguridad, ambas ambas aplicaciones han de de estar estar firmadas firmadas con con el el mismo mismo certificado certificado digital. digital.
7 .1.2. El esquema esquema de de permisos permisos en en Android Android 7.1.2. Para proteger ciertos ciertos recursos recursos yy características características especiales especiales del del hardware, hardware, Android Android define un esquema de permisos. permisos. Toda Toda aplicación aplicación que que acceda acceda aa estos estos recursos recursos está está obligada a declarar su intención intención de de usarlos. usarlos. En En caso caso de de que que una una aplicación aplicación intente intente acceder a un recurso del del que que no no ha ha solicitado solicitado permiso, permiso, se se generará generará una una excepción excepción de permiso y la aplicación aplicación será será interrumpida interrumpida inmediatamente. inmediatamente. Cuando el usuario usuario instala instala una una aplicación, aplicación, este este podrá podrá examinar examinar lala lista lista de de permisos que que solicita la la aplicación aplicación yy decidir decidir sisi considera considera oportuno oportuno instalar instalar dicha dicha aplicación. Veamos la de algunos algunos permisos. permisos. Abajo Abajo se se describe describe elel aplicación. la descripción descripción de esquema utilizado: utilizado:
A
NOMBRE_DEL_PERMISO Grupo al a! que que pertenece perteneceDescripción del del permiso permiso nombre_del_permiso A Grupo - Descripción que se mostrará a! al usuario usuario antes antes de de instalar instalar la la aplicación aplicación (grado (grado de de relevancia relevancia de seguridad ajuicio a juicio del del autor). autor). Descripción Descripción yy comentarios. comentarios.
A continuación, continuación, se muestra muestra una una lista lista con con algunos algunos de de los los permisos permisos más más utilizados utilizados en Android: Android: ••
A
CALL_ PHONE A Servicios Servicios por por los los que que tienes tienes que que pagar pagar -- Llamar Llamar call_phone directamente a números de de teléfono teléfono (muy (muy alta). alta). Permite Permite realizar realizar llamadas llamadas sin sin la intervención del usuario. usuario.
A Servicios ~ Envío • SEND_SMS SEND_SMsA servlcíos por por ios !os que que tienes tienes que que pagar pagarEnvío SMS/MMS SMS/MMS (muy alta). alta). Permite aa la la aplicación aplicación mandar mandar SMS. SMS.
225
El gran libro de Android •
WRITE_EXTERNAL_STORAGE A» A Almacenamiento -- Modificar/borrar Modifíc::u!borrar archivos write_external_storage en SD (alta). Permite la lectura de archivos y su modificación, modificación, típico en aplicaciones de backup. backup.
•
READ__OWNEH__DATA Aiy Tu lnform.ación read_owner_.data información personal -- Leer datos de contacto (alta). (alta). Permite leer información sobre scbre el propietario del teléfono (nombre, (nombre, correo electrónico, número de teléfono). teléfono). Muy peligroso, peligroso, algunas aplicaciones lícita. podrían utilizar esta información de forma no licita.
•
READ__calendar CALENDAR A ru -· Leer datos de calendario read Tu información personal (moderada). Solo otorga este permiso si consideras que la aplicación realmente lo necesita. necesita.
•
write_calendar A A ru Tu información personal ····· Escribir datos de calendario WRITE_CALENDAR calendano (moderada). Este permiso no permite leer el calendario. (moderada). calendario. Por las mismas razones que el anterior permiso, permiso, hay que plantearse si una aplicación nos pide este permiso con sentido o no. no.
•
READ_PHONE_STATE de! teléfono e read_phone_state A Llamadas de teléfono - Leer estado del identidad (alta). Muchas aplicaciones piden este permiso para ponerse en pausa mientras hablas por teléfono. teléfono Sin embargo, embargo, también permite el acceso al IMEI, IMSI y al identificador único de 64 bits que Google asigna a cada terminal. En las primeras versiones de SDK este permiso no era necesario.
•
ACCESS_.FINE_LOCATION Atu A ru ubicación ubicac ión - Precisar PrecH:>ar !a access_fine_location la ubicación (GPS) (moderada). Localización basada en GPS. GPS.
•
ACCESS_COARSE_LOCATION - Ubicacíón (l;asac!a en access_coarse_l.ocation ,j\ A Tu ubicación Ubicación común (basada red) (moderada). (moderada). Localización basada en telefonía móvil (Cei-ID) (Cel-ID) y Wi-Fi.
•
BLUETOOTH A A Comunicación de red ~- Crear conexión Biuetooth Bluetooth (baja). (baja). bluetooth Conexión con otro dispositivo Bluetooth. 8/uetooth.
•
INTERNET ····· Acceso /\cceso íntegro a Internet internet A Comunicación de md red internet (muy alta). Permite establecer conexiones a través de Internet. Este es posiblemente el permiso más importante, importante, en el que hay que fijarse más a quién se le otorga. Muchas aplicaciones lo piden, piden, pero no todas lo necesitan. Cualquier malware necesita una conexión para poder enviar datos de nuestro dispositivo. dispositivo.
A
A
»
A
A Comunícación Comunicación de red - Ver estado de conexión, • access_wifi_state ACCESS_INIFLSTATEA red···· conex:ón. ver estado de Wi+i Wi-Fi (baja). (baja). Información sobre redes WiFi disponibles. A Herramientas del sistema -- lmpedír impedir que ei ele •• A el teléfono entre en modo rnodo de suspensión (baja). (baja). Algunas aplicaciones aplicacones pueden necesitar este permiso, permiso, y realmente a lo único que puede afectar es a nuestra batería, batería.
•
226
A
CHANGE_CONFiGURATiON A Herramientas del sistema CHANGE_CONFIGURATION - Modificar la conflguracíón global de! configuración globaí del sistema (moderada). Permite cambiar la configuración (como loca/e). común, así por lócale). Pese a que es importante en sí, es muy común, ejemplo los widgets lo necesitan. necesitan.
Seguridad y posicionamiento
A
••
READ_SYNC_SETIINGS read_sync_settings A Herramientas
de! del sistema sistema -- Leer Leer ajustes ajustes de de sincronización (baja). Tan solo permite permite saber saber si si tienes tienes sincronización sincronización en en segundo plano con alguna aplicación aplicación (como (como con con un un cliente cliente de de Twitter Twitter oo Gmail).
••
WRITE_APN_SETTINGS Herramientas del WRiTE_APN_SETTiNGS«b del sistema sistema -- Escribir Escribir configuración configuración d<~l del Punto de Acceso (moderada). Permite Permite la la configuración configuración del del punto punto de de acceso a la red. Por ejemplo, encender encender yy apagar apagar tu tu conexión conexión de de red red oo Wi-Fi. Wi-Fi.
••
MANAGE_APP _TOKENS » Herramientas del manage_app_tokens del sistema sistema -- Recuperar Recuperar aplicaciones aplicaciones en ejecución e;ecución {moderada). (moderada). Permite Permite saber qué qué aplicaciones aplicaciones están están corriendo corriendo en en segundo plano y cambiar el orden.
••
SET_PREFERRED_APPLICATIONS set_preferred_appücations A Herramientas Herramientas del del sistema sistema -- Establecer Establecer aplicac;ones aplicaciones preferidas (moderada). (moderada). Permite Permite la la asignación asignación aa una una aplicación aplicación para que haga determinada tarea. Por Por ejemplo, ejemplo, la la reproducción reproducción de de vídeos. vídeos.
••
VIBRATEÁControles víbrate A Controles de hardwarehardware - Control Control de de la la vibración vibración (baja). (baja). Permite Permite hacer vibrar al teléfono. Los juegos lo lo suelen suelen utilizar utilizar bastante. bastante.
••
camera A Controles de hardware hardware -~ Realizar Realizar fotografías fotografías (baja). (baja). Permite Permite CAMERA acceso al control de la cámara yy aa la la toma toma de de imágenes. imágenes.
••
RECORD_AUDIO record_audio A Controles Controles
A
A
A
A
A
de de hardware hardware - Grabar Grabar audio audio (moderada). (moderada). Permite grabar sonido desde el el micrófono micrófono del del teléfono. teléfono.
Para solicitar un determinado permiso permiso en en tu tu aplicación, aplicación, no no tienes tienes más más que que incluir incluir Una etiqueta xml de en en el el fichero fichero l~ndroidManifest. AndroidManifest.xml de tU tu aplicación. En el siguiente ejemplo ejemplo se se solicitan solicitan dos dos permisos: permisos:
>
>
android:name="android.permissioa.RECEIVE_SMS"/> >
7 .1.3. Permisos definidos por 7.1.3. por el el usuario usuario en en Android Android por el el sistema, sistema, los los desarrolladores desarrolladores vamos vamos aa Además de los permisos definidos por para restringir restringir el el acceso acceso aa elementos elementos de de nuestro nuestro poder crear nuevos permisos para software.
ü Poli [Media]:|: Permisos definidos Pol¡|Med¡a def nidos por por el el usuario usuario en en Android. Android.
227
El gran libro de Android Android Abordaremos el estudio estudio de de la la creación creación de de nuevos nuevos permisos permisos utilizando utilizando elel Somos la la empresa empresa PayPerView PayPerView especializada especializada en en ofrecer ofrecer siguiente ejemplo. Somos servicios de reproducción reproducción de de vídeos vídeos bajo bajo demanda. demanda. Queremos Queremos crear crear un un software software que permita a cualquier cualquier desarrollador desarrollador reproducir reproducir nuestros nuestros vídeos vídeos desde desde sus sus aplicaciones. No No obstante, este este servicio servicio no no es es gratuito, gratuito, por porloloque quenos nosinteresa interesaque que el usuario sea advertido advertido cuando cuando se se instale instale lala aplicación aplicación del deltercero, tercero, indicándole indicándoleque que esta aplicación va hacer hacer uso uso de de un un servicio servicio no no gratuito. gratuito. Para definir el nuevo nuevo permiso permiso utilizaremos utilizaremos elel tag tag en en elel fichero fichero Andro idNanifest..xmi xml de AndroídManifest de nuestro nuestro software. software. AA continuación, continuación, se semuestra muestraun unejemplo: ejemplo: craanifest package="com.payperview.servicios"» 11
11
,!
> android:proteotionLevel-"danaerous"
El atributo android: name indica android;ñame indica el el nombre nombre del del permiso. permiso. Como Como ves ves ha hade deestar estar dentro del mismo mismo dominio dominio que que nuestra nuestra aplicación. aplicación. ElEl atributo atributo android:permissionGroup android: permiss ionGroup es es opcional opcional yy permite permite incluir incluir nuestro nuestro permiso permiso en en un un grupo. po de grupo. En el ejemplo se se ha ha incluido incluido en en elel gr grupo de permisos permisosque quepueden puedensuponer suponerun un coste económico al usuario. usuario. El El atributo atributo androi.d:protectionLevel androidrprotectionLevel informa informa alal sistema cómo el usuario ha ha de de ser ser informado informado yy qué qué aplicaciones aplicaciones tienen tienen acceso accesoalal permiso. Los valores posibles posibles se se indican indican aa continuación: continuación: nnormal o rmal
El El usuario usuario no no es es advertido advertido de de que que se se va va utilizar utilizar elel permiso. permiso.
dangerous
El El usuario usuario es es advertido. advertido.
signature signatura
Solo Solo aplicaciones aplicaciones firmadas firmadascon con lalamisma mismafirma firmadigital digitalque que la aplicación que declara declara el el permiso. permiso.
signatureOrSystem
Igual Igual que que signature signatura pero pero además además puede puede ser serusado usadopor por el sistema. Caso Caso poco poco frecuente, frecuente, donde donde varios varios fabricantes necesitan compartir compartir características características aa través través de la imagen del sistema. sistema.
nciroid::iabei label yy android: descript ion son Los atributos aandroid android idescription son opcionales opcionalesyyhan hande de través de de un un recurso recurso de de cadena. cadena. En En estas estas cadenas cadenas hay hay que que ser introducidos a través de forma forma abreviada abreviada yy extensa, extensa, respectivamente. respectivamente. Veamos Veamoscómo cómo describir el permiso de podría ser en el ejemplo: reproducc i ón de videos bajo reproducci6n de videos bajo demanda demandac/string> Permi te aa la
android:permission= android:permission= servícios VER_ VIDEOS "">> "com. paypei-vi ew.servi cios.. VER_VIDEOS cintent-f ilter> < ac t iion on andró i d:nams="andróid.intent.action. MAIN" /> "andToid . inte.nt. c.·ategol:y~ . LAfJlVCHER "í > "co1n . ~payperview.
11
4.
Copia detrás de de la la etiqueta etiqueta ... /> del del ejemplo anterior. anterior.
5. 5.
Recuerda definir etiqueta yy descripción. descripcion. definir los los recursos recursos de de cadena cadena etiqueta
6.
Ejecuta el proyecto. proyecto. Es Es imprescindible imprescindible para para registrar registrar en en elel teléfono teléfono elel nuevo permiso yy la la nueva nueva actividad actividad que que queremos queremos lanzar lanzar desde desde otras otras aplicaciones. aplicaciones.
7. 7.
Para usar este este servicio servicio crea crea un un nuevo nuevo proyecto proyecto con con los los siguientes siguientesdatos: datos: Project ñame: name: UsarPermiso UsarPermiso Build Target: Androidl.5 Androidl.5 ¡,pplication name: UsarPermiso UsarPermiso Application ñame:
229
El gran libro de Android Package ñame: name: ora.example.usarpermiso org.exampJ.e.usarpermiso Create Activity: Ac tivity: UsarPermiso IJsarPermiso Min SDK Versión: Version: 3
8.
Abre el fichero main.xmi main . xml e inserta el siguiente botón dentro del : >: andró i d:iid= d="0+i d/Bu 11on01"/>
9.
Abre el fichero usarPermisos. java y añade el siguiente código al final de {):: la función onCreate ()
Button = B u tton b b =
(Button)findViewByldÍR.id.ButtonOl); (Button) findViewByid(R.id.BuUon01);
b.setOnClickListener(new OnClickListener(}{{ b.setOnCli ckListener( new OnClickListener() @Override ©Overrida
public void onClick(View view){ v iew ){ Intent ii == new IntentO Intent();; i. setClassNarne ( "com.paypervie\AJ. servicios 11 i.setClassName("com,payperview.servicios", 11 corn.payperview.servicios.VerVideo 11 ) ; "com.payperview.servicios,VerVideo"); 1
startActivity(i); startActivlty í i )• ;
})
;
.~t
<;,-:;::;;. Nota sobre Java: En este código no se han incluido los import, impon, pulsa CtrlShift-0 Shift-O para añadirlos de forma automática. 10. Ejecuta la aplicación. aplicación. Cuando pulses el botón la aplicación provocará un error. error.
permiso. 11. Visualiza la ventana LogCat para verificar que se trata de un error de permiso. 12. Para solucionar el problema tendrás que incluir el siguiente código al final del fichero AndroidManifest.xmi; Jl.ndroid.Manitest .xn'll: ...>
android:name="com . payperview.servicios.VER.VIDEOS"Í android:name="com.payperview.servicios.VER VIDEOS"/>> <ímanite st >
13. Comprueba cómo ahora se accede al servicio sin problemas. problemas.
Preguntas de repaso y reflexión: Los permisos en Androíd. Android.
230
Seguridad yy posicionamiento posicionamiento 7.2. Localización 7 .2. Localización La plataforma Android Android dispone dispone de de un un interesante interesante sistema sistema de de posicionamiento posicionamiento que que combina varias tecnologías: tecnologías: Sistema de localización localización global global basado basado en en GPS. GPS. Este Este sistema sistema solo solo funciona funciona sisi disponemos de visibilidad visibilidad directa directa de de los los satélites. satélites. Sistema de localización basado basado en en la la información información recibida recibida de de las las torres torres de de telefonía telefonía celular y de puntos de acceso acceso Wi-Fi. Wi-Fi. Funciona Funciona en en el el interior interiorde de los losedificios. edificios. Estos servicios se encuentran encuentran totalmente totalmente integrados integrados en en el el sistema sistema yy son son usados usados por gran variedad de aplicaciones. aplicaciones. Por Por ejemplo, ejemplo, la la aplicación aplicación Loca/e Locale1 1 de de Android Android puede adaptar la la configuración configuración del del teléfono teléfono según según donde donde se se encuentre. encuentre. Podría Podría por por en elel trabajo. trabajo. ejemplo poner poner el el modo de de llamada llamada en en vibración vibración cuando cuando estemos estemos en El sistema de posicionamiento posicionamiento global, global, GPS, GPS, fue fue diseñado diseñado inicialmente inicialmente con con fines fines militares pero hoy hoy en en día día es es ampliamente ampliamente utilizado utilizado para para uso uso civil. civil. Gracias Graciasalaldesfase desfase 31 satélites satélites desplegados, desplegados, este este temporal de las las señales señales recibidas recibidas por por varios varios de de los los 31 sistema es capaz de posicionarnos posicionarnos en en cualquier cualquier parte parte del del planeta planeta con con una una precisión precisión de 15 metros. metros. El GPS presenta un un inconveniente; inconveniente; solo solo funciona funciona cuando cuando tenemos tenemos visión visión directa directa de los satélites. satélites. Para Para solventar solventar este este problema, problema, Android Android combina combina esta esta información información con la recibida de las las torres torres de de telefonía telefonía celular celular yy de de puntos puntos de deacceso accesoWi-Fi. Wi-Fi.
Ejercicio paso aa paso: paso: El API API de de localización localización de de Android. Android. En este ejercicio crearemos crearemos una una aplicación aplicación que que es es capaz capaz de de leer leer información informaciónde de un cambio. cambio. localización del del dispositivo dispositivo yy actualizarla actualizarla cada cada vez vez que que se se produce produce un 1.
Crea un un nuevo nuevo proyecto proyecto con con los los siguientes siguientes datos: datos: Pruject ñame: name: Localización Localizacion Project Bui.ld 1. 6 Euild Target: Android 1.6 Application ñame: name: Localización Localizacion Package ñame: name : org.example.localización oz·g. example .localizacion Create Activity: Localización Localizacion Min SDK Versión: Version: 4
2.
11
Por razones de de seguridad seguridad acceder acceder aa la la información información de de localización localizaciónestá, está,en en principio, prohibido aa las las aplicaciones. aplicaciones. Si Si estas estas desean desean hacer hacer uso uso de dedicho dicho servicio han de de solicitar solicitar permisos permisos especiales. especiales. Estos Estos permisos permisos hay hay que que AndroidManiest.,xmi. xml. En En concreto, concreto, esta esta aplicación aplicación indicarlos en el fichero fichero AndroidManiest necesita los permisos permisos de de localización localización precisa precisa yy localización localización imprecisa: imprecisa:
http://www.androidlocale.com http://www.androidlocale.com
231
El gran libro de Android ACCESS FINE LOCATION ACCESS_FIKEJLOCATION ACCESS COA!
Puedes hacerlo a través de los cuadros de diálogos, como se muestra a continuación: continuación: g; ^ Android Manifest Manifost Permissions Pefmíssioivs ÍF| (P) A: A; 0 (Q) @ fF| 0 ®
(Ü) Uses P«rniss»or>
Attrlbutes fot Uses Permiswoft @ The jses~:St'1.-pt:rmis>:ion tag requests requ5ts aa (í¿) The p-etmisiion tag '"penni.ssion that the package mast mu-rt "permaskm".. that the containing package be 9 r-.tnte:d in ín otder for It it to to opérate operate ccrrectly. be grántcd order for N.tme .android.perm.~'iion.ACCESS_COARSI_lO Ñame 3ndrotd.pefmKsion,ACC£SS_C0ARS£_L0 ▼... :
' Atttlbut.. for Oses P
contain/~"' 9
~crrectly.
Manifejt \ Applicatior: i Permissions i ÍRítrumentotior» AodroisJManrfest.xml i Añadiendo las siguientes líneas en el fichero XML. FINE....LOC?\.TION" 1 >
3.
En este ejemplo de posicionamiento vamos a utilizar tanto la localización fina, fina, que nos proporciona el GPS, GPS, como una menos precisa, precisa, proporcionada por las torres de telefonía celular y WiFi. En este ejemplo nos limitaremos a mostrar en modo de texto la API de localización, información obtenida desde el API localización, tal y como se muestra en la siguiente pantalla: pantalla:
hisa
232
ame© ti
Seguridad yy posicionamiento Como puede observarse nos nos limitaremos limitaremos aa mostrar mostrar un un Textview Textview con con scroll. Sustituye el scro/1. el fichero fichero res/layout/main.xral res/layout/mai n . xml por: por:
arent" " f i l l ....Parent" aandroid: n droid : layout_height= layout_height= ""fi l l _.parent "> parent "> < /Se rol IV ielA'> 4.
Ve ahora al fichero fichero LocationTest Locat ionTest..java java yy copia copia el el siguiente siguiente código: código: public clasa class Localización Localizacion extends extends Activity Activity implements implementa LocationListener LocationListener { priva te static ata tic final StringH String[] AA == { "n/d", prívate "preciso", "impreciso" n/cl preciso "impreciso }; p = { "n/d", priva te static final Stringt] String[] P= prívate "bajo", n/d "bajo'', "medio","alto" tnedi.o" , alto }; priva te static final String[] String[] E¡;; == { "fuera prívate " fuera de de servicio", servicio", "tempo:r-almente disponible ", " , "disponible" "disponible" }; }; "temporalmente no disponible priva te LocationManager manejador; mane j ado:r·; prívate priva te Textview TextView salida; salida ; prívate priva te String proveedor; proveedor ; prívate 11
11 ,
'1
11 ,
11
11 ,
11
11
11
}
11
} ;
;
~:YC-·verride ■SOverride public void onCreate(Bundle onCreate(Bundle savedlnstanceState) savedlnstanceState) { super.onCreate(savedlnstanceState); super .onCreate(savedlnsta nceState); ssetContentView(R.layout.main); e t ContentView(R . l ayou t.main); salida == (Textview) (TextView) findViewById(R.id.salida); findViewByld (R. id . sa.Iúla );
manejador == (LocationManager) (LocationManager) getSystemService getSystemService (LOCATiOALSiíR'/XCBj (L.OC.'A'TlON_SERVICE) ,• ; log ("Proveedores ( 11 Proveedores de localización: localización: \n \n "); ") ; muestraProveedores(); Criteri c riteria == new new Gritería(); Criteria(); Griteríaa criteria proveedor == manejador.getBestProvider(criteria, manejador . getBestProvider (criter ia, true); true); log( 11 Hejor proveedor: "" ++ proveedor proveedor ++ "\n"); "\n"); log("Mejor log("Comenzamos con la la última última localización l ocalizaci ón conocida:"); conocida:" ); Location localización l ocalizac i on == manejador.getLastKnownLocation(proveedor); manejador .ge tLastKnownLocation(proveedor); muestraLocaliz(localización); mues t raLocaliz (localizacion);
} La primera línea línea que que nos nos interesa interesa es es lala llamada llamada aa getsystemservice getSystemse :rvic e { locat i on_servi ce ) que que crea crea el el objeto objeto manejador manejador de de tipo tipo (LOCATION_SEEVICE) LocationManager. Loca t i onManage r. La siguiente línea línea hace hace una una llamada llamada al al método método log log ()<) que que será será definido definidomás más el Textview TextView el el texto texto indicado. indicado. adelante. Simplemente saca por por el adelante. Simplemente
233
El gran libro de Android Androíd La siguiente llamada aa muestra Proveedores ()O también muestraProveedores también es es un un método método que listará listará todos todos los los proveedores proveedores de de localización localización definido por nosotros, que disponibles. En las tres siguientes siguientes líneas líneas vamos vamos aa seleccionar seleccionar uno uno de de estos estos proveedores de de localización. localización. Para Para ello ello usaremos usaremos elel método método getBest.Provider (). En getBestProvider(). En este este método método hay hay que que indicar indicar un un criterio criterio de de se podría podría indicar indicar restricciones restricciones de de coste, coste, potencia, potencia, selección. Aquí se En este este ejemplo ejemplo no no indicamos indicamos ninguna ninguna restricción. restricción. precisión, etc. En Dependiendo del proveedor proveedor puede puede tardar tardar un un cierto cierto tiempo tiempoen endarnos darnosuna una primera posición. No No obstante, obstante, Android Android recuerda recuerda lala última última posición posición que que fue devuelta por este este proveedor. proveedor. Es Es lo lo que que se se hace hace en en las las últimas últimaslíneas líneas del programa. El El método método muestraLocaliz muestraLocaiiz ()t) será será definido definido más más tarde tarde yy muestra en pantalla una una determinada determinada localización. localización. 5. 6.
Copia, a continuación, continuación, el el resto resto del del código: código:
!! // Métodos del del ciclo ciclo de de vida vida de de la la actividad actividad &Override () {{ ©Override protected void void onResume onResumeí) super .onResume();; super.onResume{! // r\ctivarnos o c alización Activamos notificaciones notificaciones de de llocalización manejador. request.Locat.ionUpdat.es ); ; requestLocationlTpdat.es (proveedor, (proveedor, 1.0000, 10000, 1,1, this this)
} &Override () {{ ©Override protected void void onPause onPause() super. onPause(); super.onPause(); // Desactivarnos es para Desactivamos notificacion notificaciones para ahorrar ahorrar baLería batería manejador . removeUpdates( this) ; manej ador.removeUpdates(this);
}
!i i! 1>1ét:odos Métodos de J.a la int.erfaz interfaz Locati.onList.enet· LocationListener public public void onLocationChanged(Location onLocationChanged ■(Location locat:.ion) location) log( "Nueva localización log("Nueva localización:: "); "); muestraLocaliz (locat.ion); inuestraLocaliz(locationi;
{
} public void onProviderDisabl ed(String proveedor) onProvlderDisabled(String proveedor) { log("Proveedor o: "" +proveedor+ loq("Proveedor deshabilitad deshabilitado: + proveedor + "\n"); "\n");
} public public void onProvid.er·Enabled onProviderSnabled(St:.ring
} public void onStatusChanged(String , onStatusChanged(String proveedor, proveedor, int int estado estado,
Bundle extras) extras! {{ log ( 11 Cam.bía e(·Jtado n log("Cambia estado proveedor proveedor:: ti" ·-+-+ proveedor proveedor ++ 11 estado:.:: estado=" 11 E[Jv1ath.rnax{0 1 estado) J ++ "",, extras= ++ eextras xtra s ++ ""\n"); \ n 11 ) ; + £[Math.max(0,estado)) extras=" 1
234
Seguridad y posicionamiento 11 ,// t•lét.odos Métodos para para mostrar mostrar info:nna,ción información prívate void log(String log(String cadena) cadena) {{ salida.append(cadena ++ "\n"); "\n");
} void muestraLocaliz(Location rcuestraLocaliz(Location localízacion) localización) prívate voíd íf localizacion == if ((localización == null nuil)) log("Localización desconocida\n"); desconocida\n"); el se else log(localizacíon.toString() log(localización. toString() ++ "\n"); "\n");
{
} prívate voíd void muestraProveedores() itvuestraProveedores {) {{ List proveedores proveedores == manejador.getAllProviders(); manejador.getAUProviders(); for (Strlng (String proveedor proveedor :: proveedores) proveedores) {{ muestraProveedor(proveedor); muestraProveedor(proveedor);
} } prívate voíd void muestraProveedor(String muestraProveedor(String proveedor proveedor)) {{ Locat.ionProvider LocationProvider info info == manejador.getProvider(proveedor); manejador.getProvider(proveedor); log("Locat.ionProvider[ log ("LocationProvider [ "+"getName=" "•t-"getName=" ++ info.getName{)+ info. getName ()+ isProviderEnabled=" ++ manejador.isProviderEnabled(proveedor)+ manejador.isProviderEnabled(proveedor)+ A[Math.max(0, info.getAccuracy())] info.getAccuracy!))]+ getAccuracy=" ++ A[Math.max(O, + " , getPo1r1erRequirement=" getPov/erRequirement=" ++ P[Math.max(0, info.getPowerRequirement{))]+ P [l'lath. max (O, info. getPowerRequi:r·ement () ) ] + hasMonetaryCost-"'' ++ info. info.hasMonetaryCost()+ hasl'lonetaryCost= hasr"'onetaryCost. () + requiresCell=" ++ info.requiresCell()+ info.requiresCell()+ requ i resNetwork=" + info. requiresNetvmrk () + requiresNetwork=" info.requiresNetwork()+ requiresSatellite=" requires3atellite=" ++ info.requiresSatellite()+ info.requiresSatellite()+ " , supports.Zütitude=" supportsAltitude=" +-i- info.supportsAltitude()+ info. supportsAltitude () + supportsBearing=" ++ info.supportsBearing()+ info.supportsBearing()+ ", supportsSpeed=" suoportsSpeed^" ++ info.supportsSpeed()+" info.supportsSpeed()+" ]\n"); ]\n");
} notifiquen cambios cambios de de posición posición hay hay que que llamar llamaralal Para conseguir que se notifiquen requestLocationüpdates (()Jyy para para indicar indicar que que se se dejen dejen de de método requestLocationUpdat.es hacer las notificaciones hay hay que que llamar llamar aa removeUpdates removeupdates(). (). Dado Dado que que batería, nos nos interesa interesa que que se se reporten reporten notificaciones notificaciones queremos ahorrar batería, aplicación esté esté activa. activa. Por Por lolo tanto, tanto, tenemos tenemos que que solo cuando la aplicación lOS métodos oonResume () yy onPause onPause {(). reescribir los nResume () l. El método requestLocationUpdates requestLocationüpdates ()(¡dispone de 44 parámetros: parámetros: elel dispone de nombre del proveedor, el el tiempo tiempo entre entre actualizaciones actualizaciones en en ms ms (se (se recomiendan valores mayores mayores de de 60.000 60.000 ms), ms), lala distancia distancia mínima mínima (de (de manera que si es menor, menor, no no se se notifica) notifica) yy un un objeto objeto LocationListener. LocationListener.
235
El gran libro de Android A continuación, implementamos implementamos los los métodos métodos de de un un Locationiástener: r.ocationListener: onLocationChanged cada vez vez que que cambie cambie la la posición. posición. Los Los onLocationchanged se activará cada en otros tres métodos pueden ser usados usados para para cambiar cambiar de de proveedor proveedor en caso de que se active uno uno mejor mejor o deje deje de de funcionar funcionar el el actual. actual. Sería Sería buena idea llamar de nuevo nuevo aquí al al método método getBestProvider getBest.Provider o. () . El resto del código resulta fácil de de interpretar. interpretar.
6, 6.
Verifica el funcionamiento del del programa, programa, si si es es posible posible con con un un dispositivo dispositivo real. real.
7.2.1. Emulación del GPS GPS con con Eclipse Ecli se Muy probablemente el ordenador ordenador donde donde estés estés trabajando trabajando no no disponga disponga de de GPS, GPS, por por lo que sería muy difícil difícil que que este este programa programa funcione funcione en en el el emulador. emulador. No No hay hay un sistema sistema de de emulación emulación problema, el plug-in de de Android Eclipse proporciona proporciona un Android para para Eclipse al menú menú Window Window >> Show Show GPS. Para activarlo sigue sigue los los siguientes siguientes pasos. pasos. Accede Accede al del GPS. View >Others ... Control. .. > Android > Emulator Control.
f íM ShrfcwVtf»»/
t'
^
^ TI
j type flfcer t&xt General ~General @} Andro~d igj» Android 1lll DEl/ice< Q Devices 1 (p Emulator JI Eqmlator Controi <2'¡~¡c¡l ; $Y File Explorer E~plore 0 File til Heap § Heap ,g L<>gC•t 0i LogCat ■0 Resource .gi Resaurce Explorer Explorer Threads ~Threads . g; §?. Ant ~~~ Use F2 to to display display the the descfipt description for aa selected selededview. view. Use F2 on for
Aparecerá una ventana como la la siguiente: siguiente:
lf_ Prcblems O Consolé Q LogCat Q Emolí torCootrol I : i Location Controls I Manml | KML 1 i # Decimal i 0 Sexagesimal i i Longrtude -122.084095 | Latitude i 37422006 j
236
H
„
r
,
" d" *1 § h iJ
Seguridad y posicionamiento NOTA: Es posible que el GPS GPS no no reciba reciba ninguna ninguna señal. señal. El El problema problema está está relacionado relacionado con el formato formato de las coordenadas coordenadas enviadas enviadas al al emulador. emulador. Por Por algún algún motivo motivo (probablemente relacionado con el el carácter carácter usado usado para para separar separar los los decimales, decimales, la lacoma coma en español y y punto en inglés) el el emulador emulador solo solo recibe recibe correctamente correctamente las las actualizaciones actualizaciones si la configuración regional está está establecida establecida en en el el idioma idioma inglés. inglés. Para Para solucionar solucionar este este problema basta cambiar el loca/e lócale del del runtime runlime de de java. java. Si Si utilizas utilizas el el plug-in plug-in para para Eclipse, añade '-Duser.fanguage '-Duser. language=en' =en' al al archivo archivo ec/ipse.ini. eclipse, ¡ni. Más Más información informaciónen en2.2.
7 .3. Google Maps 7.3. Google Maps nos proporciona un servicio servicio de de cartografía cartografía online online que que podremos podremos utilizar utilizaren en nuestras aplicaciones Android. Veamos Veamos las las claves claves necesarias necesarias para para utilizarlo. utilizarlo.
hacer uso uso de de este este servicio servicio necesitas necesitas una una clave clave de de Google Google En primer lugar, para hacer Maps. Puedes encontrar información información en en http://code.google.com/intl/es/apís/maps/ http://code.qooale.com/intl/es/apis/maps/ sianup.html. Siempre es una una buena buena idea idea revisar revisar los los términos términos yy condiciones. condiciones. He He aquí aquí signup.html. algunos ejemplos: •
número de de solicitudes solicitudes de de codificación codificación geográfica geográfica por pordía, día, Hay un límite en el número 15.000. 15.000.
•
AdWords) no no está está incluida incluida en en lala API API de de Google Google Publicidad (AdSense 1/ AdWords) Maps.
•
mapas de de Google Google como como un un servicio servicio gratuito gratuito para para Estás obligado a ofrecer mapas tus usuarios.
Google Maps Maps 7.3.1. Obtención de una clave Google servicio de de Google, Google, igual igual que que como como ocurre ocurre cuando cuando se se utiliza utiliza Para poder utilizar este servicio desde una página web, va aa ser necesario necesario registrar registrar la la aplicación aplicación que que lolo utilizará. utilizará. se nos nos entregará entregará una una clave clave que que tendremos tendremos que que indicar indicar Tras registrar la aplicación, se en la aplicación. dos claves claves diferentes, diferentes, una una durante durante el el proceso proceso de de Realmente vamos a necesitar dos desarrollo y otra para para la la aplicación aplicación final. final. La La razón razón es es que que se se genera genera una una clave clave certificado digital digital con con la la que que se se firma firma la la aplicación. aplicación. En En lala diferente en función del certificado aplicaciones también también han han de de ser ser firmadas firmadas digitalmente, digitalmente, pero pero fase de desarrollo las aplicaciones utiliza un un certificado certificado especial especial utilizado utilizado solo solo en en lala fase fase de de en este caso el SDK utiliza desarrollo. clave Google Google Maps Maps para para el el certificado certificado de de depuración. depuración. Veamos cómo obtener la clave En caso de querer distribuir tu aplicación, aplicación, una una vez vez terminada terminada tendrás tendrás que que firmarla firmarla con un certificado digital propio. propio. Este Este proceso proceso se se explica explica en en elel último último capítulo. capítulo. remplazar la la clave clave Google Google Maps Maps por por otra, otra, esta esta última última Recuerda que será necesario remplazar digital. asociada a tu certificado digital.
2
http://code.goog le.com/p/androídlissues/detail?id=9155 http://code.google.com/p/android/issues/detai^id^QI
237
El gran libro de Android
Ejercicio paso a paso: Obtención de una clave Google Maps. Maps. 1.
El primer paso va a consistir en descubrir donde está almacenado el certificado digital de depuración. depuración. Utilizando el entorno Eclipse accede al menú Windows > Preferences > Android > Build. Build. Aparecerá el siguiente cuadro de diálogo; diálogo: ::~r~L~~~~~~::::~:::
>:;. General General * Android .. Android íBuMj (~j¡~j DÍ)MS DDMS Launch Laun.ch LogCat LogCat üsdge Stats Us:a.g:eStats }í> Ant Ant ;-p Data Management Management í> Help > Hdp p. lmtaJVUpdate Instaftdjpdate & lava Java E£ l!MH Oat~
2. 2.
..J
Buíld iht>Jd Mttíng~ 3aifd Settingíu &iAutcmati:c.ally ref~em Picurees P..e;ourc.s and and Assets A..\lets fotder folde:r on on build buíld j m Automatically refresh Build output Build output f; Silent tS Silent
:§) Nofmal
{f} \l~rtcse
<> ú\Usets\jtom~s\.androtd\debug,keyst:ore Defauit ciebug feeystore C:\üsef5 jtomas\.android\dBbug.key5£ofe Custom debug keystore : 8 ........ ··········-·
Oefa.uh rl,e.bug keyst:ore
En el cuadro informativo Default debug keystore: aparece la ruta del fichero donde se almacena el certificado digital de depuración. depuración. ruta. Copia en el portapapeles esta ruta.
3.
Ahora necesitamos extraer la huella digital MD5 de este fichero. fichero. Para k e ytool. En Windows extraer la huella digital puedes utilizar el programa keytooi. este programa se encuentra e : \ P r og:r·am en la carpeta C:\Program Files\Java\jre6\bin\ Fiies\Java\jre6\bin\ o en una similar. similar. Abre un intérprete de comandos y sitúate en la carpeta anterior. anterior.
4.
Ejecuta el siguiente comando remplazando el nombre del del fichero por el que acabas de copiar en el portapapeles. portapapeles. kkeytool e yt ool -v ·· v -list ·· list ··keystore ['I'u debug.keystore d ehug . keyst ore path] path.l -keystore [Tu
En nuestro ejemplo: ejemplo: kkeytool eytool -v - v -list - li s t -keystore - k e ysto re C: \ Users \ jtornas\ . android\ debu9 .key sto re C:\Users\jtonias\.android\debug.keystore
gn Sín-.bcic ce! í;iten-a C:\Pro¡Jram files (x86)\Java\jre6\bin>keytool (•86)\Java\jre6\bin>keytool C:\Program Files droidNdebug.keystore dro i d\debug . keys tore Escriba la Escriba la contraseña contrase'a del del almacún almacún de de claves: claves:
.- lli st -keystore keys tore C:\Users\jtoMas\.an C \Users\jtomas \ an list
1 ipo de almacún al macún de claves: el aves . JKS Tipo Proveedor de de almacún almacún de de claves: claves : SUN SUN Proveedor Su almacún almacún de de claves claves contiene contiene entrada entrada 1 Su andro i ddebugkey. 02-feb-2010, 0/- f eb -201 O. PrivateJeyEntry. Pr i val eKeyFnt ry. androiddebugkey. Huella digital de certificado (MD5): (HD5l: FD :RF: 7R :5F: 52: RE: 51:62:84:18:60: lF : Dl: OO :F Huella digital de certificado fD:flF:7fi:5F:52:flE:51:62:84:18:60:lF:Dl:00:F E:79 C:\Program Files Fil es (x86)\Java\jre6\bin> (•86)\Java\jre6\bin> C:\Program
238
Seguridad y posicionamiento posicionamiento
5.
El programa te solicitará una una contraseña contraseña para para proteger proteger el el almacén almacén de de claves. es android android oo no no introduzcas introduzcas nada. nada. AA claves. La contraseña es continuación, la huella huella digital digital del del certificado certificado DM5. DM5. Como Como continuación, te indicará la puedes ver en la captura anterior anterior está está formado formado por por los los siguientes siguientes 16 16 FD:AF:7A:5F:52:AE:Sl:62:B4:18: bytes expresados en hexadecimal: hexadecimal: FD:AF:7A:5F:52:AE:51:62:84:18: 60:1F:Dl:OO:FE:79 60:1F:D1:00:FE:79 Copia en el portapapeles esta secuencia secuencia de de dígitos. dígitos.
6.
Para obtener la clave Google Maps Maps entra entra en en la la siguiente siguiente página página Web: Web: http://code.gooqle.com/android/maps-apí-signup.html http://code.aooaie.com/android/maDs-api-sianup.html
7.
Tendrás que introducir introducir tu huella huella digital digital yy el el usuario usuario de de Google Google Mail Mail que que realiza la solicitud. solicitud. Si Si no dispones dispones de de un un usuario usuario en en Google Google puedes puedes crear crear de minutos. minutos. El El resultado resultado final final se se muestra muestra aa uno nuevo en un par de continuación. continuación,
ala
Graaas clave del del API API de de Android Android Maps. Maps. Gracias Por por suscribirte a la clave Tu clave es:
f HcRHUdAOASRI35:r.llqst6U2'PM5ATMJ !ikl';g [ O}fhSa 0!fiiSa£Hc:RHlMÁOÁSEI86:ri!qst6n?PMSATMjnkKg
Esta clave es válida pata todas las apítcaciones firmadas con el certificada cuya huella dactilar sea;
Esta cirlificado cuya huella dactilar sea:
FD :AF ?A:5F:52:AE:SI:62:84;18:60:1F:DI:00:FE:79 7A : SF:52 :AE:51:&2.84:18.60:1F:D1.00 :FE : 79 FD;¿F
8.
Recuerda copiar en en el el portapapeles portapapeles la la clave clave obtenida, obtenida, la la necesitaremos necesitaremos en en el siguiente ejercicio.
Ejercicio paso aa paso paso:: Un programa de ejemplo con Google Goog/e fí Maps. Maps. Veamos un sencillo ejemplo que que nos nos permite permite visualizar visualizar un un mapa mapa centrado centrado en en las coordenadas geográficas detectadas detectadas por por el el sistema sistema de de posicionamiento. posicionamiento. 1.
Crea un nuevo proyecto proyecto con con los los siguientes siguientes datos: datos: name: EjemploGoogleMaps EjemploGoogle!Vlaps Project ñame; Build Target: Google Apis APis 1,6 1.6 Application ñame: name: Ejemplo Google Google Maps Maps Packagename: org.example.ej org.exarnple.ejemplogooglemaps emplogooglemaps Create EjemploGoogleMaps Cx-eate Activity: EjemploGoogleMaps Min SDK Versión: Version: 44
2.
Añade los siguientes siguientes permisos permisos aa tu tu aplicación aplicación en en Android,xml: Android.xml: INTERNET ACCESS FINE LOCJI.TION ACCESS_F1NE_L0CATI0N COAI-I.SE LOCATION LOCA'I'ION ACCESS COARSE
239
El gran libro de Android Android 3.
Indica que necesitamos la la librería librería de de Google Google Maps Maps en en tu tu aplicación. aplicación. Entra Entra en AndroidManifest. Android1'1anifest. xmi xml yy selecciona selecciona la la lengüeta lengüeta Application. Application. Utilizando Utilizando "Add ..." añade añade un un "Uses library" library'' con con la la librería: librería: com.google, com. google. el botón "Add..." android. ma.ps android.maps
.9a< Android Android Mamtest Manifost Application Application ~ 'APPii~~ú~~-i~l~--
g> tag csscrrbes riescrib6: application-lrvef ap.plicatíon- levrl components componenti cc-ntairted cc.ntainrd¡f> the th-epactcage. pa..: :lcage.as aswetí wellsi ¡ugeneral ger-,era!applicaticn attributes attribut es, W Thr app;catión tag [#}, Define an < 3ppiicstton> tag tn the AndroidManif est jrml -~:-- AWi~~-Attrarute* Attt~t~ Otfines tht atributes .rttribotu specífic sptc:fic lo to the the appücation applic:atlon. Defines the t:Wru:i ...... [i~~~ AUow tiD rre¡Hrentmg Rttow tasfc eparentmg . ..... ............................................. t-f-!s code rias code la.be1 Glstringi:s.pp_name ........................... t~f~~ PErsistent ©slrrng/ 3pp_oame 8r,®*íS«.,.j Persisten» kon :8re»5e«« rnabled hm-,~,~J E:1abled ■árcc^ss.,, ¡ -lefcuqgabk [,~t\m!,I\\~,J Debuggah~ ············r;~J •▼ . Managespace ...lanage spa.'e activitv .. .. . . ,. . . . . .._. . . SíQaíifcu? Allow A llow dear ust f dat• dear use? data ;sppik~tion
:~
applic:at~n
@j Define an < apphc-B'ti·tm> tag m the AndroidManifest.)U'lll
Th~me
[~~Mm]
@dfawahlt~hccn
~
Procr~s
~--~~ ---~----·--
~
~
~d1v.ty
Li"i1i~;;J
[Jiiiti,!~d
Task affinit';
Applw.af¿ao Wod<»
(A| [KJ (S| $4 [Pj (0) (Ñkz Attrlhirtes fot<«r»jí|o©gfo.^Mwfr{«d.tnaf«{usesiitwsry} @ The s~illes s
~~--
En el fichero AndroidManif AndroidMani fest. xml se se añadirá añadirá la la siguiente siguiente línea: línea: est. xmi android:name,"com . google . android.maps" />
4. 4.
Remplaza el contenido contenido del del layout layout main. main. xml xml por por el el siguiente siguiente código: código:
xmlns: a.nd.roid= ' http: //schemas . a.ndro.i cl. com/ap.k:/res/androi.d!! xmlns:android»"http://sch.emas.android.com/apk/res/andró id" android: orient.ation= orientaticn= "vertical !!ve.rt .i.ca.l''" 1
and.ro:id: l ayout w:idth= "f'iil_parent" " fi: l __pa1·ent" android; layout_widthandroid: layout__height= layout_height = ""fil l ____oa.r ent" >> fill_jparent"
<<<"/> android:apiKey»">>>
5. 6.
240
" »> Tu clave clave de de Google Gocgle Maps r'laps <<<" «<" por por la la clave clave de de Google Google Sustituye ">>> Maps obtenida en el apartado apartado anterior. anterior.
Seguridad yy posicionamiento 6.
código de de tjempioGoogieMaps Ej emploGou,JleMaps .java . java por: por: Remplaza el código
public publ ic class c l a s s EjemploGoogleMaps EjemploGoogleMaps extends extends MapActivity MapActivity implements LocationListener LocationListener { pprívate r i vat e MapController mapController; mapCon t r o lle r ; pri vat e MapView mapView; prívate pri vat e LocationManager manejador; mane j ador; prívate ®Override pub lic void onCreate(Bundle onCreate( Bundle bundle) bundle) { public s upe r ..onCreate onCreate(bundle); ssuper (bundle) setContentView (R. (R . layout.inain) layout. ma .i.n ) ;; mapView id.jriapa) mapVi ew == (MapView) findViewById(R. findViewByid(R.id .mapa );; mapView.setBuiltinZoomControls( true ); mapView.setBuíltlnZoomControls(true); //Activa //Activa controles controles toom zoom m a pView. setSatelli te ( t rue) ; mapView.setSatellite(true); //Activa i /Acti,Ja vista vista satélite satélite mapView.setStreetView(false ); //Desactiva StreetView Street.View mapView.setStreetView(false); //Desactiva mapView.setTraffic(false); //Desactiva información información de de tráfico tráfico mapView.setTraffie(false); //Desactiva m a pController == mapView.getControllerO; mapView . getController(); mapController mapController. setZoom ( 14) ; ! / Zoom Zoom 11 ver ve~· todo todo el el mundo mundo mapController.setZoom(14); // inane mane j ador == (LocationManager) (LocationManager) getSystemService (Context. LOCA'T'ION___SERVICE); SERiliCE) ; getSystemService(Context.LGCATION_ manejador requestLocationUpdates (LocationManager. (LocationManager.GPS__PROVIDER, GP$...PROVIDER , manejador.. requestLocationUpdates 110000, 0 0 00 , 1, l, this);
} :;.'Ovel-ride p rot ect ed boolean boolean isRouteDisplayedf) isRouteDisplayed () {{ ©Override protected fal se ; r e t urn false; return
} ©Override onLocationChanged(Location location) location) {{ public void onLocationChanged(Location int lat == (int) (int ) (location.getLatitude() (location.getLatitude() ** 1E6); 1E6); int Ing int lng == (Int) (int ) (location.getLongitudeO (location.getLongitude() ** 1E6); 1E6); GeoPoint point = new ne w GeoPoint(lat, GeoPoint (lat, Ing); lng); mapControl ler. setCenter(point); mapController.setCenter(point); } ©Override public publ ic void onProviderDisabled(String onProviderDisabled(String provider) provider) {} {} @ Overr.i de ©Override publicc void onProviderEnabled(String publi onProviderEnabled(String provider) provide r) {} {}
©Override public void onStatusChanged(String onStatusChanged(String provider, provider, int int status, status, Bundle extras){}
} Si estás utilizando el el emulador, emulador, en en lugar lugar de de un un teléfono teléfono real, real, utiliza utiliza lala vista vista "Emulator Control" para para indicar indicar las las coordenadas coordenadas geográficas geográficas aa visualizar. visualizar. 241
El gran libro de Android Por ejemplo, la Universidad Politécnica de Valencia se se encuentra encuentra en Latitud: Latitud: 39.47987 y Longitud: -0.33874. El resultado se muestra muestra a continuación:
*"* SSM (jiiiufc" API ülSfflQ 11:07 PM
mw
Práctica: Un programa de ejemplo con Google Maps. Maps. 1.
En el ejemplo anterior, modifica algunos parámetros parámetros de de configuración, configuración, como como inicial de de zoom. Verifica Verifica los los resultados. resultados. visualizar recorrido StreetView o nivel inicial
2.
Si estás utilizando un teléfono real real reemplaza reemplaza en en el el código código anterior anterior LocationManager .NETWORK_ PROpor LocationManager.NHTWORK_PROVIDER. las dos dos posiciones posiciones vider. Ejecuta de nuevo el programa y compara las geográficas obtenidas. obtenidas. Locati o nl'lanager . GPS_PROVIDER LocationManager.GPS_PROVIDER
7 .4. Fragmentando Fragmentando los los asteroides asteroides 7.4. Siguiendo con el juego Asteroides, queremos queremos que que cuando cuando el el misil misil alcance alcance un un asteroide, este se divida en varios fragmentos. fragmentos. Para Para conseguirlo conseguirlo puedes puedes seguir seguir las las instrucciones del siguiente ejercicio:
242
Seguridad y y posicionamiento posícionamiento
Ejercicio paso a paso: Fragmentando los asteroides. asteroides. 1.
Convierte la variable local drawable.l\steroide drawabieAsteroide declarada en el constructor de la clase vista VistaJuego, global, que será un array de tres Juego, en una variable global, elementos: elementos: prívate Drawable drawabieAsteroide[]= drawableAsteroide[J= new Drawable[3]; Drawable[3);
2.
En el constructor cuando se quiera trabajar con bitmaps inicializaremos esta variable de la siguiente forma: forma: drawableAsteroide[O] drawabieAsteroide[0]
= context.getResources(). getDrawable (R. drawable. asteroú1e1) ; getDrawable(R.drawable.asteroide!); drawableAsteroide[l] drawabieAsteroide[1] == context.getResources(). context.getResourcesO. getDra•,..able (R. drawable. ast,;oroidt22) ; getDrawable(R.drawable.asteroides); drawableAsteroide[2] drawabieAsteroide[2] = context.getResources(). getDrawable(R.drawable.asteroide3 ); getDrawable(R.drawable.asteroide!); 3.
Y en caso de querer trabajar con gráficos vectoriales: vectoriales: for (int i=O; .i<3; i++) {{ i=0; i<3; ShapeDrawable n ew ShapeDrawable(new ShapeDrawable (new PathShape( Pa.thShape ( ShapsDrawable dAsteroide = new pathAsteroide, 1, 1)); dAsteroide.getPaint() .setColor(Color.WHlTE); dAsteroide.getPaint().setColor(Color.WHITE); dAsteroide. getPaint. () . setStyle (Style. ST'ROKE) ; dAsteroide.getPaint().setStyle(Style.STROKE); d1\steroide.setintrinsicWidth(50 ); dAsteroide.setIntrinsicWidth(50 - ii ..* 14 14); dAsteroide.setintrinsicHeiq-ht(SOdAsteroide.setlntrinsicHeight{50 - ii * 14); 14); drawableAs teroide [ i] == dAsteroide; cU\s teroide; drawableAsteroide[i]
} 4.
Añade al principio del método destruyeAsteroide código: dest.ruyeAstero.i.de (int i) el código: int t a m; int tam; if(Asteroides . get(i) . ge t Drawable() !=drawableAsteroide [2]) { if(Asteroides.get(i).getDrawable()!=drawableAsteroide[2]){ iif(Asteroides.get(i).getDrawable{)==drawableAsteroide[1]){ f {Asteroides .get (i) . getor·awable () ==drawableAsteroide [1]) { tara=2; tam=2; } else {{ tam=l;
} for (int n=0;n
Corrige algún error adicional ocasionado por este cambio. cambio.
243
El gran libro de Android 6. 6.
Prueba los cambios propuestos anteriormente y verifica que cuando se destruye un asteroide no siempre aparece el mismo número de fragmentos. También es posible que el programa se interrumpa. interrumpa. ¿A qué puede deberse este problema? El siguiente ejercicio trata de explicar este extraño comportamiento. comportamiento.
«Ejercicio Ejercicio paso a paso: Introduciendo secciones críticas en Java (synchronized) . Cuando se realiza una aplicación que ejecuta varios hilos de ejecución hay que prestar un especial cuidado a que ambos hilos pueden acceder de forma simultánea a los datos. Cuando se limitan a leer las variables, no suele haber problemas. El problema aparece cuando un hilo está modificando algún dato y justo en este instante se pasa a ejecutar un segundo hilo que ha de leer estos datos. Este segundo hilo va a encontrar unos datos a mitad de modificar, lo que posiblemente cause errores en su interpretación. El método más común para evitar que dos hilos Java, se accedan al mismo tiempo a un recurso es el de la exclusión mutua. mutua. En Java, consigue utilizando la palabra reservada synchronized. synchronized,
1. 1.
Prueba a introducir la palabra reservada synchronized delante del método onDraw() onDraw () y actualizaFisica{). actualizaFisica ().
2. 2.
problema. Verifica si se ha corregido el problema.
~)
Nota sobre Java: La palabra clave synchronized permite definir una sección ""~Nota crítica en Java. Expliquemos en qué consiste: Cada vez que un hilo de ejecución (thread) entra en un método o bloque de instrucciones marcado con synchronized se pregunta al objeto si ya hay algún otro thread que haya entrado en la sección crítica de ese objeto. La sección crítica está formada por todos los bloques de instrucciones marcados con synchronized Si nadie ha entrado en la sección crítica, se entrará normalmente. Si ya hay otro thread dentro, entonces el thread actual es suspendido y ha de esperar hasta que la sección crítica quede libera. libera. Esto ocurrirá cuando el thread, que está dentro de la sección crítica, salga. Dos matizaciones importantes: La primera es que la sección crítica se define a nivel de objeto no de clase. clase. Es decir, cada objeto instanciado no influye en las secciones críticas de otros objetos. objetos. En segundo lugar, solo se define una sección crítica por clase. clase. Aunque métodos, realmente solo hay una sección se haya utilizado synchronized en varios métodos, crítica. crítica.
a
~
244
Práctica: Práctica: Mejorando preferencias en Asteroides 1.
Modifica el programa para que el número de fragmentos generados corresponda con el valor introducido en las preferencias.
2.
Puedes aprovechar para que la reproducción de música de fondo y los efectos de audio sean también configurables por el usuario. usuario.
CAPÍTULO CAPÍTULO 8. 8.
Servicios, Servicios, notificaciones notificaciones y y receptores de anuncios receptores de anuncios
Las aplicaciones que hemos creado hasta el momento estaban formadas por una serie de actividades, cada una de las cuales permitía construir un elemento de usuario. Una aplicación en Android va a disponer de otros tipos de interacción con el usuario. componentes; estos serán estudiados en este capítulo. Cuando sea necesario que parte de una aplicación se ejecute en segundo plano, debajo de otras actividades y, además, además, no precise de ningún tipo de interacción con el usuario, la opción más adecuada es crear un servicio. Un servicio puede estar en ejecución indefinidamente o puede ser controlado desde una actividad. A lo largo de este capítulo aprenderemos las facilidades proporcionadas actividad. por Android para la creación de servicios. servicios. Por otra parte, las notificaciones de la barra de estado constituyen un mecanismo de comunicación vital en Android. Permiten a las aplicaciones que corren en un segundo plano advertir al usuario sobre alertas, avisos o cualquier tipo de información. Las notificaciones se representan como pequeños iconos en la barra superior de la pantalla y se utilizan, utilizan, habitualmente, para indicar al usuario la llegada de un mensaje, una cita de calendario, una llamada perdida o cualquier otra interés. Se trata de una comunicación que no requiere una interacción incidencia de interés. inmediata del usuario; este puede estar utilizando otra aplicación sin ser interrumpido o puede no estar utilizando el teléfono en ese momento. Este hecho hace de las notificaciones un mecanismo de comunicación ideal para un servicio o anuncios. Por lo tanto, este capítulo parece el sitio ideal para para receptores de anuncios. describir cómo podemos crear nuestras propias notificaciones y utilizarlas desde nuestras aplicaciones. aplicaciones. Terminaremos el capítulo estudiando otro componente de una aplicación Android, anuncios. Un receptor de anuncios (BroadcastReceiver en inglés) los receptores de anuncios. permite realizar acciones cuando se producen anuncios globales de tipo broadcast. Existen muchos anuncios originados por el sistema; como por ejemplo Batería baja, llamada entrante,... aunque, aunque, las aplicaciones también pueden lanzar un anuncio
245
El gran libro de Android crear nuevos nuevos tipos. tipos. Los Los receptores receptores de de anuncios anunciostetepermitirán permitiráncrear crear broadcast o incluso crear aplicaciones mucho más integradas integradas en en el el entorno entornodonde dondese seejecutan. ejecutan.
1 Objetivos: •
de servicios servicios en en Android Android.. Describir el uso de
•
Enumerar los pasos pasos aa seguir seguir cuando cuando queramos queramos crear crear un un servicio servicio para para que una tarea se se ejecute ejecute en en segundo segundo plano. plano.
•
las notificaciones notificaciones de de lala barra barra de de estado estado pueden pueden ser ser Mostrar cómo las utilizadas como mecanismo mecanismo de de comunicación comunicación eficaz eficaz con con elel usuario. usuario.
•
de avisos avisos que que pueden pueden utilizar utilizarlas lasnotificaciones. notificaciones. Repasar los tipos de
•
Describir el uso de de un un servicio servicio como como mecanismo mecanismo de de comunicación comunicaciónentre entre aplicaciones.
•
pasos aa seguir seguir para para crear crear un un receptor receptorde deanuncios. anuncios. Enumerar los pasos
•
receptores de de anuncios anuncios más más importantes importantes disponibles disponibles en en Enumerar los receptores Android
8.1. 8.1. Introducción Introducción a a los los servicios servicios en en Android Android
--·
't~"''"
•
,..-·
Poli[Media]: Pol¡[Media |: Los Los servicios servicios en en Android. Android.
't~"''" PoU[MedÍa]: Un Un servicio servicio para para ejecución ejecución en en segundo segundoplano. plano. - - - Poii[Media]: En muchos casos, será necesario necesario añadir añadir un un nuevo nuevo componente componente aa tutu aplicación aplicación para ejecutar algún tipo tipo de de acción acción que que se se ejecute ejecute en en segundo segundo plano, plano, es esdecir, decir,que que no requiera una interacción interacción directa directa con con elel usuario, usuario, pero pero que que queramos queramos que que permanezca activo aunque aunque el el usuario usuario cambie cambie de de actividad. actividad. Este Este es eselel momento momentode de crear un servicio. En Android los servicios servicios tienen tienen una una doble doble función función:: La primera función permite permite indicar indicar al al sistema sistema que que elel elemento elemento que que estamos estamos creando ha de ejecutarse ejecutarse en en segundo segundo plano, plano, normalmente normalmente durante durante un un largo largo Este tipo tipo de de servicios servicios son son iniciados iniciados mediante mediante elel método método período de tiempo. Este startservice () , que startservice(), que indica indica al al sistema sistema que que lolo ejecute ejecute de de forma forma indefinida indefinidahasta hasta que alguien le le indique indique lo lo contrario. contrario.
246
-------------
---··
----
Servicios, notificaciones y receptores de de anuncios anuncios La segunda función permite que nuestra nuestra aplicación aplicación se se comunique comunique con con otras otras aplicaciones, aplicaciones, para lo cual ofreceremos ciertas ciertas funciones funciones que que podrán podrán ser ser llamadas llamadas desde otras aplicaciones. Este tipo tipo de de servicios servicios son son iniciados iniciados mediante mediante elel método método bindservice (), que permite establecer una una conexión conexión con con el el servicio servicio ee invocar invocar alguno de los métodos que son ofrecidos. Cada vez que un servicio es es creado creado por por alguna alguna de de las las razones razones anteriores, anteriores, elel sistema instancia el servicio y llama llama al al método método oncreate oncreate (l. (). Corresponde Corresponde alal servicio servicio adecuado; habitualmente habitualmente creará creará un un hilo hilo de de implementar el comportamiento adecuado; ejecución (thread) secundario donde se se realizará realizará el el trabajo. trabajo. Un servicio en sí es algo muy simple; simple; en en este este capítulo capítulo se se verán verán ejemplos ejemplos de de servicios locales escritos en en muy muy pocas pocas líneas. líneas. No No obstante, obstante, también también pueden pueden complicarse, como veremos al final final del del capítulo capítulo cuando cuando tratemos tratemos de de invocar invocar servicios remotos por medio de una interfaz interfaz AIDL. AIDL. Un servicio, servicio, como el resto de componentes componentes de de una una aplicación, aplicación, se se ejecuta ejecuta en en elel hilo principal del proceso de la aplicación. Por Por lo lo tanto, tanto, sisi el el servicio servicio necesita necesita un un uso uso intensivo de la CPU o puede quedar bloqueado bloqueado en en ciertas ciertas operaciones, operaciones, como como uso uso de redes, debes crear un un hilo diferente diferente para para ejecutar ejecutar estas estas acciones. acciones. También También puedes utilizar la clase rntentservice mtentservice para para lanzar lanzar un un servicio servicio en en su su propio propio hilo. hilo.
8.1.1. Ciclo de vida de un servicio. servicio. Es importante que recuerdes que un un servicio servicio tiene tiene un un ciclo ciclo de de vida vida diferente diferente aa una una actividad. A continuación, podemos podemos ver ver un un gráfico gráfico que que ilustra ilustra el el ciclo ciclo de de vida vida de de los los actividad. servicios: servicios:
startServiceQ Servicio creado por startService() 1
bindServiceQ Servicio creado por bindService() 1
onCreat.e () onCreate()
onCreate () onCreate()
onSt art () / onSta rt.Command () onStart()/onStartCommand()
onBind() onBind(}
Corriendo
Conectado onUnbind() onUnbind{)
onRebind() onRebí.nd ()
Desconectado onDestroy () onDestroyO
anDes t ro y ( ) onDestroyO
Destruido
Destruido Figura 1: Ciclo de vida de /os los servicios. servicios.
247
El gran libro de Android Como acabamos de explicar existen dos tipos de servicios según según como hayan hayan lo tanto, tanto, sido creados. creados. Las funciones de estos servicios son diferentes diferentes y, y, por lo también su ciclo de vida. Si el servicio es iniciado mediante startservice () el sistema sistema comenzará comenzará creándolo yy llamando a su método oncreate (), () . A continuación llamará a a su método método 1 oonStartCommand nStartCommand (Intent intent, int flags, int startld) 1 con con los los argumentos argumentos ejecución hasta hasta que que sea sea proporcionados por el cliente. El servicio continuará en ejecución invocado el método stopService o () O o stopSelf (). NOTA: Si se producen varias llamadas a starcService start:Service () ( j esto est:o no no supondrá supondrá ¡a la creación de varios servicios, aunque sí que se realizarán múltiples múltiples llamadas aa onStartCommancl (). r ). No inqjorta importa cuántas veces el servicio haya sido creado, creatlo,parará onStartCommand parará con la primera invocación de stopService() stopSE.· rv.ice () o stopSelf stopSe.Lf (). í i . Sin embargo, podemos potlemos utilizar el método stopSelf st:opSeli: (int (int: startld) scart:ld) para asegurarnos de que que el servicio no no parará hasta que todas las llamadas hayan sido procesadas.
Cuando se inicia un servicio para realizar alguna alguna tarea en en segundo plano, plano, el proceso donde se ejecuta podría ser eliminado ante una una situación situación de de baja memoria. memoria. ante esta esta circunstancia circunstancia Podemos configurar la forma en que el sistema reaccionará ante ons tartcommand (). Existen Existen dos modos modos según el valor que devolvamos en onstartcommandO. principales: STi\RT...sTICKY queremos que que el sistema sistema trate de de crear crear principales: devolveremos start sticky si queremos memoria suficiente. suficiente. Devolveremos Devolveremos de nuevo el servicio cuando disponga de memoria START_NOT_STICKY nuevo sólo cuando cuando start_not_sticky si queremos que el servicio sea creado de nuevo llegue una nueva solicitud de creación. creación. Teniendo en cuenta que los servicios pueden estar largo largo tiempo en en ejecución, ejecución, el el un asunto de de gran gran es un ciclo de vida del proceso que contiene nuestro servicio es situaciones donde donde el sistema necesite necesite importancia. Conviene aclarar que en situaciones memoria para conservar un servicio, este será siempre menos prioritario que que la la otras actividades actividades en en actividad visible en pantalla, aunque más prioritario que otras segundo plano. Dado que el número de actividades visibles es siempre siempre reducido, reducido, un un servicio sólo será eliminado en situaciones de extrema necesidad de de memoria. memoria. Por Por otra parte, si un cliente visible está está conectado aa un servicio, servicio, éste éste también también será será considerado como visible, visible, siendo tan prioritario como el cliente. cliente. En En el el caso caso de de un un proceso que contenga varios componentes, componentes, por ejemplo una actividad actividad yy un servicio, servicio, su prioridad se obtiene como el máximo de sus componentes. componentes. bindService (Intent servicio, servicio, ServiceConnection ServiceConnection Podemos también utilizar bindServiceíIntent conexion, int flags) para obtener una conexión persistente con con un un servicio. servicio. Si Si conexión, dicho servicio no está en ejecución, ejecución, será creado (siempre que que el el flag flag BU.lD_.l\tJTO_CRE.l\TE activo), llamándose al al método onCreate oncreate (), (), p6r0 pero DO no S6 se I{3rn3r3 llamará bind_auto_create esté activo), a onStartCosTimand onStartCommand (>. (). En SU su lugar se llamará al método onBindíIntent onBind ( Intent intención) intencion) que ha de devolver al cliente un objeto iBinder IBinder a través del cual se se podrá establecer establecer servicio. Esta comunicación se establece establece por medio de de una comunicación entre cliente y servicio.
11
el método llamado llamado será onstartí). onstart () . En En En versiones del API inferiores a 2.0 el versiones recientes se mantiene por razones de compatibilidad. compatibilidad.
248
Servicios, notificaciones notificaciones yy receptores de de anuncios anuncios una interfaz escrita en AIDL, AIDL, que que permite permite ei el intercambio intercambio de de objetos objetos entre entre aplicaciones aplicaciones El servicio servicio permanecerá permanecerá en en ejecución ejecución tanto tanto tiempo tiempo que corren en procesos separados. separados. El como la conexión esté establecida, establecida, independientemente independientemente de de que que se se mantenga mantenga oo no no lala referencia al objeto iBinder. IBinder. También es posible diseñar diseñar un un servicio servicio que que pueda pueda ser ser arrancado arrancado de de ambas ambas (star-tservice () () yy bindservice bindService ()). () ). Este Este servicio servicio permanecerá permanecerá activo activo se se formas (startservice desde la la aplicación aplicación que que lo lo contiene contiene oo sisí recibe recibe conexiones conexiones desde desde ha sido creado desde otras aplicaciones. Todo servicio terminará terminará llamando llamando al al método método ancestro ancestro () ( J cuando cuando vaya vaya aa terminar terminar de forma efectiva. efectiva.
8.1.2. Permisos Podemos conseguir que que el el acceso acceso global global aa un un servicio servicio declarándolo declarándolo en en lala etiqueta etiqueta de AndroidManifest An droidManifest..xmi. x ml. También También podemos podemos definir definir un un permiso permiso para para restringir su acceso. acceso. En este este caso, caso, las las aplicaciones aplicaciones han han de de declarar declarar este este permiso, permiso, en en su su propio propio manifiesto. manifiesto. con el correspondiente un servicio. servicio. De De Podemos definir un permiso permiso para arrancar, arrancar, parar parar oo conectarse conectarse aa un forma adicional, podemos pociemos restringir restringir el el acceso acceso aa funciones funciones específicas específicas de de las las ofertadas por un servicio. servicio. Para Para este este propósito, propósito, podemos podemos llamar llamar al al principio principio de de checkCallingPermission (String) para para verificar verificar sisi elel cliente cliente nuestra función a checkcaiilngpermission(string) en concreto. concreto. dispone de un permiso en Para más información sobre sobre permisos permisos se se recomienda recomienda la la lectura lectura del del capítulo capítulo 7.7.
8.2. Un servicio servicio para para ejecución ejecución en en segundo segundo plano plano Dentro de los los dos usos usos de de un un servicio, servicio, el el más más frecuente frecuente es es permitirnos permitirnos ejecutar ejecutar parte de nuestra aplicación aplicación en en segundo segundo plano. plano.
Ejercicio paso a paso: paso: Un servicio servicio para para ejecución ejecución en en segunsegundo plano de reproducción de música. música. Veamos un ejemplo de servicio servicio que que corre corre en en el el mismo mismo proceso proceso de de la la aplicación aplicación que lo utiliza. El servicio servicio será será creado creado con con la la finalidad finalidad de de reproducir reproducir una una música música de de la actividad actividad principal. principal. fondo y podrá ser arrancado arrancado yy detenido detenido desde desde la 1.
un nuevo proyecto proyecto con con los los siguientes siguientes datos: datos: Crea un Project ñame: name: ServicioMusica ServicioMusica Bui.ld Target : Android 2.0 2. O Build Target; Appl i.cation ñame: name: Servicio Servicio de de Música t"lúsica Application
Package ñame; name: org.example.serviciorausica org.example.serviciomusica Create Activity; Activity: ActividadPrincipal ActividadPrincipal Min SDK Versión: Version: 5
249
El gran libro de Android Android 2.
Remplaza el código código del del layout layout main. main. xmi xml por: por: -
sion= "i "?>
x1nlns: android= ''11 t tp: //sclJernas . android. coln/a:pk/Les / and.r:oid '' xmlns;android»"hctp://schemas.android.com/apk/res/android"
android:orientatio n=" vertical" android:orientation="vertical" android: layout_width= " fill t i l l ___parent" parent" android:layout_width= android: Iayout_height= layo ut height= "fill_„parent fill. 1:•are:.1t "> "> música"/>