I am planning to create a new android gesture Api

Currently, the most used gesture api for android is ketaigesture. This api solved the basic android gesture requirements.
However, there are problems like some functions will falsely call multiple times (ontap for example), using with standard touchstarted(), touchmoved(), touchended() have some levels of delay(not synchronised) which really caused some issues.

I will update this post when I have some progress.

5 Likes

I have some progress now.
All the gestures are based on standard touchstarted(), touchmoved(), touchended(), no need to override surfaceTouchEvent() like ketaigesture.
Currently working gestures includes onTap, onLongpressed, onDrag, onPinch.
These gestures will be detected precisely.
After some further tests, I will publish a beta version.

2 Likes

Hi @technew – thanks so much for sharing what you found. Were you able to make any progress on this? Do you have any follow-up questions for the forum?

Hi, forum.
It has been a long time due to some unexpected issue, I might not be able finish this plan.
Some parts of this new api are kind of useable. I will post the source code below, anyone interested in this topic can test themselves.

import java.lang.reflect.Method;

import processing.core.PApplet;

public class PGestureHelper {

    private PApplet p;
    public float rangeX, rangeY;
    public float xOffset, yOffset; //viewport offset
    public float scale = 1f;
    private Method tapMethod, dragMethod, longPressMethod, pinchMethod;

    public float width,height; //actual
    private float cWidth, cHeight; //canvas
    private float oWidth, oHeight; //original

    public PGestureHelper(PApplet parent, float canvasWidth, float canvasHeight) {
        p = parent;
        cWidth = canvasWidth;
        cHeight = canvasHeight;
        rangeX = (cWidth - p.width) / 2;
        rangeY = (cHeight - p.height) / 2;
        xOffset = -scaleFactor() * p.width / 2;
        yOffset = -scaleFactor() * p.height / 2;
        width = oWidth = p.width;
        height = oHeight = p.height;
        initialiseMethods();
    }

    private boolean longPressDetected = false;
    private boolean locked = false;
    private int pinches = 0; //>=2 valid
    private float startX, startY;
    public float dragX, dragY;
    public float preMidX, preMidY;
    private int touchTime;
    private float[] preXY, curXY;

    public void start() {

        if (p.touches.length == 1) {
            preXY = null;
            pinches = 0;
            startX = dragX = p.mouseX;
            startY = dragY = p.mouseY;
        }

        if (locked) {return;}
        locked = true;

        touchTime = p.millis();
        new Thread(() -> {
            longPressDetected = false;
            while (!longPressDetected && locked) {
                if (p.millis() - touchTime >= 500) {
                    longPressDetected = true;
                    if (p.touches.length > 0 && PApplet.dist(startX, startY, p.mouseX, p.mouseY) < 36) {
                        call(longPressMethod, startX, startY);
                    }
                    locked = false;
                }
            }
        }).start();
    }

    public void move() {

        if (p.touches.length == 1) {

            if (pinches > 0) {
                //reset drag position
                pinches = 0;
                dragX = p.mouseX;
                dragY = p.mouseY;
            } else {
                if (!call(dragMethod, p.mouseX, p.mouseY)) {
                    onDrag(p.mouseX, p.mouseY);
                }
                dragX = p.mouseX;
                dragY = p.mouseY;
            }

        } else if (p.touches.length == 2) {

            pinches++;

            curXY = new float[]{p.touches[0].x, p.touches[0].y, p.touches[1].x, p.touches[1].y};

            if (preXY == null) {
                preXY = curXY;
                return;
            }

            float midX = (curXY[0] + curXY[2]) / 2;
            float midY = (curXY[1] + curXY[3]) / 2;

            float dp = PApplet.dist(preXY[0], preXY[1], preXY[2], preXY[3]);
            float dc = PApplet.dist(curXY[0], curXY[1], curXY[2], curXY[3]);

            if (pinches > 2) {
                if (!call(pinchMethod, midX, midY, dc - dp)) {
                    onPinch(midX, midY, dc - dp);
                }
            }

            preMidX = midX;
            preMidY = midY;

            preXY = curXY;
        }
    }

    public void end() {
        if (!longPressDetected && p.millis() - touchTime <= 250) {
            if (PApplet.dist(startX, startY, p.mouseX, p.mouseY) < 36) {
                call(tapMethod, startX, startY);
            }
            locked = false;
        }
    }

    //default drag method
    private void onDrag(float x, float y) {
        xOffset = PApplet.constrain(xOffset + (x - dragX) / scale, -rangeX - scaleFactor() * width, rangeX);
        yOffset = PApplet.constrain(yOffset + (y - dragY) / scale, -rangeY - scaleFactor() * height, rangeY);
    }

    //default pinch method
    private void onPinch(float x, float y, float d) {

        float realX = toRealX(preMidX);
        float realY = toRealY(preMidY);
        scale = PApplet.constrain(scale + d / 250, 0.25f, 2.5f);

        if (d < 0) {
            width = (int) PApplet.min(scale * cWidth, width);
            height = (int) PApplet.min(scale * cHeight, height);
        } else {
            width = (int) PApplet.min(scale * cWidth, oWidth);
            height = (int) PApplet.min(scale * cHeight, oHeight);
        }

        rangeX = (cWidth - width) / 2;
        rangeY = (cHeight - height) / 2;

        xOffset = PApplet.constrain(x / scale - realX, -rangeX - scaleFactor() * width, rangeX);
        yOffset = PApplet.constrain(y / scale - realY, -rangeY - scaleFactor() * height, rangeY);
    }

    @SuppressWarnings("ConstantConditions")
    private boolean call(Method method, float... args) {
        try {
            if (args.length == 2) {
                return (Boolean) method.invoke(p, args[0], args[1]);
            } else if (args.length == 3) {
                return (Boolean) method.invoke(p, args[0], args[1], args[2]);
            }
        } catch (Exception ignored) { }
        return false;
    }

    private void initialiseMethods() {
        try {
            tapMethod = p.getClass().getMethod("onTap",
                    float.class, float.class);
            dragMethod = p.getClass().getMethod("onDrag",
                    float.class, float.class);
            longPressMethod = p.getClass().getMethod("onLongPress",
                    float.class, float.class);
            pinchMethod = p.getClass().getMethod("onPinch",
                    float.class, float.class, float.class);
        } catch (Exception ignored) { }
    }

    public float scaleFactor() { return (scale - 1) / scale;}
    public float toRealX(float touchX) { return touchX / scale - xOffset;}
    public float toRealY(float touchY) { return touchY / scale - yOffset;}

    public void adjustView() {
        p.scale(scale, scale);
        p.translate(xOffset + (cWidth * scale < oWidth ? (oWidth / scale - cWidth) / 2 : 0),
                yOffset + (cHeight * scale < oHeight ? (oHeight / scale - cHeight) / 2 : 0));
    }

}
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import processing.core.PApplet;

public class PDebugInfo {

    private Map<String, Integer> heights;

    public final String fps = "fps";

    private PApplet p;
    private int x, y;
    private int space = 50;
    private float mTextSize = 30;

    private int preS, preF, curF;  // variables to calculate current fps

    public PDebugInfo(PApplet parent) {
        heights = new HashMap<>();
        p = parent;
        x = p.width - 225;
        y = 50;

        preS = Calendar.getInstance().get(Calendar.SECOND);
    }

    @SuppressWarnings("ConstantConditions")
    public void display(String name, Number... value) {

        float textSize = p.g.textSize;
        p.textSize(mTextSize);
        p.fill(0, 255, 0);

        if (!heights.containsKey(name)) {
            heights.put(name, y);
            y += space;
        }

        switch (name) {
            case fps:
                p.text(name + ": " + fpsCur(), x, heights.get(name));
                break;
            default:
                p.text(value.length == 1 ? name + ": " + new DecimalFormat(".00").format(value[0]) : "invalid arg(s)!", x, heights.get(name));
                break;
        }

        p.noFill();
        p.textSize(textSize);
    }

    public void setSpace(int space) {
        this.space = space;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void setTextSize(float textSize) {
        mTextSize = textSize;
    }

    private int fpsCur() {
        if (Calendar.getInstance().get(Calendar.SECOND) != preS) {
            preS = Calendar.getInstance().get(Calendar.SECOND);
            curF = p.frameCount - preF;
            preF = p.frameCount;
        }
        return curF;
    }
}
import processing.core.PApplet;

public class PSketchMain extends PApplet {

    private PGestureHelper gsh;
    private PDebugInfo di;

    public void settings() {
        size(width, height,JAVA2D);
    }

    public void setup() {

        float canvasWidth = 4000;
        float canvasHeight = 4000;

        gsh = new PGestureHelper(this, canvasWidth, canvasHeight);
        di = new PDebugInfo(this);

        background(255);
        smooth(8);
    }

    public void draw() {
        update();

        background(255);
        scale(1, 1);


        displayDebugInfo();

        //rect(0, 0, width, height);

        strokeWeight(5);

        //actual width range    -rangeX ~ width + rangeX (canvasWidth - rangeX)
        //actual height range   -rangeY ~ height + rangeY (canvasHeight - rangeY)

        gsh.adjustView();

        rect(0 - gsh.rangeX, 0 - gsh.rangeY, 200, 200);
        rect(gsh.width / 2f - 100, gsh.height / 2f - 100, 200, 200);
        rect(gsh.width - 200 + gsh.rangeX, gsh.height - 200 + gsh.rangeY, 200, 200);
    }

    private void update() {

    }

    private void displayDebugInfo() {
        di.display(di.fps);
        di.display("xOffset", gsh.xOffset);
        di.display("yOffset", gsh.yOffset);
        di.display("scale", gsh.scale);
        di.display("realX", mouseX / gsh.scale - gsh.xOffset + gsh.rangeX);
        di.display("realY", mouseY / gsh.scale - gsh.yOffset + gsh.rangeY);

    }

    public void touchStarted() {
        System.out.println("testmy start");
        gsh.start();
    }

    public void touchMoved() {
        System.out.println("testmy move");
        gsh.move();
    }

    public void touchEnded() {
        System.out.println("testmy end");
        gsh.end();
    }

    public boolean onTap(float x, float y) {
        return true; //no default method
    }

    public boolean onDrag(float x, float y) {
        return false; //call default drag method
    }

    public boolean onLongPress(float x, float y) {
        return true; //no default method
    }

    public boolean onPinch(float x, float y, float d) {
        return false; //call default pinch method
    }



}
1 Like