الإيماءة التي تعمل باللمس المتعدّد هي عندما تنقر مؤشرات متعددة (أصابع) على الشاشة في
الوقت نفسه. يوضّح هذا المستند كيفية رصد الإيماءات التي تتضمّن
مؤشرات متعددة.
تتبُّع مؤشرات متعددة
عندما تنقر مؤشرات متعددة على الشاشة في الوقت نفسه، ينشئ النظام
أحداث اللمس التالية:
ACTION_DOWN:
يتم إرسالها عندما ينقر المؤشر الأول على الشاشة. يؤدي ذلك إلى بدء الإجراء. إنّ بيانات
المؤشر لهذا المؤشر تكون دائمًا في الفهرس 0 في
MotionEvent.
ACTION_POINTER_DOWN:
يتم إرسالها عندما تدخل مؤشرات إضافية إلى الشاشة بعد المؤشر الأول. يمكنك الحصول على
فهرس المؤشر الذي انخفض للتو باستخدام
getActionIndex().
ACTION_MOVE:
يتم إرسالها عند حدوث تغيير في إيماءة، يشمل أي عدد من
المؤشرات.
ACTION_POINTER_UP:
يتم إرسال هذا الحدث عندما يرتفع مؤشر غير أساسي. يمكنك الحصول على فهرس
المؤشر الذي تمّت ترقيته للتو باستخدام getActionIndex().
ACTION_UP:
يتم إرسالها عندما يغادر المؤشر الأخير الشاشة.
ACTION_CANCEL:
يشير إلى إلغاء الإيماءة بالكامل، بما في ذلك جميع المؤشرات.
إيماءات البدء والانتهاء
الإيماءة هي سلسلة من الأحداث تبدأ بحدث ACTION_DOWN
وتنتهي إما بحدث ACTION_UP أو
ACTION_CANCEL. تكون هناك إيماءة نشطة واحدة في كل مرة. تنطبق
الإجراءات DOWN وMOVE وUP وCANCEL على الإيماءة بأكملها. على سبيل المثال، يمكن أن يشير حدث
باستخدام ACTION_MOVE إلى حركة لجميع المؤشرات
الهابطة في تلك اللحظة.
تتبُّع المؤشرات
استخدِم فهرس المؤشر ورقم تعريفه لتتبُّع مواضع المؤشرات الفردية في MotionEvent.
الفهرس: يخزّن MotionEvent مؤشرًا
للمعلومات في مصفوفة. فهرس المؤشر هو موضعه ضمن هذه الصفيف. تأخذ معظم طرق MotionEvent فهرس المؤشر كأحد
المَعلمات، بدلاً من رقم تعريف المؤشر.
رقم التعريف: يحتوي كل مؤشر أيضًا على تعيين رقم تعريف يظل
ثابتًا في أحداث اللمس للسماح بتتبُّع مؤشر فردي
على مستوى الإيماءة بأكملها.
تظهر المؤشرات الفردية ضمن حدث حركة بترتيب غير محدّد. وبالتالي،
يمكن أن يتغيّر فهرس المؤشر من حدث إلى آخر، ولكن يُضمن أن يظلّ معرّف
المؤشر ثابتًا ما دام المؤشر
نشطًا. استخدِم الأسلوب
getPointerId()
للحصول على معرّف المؤشر لتتبُّع المؤشر في جميع أحداث التحرك التالية في إيماءة. بعد ذلك، بالنسبة إلى أحداث الحركة المتتالية، استخدِم الأسلوب
findPointerIndex()
للحصول على فهرس المؤشر لرقم تعريف مؤشر معيّن في حدث الحركة هذا.
مثلاً:
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);...}
لتفعيل مؤشرات اللمس المتعدّدة، يمكنك تخزين جميع المؤشرات النشطة في ذاكرة التخزين المؤقت مع
معرّفاتها في وقت حدثَيACTION_POINTER_DOWN و
ACTION_DOWN الفرديين. أزِل المؤشرات من ذاكرة التخزين المؤقت عند حدثَي ACTION_POINTER_UP وACTION_UP. قد
تجد أرقام التعريف المخزّنة مؤقتًا هذه مفيدة للتعامل مع أحداث الإجراءات الأخرى بشكل صحيح. على سبيل المثال، عند معالجة حدث ACTION_MOVE، ابحث عن فهرس
كل معرّف مؤشر نشط محفوظ في ذاكرة التخزين المؤقت، واسترِد إحداثيات المؤشر باستخدام الدالتَين
getX()
و
getY()
، ثم قارِن هذه الإحداثيات بالإحداثيات المحفوظة في ذاكرة التخزين المؤقت لتحديد المؤشرات التي تمّ نقلها.
استخدِم الدالة getActionIndex() مع
أحداث ACTION_POINTER_UP وACTION_POINTER_DOWN
فقط. لا تستخدِم هذه الدالة مع أحداث ACTION_MOVE، لأنّه
يتم عرض 0 دائمًا.
استرداد MotionEvent إجراء
استخدِم الأسلوب
getActionMasked()
أو إصدار التوافق
MotionEventCompat.getActionMasked()
لاسترداد إجراء MotionEvent. على عكس الطريقة السابقة
getAction()
، تم تصميم getActionMasked() للعمل مع عدة
مؤشرات. ويعرض الإجراء بدون فهرسات المؤشر. بالنسبة إلى الإجراءات التي تحتوي على
فهرس مؤشر صالح، استخدِم getActionIndex() لعرض فهرس
المؤشرات المرتبطة بالإجراء كما هو موضّح في المقتطف التالي:
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"";}
الشكل 1. أنماط الرسم باللمس المتعدّد
مصادر إضافية
لمزيد من المعلومات المتعلّقة بأحداث الإدخال، اطّلِع على المراجع التالية:
يخضع كل من المحتوى وعيّنات التعليمات البرمجية في هذه الصفحة للتراخيص الموضحّة في ترخيص استخدام المحتوى. إنّ Java وOpenJDK هما علامتان تجاريتان مسجَّلتان لشركة Oracle و/أو الشركات التابعة لها.
تاريخ التعديل الأخير: 2025-07-26 (حسب التوقيت العالمي المتفَّق عليه)
[[["يسهُل فهم المحتوى.","easyToUnderstand","thumb-up"],["ساعَدني المحتوى في حلّ مشكلتي.","solvedMyProblem","thumb-up"],["غير ذلك","otherUp","thumb-up"]],[["لا يحتوي على المعلومات التي أحتاج إليها.","missingTheInformationINeed","thumb-down"],["الخطوات معقدة للغاية / كثيرة جدًا.","tooComplicatedTooManySteps","thumb-down"],["المحتوى قديم.","outOfDate","thumb-down"],["ثمة مشكلة في الترجمة.","translationIssue","thumb-down"],["مشكلة في العيّنات / التعليمات البرمجية","samplesCodeIssue","thumb-down"],["غير ذلك","otherDown","thumb-down"]],["تاريخ التعديل الأخير: 2025-07-26 (حسب التوقيت العالمي المتفَّق عليه)"],[],[],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)"]]