Вы находитесь на странице: 1из 18

Creando una aplicacin de Android:

mejoras
Bienvenidos a la quinta parte de la serie de entradas en el ciclo Creando una aplicacin de
Android. Si an no lo has hecho, comienza desde la primera entrada mostrada en el men
inmediatamente superior.
En esta entrada vamos a continuar con el resultado de la entrada anterior, en la que
habamos acabado la lgica de movimientos y las colisiones. Aadiremos funcionalidades
durante esta entrada y la siguiente para acabar la aplicacin.
Para esta entrada aadiremos vibracin del dispositivo, reproduccin de sonidos y uso del
acelermetro para los movimientos de las raquetas.
Comenzamos!
Importante: esta entrada va a basarse en los cdigos creados en las
anteriores entradas del ciclo al que pertenece. Tienes disponible pinchando aqu (MD5:
31b264db2de4125fa01c52d4d169bc99) el proyecto de Eclipse con todo lo hecho hasta
ahora, el cual puedes importar a tu Eclipse y comenzar a trabajar.
El Android SDK incluye una gran cantidad de clases que hace muy sencillo el uso de los
sensores del dispositivo, la vibracin, la reproduccin de audio Para mostrar cmo de
sencillo es, comenzamos aadindole vibracin a nuestra aplicacin. Comenzamos
abriendo nuestro Eclipse.

Vibracin
Para que una aplicacin pueda vibrar, ya que necesita acceder a un elemento del sistema,
necesita que el usuario acepte el uso de este elemento. Para ello tenemos que aadir al
AndroidManifest.xml que queremos usar vibracin. Tenemos dos formas, la primera es
aadiendo <uses-sdk android:minSdkVersion=7 /> justo detrs de la etiqueta <manifest
>.
La segunda forma consiste en ir a la pestaa Permissions del manifest, pulsar en Add, elegir
Uses Permission y aceptar, y luego escribir android.permission.VIBRATE en el cuadro de
la derecha. Lo siguiente es programar el uso del vibrador.
El uso de la vibracin est definido por un Context, por lo que vamos a necesitar usar el
contexto de nuestra aplicacin para poder obtener el vibrador y posteriormente hacerlo
vibrar. La vibracin la vamos a realizar solamente cuando la Bola rebote en una de las dos
Raquetas. Si recordamos las entradas anteriores, nuestra Bola y nuestra Raqueta tenan una
funcin llamada puedoMover(), heredada de su superclase ElementoPong, que deca si se
poda mover este elemento sin salirse de la pantalla. Sabemos que cuando una bola rebota
es porque se ha chocado con algo. Si se ha chocado con algo pero an se puede mover por

la pantalla significa que, por eliminacin, se ha chocado con una raqueta. Ya tenemos
planteado lo que vamos a hacer, as que hagmoslo.
Primero vamos a aadir un nuevo atributo a la clase BolaMoveThread de la siguiente
forma:
1 private Vibrator v = null;

Este ser nuestro vibrador. Ahora necesitamos inicializarle, puesto que est a null y si lo
usramos ahora la aplicacin se morira debido a un NullPointerException (de ah que se
inicialice a null, para debuggearlo ms fcil). Para inicializarle necesitamos el Context de la
aplicacin, de modo que al constructor de BolaMoveThread le vamos a pasar el Context,
quedando as:
1 public BolaMoveThread(Bola bola, Raqueta izda, Raqueta dcha,
2
Rect screen, Context context) {
3
4

this.bola = bola;
this.raquetaIzda = izda;

5
6

this.raquetaDcha = dcha;
this.screen = screen;

7
8

this.run = false;
this.speed = 1;

this.v =
(Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
10 }
9

Faltara cambiar la forma en que se crea este Thread de la siguiente forma (en el mtodo
surfaceCreated() del PongGameView:
1 bolaThread = new BolaMoveThread((Bola)bola, (Raqueta)raquetaIzda,
2
(Raqueta)raquetaDcha, new Rect(0,0,getWidth(),getHeight()),
3

this.getContext());

Por ltimo slo queda hacer que vibre, para ello vamos a modificar el cdigo del run() del
BolaMoveThread de la siguiente forma:
1 @Override
2 public void run() {
3
4
5
6
7
8

while(run) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}

if(!bola.puedoMover(speed, speed, screen,


raquetaIzda.getRectElemento(), raquetaDcha.getRectElemento())) {
bola.rebota(speed, speed, screen,
10
raquetaIzda.getRectElemento(), raquetaDcha.getRectElemento());
9

11
12

if(bola.puedoMover(speed, speed, screen))


v.vibrate(50);

13
14

}
bola.move(speed, speed);

15
16 }

Con esto estamos haciendo una vibracin breve (50 milisegundos) cada vez que rebotamos
contra una raqueta. Fcil, verdad?
Como informacin extra tenemos que saber que un objeto de la clase Vibrator tambin
puede vibrar dado un patrn, utilizando la funcin vibrate(pattern, repeat), siendo pattern
un long[] y repeat un int que dice cuntas veces se repite (-1 si no queremos repetir).
Sonido
El siguiente paso consistir en aadir sonido a los rebotes, el tpico sonido metlico.
Existen diversas formas de usar sonidos en Android, pero vamos a usar la ms simple de
todas: el MediaPlayer. Lo primero que tenemos que hacer es buscar un sonido que nos
valga. Por lo general se recomienda usar ficheros codificados en formato .ogg (Ogg Vorbis)
por ser el ms compatible y tener una gran compresin. Yo he elegido el siguiente sonido.
Si quieres usar el mismo, haz click derecho sobre el enlace y selecciona Guardar enlace
como (y gurdalo con un nombre distinto, en minsculas, sin espacios y sin tildes).
Ahora crearemos una nueva carpeta dentro de la carpeta res de nuestro proyecto, y la
llamaremos raw. Dentro de ella meteremos nuestro fichero de audio (en mi caso pong.ogg).
A partir de ahora podremos acceder a ello referenciando a R.raw.pong. Aadimos un nuevo
atributo a BolaMoveThread:
1 private MediaPlayer mp = null;

Y el constructor queda as:


1 public BolaMoveThread(Bola bola, Raqueta izda,
2
Raqueta dcha, Rect screen, Context context) {
3
4

this.bola = bola;
this.raquetaIzda = izda;

5
6

this.raquetaDcha = dcha;
this.screen = screen;

7
8

this.run = false;
this.speed = 1;

this.v =
(Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
10
this.mp = MediaPlayer.create(context, R.raw.pong);
9

11 }

La llamada a MediaPlayer.create() crea y prepara el audio local para ser reproducido.


Existe una funcin de los objetos MediaPlayer llamada prepare() que hace lo mismo (pero
slo hay que llamarla en ciertos momentos, ahora explicamos ms). Falta hacer que
reproduzca el sonido, de modo que vamos a la funcin run(), que queda:
1 @Override
2 public void run() {
3
4

while(run) {
try {

5
6

Thread.sleep(10);
} catch (InterruptedException e) {

7
8

e.printStackTrace();

if(!bola.puedoMover(speed, speed, screen,


raquetaIzda.getRectElemento(), raquetaDcha.getRectElemento())) {
10
mp.start();
9

bola.rebota(speed, speed, screen,


raquetaIzda.getRectElemento(), raquetaDcha.getRectElemento());
12
if(bola.puedoMover(speed, speed, screen))
11

13
14
15
16

v.vibrate(50);
}
bola.move(speed, speed);
}

17 }

Podramos hacer un stop() y prepare() despus y antes (respectivamente) de cada start().


Sin embargo eso sera un consumo muy grande de los recursos, y start() ya mantiene
preparado el audio para hacer un replay. Sera interesante, sin embargo, hacer una llamada
a stop() y prepare() dentro del setRunning() dependiendo de si paramos o iniciamos el
thread. Agregar sonidos simples ha resultado ser tambin muy fcil, no?

Activar y desactivar
Ahora mismo tenemos que la aplicacin hace que el dispositivo vibre al rebotar con una
pala, y hace que reproduzca un sonido de choque metlico cada vez que rebote con algo
(sea pared o raqueta). Recordemos que tenemos un men en el cual tenamos una parte de
Opciones, pero que no tena nada dentro. Es ahora el momento de crear las opciones del
juego, permitiendo activar y desactivar estas dos funcionalidades.
Comenzamos creando un nuevo layout que tendr el siguiente cdigo:

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


2 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
4

android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TableRow android:id="@+id/tableRow1"
android:layout_width="wrap_content"
6
android:layout_height="wrap_content">
5

<TextView
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
8
android:layout_height="wrap_content"
7

android:width="250dip" android:paddingLeft="30dip"
android:id="@+id/labelSonido"
10
android:text="@string/sonidoMenu"></TextView>
9

<CheckBox android:layout_width="wrap_content"
android:id="@+id/checkBoxSonido"
android:layout_height="wrap_content"
12
android:checked="true"></CheckBox>
11

13

</TableRow>
<TableRow android:id="@+id/tableRow2"
14
android:layout_width="wrap_content"
15

android:layout_height="wrap_content">
<TextView
16
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
18
android:paddingLeft="30dip" android:id="@+id/labelVibracion"
17

19

android:text="@string/vibracionMenu"></TextView>
<CheckBox android:layout_width="wrap_content"
20
android:id="@+id/checkBoxVibracion"
android:layout_height="wrap_content"
android:checked="true"></CheckBox>
22
</TableRow>
21

23 </TableLayout>

Teniendo en cuenta que tenemos que aadir los Strings a strings.xml:


1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3
4

<string name="app_name">Pong</string>
<string name="menu_play">Jugar</string>

5
6

<string name="menu_options">Opciones</string>
<string name="menu_exit">Salir</string>

7
8

<string name="sonidoMenu">Sonido</string>
<string name="vibracionMenu">Vibracin</string>

9 </resources>

Tenemos que aadir la Activity al AndroidManifest.xml igual que hicimos en otra de las
entradas. Para ello abrimos el AndroidManifest.xml y aadimos lo siguiente antes de
</application>:
1

<activity android:name=".PongOpcionesActivity"
android:screenOrientation="portrait"></activity>

Lo siguiente que necesitamos hacer es aadir el cdigo necesario para crear una actividad
nueva que muestre este nuevo layout. Creamos una nueva clase llamada
PongOpcionesActivity, cuyo cdigo es el siguiente:
1 package com.vidasconcurrentes.pongvc;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.view.Window;
6 import android.view.WindowManager;
7
8 public class PongOpcionesActivity extends Activity {
9
10

@Override

11
12

protected void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);

1
3
1
4 N,

requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREE

1
5 );
1
6
17
18 }

WindowManager.LayoutParams.FLAG_FULLSCREEN
setContentView(R.layout.options);
}

Ahora slo falta hacer el cdigo en la actividad principal para que se ejecute esta. Para ello
vamos a la clase PongvCActivity (la principal), y aadimos la funcin:
1 private void muestraOpciones() {
2
Intent opciones = new Intent(this, PongOpcionesActivity.class);
3
4}

this.startActivity(opciones);

Ahora en el onCreate() de esta clase, vamos al lugar donde registramos el listener para el
botn de Opciones y cambiamos el contenido por lo siguiente:
1 TextView options = (TextView)findViewById(R.id.options_button);
2 options.setOnClickListener(new OnClickListener() {
3
4
5
6

@Override
public void onClick(View v) {
muestraOpciones();
}

7 });

Ahora s, si hacemos una ejecucin, obtendremos esto tras pulsar en el botn Opciones del
men principal:

Ahora tenemos que programar qu pasa cuando activamos y desactivamos estos


checkboxes. He elegido hacer lo siguiente con un patrn Singleton. A grandes rasgos, un
patrn Singleton ofrece la garanta de que existe como mximo una instancia de una clase y
que es accesible globalmente. Nosotros queremos poder acceder al estado de estas opciones
desde el juego, pero modificarlas desde esta actividad. De modo que comenzamos creando
nuestra clase PongOpciones dentro de un nuevo paquete llamado
com.vidasconcurrentes.pongvc.opciones:
1 package com.vidasconcurrentes.pongvc.opciones;
2
3 public class PongOpciones {
4
5
6

private static PongOpciones opciones = null;


private boolean sonido;

private boolean vibracion;

8
9
10

private PongOpciones() {
sonido = true;

11
12

vibracion = true;

13
14

public static synchronized PongOpciones getInstance() {

15
16

if(opciones == null)
opciones = new PongOpciones();

17
18

return opciones;
}

19
20

public void toggleSound() {

21
22

sonido = !sonido;

23
24

public void toggleVibration() {

25
26

vibracion = !vibracion;
}

27
28

public boolean soundEnabled() {

29
30

return sonido;
}

31
32

public boolean vibrationEnabled() {

33
34

return vibracion;

35 }

Las ponemos inicializadas a true porque, por defecto, nuestra aplicacin va a tener ambas
activadas. Ahora aadimos el comportamiento al pulsar sobre los checkboxes. Para ello
vamos al onCreate() de la clase PongOpcionesActivity y aadimos lo siguiente al final:
1 CheckBox sonido = (CheckBox) findViewById(R.id.checkBoxSonido);
2 sonido.setOnClickListener(new OnClickListener() {
3
4

@Override
public void onClick(View v) {

5
6

7 });
8

PongOpciones.getInstance().toggleSound();

9 CheckBox vibracion = (CheckBox) findViewById(R.id.checkBoxVibracion);


10 vibracion.setOnClickListener(new OnClickListener() {
11
12

@Override
public void onClick(View v) {

13
14

PongOpciones.getInstance().toggleVibration();
}

15 });
16
17 sonido.setChecked(PongOpciones.getInstance().soundEnabled());
18 vibracion.setChecked(PongOpciones.getInstance().vibrationEnabled());

Como vemos, lo ltimo que hacemos es poner el estado de cada CheckBox acorde con el
estado de la instancia del patrn Singleton. Cuando pulsamos en un CheckBox, cambiamos
el valor de la variable a la que se refiere de true a false y viceversa.
Efectivamente, si ejecutsemos ahora, comprobaramos que podemos tener ambas activas,
desactivadas o una activa y otra no.

Acelermetro
El acelermetro del dispositivo va a permitirnos registrar movimientos de ste para saber si
se ha cambiado la inclinacin con respecto de la posicin inicial con la que se comenz el
juego. Existen varias formas de usar el acelermetro, y por supuesto existen muchsimas e
infinitas formas de programar dnde debe ir cada cosa en nuestro proyecto.
Mis decisiones han sido las siguientes:

Queremos que se ejecute un Thread que controle la raqueta izquierda con el


acelermetro.
Queremos quitar el registro del acelermetro cuando no lo necesitemos.
Queremos reanudar el uso del acelermetro al volver al juego.

Al igual que hicimos al crear los hilos anteriores (pintado y movimiento de la bola), vamos
a crear un hilo nuevo para las raquetas:
1 package com.vidasconcurrentes.pongvc.juego;
2
3 import android.graphics.Rect;
4
5 public class RaquetaMoveThread extends Thread {
6
7
8

private Raqueta raqueta;


private Rect screen;

9
10

private boolean run;

11
12

public RaquetaMoveThread(Raqueta r, Rect s) {

13
14
15

raqueta = r;
screen = s;
}

16
17
18

public void setRunning(boolean run) {


this.run = run;

19

20
21
22

public void run() {


while(run) {

23
24

try {
Thread.sleep(10);

25
26

} catch (InterruptedException e) {
e.printStackTrace();

27
28

}
// nuestro codigo va aqui

29
30

}
}

31 }

En PongGameView creamos el hilo en surfaceCreated() y lo matamos en


surfaceDestroyed():
1 // esto dentro de surfaceCreated()
2 raquetaThread = new RaquetaMoveThread((Raqueta)raquetaIzda,
3
new Rect(0,0,getWidth(),getHeight()));
4 raquetaThread.setRunning(true);
5 raquetaThread.start();
6
7 // esto seria el surfaceDestroyed():
8 @Override
9 public void surfaceDestroyed(SurfaceHolder arg0) {
10
boolean retry = true;
11
12

paintThread.setRunning(false);
bolaThread.setRunning(false);

13
14

raquetaThread.setRunning(false);
while (retry) {

15
16
17

try {
paintThread.join();
bolaThread.join();

18

raquetaThread.join();

19
20
21
22 }

retry = false;
} catch (InterruptedException e) { }
}

Ahora es el momento de crear nuestro acelermetro. Para ello vamos a crear una clase
envolvente para la funcionalidad que queremos que nos ofrezca. Un dispositivo emulado no
puede usar acelermetro, as que ser necesario el uso de un dispositivo fsico.
Nosotros vamos a querer que se mueva la raqueta. Esta raqueta pertenece a una actividad
que est en modo landscape (apaisado) continuamente. Un dispositivo fsico tiene su eje X
a lo alto, es decir, es una lnea imaginaria que va desde los botones del dispositivo hasta el
auricular. Si lo prefers: de abajo a arriba. Su eje Y es el perpendicular a ste que cruza de
lado a lado. Como hasta ahora hemos visto a la hora de pintar, con el dispositivo en modo
apaisado, el eje X ser el ancho y el eje Y ser el alto. De modo que si nosotros rotamos el
dispositivo hacia delante en modo apaisado, queremos que nuestra pala suba. Si rotamos el
dispositivo hacia nosotros en modo apaisado, queremos que nuestra pala baje.
Dicho en otras palabras: si la rotacin del eje X es negativa, queremos que vaya arriba; si
es positiva, queremos que vaya abajo. Para ms informacin sobre los ejes de coordenadas
tridimensionales pulsa aqu.
Por tanto slo nos interesa la rotacin del eje X para nuestra pala, pero podemos acceder a
los valores de Y y Z consultando event.values[1] y event.values[2] respectivamente.
Creamos una nueva clase AcelerometroPong en el paquete
com.vidasconcurrentes.pongvc.juego:
1 package com.vidasconcurrentes.pongvc.juego;
2
3 import android.content.Context;
4 import android.hardware.Sensor;
5 import android.hardware.SensorEvent;
6 import android.hardware.SensorEventListener;
7 import android.hardware.SensorManager;
8
9 public class AcelerometroPong implements SensorEventListener {
10
11
12

private SensorManager sm = null;


private int x;

13
14

public AcelerometroPong(Context context) {

sm = (SensorManager)
context.getSystemService(Context.SENSOR_SERVICE);
16
}
15

17

18

public void register() {

sm.registerListener(this,
sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
20
SensorManager.SENSOR_DELAY_GAME);
19

21

22
23
24

public void unregister() {


sm.unregisterListener(this);

25

26
27
28

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { }

29
30

@Override

31
32

public void onSensorChanged(SensorEvent event) {


if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

33
34
35

x = Math.round(event.values[0] * 100);
}
}

36
37
38

public int getXInclination() {


return x;

39
40 }

Desde la Activity PongJuego vamos a usar esta clase como atributo, y aadiremos dos
nuevos mtodos de modo que quedar as:
1 package com.vidasconcurrentes.pongvc;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.view.Window;
6 import android.view.WindowManager;
7
8 import com.vidasconcurrentes.pongvc.juego.AcelerometroPong;
9 import com.vidasconcurrentes.pongvc.pintado.PongGameView;
10
11 public class PongJuego extends Activity {
12
13
14

private AcelerometroPong acelerometro;

15
16

@Override
protected void onCreate(Bundle savedInstanceState) {

17
18

super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);

1
9 N,
2
0 );

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREE
WindowManager.LayoutParams.FLAG_FULLSCREEN

acelerometro = new
AcelerometroPong(this.getApplicationContext());
22
setContentView(new PongGameView(this, acelerometro));
21

23

24
25
26

@Override
protected void onResume() {

27
28
29

super.onResume();
acelerometro.register();
}

30
31
32

@Override
protected void onStop() {

33
34
35
36 }

super.onStop();
acelerometro.unregister();
}

Es importante no usar el sensor si no es necesario, de ah onResume() y onStop().


Nuestro PongGameView ahora recibe tambin el acelermetro, as que tendremos que
modificar el constructor para que as lo reciba y aadir un atributo nuevo.
1 private Integer xInit = null;
2 private AcelerometroPong acelerometro;
3
4 public PongGameView(Context context, AcelerometroPong acelerometro) {
5
6

super(context);
getHolder().addCallback(this);

7
8

this.acelerometro = acelerometro;

9}

Ahora, queremos que sea el Thread que controla el movimiento de la raqueta el encargado
de consultar el acelermetro, as que igualmente cambiamos el constructor de ste y lo
aadimos como atributo a RaquetaMoveThread:

1 private AcelerometroPong acelerometro;


2
3 public RaquetaMoveThread(Raqueta r, Rect s, AcelerometroPong a) {
4
raqueta = r;
5
6

screen = s;
this.acelerometro = a;

7}

Adems, aadimos esta nueva variable xInit de tipo Integer (para poder inicializarla a null).
Y por qu queremos inicializarla a null? Es tan slo una pequea treta que se me ha
ocurrido para poder realizar una calibracin en cada ejecucin. Si es null es que an no
hemos calibrado la posicin en la que est el dispositivo al arrancar el Thread, si no es null
es que ya est calibrado.
Ahora modificaremos el run() de este Thread, de la siguiente forma:
1 public void run() {
2
if(xInit == null)
3

xInit = acelerometro.getXInclination();

4
5
6

while(run) {
try {

7
8

if(Math.abs(xInit - acelerometro.getXInclination()) < 200)


Thread.sleep(5);

9
10

else
Thread.sleep(2);

11
12

} catch (InterruptedException e) {
e.printStackTrace();

13
14

}
if(xInit < acelerometro.getXInclination() - UMBRAL ||

15
16

xInit < acelerometro.getXInclination() + UMBRAL)


if(raqueta.puedoMover(0, 1, screen))

17
18

raqueta.move(0, 1);
if(xInit > acelerometro.getXInclination() - UMBRAL ||

19
20

xInit > acelerometro.getXInclination() + UMBRAL)


if(raqueta.puedoMover(0, -1, screen))

21
22

raqueta.move(0, -1);
}

23 }

Lo primero que hacemos es inicializar la posicin de calibrado, si es null. Lo siguiente que


hacemos es hacer que el movimiento de la raqueta sea ms o menos rpido dependiendo de
si hemos inclinado mucho el dispositivo o no. En nuestro caso, si hay una diferencia

absoluta (positiva o negativa) de menos de 200 unidades, entonces nos vamos a mover a
una velocidad normal. Si esto es mayor significa que hemos inclinado el dispositivo mucho
ms, por lo que deseamos que se mueva ms rpido. La variable UMBRAL no es ms que
una variable final de la clase que en este caso tiene el valor de 20. Este UMBRAL nos sirve
para no mover la raqueta si el movimiento es demasiado pequeo (quiz por errores de
medida o de redondeo). Adems aadimos la comprobacin de si puedoMover() para evitar
salirse de la pantalla.
Con esto ya tenemos la raqueta izquierda funcionando para moverse con el acelermetro,
pero a la vez funciona con el dedo y esto no es lo deseable.

Activar / desactivar acelermetro


Igual que hicimos con el sonido y la vibracin, ahora queremos poder cambiar acelermetro
por tctil y viceversa. Para ello vamos a nuestra clase PongOpciones y aadimos un nuevo
atributo, de modo que queda:
1 private static PongOpciones opciones = null;
2 private boolean sonido;
3 private boolean vibracion;
4 private boolean acelerometro;
5
6 private PongOpciones() {
7
8
9
10 }

sonido = true;
vibracion = true;
acelerometro = false;

Aadimos tambin los siguientes dos mtodos, para cambiar el valor y consultarlo:
1 public void toggleAcelerometro() {
2
acelerometro = !acelerometro;
3}
4
5 public boolean accelerometerEnabled() {
6
return acelerometro;
7}

Ahora tenemos que llamar a estos mtodos desde la actividad de las opciones, la cual
necesita ser cambiada para aadir una nueva fila. Por tanto, antes del </TableLayout> de
options.xml, aadimos:
<TableRow android:id="@+id/tableRow3"
1 android:layout_width="wrap_content"
android:layout_height="wrap_content">

<TextView android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
2 android:layout_height="wrap_content" android:id="@+id/labelAccel"
android:text="@string/accel"
android:layout_marginLeft="30dip"></TextView>
<CheckBox android:layout_width="wrap_content"
3 android:layout_height="wrap_content"
android:id="@+id/checkBoxAccel"></CheckBox>
4 </TableRow>

Hay que aadir tambin el String al fichero strings.xml. De esta forma veramos la siguiente
imagen al ejecutar:

En el onCreate() de PongOpcionesActivity, aadimos:


1 CheckBox acelerometro = (CheckBox) findViewById(R.id.checkBoxAccel);
2 acelerometro.setOnClickListener(new OnClickListener() {
3
4

@Override
public void onClick(View v) {

5
6

PongOpciones.getInstance().toggleAcelerometro();

7 });
8
9

acelerometro.setChecked(PongOpciones.getInstance().accelerometerEnabled
());

De esta forma cambiamos la variable de activado a desactivado y viceversa.

Es el momento ahora de hacer que la aplicacin use una u otra. Todo el cdigo se va a
poner por tanto en PongGameView. El Thread de la raqueta slo se ejecuta cuando est
activado el acelermetro, por tanto modificamos el cdigo de surfaceCreated() y
cambiamos:
1 if(PongOpciones.getInstance().accelerometerEnabled()) {
2
raquetaThread = new RaquetaMoveThread((Raqueta)raquetaIzda,
3
4

new Rect(0,0,getWidth(),getHeight()), acelerometro);


raquetaThread.setRunning(true);

5
6}

raquetaThread.start();

Adems, si este Thread no se ha iniciado, no se puede matar. As que en


surfaceDestroyed():
1 @Override
2 public void surfaceDestroyed(SurfaceHolder arg0) {
3
4

boolean retry = true;


paintThread.setRunning(false);

5
6

bolaThread.setRunning(false);
if(PongOpciones.getInstance().accelerometerEnabled())

7
8

raquetaThread.setRunning(false);
while (retry) {

9
10

try {
paintThread.join();

11
12

bolaThread.join();
if(PongOpciones.getInstance().accelerometerEnabled())

13
14

raquetaThread.join();
retry = false;

15
16

} catch (InterruptedException e) { }
}

17 }

Adems, si activamos el acelermetro no queremos poder mover la raqueta con el dedo. De


modo que en el onTouchEvent() vamos a envolverlo todo con un:
1 if(!PongOpciones.getInstance().accelerometerEnabled() {
2
// aqui todo el codigo anterior, incluyendo el switch
3}
4 return true;

Hecho esto podemos ejecutar la aplicacin y trastear con ella activando y desactivando
cosas, reiniciando el juego La idea es iniciar el juego, pulsar la tecla de actividad

anterior que nos lleva al men, elegir Opciones y cambiarlas, dar a actividad anterior y
luego a Jugar.
Aqu llega el final de la entrada de hoy. En ella hemos visto cmo usar distintos sensores
del sistema como la vibracin o el acelermetro, adems de las herramientas que nos ofrece
para reproducir sonidos locales. En la siguiente entrada acabaremos la aplicacin aadiendo
las ltimas mejoras como un pequeo umbral para el juego tctil, el marcador de juego y la
posibilidad de que se cuele la bola y por ltimo una pequea Inteligencia Artificial para la
raqueta derecha.

Вам также может понравиться