Try the Compose way
Jetpack Compose is the recommended UI toolkit for Android. Learn how to use touch and input in Compose.

A multi-touch gesture is when multiple pointers (fingers) touch the screen at the same time. This lesson describes how to detect gestures that involve multiple pointers.
Refer to the following related resources:
When multiple pointers touch the screen at the same time, the system generates the following touch events:
ACTION_DOWN
—For the first pointer that
touches the screen. This starts the gesture. The pointer data for this pointer is
always at index 0 in the MotionEvent
.ACTION_POINTER_DOWN
—For
extra pointers that enter the screen beyond the first. The index of the pointer that just went down
can be obtained by using getActionIndex()
.ACTION_MOVE
—A change has happened in a gesture to any
(one or more) pointers.ACTION_POINTER_UP
—Sent when a non-primary pointer goes up.
The index of the pointer that just went up can be obtained by using
getActionIndex()
.
ACTION_UP
—Sent when the last pointer leaves the screen.You keep track of individual pointers within a MotionEvent
via each pointer's index and ID:
MotionEvent
effectively
stores information about each pointer in an array. The index of a pointer is its position
within this array. Most of the MotionEvent
methods you use to interact with pointers take the
pointer index as a parameter, not the pointer ID. The order in which individual pointers appear within a motion event is
undefined. Thus the index of a pointer can change from one event to the
next, but the pointer ID of a pointer is guaranteed to remain constant as long
as the pointer remains active. Use the getPointerId()
method to obtain a
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 obtain
the pointer index for a given pointer ID in that motion event. For example:
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) } ... }
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); ... }
ACTION_POINTER_DOWN
and ACTION_DOWN
event time; remove the pointers from your cache at their
ACTION_POINTER_UP
and ACTION_UP
events.
Those cached IDs may be necessary in order to handle other action events correctly; for
example, when processing ACTION_MOVE
event,
you can find the index for each cached active pointer ID, retrieve the pointer's coordinates by using the
relavent functions (getX()
,
getY()
, etc.), then
compare with your cached coordinates to discover the actually moved pointers.
There can be multiple moved pointers in one
ACTION_MOVE
event.
The getActionIndex()
function does not apply to the ACTION_MOVE
event.
You should always use the method
getActionMasked()
(or better yet, the compatibility version
MotionEventCompat.getActionMasked()
) to retrieve
the action of a
MotionEvent
. Unlike the older getAction()
method, getActionMasked()
is designed to work with
multiple pointers. It returns the masked action
being performed, without including the pointer index bits. For actions with a valid pointer index,
which includes ACTION_POINTER_DOWN
and ACTION_POINTER_UP
, you can then use
getActionIndex()
to return the index of the pointer associated with the action. This is illustrated in the snippet below.
Note: This example uses the
MotionEventCompat
class. This class is in the
Support Library. You should use
MotionEventCompat
to provide the best support for a wide range of
platforms. Note that 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.
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 -> "" } }
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 discussion of multi-touch and some examples, see the lesson Drag and Scale.
Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
Last updated 2023-06-02 UTC.