miércoles, 22 de mayo de 2013

Android Tip: Impedir que se puedan seleccionar las filas de un ListView

No es exactamente lo indicado en el título, pero el caso es que al mostrar una lista con un ListView, si el usuario pulsa sobre un elemento éste se selecciona y queda resaltado. Si lo único que queremos es evitar este efecto, basta con añadir estas propiedades en la etiqueta ListView.

android:cacheColorHint="@android:color/transparent"
android:listSelector="@android:color/transparent"

jueves, 9 de mayo de 2013

CalledFromWrongThreadException


Uno de los requisitos clásicos de Android es que cualquier modificación sobre una vista debe ser realizada desde el hilo que la creó (generalmente referido como el UI Thread). Esto hace que no sea raro que cuando empecemos a trabajar con hilos acabemos encontrándonos con la excepción:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Ante esta excepción lo primero que tenemos que preguntarnos es si realmente queremos modificar la vista desde donde lo estamos haciendo. Si la respuesta es afirmativa, podemos recurrir al método runOnUiThread de la clase Activity. Un posible ejemplo de uso sería el siguiente.

Tenemos una actividad de una aplicación que permite conexiones por bluetooth. En pantalla se muestra un botón, y cuando lo pulsamos, ponemos en marcha un servidor, que se mantiene a la espera de conexiones. Este trabajo se realiza en un hilo independiente que es lanzado desde nuestra actividad. Lo que queremos conseguir es mostrar un texto informando de que el servidor está esperando conexiones, y ocultarlo cuando éste se detenga.

Damos a nuestro hilo una referencia a la actividad, y en ésta añadimos un método para mostrar u ocultar el texto informativo. Al final del método run() del hilo añadimos una llamada a ese nuevo método.

public class XXX extends Thread {
....

public void run() {
...
activity.setTextVisible(false);
}
...
}

public class XXX extends Activity {
...
public void setTextVisible(boolean visible) {
text.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}

Excepción asegurada.

Pero si cambiamos la implementación del método setTextVisible a:

public void setTextVisible(boolean visible) {
runOnUiThread(new Runnable() {
public void run(){
text.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
});
}

Todo marchará como la seda.

martes, 7 de mayo de 2013

Abrir una url usando LibGDX


Cuando hacemos un juego en Android, es muy normal que queramos dar al usuario la posibilidad de abrir una página Web. Por ejemplo, para tratar de convencer a nuestros jugadores de que pierdan un minuto en valorar nuestro trabajo en Google Play.

Abrir una url desde Android no tiene ningún misterio; basta con ejecutar este código desde alguna actividad.

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(myIntent);
  
Pero, ¿qué pasa si hemos desarrollado nuestro juego con LibGDX? Pues que la librería nos abstrae de la implementación concreta (Android en nuestro caso), por lo que cuando estemos programando una pantalla no tendremos acceso a las clases de Android, ni actividades, ni ninguna de esas historias. Y además, a día de hoy, que yo conozca, la librería no provee ningún método que se encargue de esto.

Una posible solución sería hacer que nuestro juego delegue la acción de abrir la url en el componente encargado de lanzarlo para cada implementación (una actividad para Android, una clase para escritorio...). Para conseguirlo, crearemos una interfaz en el proyecto principal de nuestro juego, que declarará el método para abrir una url.

public interface IOperations {
 public void openUrl(String url);
}

A continuación, añadiremos a nuestro juego una atributo de tipo IOperations, y modificaremos el constructor para que requiera este valor como parámetro.

public class MyGame extends Game {

 private IOperations myOperations;
 
 public SaveMeGame(IOperations myOperations){
  this.myOperations = myOperations;
 }
 
 //... Resto de la clase ...
}
 
Ahora podemos agregar a la clase MyGame un método que delegue en IOperations la apertura de la url.

public void openUrl(String url){
 myOperations.openUrl(url);
}
 
Una vez hecho esto, tendremos que modificar los lanzadores de las distintas tecnologías (Android, HTML5, J2SE...) para implementar la interfaz IOperations, y proveer por tanto una implementación concreta del método que abre una url. Para el caso de Android sería algo así.

public class MyActivity extends AndroidApplication implements
  IOperations {

 @Override
 public void onCreate(Bundle savedInstanceState) {
  // ... Preparación del juego ... 

  View gameView = initializeForView(new MyGame(this), config);

  setContentView(gameView);
 }

 @Override
 public void openUrl(String url) {
  Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        startActivity(myIntent);
 }
}

Con esto tendríamos la estructura completa, y podemos usar este método desde cualquier pantalla de nuestro juego.

public class MyScreen implements Screen {

 private final String gameURI = "http://play.google.com/store/apps/details?id=package.game";
 private Game game;

 //... En algún lugar de la pantalla ...
 
 ((MyGame) game).openUrl(gameURI);
 
 //...
}
 
Y eso sería todo. Una vez completado el círculo, un pequeño repaso para asentar la idea.

En nuestra situación de partida estamos escribiendo código en una pantalla de nuestro juego (implementación de Screen), y queremos abrir una url, pero la librería no nos ofrece un método para hacerlo, y la forma de conseguirlo depende de la tecnología en que despleguemos finalmente el juego.

Por tanto, definimos una interfaz IOperations con un método para abrir una url. En esta interfaz podríamos añadir cualquier otro método que necesitemos, y que sea dependiente de la tecnología.

Añadimos a nuestra clase Game un nuevo atributo de tipo IOperations, y modificamos el constructor para que requiera un objeto que implemente la interfaz.

Por último, implementamos la interfaz IOperations en los proyectos correspondientes a cada una de las tecnologías en que vayamos a desplegar nuestro juego. En el ejemplo hemos visto una forma de hacerlo para Android, con la actividad de inicio implementando directamente IOperations. También se podría haber creado una nueva clase que contuviese exclusivamente la implementación de IOperations (y de hecho sería recomendable, sobre todo si empieza a crecer el número de operaciones disponibles).

Y ya podemos invocar el método openUrl sobre una instancia de nuestro juego.