touchEnded event location

Hello guys!
I am coding an app which needs to react when a touch is ended (touchEnded) and get the event’s location on the screen, but I failed to get where the event has been triggered.
Among other methods, I tried to use the native event (but any arithmetical or logical comparison systematically fails), and to compare the touches array (touches) with a touches history (prevTouches) I created. These two methods failed.
Is there a simple (or anyway easier) way to do it?

A last thing: I don’t and won’t use Ketai library, it did not work on the devices I tried, and until this point my code perfectly runs using parts of the Android API.

Hi and welcome

void setup() {
  fullScreen();
}

void draw() {
  if (touchIsStarted) {
    background(150);
  } else {
    background(255);
  }
}    

void touchStarted() {
  println("touch started");
}

void touchMoved() {
  println("touch moved"); 
}

void touchEnded() {
  println("touch ended");  
}

https://android.processing.org/reference/multitouch/events.html

I know how to handle touchEnded events. Now the problem is to get the location of the events on the screen.
Thanks anyway!

1 Like

Hi

Mouse working the same

It does not work with multitouch

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!

2 Likes

This is great!
Now I finally have a viable starting point to make a multitouch gui/controller to create games.
Something like a directional knob and action buttons…

Your solution works on Android using apde!
Which is a godsend, since I only have my Android phone with apde to work with :wink:

Thank you so much for that!

I just had to remove all the println’s so it’s not stuttering :sweat_smile:

The following technique appears to get touch up location in Android:

import android.app.Activity;
import android.view.View.OnTouchListener;
import android.view.MotionEvent;
import android.view.View;

Activity activity;

int x, y;

void getTouch() {
  View contentView = activity.getCurrentFocus();
  contentView.setOnTouchListener(new View.OnTouchListener() {
    @Override
      public boolean onTouch(View v, MotionEvent event) {
      if (event.getAction() == MotionEvent.ACTION_UP) {
        x = (int)event.getX();
        y = (int)event.getY();
        println("touch x,y: " + x + "," + y );
      }
      return true;
    }
  }
  );
}

void setup() {
  fullScreen();
  orientation(LANDSCAPE);
  background(color(0, 0, 255));
  activity = this.getActivity();
  runOnUiThread(new Runnable() {
    void run() {
      getTouch();
    }
  }
  );
}

void draw() {
  fill(255);
  if ((x > 0) && (y > 0)){
      circle(x, y, 30);
  }
}