Gestures

La pantalla táctil es uno de los mecanismos más cómodos para interaccionar con un teléfono Android. No obstante el reducido tamaño de la pantalla en los teléfonos móviles hace que el diseño de la interfaz de usuario sea complejo. Por ejemplo, tratar de introducir las decenas de botones y menús que incorporan la mayoría de aplicaciones de sobremesa sería imposible en una pantalla de 3 pulgadas. Para tratar de dar nuevas alternativas en el diseño de interfaz de usuario, a partir del SDK 1.6, se incorporan las gestures.

Una gesture es un movimiento pregrabado sobre la pantalla táctil, que la aplicación puede reconocer. De esta forma, la aplicación podrá realizar acciones 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 gestures en una aplicación real, te recomendamos que bajes “Google Gesture Search” del Market. Como puedes ver en la siguiente 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...).

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.

 

Ejercicio paso a paso: Creación de una librería de gestures

1. Abre un emulador con nivel de API 24 y con memoria 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) a continuación introduce un nombre asociado a esta gesture (por ejemplo “configurar”). El resultado se muestra a continuación:

4. Si no te gusta como ha quedado el trazado, no tienes más que realizar uno nuevo. El anterior será descartado.

5. Pulsa el botón “Done” para aceptar la gesture. Tras pulsar este botón aparecerá un cuadro de texto indicándote donde se acaba de guardar el fichero con la librería de gestures.

NOTA: Si intentas crear con el emulador una gesture formada por varios trazos (por ejemplo el símbolo “X”). Es posible que solo quede almacenado el último trazo. Para que ambos trazos sean reconocidos en la misma gesture, has de introducir el segundo justo a continuación del primero. Puede resultar algo difícil, pero tras un par de intentos lo conseguirás. No te preocupes, introducir una gesture de varios trazos en un dispositivo real no resulta tan complicado como en el emulador. Concretamente, el problema está en el valorFadeOffset que indica el tiempo máximo en milisegundos entre dos trazos de la misma gesture. Si al introducir dos trazos el tiempo entre ellos es mayor que FadeOffset, se considerará que se han introducido dos gestures diferentes. Por defecto, este valor es asignado a 420 milisegundos. El valor resulta adecuado con un dispositivo real, pero muy pequeño para el emulador. En el ejemplo descrito más adelante daremos un valor más alto a FadeOffset si queremos trabajar de forma más cómoda con el emulador.

6. Trata de introducir las gestures mostradas en la siguiente captura. Para mejorar el porcentaje de reconocimientos correctos, puede ser interesante introducir varias veces la misma gesture con trazados alternativos. Esto se consigue dándole el mismo nombre a dos gestures

7. Cada vez que una nueva gesture es introducida aparece una ventana de texto que indica el fichero donde está almacenanda nuestra librería. Seguramente será “/sdcard/gestures”. Con Android Studio pulsa el botón Android Device Monitor  de la barra de herramientas y selecciona la lengüeta File Explorer.

8. Selecciónalo y pulsa el botón "guardar" para guardarlo en tu ordenador.

 

Ejercicio paso a paso: Añadiendo la librería de gestures a nuestra aplicación

1. Para utilizar la librería que acabas de guardar, crea un nuevo proyecto con los siguientes datos:

Application name: Gestures
Package name: org.example.gestures
☑ Phone and Tablet
    Minimum SDK: API 15 Android 4.0.3 (IceCreamSandwich)
Add an activity: Empty Activity

2. El siguiente paso va a consistir en crear la carpeta res/raw en el proyecto y copiar en ella el fichero que has guardado (gestures) en el ejercicio anterior. 

3. Reemplaza res/layout/activity_main.xml por el siguiente código:

<LinearLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent">

  <TextView 

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:gravity="center_horizontal"

    android:text="Introduce una gesture"

    android:textSize="8pt"

    android:layout_margin="10dip"/>

  <TextView 

    android:id="@+id/salida"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"/>

  <android.gesture.GestureOverlayView

    android:id="@+id/gestures"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:gestureStrokeType="multiple"

    android:fadeOffset="800"/>

</LinearLayout>

El layout anterior está formado por un LinearLayot que contiene: un TextView con un título, un TextView para mostrar la salida del programa y un GestureOverlayView que será utilizado para introducir los gestures. En esta última etiqueta el parámetro gestureStrokeType indica que permitimos gestures formados por varios trazos. El parámetro fadeOffset ha sido explicado en el apartado anterior.

4. Reemplaza el código de la actividad por:

public class Gestures extends Activity implements OnGesturePerformedListener {

    private GestureLibrary libreria;

    private TextView salida;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        libreria = GestureLibraries.fromRawResource(this, R.raw.gestures);

        if (!libreria.load()) {

          finish();

        }

        GestureOverlayView gesturesView = (GestureOverlayView) findViewById(R.id.gestures);

        gesturesView.addOnGesturePerformedListener(this);

        salida = (TextView) findViewById(R.id.salida);

    }

  

    public void onGesturePerformed(GestureOverlayView ov, Gesture gesture) {

        ArrayList<Prediction> predictions = libreria.recognize(gesture);

        salida.setText("");

        for (Prediction prediction : predictions){

          salida.append(prediction.name+" " + prediction.score+"\n");       

         }

    }

}

En este código se comienza declarando dos campos de la clase librería contendrá la librería de gestures creada en el ejercicio anterior y salida que corresponde al TextView donde escribiremos los resultados.

En el constructor, tras realizar las operaciones habituales, se carga la librería de gestures desde los recursos y en caso de no ser cargada finaliza la aplicación. A continuación, asocia el GestureOverlayView de main.xml a el objeto gestureView y se indica que nuestra clase será el escuchador de este elemento.  Finalmente se asocia el TextView donde queremos sacar la salida al objeto salida.

El método onGesturePerformed se introduce para implementar la interfaz OnGesturePerformedListener. Este método tiene dos parámetros, el GestureOverlayView donde se ha introducido el gesture y el objeto Gesture que ha sido introducido. El primer paso consiste en reconocer el gerture comparándolo con la lista de nuestra librería. El resultado es una lista ordenada de Predictions con las gestures que considera más parecidas a la introducida. Tras borrar salida, se recorre todos los elementos de esta lista mostrando el nombre del gesture (prediction.name) y la puntuación de reconocimiento (prediction.score). Resulta complicado fijar un umbral, pero una puntuación inferior a 1 se suele considerar demasiado baja para tenerla en cuenta como predicción.

5. Ejecuta la aplicación y estudia las puntuaciones obtenidas.

 

Añadiendo gestures a Asteroides

En el siguiente ejercicio trataremos de aplicar lo aprendido a la aplicación Asteroides. La idea es que de forma alternativa a usar el menú de cuatro botones que se muestra al arrancar la aplicación, se pueda utilizar gestures.

Práctica: Añadiendo gestures a Asteroides

1. Crea la carpeta res/raw y copia el fichero gestures, que contiene la librería creada anteriormente.

2. Modifica el Layout main.xml para que disponga de un GestureOverlayView.

3. 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.

 

Solución:

Los pasos a seguir para realizar el ejercicio anterior se describen a continuación:

1. Añade al principio de res/layout/main.xml el siguiente código. Cierra la etiqueta al final del fichero.

<android.gesture.GestureOverlayView

   xmlns:android="http://schemas.android.com/apk/res/android"

   android:id="@+id/gestures"

   android:layout_width="fill_parent"

   android:layout_height="fill_parent"

   android:gestureStrokeType="multiple"

   android:fadeOffset="800">
 

2. En la clase MainActivity añade en la definición:

public class MainActivity extends Activity implements OnGesturePerformedListener{

    private GestureLibrary libreria;

    …         
3. Añade al final del método onCreate:

libreria= GestureLibraries.fromRawResource(this, R.raw.gestures);

if(!libreria.load()) {

   finish();

}

GestureOverlayView gesturesView = (GestureOverlayView) findViewById(R.id.gestures);

gesturesView.addOnGesturePerformedListener(this);
 

4.  Añade el siguiente método:

public void onGesturePerformed(GestureOverlayView ov, Gesture gesture) {

   ArrayList<Prediction> predictions=libreria.recognize(gesture);

   if(predictions.size()>0){

          String comando = predictions.get(0).name;

          if(comando.equals("play")){

                 lanzarJuego();

          } else if(comando.equals("configurar")){

                 lanzarPreferencias();

          } else if(comando.equals("acerca_de")){

                 lanzarAcercaDe();

          } else if(comando.equals("cancelar")){

                 finish();

          }

   }

}