A multi-touch gesture is when multiple pointers, such as fingers, touch the screen at the same time. This guide describes how to detect gestures that involve multiple pointers.
Refer to the following related resources:
Track multiple pointers
When multiple pointers touch the screen at the same time, the system generates the following touch events:
ACTION_DOWN
: Starts the gesture when the first pointer touches the screen. This pointer is always at index 0 in theMotionEvent
.ACTION_POINTER_DOWN
: Records extra pointers that enter the screen after the first. Obtain the index of each following pointer usinggetActionIndex()
.ACTION_MOVE
: Sent when a change has happened in a gesture to one or more pointers.ACTION_POINTER_UP
: Sent when a non-primary pointer goes up. Obtain the index of the pointer that just went up usinggetActionIndex()
and check ifFLAG_CANCELED
is set.ACTION_UP
: Sent when the last pointer leaves the screen.ACTION_CANCEL
: Indicates that the entire gesture, including all pointers, is canceled.
Start and end gestures
A gesture is a series of events starting with anACTION_DOWN
event and ending with
either an ACTION_UP
or ACTION_CANCEL
event. There is one active gesture at
a time. The actions DOWN, MOVE, UP, and CANCEL apply to the entire gesture. For example, an event
with ACTION_MOVE
can indicate a movement for all pointers down at that moment.
Keep track of pointers
Use the pointer's index and ID to keep track of the individual pointers positions within a
MotionEvent
.
- Index: A
MotionEvent
stores pointer information in an array. The index of a pointer is its position within this array. Most of theMotionEvent
methods take the pointer index as a parameter, rather than the pointer ID. - ID: Each pointer also has an ID mapping that stays persistent across touch events to allow for tracking of an individual pointer across the entire gesture.
The order in which individual pointers appear within a motion event is undefined. Although the
index of a pointer can change from one event to the next, the pointer ID is guaranteed to remain
constant as long as the pointer remains active. Use the
getPointerId()
method to get the pointer's ID to track the pointer across all subsequent motion events in a
gesture. Then for successive motion events, use the
findPointerIndex()
method to get the index of a given pointer ID in that motion event, as shown in the following
example:
Kotlin
private var mActivePointerId: Int = 0 override fun onTouchEvent(event: MotionEvent): Boolean { ... // Get the pointer ID mActivePointerId = event.getPointerId(0) // ... Many touch events later... // Use the pointer ID to find the index of the active pointer // and fetch its position val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex -> // Get the pointer's current position event.getX(pointerIndex) to event.getY(pointerIndex) } ... }
Java
private int mActivePointerId; public boolean onTouchEvent(MotionEvent event) { ... // Get the pointer ID mActivePointerId = event.getPointerId(0); // ... Many touch events later... // Use the pointer ID to find the index of the active pointer // and fetch its position int pointerIndex = event.findPointerIndex(mActivePointerId); // Get the pointer's current position float x = event.getX(pointerIndex); float y = event.getY(pointerIndex); ... }
To support multiple touch pointers, you can cache all active pointers with their IDs at their
individual ACTION_POINTER_DOWN
and ACTION_DOWN
event time. Remove the
pointers from your cache at their ACTION_POINTER_UP
and ACTION_UP
events.
You may find these cached IDs helpful to handle other action events correctly. For example, when
processing an ACTION_MOVE
event, find the index for each cached active pointer ID,
retrieve the pointer's coordinates using the
getX()
and
getY()
functions, then
compare these coordinates with your cached coordinates to discover which pointers moved.
Use the getActionIndex()
function with ACTION_POINTER_UP
and
ACTION_POINTER_DOWN
events only. Do not use this function with ACTION_MOVE
events as this will always return 0.
Retrieve MotionEvent
actions
Use the
getActionMasked()
method or the compatibility version
MotionEventCompat.getActionMasked()
to retrieve the action of a
MotionEvent
. Unlike the earlier
getAction()
method, getActionMasked()
is designed to work with multiple pointers. It returns the
action without the pointer indices. For actions with a valid pointer index, use
getActionIndex()
to return the index of the pointers associated with the action as
shown in the following snippet:
Note: This example uses the
MotionEventCompat
class. This class is in the Support Library. Use the
MotionEventCompat
to provide the best support for a wide range of platforms. The
MotionEventCompat
is not a replacement for the MotionEvent
class. Rather,
it provides static utility methods to which you pass your MotionEvent
object in
order to receive the desired action associated with that event.
Kotlin
val (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action -> Log.d(DEBUG_TAG, "The action is ${actionToString(action)}") // Get the index of the pointer associated with the action. MotionEventCompat.getActionIndex(event).let { index -> // The coordinates of the current screen contact, relative to // the responding View or Activity. MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt() } } if (event.pointerCount > 1) { Log.d(DEBUG_TAG, "Multitouch event") } else { // Single touch event Log.d(DEBUG_TAG, "Single touch event") } ... // Given an action int, returns a string description fun actionToString(action: Int): String { return when (action) { MotionEvent.ACTION_DOWN -> "Down" MotionEvent.ACTION_MOVE -> "Move" MotionEvent.ACTION_POINTER_DOWN -> "Pointer Down" MotionEvent.ACTION_UP -> "Up" MotionEvent.ACTION_POINTER_UP -> "Pointer Up" MotionEvent.ACTION_OUTSIDE -> "Outside" MotionEvent.ACTION_CANCEL -> "Cancel" else -> "" } }
Java
int action = MotionEventCompat.getActionMasked(event); // Get the index of the pointer associated with the action. int index = MotionEventCompat.getActionIndex(event); int xPos = -1; int yPos = -1; Log.d(DEBUG_TAG,"The action is " + actionToString(action)); if (event.getPointerCount() > 1) { Log.d(DEBUG_TAG,"Multitouch event"); // The coordinates of the current screen contact, relative to // the responding View or Activity. xPos = (int)MotionEventCompat.getX(event, index); yPos = (int)MotionEventCompat.getY(event, index); } else { // Single touch event Log.d(DEBUG_TAG,"Single touch event"); xPos = (int)MotionEventCompat.getX(event, index); yPos = (int)MotionEventCompat.getY(event, index); } ... // Given an action int, returns a string description public static String actionToString(int action) { switch (action) { case MotionEvent.ACTION_DOWN: return "Down"; case MotionEvent.ACTION_MOVE: return "Move"; case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down"; case MotionEvent.ACTION_UP: return "Up"; case MotionEvent.ACTION_POINTER_UP: return "Pointer Up"; case MotionEvent.ACTION_OUTSIDE: return "Outside"; case MotionEvent.ACTION_CANCEL: return "Cancel"; } return ""; }
For more information about multi-touch and additional examples, see Drag and scale.