En el tutorial anterior analizamos el manejo de eventos de mouse y teclado, tanto impulsados por eventos como sondeados. Ahora veremos cómo funciona el tacto. Para continuar en este punto, debe tener un dispositivo habilitado para tocar (¡el toque múltiple con un mouse es complicado por decir lo menos!) aunque todo el código funcionará en objetivos de escritorio y HTML, simplemente no podrá probarlo. Empecemos con un ejemplo. Este ejemplo muestra cómo manejar múltiples toques simultáneos:
Multitáctil
package com.gamefromscratch; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds; import com.badlogic.gdx.graphics.g2d.SpriteBatch; public class InputDemo2 implements ApplicationListener, InputProcessor { private SpriteBatch batch; private BitmapFont font; private String message = "Touch something already!"; private int w,h; class TouchInfo { public float touchX = 0; public float touchY = 0; public boolean touched = false; } private Map<Integer,TouchInfo> touches = new HashMap<Integer,TouchInfo>(); @Override public void create() { batch = new SpriteBatch(); font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false); font.setColor(Color.RED); w = Gdx.graphics.getWidth(); h = Gdx.graphics.getHeight(); Gdx.input.setInputProcessor(this); for(int i = 0; i < 5; i++){ touches.put(i, new TouchInfo()); } } @Override public void dispose() { batch.dispose(); font.dispose(); } @Override public void render() { Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); batch.begin(); message = ""; for(int i = 0; i < 5; i++){ if(touches.get(i).touched) message += "Finger:" + Integer.toString(i) + "touch at:" + Float.toString(touches.get(i).touchX) + "," + Float.toString(touches.get(i).touchY) + "n"; } TextBounds tb = font.getBounds(message); float x = w/2 - tb.width/2; float y = h/2 + tb.height/2; font.drawMultiLine(batch, message, x, y); batch.end(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } @Override public boolean keyDown(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyUp(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyTyped(char character) { // TODO Auto-generated method stub return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { if(pointer < 5){ touches.get(pointer).touchX = screenX; touches.get(pointer).touchY = screenY; touches.get(pointer).touched = true; } return true; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if(pointer < 5){ touches.get(pointer).touchX = 0; touches.get(pointer).touchY = 0; touches.get(pointer).touched = false; } return true; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { // TODO Auto-generated method stub return false; } @Override public boolean mouseMoved(int screenX, int screenY) { // TODO Auto-generated method stub return false; } @Override public boolean scrolled(int amount) { // TODO Auto-generated method stub return false; } }
Ahora, cuando lo ejecute, se mostrará información de diagnóstico para cada dedo con el que toque:
Para cada dedo muestra las coordenadas en las que se toca el dedo, hasta un total de 5 dedos.
Entonces, ¿qué está pasando aquí exactamente? Creamos una clase simple TouchInfo para contener detalles táctiles: si se toca, las coordenadas X e Y. Luego creamos un HashMap de toques con un Integer como clave y una clase TouchInfo como datos. La clave será el índice del dedo con el que toque. La lógica está realmente en los controladores de eventos touchDown y touchUp. Al aterrizar, actualizamos el mapa de toques en el índice representado por el puntero de valor. Como recordará del tutorial anterior, el puntero de valor representa qué dedo se presiona actualmente. Cuando se suelta el dedo, se dispara TouchUp y simplemente borramos la entrada táctil en esa ubicación. Finalmente, en render() recorremos el mapa de toques y mostramos los que se tocan y dónde.
Al final del día, los toques son básicamente idénticos a los clics del mouse, excepto que puede tener varios de ellos y no hay botones. Oh, supongo que debería mencionar que el límite de 5 toques en este ejemplo fue solo un número que elegí arbitrariamente. LibGDX admite hasta 20 toques, aunque ningún dispositivo lo hace. El iPad, por ejemplo, puede rastrear hasta 11, el iPhone rastrea hasta 5, mientras que el HTC One rastrea 10. En su teléfono de Google, puede rastrear cuántos toques admite. usando esta aplicación. Dicho esto, 5 es un número bastante seguro y razonable… Diablos, no creo haber usado nunca más de 4 en ningún dispositivo.
Gestos táctiles
Hay una serie de gestos comunes que se han vuelto omnipresentes en el mundo móvil. Cosas como pellizcar para hacer zoom, o mover / lanzar y presionar prolongadamente se han convertido en la norma. Afortunadamente, GDX es compatible con todas estas cosas desde el primer momento. Pasemos directamente a una demostración simple:
package com.gamefromscratch; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.input.GestureDetector; import com.badlogic.gdx.input.GestureDetector.GestureListener; import com.badlogic.gdx.math.Vector2; public class InputDemo3 implements ApplicationListener, GestureListener { private SpriteBatch batch; private BitmapFont font; private String message = "No gesture performed yet"; private int w,h; @Override public void create() { batch = new SpriteBatch(); font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false); font.setColor(Color.RED); w = Gdx.graphics.getWidth(); h = Gdx.graphics.getHeight(); GestureDetector gd = new GestureDetector(this); Gdx.input.setInputProcessor(gd); } @Override public void dispose() { batch.dispose(); font.dispose(); } @Override public void render() { Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); batch.begin(); TextBounds tb = font.getBounds(message); float x = w/2 - tb.width/2; float y = h/2 + tb.height/2; font.drawMultiLine(batch, message, x, y); batch.end(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } @Override public boolean touchDown(float x, float y, int pointer, int button) { // TODO Auto-generated method stub return true; } @Override public boolean tap(float x, float y, int count, int button) { message = "Tap performed, finger" + Integer.toString(button); return true; } @Override public boolean longPress(float x, float y) { message = "Long press performed"; return true; } @Override public boolean fling(float velocityX, float velocityY, int button) { message = "Fling performed, velocity:" + Float.toString(velocityX) + "," + Float.toString(velocityY); return true; } @Override public boolean pan(float x, float y, float deltaX, float deltaY) { message = "Pan performed, delta:" + Float.toString(deltaX) + "," + Float.toString(deltaY); return true; } @Override public boolean zoom(float initialDistance, float distance) { message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) + " Distance: " + Float.toString(distance); return true; } @Override public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { message = "Pinch performed"; return true; } }
Si lo ejecuta, a medida que realiza varios gestos, se mostrarán centrados en la pantalla. Los gestos admitidos incluyen tocar, lanzar (mover), pellizcar (dos dedos que se acercan), zoom (dos dedos que se separan), desplazamiento (mantener y deslizar con un dedo) y presión prolongada (tocar y mantener), así como un buen toque de moda. .
Al igual que implementamos InputProcessor para manejar eventos táctiles, del mouse y del teclado, implementamos GestureListener para aceptar eventos de gestos de LibGDX. En create() creas un GestureDetector usando tu GestureListener y una vez más lo registras usando Gdx.input.setInputProcessor(). Cada gesto diferente desencadena el correspondiente incluso en su GestureListener. En cada uno, simplemente actualizamos el mensaje mostrado para reflejar el evento realizado más recientemente.
Un concepto importante que no cubrimos aquí es configurar su GestureDetector… ¿cómo determina cuánto dura un toque prolongado o cuánto tiempo debe transcurrir antes de que un arrastre se convierta en un movimiento rápido? La respuesta simple es usar el constructor GestureDetector. Puedes leer más sobre eso aquí.
Manejo de múltiples procesadores de entrada
Entonces, ¿qué pasaría si quisiera manejar gestos Y eventos de teclado… entonces qué? Afortunadamente, la respuesta es bastante simple, en lugar de pasar setInputProcessor un InputProcessor o GestureDetector, en su lugar pasa un InputMultiplexer. Al igual que:
package com.gamefromscratch; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.input.GestureDetector; import com.badlogic.gdx.input.GestureDetector.GestureListener; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.math.Vector2; public class InputDemo4 implements ApplicationListener, GestureListener, InputProcessor { private SpriteBatch batch; private BitmapFont font; private String message = "No gesture performed yet"; private int w,h; @Override public void create() { batch = new SpriteBatch(); font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false); font.setColor(Color.RED); w = Gdx.graphics.getWidth(); h = Gdx.graphics.getHeight(); InputMultiplexer im = new InputMultiplexer(); GestureDetector gd = new GestureDetector(this); im.addProcessor(gd); im.addProcessor(this); Gdx.input.setInputProcessor(im); } @Override public void dispose() { batch.dispose(); font.dispose(); } @Override public void render() { Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); batch.begin(); TextBounds tb = font.getBounds(message); float x = w/2 - tb.width/2; float y = h/2 + tb.height/2; font.drawMultiLine(batch, message, x, y); batch.end(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } @Override public boolean touchDown(float x, float y, int pointer, int button) { message = "Touch down!"; Gdx.app.log("INFO", message); return true; } @Override public boolean tap(float x, float y, int count, int button) { message = "Tap performed, finger" + Integer.toString(button); Gdx.app.log("INFO", message); return false; } @Override public boolean longPress(float x, float y) { message = "Long press performed"; Gdx.app.log("INFO", message); return true; } @Override public boolean fling(float velocityX, float velocityY, int button) { message = "Fling performed, velocity:" + Float.toString(velocityX) + "," + Float.toString(velocityY); Gdx.app.log("INFO", message); return true; } @Override public boolean pan(float x, float y, float deltaX, float deltaY) { message = "Pan performed, delta:" + Float.toString(deltaX) + "," + Float.toString(deltaY); Gdx.app.log("INFO", message); return true; } @Override public boolean zoom(float initialDistance, float distance) { message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) + " Distance: " + Float.toString(distance); Gdx.app.log("INFO", message); return true; } @Override public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { message = "Pinch performed"; Gdx.app.log("INFO", message); return true; } @Override public boolean keyDown(int keycode) { message = "Key Down"; Gdx.app.log("INFO", message); return true; } @Override public boolean keyUp(int keycode) { message = "Key up"; Gdx.app.log("INFO", message); return true; } @Override public boolean keyTyped(char character) { message = "Key typed"; Gdx.app.log("INFO", message); return true; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { message = "Touch Down"; Gdx.app.log("INFO", message); return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { message = "Touch up"; Gdx.app.log("INFO", message); return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { message = "Touch Dragged"; Gdx.app.log("INFO", message); return false; } @Override public boolean mouseMoved(int screenX, int screenY) { message = "Mouse moved"; Gdx.app.log("INFO", message); return false; } @Override public boolean scrolled(int amount) { message = "Scrolled"; Gdx.app.log("INFO", message); return false; } }
Debido al hecho de que se pueden disparar varios eventos a la vez, además de imprimirlos en la pantalla, también los registramos usando Gdx.app.log(). Puede ver los eventos registrados en la ventana de LogCat en Eclipse:

También hay un programa llamado ddms (es un script BAT en Windows) en la carpeta de herramientas de Android-sdk que mostrará la misma información.

Entonces, eso es lo que hace log()… ahora volvamos al código. La parte clave aquí es:
InputMultiplexer im = new InputMultiplexer(); GestureDetector gd = new GestureDetector(this); im.addProcessor(gd); im.addProcessor(this); Gdx.input.setInputProcessor(im);
Esencialmente, usted crea el multiplexor, luego le agrega el InputProcessor y el GestureDetector a través de addProcessor(), luego es el multiplexor el que se pasa a setInputProcessor(). De lo contrario, el código funciona prácticamente exactamente igual. Hay dos cosas de importancia crítica aquí. En primer lugar, el orden en que se agregan los procesadores al multiplexor parece determinar el orden en el que tendrán la primera respuesta a los eventos que ocurrieron. A continuación, y este es muy importante, en los controladores de eventos, si devuelve verdadero, eso significa que el evento está controlado. Piense en eso por un segundo, es un concepto importante de entender. Si bien algo como un evento de retoque hacia arriba o hacia abajo es bastante sencillo, un evento de pellizco, digamos, no lo es. De hecho, un evento de pellizco se compone de una serie de otros eventos, incluidos varios eventos táctiles. Sin embargo, si devuelve true de, digamos, el evento touchDown, ese evento no pasará al detector de gestos. Por lo tanto, si admite multitáctil, asegúrese de devolver false de los eventos más atómicos, como touchDown y TouchUp, ¡para que aún puedan manejar esos eventos!