Multitouch

Este apartado está derivado del trabajo de Erik Pavey que puede leerse aquí. En su post Erik proporciona a la comunidad una clase para tratar en Processing con los eventos de toque de la pantalla de Android. En este apartado vamos a comentar su desarrollo, e introduciremos en éste la detección de eventos “Release”.

mousePressed, mouseReleased, mouseMoved

Android Processing mantiene por ahora la funcionalidad de estos métodos con los que en Processing manejamos los eventos de ratón. Si pensamos en un contexto en el que la pantalla del dispositivo va a recibir la información de los toques como si de un ratón (un único puntero) se tratara, estas funciones de siempre van a ser las que mejor nos sirvan. Con ellas podríamos replicar, por ejemplo, un Angry Birds o idear un ejemplo sencillo como éste de Jer Thorp.

Pero en otras ocasiones buscaremos emplear la posibilidad del multitouch, tal y como lo se hace tan fabulosamente en juegos como Eliss, y entonces estas conocidas funciones se nos quedarán cortas.

La función surfaceTouchEvent

Para capturar los multitouch tendremos que incluir en nuestro código la función donde Android indica qué es lo que está pasando entre la pantalla y los dedos de los usuarios:

public boolean surfaceTouchEvent(MotionEvent me) {
 
  // Aquí tenemos que escribir el código
 
  return super.surfaceTouchEvent(me);
}

MotionEvent es el objeto en el que Android almacena la información del conjunto de toques simultáneos. Coordenadas e id, por ejemplo:

MotionEvent me;
float touchX = me.getX(index);
float touchY = me.getY(index);
int id = me.getPointerId(index);

Como vemos, getX o getY requieren como argumento un número de índice, que es con el que indicamos el ordinal del toque del que queremos recibir la información, esto es, si es del primer dedo en la pantalal, del segundo, etc. No olvidemos que cada MotionEvent no contiene la info de un toque aislado, sino la de todos los toques que estén teniendo lugar a la vez.

Interpretar entonces la información continua de los toques sobre la pantalla no es tarea tan evidente. Pero como decimos, contamos con el trabajo de Erik Pavey que nos ha proporcionado una clase de objetos “toque” que interpretan y almacenan esta información para nosotros.

La clase Multitouch

La copiamos a continuación, y le añadimos la detección de eventos release, esto es, la detección de cuando cualquier dedo deje de estar en la pantalla.

//------------------------------
// Class to store our multitouch data per touch event.
// Eric Pavey - 2011-01-02
//
// + 2012-01-01 Añadida gestión de "release" (croopier/ultra-lab.net)
 
 
class MultiTouch {
  // Public attrs that can be queried for each touch point:
  float motionX, motionY;
  float pmotionX, pmotionY;
  float size, psize;
  int id;
  boolean touched = false;
  boolean released = false;
 
  // Executed when this index has been touched:
  void update(MotionEvent me, int index) {
    // me : The passed in MotionEvent being queried
    // index : the index of the item being queried
 
    pmotionX = motionX;
    pmotionY = motionY;
    psize = size; 
 
    motionX = me.getX(index);
    motionY = me.getY(index);
    size = me.getSize(index);
 
    id = me.getPointerId(index);
    int pointerIndex, pointerId;
 
    // Cada MotionEvent tiene asociada una ACTION, que puede ser ACTION_UP o ACTION_DOWN.
    // Esta parte del código nos permite distinguir entre unas y otras, y así
    // incorporar la detección de "release"
    switch (me.getAction() & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN:
        touched = true;
        released = false;
        break;
      case MotionEvent.ACTION_UP:
        touched = false;
        released = true;
        break;
      case MotionEvent.ACTION_MOVE:
        touched = true;
        released = false;
        break;
      case MotionEvent.ACTION_POINTER_DOWN:
        pointerIndex = (me.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
        pointerId = me.getPointerId(pointerIndex);  
        if (pointerId == id) {
          touched = true;
          released = false;        
        }
        break;
      case MotionEvent.ACTION_POINTER_UP:
        pointerIndex = (me.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
        pointerId = me.getPointerId(pointerIndex);    
        if (pointerId == id) {
          touched = false;
          released = true;
        }
        break;
    }
  }
 
  // Executed if this index hasn't been touched:
  void update() {
    pmotionX = motionX;
    pmotionY = motionY;
    psize = size;
    if (touched) {
      released = true;
      touched = false;
    }
    else released = false;
  }
}

Declaramos e inicializamos objetos de esta clase Multitouch

Una vez incorporada esta clase al código de nuestro sketch, podemos declarar los objetos que utilizaremos para interpretar el flujo de eventos de toque resultado de la interacción del usuario. Tendremos para ello que declarar los objetos (en un array, en este caso):

MultiTouch[] mt;
int maxTouchEvents = 5;

e inicializarlos en el setup():

  // Setup para MultiTouch
  mt = new MultiTouch[maxTouchEvents];
  for (int n = 0; n < maxTouchEvents; n++) {
    mt[n] = new MultiTouch();
  }

Volvemos a surfaceTouchEvent

Una vez declarados e inicializados, podemos pasar a interpretar los eventos capturados por la función surfaceTouchEvent de este modo:

public boolean surfaceTouchEvent(MotionEvent me) {
  // Find number of touch points:
  int pointers = me.getPointerCount();
 
  //  Update MultiTouch that 'is touched':
  for (int i=0; i < maxTouchEvents; i++) {
    if (i < pointers) {
      mt[i].update(me, i);
    }
    // Update MultiTouch that 'isn't touched':
    else {
      mt[i].update();
    }
  }
 
  // If you want the variables for motionX/motionY, mouseX/mouseY etc.
  // to work properly, you'll need to call super.surfaceTouchEvent().
  return super.surfaceTouchEvent(me);
}

Rápidamente podemos ver lo que hace esta función. pointers es una variable que recibe el número de toques/dedos que han tenido lugar en este evento gracias al método me.getPointerCount(). Sabemos automáticamente entonces que habrá un número igual a pointers de objetos de la clase Multitouch que recibirán nueva información, y que el resto ya no están en acción. Dado que la clase incorpora dos funciones update distintas para uno y otro caso -los tocados y los no-, actualizamos unos y otros con su update correspondiente.

El código de la clase Multitouch se encarga entonces de actualizar si cada uno de sus instancias está “touched” y “released”. Esta información es la que podremos utilizar en cualquier parte de nuestro sketch. Por ejemplo, tal y como lo hace Erik en su demostración:

void draw() {
  // If the screen is touched, start querying for MultiTouch events:
  if (mousePressed == true) {
    // ...for each possible touch event...
    for(int i=0; i < maxTouchEvents; i++) {
      // If that event been touched...
      if(mt[i].touched == true) {
        // DO SOMETHING WITH IT HERE:
        // Amazing mult-touch graphics implemeneted....
      }
    }
  }
}