How To: Identify which pointer moved in Android

Heads up: When I say pointer in this article, I mean a finger or a stylus – anything in contact with the touchscreen.

Multi-touch in Android is a bit of a strange beast, although you could argue that it’s by necessity. With ACTION_DOWN (primary pointer in contact) or ACTION_POINTER_DOWN (non-primary pointer in contact), or even the corresponding ACTION_UP or ACTION_POINTER_UP events it’s fine. But when it comes to ACTION_MOVE events – you can’t just ask which finger moved, all you’re told is that A finger moved, and you have to figure out which one for yourself as far as I can tell.

This is what I’ve come up with to figure out which pointer has actually moved – it falls behind by one ACTION_MOVE event because there doesn’t seem to be a way to ask for the current X location of a pointer by its index location (that is, there’s no getX(pointerIndex), only a getHistoricalX(pointerIndex, historyPosition)) – so this code misses the very first move event when a pointer moves – BUT – moving your finger generates a large number of move events, so I’m thinking this might not be that much of an issue.

Update: I’d forgotten you can getX(pointerIndex) and as well as getHistoricalX(pointerIndex, historyLocation) – so I’ve modified the code below to do that, which means I don’t think we miss any move events anymore because we’re now comparing current location to historical(0) rather than historical(0) to historical(1).

Anyway, here’s my code which addresses this finicky problem:

@Override
public boolean onTouchEvent(MotionEvent event)
{
    // Get pointer index from the event object...
    int pointerIndex = event.getActionIndex();
 
    // ...which we can use to find the pointer ID!
    int pointerId = event.getPointerId(pointerIndex);
 
    // Get masked action (i.e. action which is not specific to a pointer)
    int maskedAction = event.getActionMasked();
 
    // Depending on what the action was, act appropriately...
    switch (maskedAction)
    {
        case MotionEvent.ACTION_DOWN:
            System.out.println("Pointer with Id: " + pointerId + " at index " + pointerIndex + " down i.e. Primary pointer down!");
            break;
 
        case MotionEvent.ACTION_POINTER_DOWN:
            System.out.println("Pointer with Id: " + pointerId + " at index " + pointerIndex + " down i.e. NON-Primary pointer down!");
            break;
 
        // The MOVE event must be done separately. This is because the ACTION_MOVE event always gets
        // zero as the pointerId for some strange reason. As such, you always have to loop over the
        // pointer indexes and compare positions to the pointer's previous (i.e. historical) position.
        case MotionEvent.ACTION_MOVE:
            int pointerCount = event.getPointerCount();
            for(int i = 0; i < pointerCount; ++i)
            {
                // To find out WHICH pointer moved we must compare pointer historical locations
                if (event.getHistorySize() > 0)
                {
                    // X or Y location for that pointer index moved?
                    // Corner-case: Pointer index changed (pointer up or down promoted or demoted pointer index while moving?) 
                    // Fix: Track by pointer Id via sparse array as outlined in second potential solution below.
                    if ( (int)event.getX(i) != (int)event.getHistoricalX(i,0) || (int)event.getY(i) != (int)event.getHistoricalY(i, 0) )
                    {
                        pointerId = event.getPointerId(i);
                        System.out.println("Pointer with Id: " + pointerId + " at index " + i + " moved!");
                    }
                }
            }
            break;
 
        case MotionEvent.ACTION_UP:
            System.out.println("Pointer with Id: " + pointerId + " at index " + pointerIndex + " up i.e. Primary pointer up!");
            break;
 
        case MotionEvent.ACTION_POINTER_UP:
            System.out.println("Pointer with Id: " + pointerId + " at index " + pointerIndex + " up i.e. NON-Primary pointer up!");
            break;
    }
 
    // Consume the event so that it is not processed any further
    return true;
 
} // End of onTouchEvent method

As I’ve been playing around with this it seems to match up with what I’m doing very well without issue, for example:

Pointer with Id: 0 at index 0 down i.e. Primary pointer down!      <--- 1st finger down
Pointer with Id: 0 at index 0 moved!                               <--- 1st finger moved
Pointer with Id: 0 at index 0 moved!
...
Pointer with Id: 1 at index 1 down i.e. NON-Primary pointer down!  <--- 2nd finger down
Pointer with Id: 1 at index 1 moved!                               <--- 2nd finger moved
Pointer with Id: 1 at index 1 moved!
...
Pointer with Id: 0 at index 0 up i.e. NON-Primary pointer up!      <--- 1st finger up, promoting 2nd finger to primary!
Pointer with Id: 1 at index 0 moved!                               <--- 2nd finger (now primary) moved
Pointer with Id: 1 at index 0 moved!
...
Pointer with Id: 0 at index 0 down i.e. NON-Primary pointer down!  <--- 1st finger back down, DEMOTING 2nd finger from primary!
Pointer with Id: 0 at index 0 moved!                               <--- 1st finger moved
...
Pointer with Id: 1 at index 1 up i.e. NON-Primary pointer up!      <--- 2nd finger up
Pointer with Id: 0 at index 0 up i.e. Primary pointer up!          <--- 1st finger up

Alternative Solution

Keep a sparse array of whatever you're keeping track of - for example, let's say we've got the world's simplest Circle class:

public class Circle
{
	public float x;
	public float y;
}

Then in your class handling the onTouchEvent() method you could have something like this (in this particular example we have a view which responds to multi-touch events):

// imports here...
 
public class MultiTouchCircleView extends View
{
    private static final int SIZE = 150;
 
    // Note: A SparseArray is kind-of like a hash-map, it maps an
    // integer to an object in a key/value manner.
    private SparseArray<Circle> circleArray;
 
    private Paint paint;
 
    // Array of 10 colours
    private int[] colours = {Color.BLUE, Color.GREEN, Color.RED,
            Color.YELLOW, Color.CYAN, Color.GRAY, Color.MAGENTA, Color.DKGRAY,
            Color.LTGRAY, Color.YELLOW};
 
    private Paint textPaint;
 
    public MultiTouchCircleView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        initView();
    }
 
    private void initView()
    {
        circleArray = new SparseArray<Circle>();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
        // set painter color to a color you like
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(50);
        textPaint.setColor(Color.BLUE);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Get pointer index from the event object
        int pointerIndex = event.getActionIndex();
 
        // Get pointer ID
        int pointerId = event.getPointerId(pointerIndex);
 
        // Get masked (not specific to a pointer) action
        int maskedAction = event.getActionMasked();
 
        switch (maskedAction) {
 
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN: {
                // A finger has touched the screen - so let's create a new
                // Circle at the touch location and add it to the list!
                Circle circle = new Circle();
                circle.x = event.getX(pointerIndex);
                circle.y = event.getY(pointerIndex);
                circleArray.put(pointerId, circle);
                break;
            }
 
            case MotionEvent.ACTION_MOVE: {
                // How many pointers are in contact with the screen?
                int pointerCount = event.getPointerCount();
 
                // Loop over them all...
                for (int i = 0; i < pointerCount; ++i) {
                    // i is the pointer index but we'll update our pointerIndex variable for clarity
                    pointerIndex = i;
 
                    // Get the pointerId at that pointerIndex (pointerId never changes while pointer is in contact)
                    pointerId = event.getPointerId(pointerIndex);
 
                    // Get access to the circle with that pointer id...
                    Circle circle = circleArray.get(pointerId);
 
                    // ...and update the circle's location.
                    circle.x = event.getX(pointerIndex);
                    circle.y = event.getY(pointerIndex);
                }
                break;
            }
 
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP: {
                // Finger has left contact - remove the circle from the array
                circleArray.remove(pointerId);
                break;
            }
 
        } // End of switch block
 
        // Trigger redraw to display any changes
        invalidate();
 
        // Return true to consume the MotionEvent
        return true;
    }
 
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
 
        // Draw circles for each pointer in contact with the screen
        int numCircles = circleArray.size();
        for (int i = 0; i < numCircles; ++i)
        {
            // Get the circle at index location i
            Circle circle = circleArray.valueAt(i);
 
            // ...to choose a colour between 0 and 9.
            if (circle != null)
            {
                int colourNum = i % 9;
                paint.setColor(colours[colourNum]);
                paint.setColor(colours[i]);
            }
 
            // Draw a circle in our chosen colour
            canvas.drawCircle(circle.x, circle.y, SIZE, paint);
        }
        canvas.drawText("Total pointers: " + numCircles, 10, 100, textPaint);
 
    } // End of onDraw method
 
} // End of MultiTouchCircleView class

This technique doesn't miss any ACTION_MOVE events because it looks them up by pointerId in the SparseArray - which is potentially a nicer solution. Go with whatever works for you.

The This Pointer in C++

2017-02 Update

I didn’t know about boxing and unboxing of primitives to objects (or much about references as it happens) when I wrote this back in 2013. Primitives like int/bool/float/char/double etc. DO get copied via the default copy constructor because they have default working copy/assignment functions. However if the Example class held an instance of, say, a DeepCopyWang object (it was the first thing I could think of) – it wouldn’t copy cleanly with the default assignment or copy constructors – any new object would just hold a reference to the original DeepCopyWang object and its properties. That is, the new object trying to be a copy of the first would be a shallow copy (i.e. it may contain references to properties of the first object) and not a deep copy (i.e. completely separate objects and all properties there-in which can be individually manipulated). So take the below with a suitably sized pinch of salt.


Still on my fundamentals trip, I’m hitting up the ‘this’ pointer. Every class that you create has a ‘this’ pointer invisibly assigned to it by the compiler. Let’s look at a simple class to see what’s going on:

class Example
{
private:
	int a;
public:
	void setA(int value) { a = value; }
	int  getA()          { return a;  }
};

When you write the above code the compiler does some fun things with it, such as invisibly adding four methods:

  • A default constructor (that takes no parameters) which is automatically executed when you instantiate an object of this type,
  • A destructor (again no parameters) which is automatically executed when an object of this type is deleted or goes out of scope,
  • A copy constructor (that takes another object of this type) and performs a shallow copy from the source object to the (new) destination object, and
  • An assignment operator (that takes another object of this type) and which again performs a shallow copy from that object the the object you’re assigning to.

If we explicitly write these four methods into our class, we end up with our (exactly, exactly equivalent) code now being:

class Example
{
private:
	int a;
public:
	// Constructor
	Example()
	{
		// Do nothing
	}
 
	// Destructor
	~Example()
	{
		// Do nothing
	}
 
	// Copy constructor
	Example(const Example& rhs)
	{
		a = rhs.a;
	}
 
	// Overloaded assignment operator
	Example& operator=(const Example& rhs)
	{
		if (this == &rhs)
			return *this;
 
		a = rhs.a;
		return *this;
	}
 
	void setA(int value) { a = value; }
	int  getA()          { return a;  }
};

You can substitute either of these classes into a project, compile it (in Release mode if you have both in there and just comment each out in turn!), and you’ll end up with byte-wise identical executables down to the very last bit. Not only are they functionally equivalent, they’re absolutely equivalent – as the compiler sees them, it’s the exact same code. Don’t take my word for it – try it out, if you’d like!

The ‘this’ pointer’s already being used, but what exactly is it doing? Well, let’s drill down into the nuts and bolts of it and take a look…

Continue reading The This Pointer in C++