Patc-
I just got done fighting this one; your post was the only one I stumbled on when looking for solutions. So, I’ll post mine here for you, if you don’t already have one (and anyone else that comes along).
The long and short of it is…Processing for Android’s documentation is lying to you. touchEnded() is not the only valid function call. If you dig into the source code, it is called by another function which consumes an Android MotionEvent as an argument. The documentation leaves this part out (among many others). So, I overwrote it. I wasn’t able to reliably track where touches were without maintaining my own ArrayList and adding/removing them as they touched/left the screen.
// demo of touches array, based on
// https://android.processing.org/reference/multitouch/touches.html
// https://developer.android.com/reference/android/view/MotionEvent
import android.view.MotionEvent;
ArrayList<Integer> touch_ids = new ArrayList<Integer>();
ArrayList<GrabbableObject> dots = new ArrayList<GrabbableObject>();
int num_dots = 6;
void setup() {
fullScreen();
textFont(createFont("SansSerif", 24 * displayDensity));
textAlign(CENTER, CENTER);
frameRate(30);
for (int i = 0; i < num_dots; i++) {
float dx = random(displayWidth);
float dy = random(displayHeight);
float dd = random(400) + 200;
Dot d = new Dot(dx, dy, dd);
dots.add(d);
}
}
void draw() {
background(255);
for (GrabbableObject dot : dots) {
dot.draw();
}
for (int i = 0; i < touches.length; i++) {
float d = (50 + 100 * touches[i].area) * displayDensity;
fill(0, 255 * touches[i].pressure);
ellipse(touches[i].x, touches[i].y, d, d);
fill(255, 0, 0);
text(touches[i].id, touches[i].x + d/2, touches[i].y - d/2);
}
}
void touchStarted(TouchEvent e) {
MotionEvent me = (MotionEvent) e.getNative(); // this pulls out the data that Android has, on all touches
println("touchStarted");
printTouchArrays(me);
ArrayList<Integer> new_ids = getMotionEventTouchIds(me);
if (me.getActionMasked() != MotionEvent.ACTION_DOWN) { // this is the first touch
new_ids.removeAll(touch_ids); // for all subsequent touches
}
for (int id : new_ids) {
int ptr_idx = me.findPointerIndex(id); // each touch has a unique ID that is not its index in the (Android) array of touch locations
float px = me.getX(ptr_idx);
float py = me.getY(ptr_idx);
for (int d = dots.size() - 1; d >= 0; d--) { // search top-down
GrabbableObject dot = dots.get(d);
boolean grab = dot.check_touch(px, py);
if (grab) {
dot.grab(id, px, py);
break;
}
}
}
touch_ids = getMotionEventTouchIds(me);
println("new_ids = " + new_ids);
println("");
}
void touchMoved(TouchEvent e) {
MotionEvent me = (MotionEvent) e.getNative();
println("touchMoved");
printTouchArrays(me);
for (GrabbableObject dot : dots) {
if (!touch_ids.contains(dot.pointer_id)) {
dot.drop();
continue;
}
if (!dot.grabbed) { continue; }
int ptr_idx = me.findPointerIndex(dot.pointer_id);
if (ptr_idx < 0) {
dot.drop(); // it got missed somehow
continue;
}
float px = me.getX(ptr_idx);
float py = me.getY(ptr_idx);
dot.move(px, py);
}
}
void touchEnded(TouchEvent e) {
MotionEvent me = (MotionEvent) e.getNative();
println("touchEnded");
printTouchArrays(me);
ArrayList<Integer> old_ids = new ArrayList<Integer>();
touch_ids = getMotionEventTouchIds(me);
int action = me.getActionMasked();
/*
ActionIndex does not correspond with the lifted touch
ACTION_UP: always the last one (also the id@ActionIndex)
ACTION_POINTER_UP: it's the id@ActionIndex but will not drop until next move
ACTION_MOVE: it will drop from ME ids but not touches
*/
if (action == MotionEvent.ACTION_UP) {
// handle last touch being removed by making sure all touches are removed
old_ids = new ArrayList<Integer>(touch_ids);
println("ACTION_UP @ " + old_ids);
} else if (action == MotionEvent.ACTION_POINTER_UP) {
// handle not-last touch(es) being removed
int idx = me.getActionIndex();
int id = me.getPointerId(idx);
old_ids.add(id);
println("ACTION_POINTER_UP @ " + old_ids);
} else if (action == MotionEvent.ACTION_MOVE) {
// handle not-last touch being removed in motion
old_ids = getTouchesTouchIds();
old_ids.removeAll(touch_ids);
println("ACTION_MOVE @ " + old_ids);
} else {
println("something weird happened @ " + me.getActionIndex() + ": " + action);
background(0);
}
for (int oid : old_ids) {
for (GrabbableObject dot : dots) {
if (oid == dot.pointer_id) {
dot.drop();
}
}
}
touch_ids.removeAll(old_ids);
println("");
}
void printTouchArrays(MotionEvent me) {
println("MotionEvent = " + getMotionEventTouchIds(me));
println("touches = " + getTouchesTouchIds());
println("touch_ids = " + touch_ids);
}
ArrayList<Integer> getMotionEventTouchIds(MotionEvent me) {
ArrayList<Integer> ids = new ArrayList<Integer>();
for (int m = 0; m < me.getPointerCount(); m++) {
int pid = me.getPointerId(m);
ids.add(pid);
}
return ids;
}
ArrayList<Integer> getTouchesTouchIds() {
ArrayList<Integer> ids = new ArrayList<Integer>();
for (int t = 0; t < touches.length; t++) {
int tid = touches[t].id;
ids.add(tid);
}
return ids;
}
class GrabbableObject {
PVector pos;
boolean active;
int pointer_id;
boolean grabbed;
PVector pointer_pos;
GrabbableObject(float ix, float iy) {
pos = new PVector(ix, iy);
active = true;
}
void draw() {}
boolean check_touch(float tx, float ty) {
return false;
}
void grab(int p, float px, float py) {
pointer_id = p;
pointer_pos = new PVector(px, py);
grabbed = true;
}
void move(float px, float py) {
if (!grabbed) { return; }
float dx = px - pointer_pos.x;
float dy = py - pointer_pos.y;
pointer_pos.x += dx;
pointer_pos.y += dy;
pos.x += dx;
pos.y += dy;
}
void drop() {
pointer_id = -1;
pointer_pos = null;
grabbed = false;
}
}
class Dot extends GrabbableObject {
float d, r;
color fill_color, stroke_color;
Dot(float ix, float iy, float id) {
super(ix, iy);
d = id;
r = d / 2.0;
stroke_color = color(255, 0, 0);
drop();
}
void draw() {
if (active) {
stroke(stroke_color);
fill(fill_color);
ellipse(pos.x, pos.y, d, d);
fill(0, 0, 255);
}
}
void grab(int p, float px, float py) {
fill_color = color(0, 0, 255);
super.grab(p, px, py);
}
void drop() {
fill_color = color(0, 255, 0);
super.drop();
}
boolean check_touch(float tx, float ty) {
if ((tx < pos.x - r) || (pos.x + r < tx)) { return false; }
if ((ty < pos.y - r) || (pos.y + r < ty)) { return false; }
float dist = sqrt(sq(pos.x - tx) + sq(pos.y - ty));
if (dist > r) { return false; }
return true;
}
}
I’m all for improvements! If you find a way to improve on this, please let me know!