Un gesto multitáctil se produce cuando varios punteros (dedos) presionan la pantalla al mismo tiempo. En este documento, se describe cómo detectar gestos que involucran varios punteros.
Cómo rastrear varios punteros
Cuando varios punteros tocan la pantalla al mismo tiempo, el sistema genera los siguientes eventos táctiles:
ACTION_DOWN: Se envía cuando el primer puntero presiona la pantalla. Este evento comienza el gesto. Los datos del puntero para este puntero siempre están en el índice 0 de MotionEvent.
ACTION_POINTER_DOWN: Se envía cuando punteros adicionales ingresan a la pantalla después del primero. Puedes obtener el índice del puntero que acaba de bajar con getActionIndex().
ACTION_MOVE: Se envía cuando se produce un cambio en un gesto, que incluye cualquier cantidad de punteros.
ACTION_POINTER_UP: Se envía cuando un puntero que no es principal va hacia arriba. Puedes obtener el índice del puntero que acaba de subir con getActionIndex().
ACTION_UP: Se envía cuando el último puntero sale de la pantalla.
ACTION_CANCEL: Indica que se cancela todo el gesto, incluidos todos los punteros.
Gestos de inicio y finalización
Un gesto es una serie de eventos que comienza con un evento ACTION_DOWN y termina con un evento ACTION_UP o ACTION_CANCEL. Hay un gesto activo a la vez. Las acciones ABAJO, MOVER, ARRIBA y CANCELAR se aplican a todo el gesto. Por ejemplo, un evento con ACTION_MOVE puede indicar un movimiento para todos los punteros hacia abajo en ese momento.
Haz un seguimiento de los punteros
Usa el índice y el ID del puntero para hacer un seguimiento de las posiciones de los punteros individuales dentro de un MotionEvent.
Índice: Un MotionEvent almacena información del puntero en un array. El índice de un puntero es su posición dentro de este array. La mayoría de los métodos MotionEvent toman el índice del puntero como un parámetro, en lugar del ID del puntero.
ID: Cada puntero también tiene una asignación de ID que se mantiene constante en los eventos táctiles para permitir el seguimiento de un puntero individual en todo el gesto.
Los punteros individuales aparecen dentro de un evento de movimiento en un orden indefinido. Por lo tanto, el índice de un puntero puede cambiar de un evento al siguiente, pero se garantiza que el ID del puntero de un puntero permanecerá constante mientras el puntero esté activo. Usa el método getPointerId() para obtener el ID de un puntero y hacer un seguimiento de él en todos los eventos de movimiento posteriores en un gesto. Luego, para eventos de movimiento sucesivos, usa el método findPointerIndex() para obtener el índice del puntero de un ID de puntero determinado en ese evento de movimiento.
Por ejemplo:
Kotlin
privatevarmActivePointerId:Int=0overridefunonTouchEvent(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)toevent.getY(pointerIndex)}...}
Java
privateintmActivePointerId;publicbooleanonTouchEvent(MotionEventevent){...// 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.intpointerIndex=event.findPointerIndex(mActivePointerId);// Get the pointer's current position.floatx=event.getX(pointerIndex);floaty=event.getY(pointerIndex);...}
Para admitir varios punteros táctiles, puedes almacenar en caché todos los punteros activos con sus IDs en sus tiempos de eventos ACTION_POINTER_DOWN y ACTION_DOWN individuales. Quita los punteros de la caché en sus eventos ACTION_POINTER_UP y ACTION_UP. Es posible que estos IDs almacenados en caché te resulten útiles para controlar otros eventos de acción de forma correcta. Por ejemplo, cuando se procesa un evento ACTION_MOVE, se encuentra el índice de cada ID de puntero activo almacenado en caché, se recuperan las coordenadas del puntero con las funciones getX() y getY(), y, luego, se comparan estas coordenadas con las coordenadas almacenadas en caché para descubrir qué punteros se movieron.
Usa la función getActionIndex() solo con eventos ACTION_POINTER_UP y ACTION_POINTER_DOWN. No uses esta función con eventos ACTION_MOVE, ya que siempre muestra 0.
Cómo recuperar acciones de MotionEvent
Usa el método getActionMasked() o la versión de compatibilidad MotionEventCompat.getActionMasked() para recuperar la acción de un MotionEvent. A diferencia del método getAction() anterior, getActionMasked() está diseñado para funcionar con varios punteros. Muestra la acción sin los índices de puntero. Para las acciones con un índice de puntero válido, usa getActionIndex() para mostrar el índice de los punteros asociados con la acción, como se muestra en el siguiente fragmento:
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()toMotionEventCompat.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.funactionToString(action:Int):String{returnwhen(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
intaction=MotionEventCompat.getActionMasked(event);// Get the index of the pointer associated with the action.intindex=MotionEventCompat.getActionIndex(event);intxPos=-1;intyPos=-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 descriptionpublicstaticStringactionToString(intaction){switch(action){caseMotionEvent.ACTION_DOWN:return"Down";caseMotionEvent.ACTION_MOVE:return"Move";caseMotionEvent.ACTION_POINTER_DOWN:return"Pointer Down";caseMotionEvent.ACTION_UP:return"Up";caseMotionEvent.ACTION_POINTER_UP:return"Pointer Up";caseMotionEvent.ACTION_OUTSIDE:return"Outside";caseMotionEvent.ACTION_CANCEL:return"Cancel";}return"";}
Figura 1. Patrones de dibujo multitáctil.
Recursos adicionales
Para obtener más información relacionada con los eventos de entrada, consulta las siguientes
referencias:
El contenido y las muestras de código que aparecen en esta página están sujetas a las licencias que se describen en la Licencia de Contenido. Java y OpenJDK son marcas registradas de Oracle o sus afiliados.
Última actualización: 2025-07-26 (UTC)
[[["Fácil de comprender","easyToUnderstand","thumb-up"],["Resolvió mi problema","solvedMyProblem","thumb-up"],["Otro","otherUp","thumb-up"]],[["Falta la información que necesito","missingTheInformationINeed","thumb-down"],["Muy complicado o demasiados pasos","tooComplicatedTooManySteps","thumb-down"],["Desactualizado","outOfDate","thumb-down"],["Problema de traducción","translationIssue","thumb-down"],["Problema con las muestras o los códigos","samplesCodeIssue","thumb-down"],["Otro","otherDown","thumb-down"]],["Última actualización: 2025-07-26 (UTC)"],[],[],null,["# Handle multi-touch gestures\n\nTry the Compose way \nJetpack Compose is the recommended UI toolkit for Android. Learn how to use touch and input in Compose. \n[Multi-touch gestures →](/develop/ui/compose/touch-input/pointer-input/multi-touch) \n\nA multi-touch gesture is when multiple pointers (fingers) tap the screen at\nthe same time. This document describes how to detect gestures that involve\nmultiple pointers.\n\nTrack multiple pointers\n-----------------------\n\nWhen multiple pointers tap the screen at the same time, the system generates\nthe following touch events:\n\n- [ACTION_DOWN](/reference/android/view/MotionEvent#ACTION_DOWN): sent when the first pointer taps the screen. This starts the gesture. The pointer data for this pointer is always at index `0` in the [MotionEvent](/reference/android/view/MotionEvent).\n- [ACTION_POINTER_DOWN](/reference/android/view/MotionEvent#ACTION_POINTER_DOWN): sent when extra pointers enter the screen after the first. You can obtain the index of the pointer that just went down using [getActionIndex()](/reference/android/view/MotionEvent#getActionIndex()).\n- [ACTION_MOVE](/reference/android/view/MotionEvent#ACTION_MOVE): sent when a change occurs in a gesture, involving any number of pointers.\n- [ACTION_POINTER_UP](/reference/android/view/MotionEvent#ACTION_POINTER_UP): sent when a non-primary pointer goes up. You can obtain the index of the pointer that just went up using `getActionIndex()`.\n- [ACTION_UP](/reference/android/view/MotionEvent#ACTION_UP): sent when the last pointer leaves the screen.\n- [ACTION_CANCEL](/reference/android/view/MotionEvent#ACTION_CANCEL): indicates that the entire gesture, including all pointers, is canceled.\n\n### Start and end gestures\n\nA gesture is a series of events starting with an `ACTION_DOWN`\nevent and ending with either an `ACTION_UP` or\n`ACTION_CANCEL` event. There is one active gesture at a time. The\nactions DOWN, MOVE, UP, and CANCEL apply to the entire gesture. For example, an\nevent with `ACTION_MOVE` can indicate a movement for all pointers\ndown at that moment.\n\n### Keep track of pointers\n\nUse the pointer's index and ID to keep track of the individual pointers\npositions within a `MotionEvent`.\n\n- **Index** : a `MotionEvent` stores pointer information in an array. The index of a pointer is its position within this array. Most of the `MotionEvent` methods take the pointer index as a parameter, rather than the pointer ID.\n- **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.\n\nIndividual pointers appear within a motion event in an undefined order. Thus,\nthe index of a pointer can change from one event to the next, but the pointer ID\nof a pointer is guaranteed to remain constant as long as the pointer remains\nactive. Use the\n[getPointerId()](/reference/android/view/MotionEvent#getPointerId(int))\nmethod to obtain a pointer's ID to track the pointer across all subsequent\nmotion events in a gesture. Then, for successive motion events, use the\n[findPointerIndex()](/reference/android/view/MotionEvent#findPointerIndex(int))\nmethod to obtain the pointer index for a given pointer ID in that motion event.\nFor example: \n\n### Kotlin\n\n```kotlin\nprivate var mActivePointerId: Int = 0\n\noverride fun onTouchEvent(event: MotionEvent): Boolean {\n ...\n // Get the pointer ID.\n mActivePointerId = event.getPointerId(0)\n\n // ... Many touch events later...\n\n // Use the pointer ID to find the index of the active pointer\n // and fetch its position.\n val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex -\u003e\n // Get the pointer's current position.\n event.getX(pointerIndex) to event.getY(pointerIndex)\n }\n ...\n}\n```\n\n### Java\n\n```java\nprivate int mActivePointerId;\n\npublic boolean onTouchEvent(MotionEvent event) {\n ...\n // Get the pointer ID.\n mActivePointerId = event.getPointerId(0);\n\n // ... Many touch events later...\n\n // Use the pointer ID to find the index of the active pointer\n // and fetch its position.\n int pointerIndex = event.findPointerIndex(mActivePointerId);\n // Get the pointer's current position.\n float x = event.getX(pointerIndex);\n float y = event.getY(pointerIndex);\n ...\n}\n```\n\nTo support multiple touch pointers, you can cache all active pointers with\ntheir IDs at their individual `ACTION_POINTER_DOWN` and\n`ACTION_DOWN` event time. Remove the pointers from your cache at\ntheir `ACTION_POINTER_UP` and `ACTION_UP`events. You might\nfind these cached IDs helpful to handle other action events correctly. For\nexample, when processing an `ACTION_MOVE` event, find the index for\neach cached active pointer ID, retrieve the pointer's coordinates using the\n[getX()](/reference/android/view/MotionEvent#getX(int))\nand\n[getY()](/reference/android/view/MotionEvent#getY(int))\nfunctions, then compare these coordinates with your cached coordinates to\ndiscover which pointers moved.\n\nUse the `getActionIndex()` function with\n`ACTION_POINTER_UP` and `ACTION_POINTER_DOWN` events\nonly. Don't use this function with `ACTION_MOVE` events, as this\nalways returns `0`.\n\nRetrieve `MotionEvent` actions\n------------------------------\n\nUse the\n[getActionMasked()](/reference/android/view/MotionEvent#getActionMasked())\nmethod or the compatibility version\n[MotionEventCompat.getActionMasked()](/reference/androidx/core/view/MotionEventCompat#getActionMasked(android.view.MotionEvent))\nto retrieve the action of a `MotionEvent`. Unlike the earlier\n[getAction()](/reference/android/view/MotionEvent#getAction())\nmethod, `getActionMasked()` is designed to work with multiple\npointers. It returns the action without the pointer indices. For actions with a\nvalid pointer index, use `getActionIndex()` to return the index of\nthe pointers associated with the action as shown in the following snippet:\n| **Note:** This example uses the [MotionEventCompat](/reference/androidx/core/view/MotionEventCompat) class, a class in the [Support\nLibrary](/tools/support-library). Use `MotionEventCompat` to provide the best support for a wide range of platforms. `MotionEventCompat` is *not* a replacement for the `MotionEvent` class. Rather, it provides static utility methods to which you pass your `MotionEvent` object to receive the desired action associated with that event. \n\n### Kotlin\n\n```kotlin\nval (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action -\u003e\n Log.d(DEBUG_TAG, \"The action is ${actionToString(action)}\")\n // Get the index of the pointer associated with the action.\n MotionEventCompat.getActionIndex(event).let { index -\u003e\n // The coordinates of the current screen contact, relative to\n // the responding View or Activity.\n MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt()\n }\n}\n\nif (event.pointerCount \u003e 1) {\n Log.d(DEBUG_TAG, \"Multitouch event\")\n\n} else {\n // Single touch event.\n Log.d(DEBUG_TAG, \"Single touch event\")\n}\n\n...\n\n// Given an action int, returns a string description.\nfun actionToString(action: Int): String {\n return when (action) {\n MotionEvent.ACTION_DOWN -\u003e \"Down\"\n MotionEvent.ACTION_MOVE -\u003e \"Move\"\n MotionEvent.ACTION_POINTER_DOWN -\u003e \"Pointer Down\"\n MotionEvent.ACTION_UP -\u003e \"Up\"\n MotionEvent.ACTION_POINTER_UP -\u003e \"Pointer Up\"\n MotionEvent.ACTION_OUTSIDE -\u003e \"Outside\"\n MotionEvent.ACTION_CANCEL -\u003e \"Cancel\"\n else -\u003e \"\"\n }\n}\n```\n\n### Java\n\n```java\nint action = MotionEventCompat.getActionMasked(event);\n// Get the index of the pointer associated with the action.\nint index = MotionEventCompat.getActionIndex(event);\nint xPos = -1;\nint yPos = -1;\n\nLog.d(DEBUG_TAG,\"The action is \" + actionToString(action));\n\nif (event.getPointerCount() \u003e 1) {\n Log.d(DEBUG_TAG,\"Multitouch event\");\n // The coordinates of the current screen contact, relative to\n // the responding View or Activity.\n xPos = (int)MotionEventCompat.getX(event, index);\n yPos = (int)MotionEventCompat.getY(event, index);\n\n} else {\n // Single touch event.\n Log.d(DEBUG_TAG,\"Single touch event\");\n xPos = (int)MotionEventCompat.getX(event, index);\n yPos = (int)MotionEventCompat.getY(event, index);\n}\n...\n\n// Given an action int, returns a string description\npublic static String actionToString(int action) {\n switch (action) {\n\n case MotionEvent.ACTION_DOWN: return \"Down\";\n\tcase MotionEvent.ACTION_MOVE: return \"Move\";\n\tcase MotionEvent.ACTION_POINTER_DOWN: return \"Pointer Down\";\n\tcase MotionEvent.ACTION_UP: return \"Up\";\n\tcase MotionEvent.ACTION_POINTER_UP: return \"Pointer Up\";\n\tcase MotionEvent.ACTION_OUTSIDE: return \"Outside\";\n\tcase MotionEvent.ACTION_CANCEL: return \"Cancel\";\n }\n return \"\";\n}\n```\n**Figure 1.** Multi-touch drawing patterns.\n\nAdditional resources\n--------------------\n\nFor more information related to input events, see the following\nreferences:\n\n- [Input events overview](/guide/topics/ui/ui-events)\n- [Sensors\n overview](/guide/topics/sensors/sensors_overview)\n- [Make a custom\n view interactive](/training/custom-views/making-interactive)\n- [Drag and scale](/develop/ui/views/touch-and-input/gestures/scale)"]]