Cử chỉ nhiều điểm chạm là khi nhiều con trỏ (ngón tay) nhấn vào màn hình cùng một lúc. Tài liệu này mô tả cách phát hiện các cử chỉ liên quan đến nhiều con trỏ.
Theo dõi nhiều con trỏ
Khi nhiều con trỏ nhấn vào màn hình cùng một lúc, hệ thống sẽ tạo các sự kiện chạm sau:
ACTION_DOWN: được gửi khi con trỏ đầu tiên nhấn vào màn hình. Thao tác này sẽ bắt đầu cử chỉ. Dữ liệu con trỏ cho con trỏ này luôn ở chỉ mục 0 trong MotionEvent.
ACTION_POINTER_DOWN: được gửi khi các con trỏ bổ sung xuất hiện trên màn hình sau con trỏ đầu tiên. Bạn có thể lấy chỉ mục của con trỏ vừa giảm xuống bằng cách sử dụng getActionIndex().
ACTION_MOVE: được gửi khi có thay đổi xảy ra trong một cử chỉ, liên quan đến bất kỳ số lượng con trỏ nào.
ACTION_POINTER_UP: được gửi khi con trỏ không phải chính di chuyển lên. Bạn có thể lấy chỉ mục của con trỏ vừa tăng lên bằng cách sử dụng getActionIndex().
ACTION_UP: được gửi khi con trỏ cuối cùng rời khỏi màn hình.
ACTION_CANCEL: cho biết toàn bộ cử chỉ, bao gồm cả tất cả con trỏ, đã bị huỷ.
Bắt đầu và kết thúc cử chỉ
Cử chỉ là một loạt sự kiện bắt đầu bằng sự kiện ACTION_DOWN và kết thúc bằng sự kiện ACTION_UP hoặc ACTION_CANCEL. Mỗi lần chỉ có một cử chỉ hoạt động. Các thao tác XUỐNG, DI CHUYỂN, LÊN và HUỶ áp dụng cho toàn bộ cử chỉ. Ví dụ: một sự kiện có ACTION_MOVE có thể cho biết một chuyển động cho tất cả con trỏ xuống tại thời điểm đó.
Theo dõi con trỏ
Sử dụng chỉ mục và mã nhận dạng của con trỏ để theo dõi các vị trí con trỏ riêng lẻ trong MotionEvent.
Chỉ mục: MotionEvent lưu trữ thông tin con trỏ trong một mảng. Chỉ mục của con trỏ là vị trí của con trỏ trong mảng này. Hầu hết các phương thức MotionEvent đều lấy chỉ mục con trỏ làm tham số thay vì mã nhận dạng con trỏ.
Mã nhận dạng: mỗi con trỏ cũng có một liên kết mã nhận dạng duy trì trên các sự kiện chạm để cho phép theo dõi một con trỏ riêng lẻ trên toàn bộ cử chỉ.
Các con trỏ riêng lẻ xuất hiện trong một sự kiện chuyển động theo thứ tự không xác định. Do đó, chỉ mục của con trỏ có thể thay đổi từ sự kiện này sang sự kiện tiếp theo, nhưng mã con trỏ của con trỏ được đảm bảo sẽ không thay đổi miễn là con trỏ vẫn hoạt động. Sử dụng phương thức getPointerId() để lấy mã của con trỏ nhằm theo dõi con trỏ trên tất cả các sự kiện chuyển động tiếp theo trong một cử chỉ. Sau đó, đối với các sự kiện chuyển động liên tiếp, hãy sử dụng phương thức findPointerIndex() để lấy chỉ mục con trỏ cho một mã con trỏ nhất định trong sự kiện chuyển động đó.
Ví dụ:
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);...}
Để hỗ trợ nhiều con trỏ cảm ứng, bạn có thể lưu tất cả con trỏ đang hoạt động vào bộ nhớ đệm bằng mã nhận dạng của các con trỏ đó tại thời điểm sự kiện ACTION_POINTER_DOWN và ACTION_DOWN riêng lẻ. Xoá con trỏ khỏi bộ nhớ đệm tại các sự kiện ACTION_POINTER_UP và ACTION_UP. Bạn có thể thấy các mã nhận dạng được lưu vào bộ nhớ đệm này hữu ích để xử lý chính xác các sự kiện hành động khác. Ví dụ: khi xử lý một sự kiện ACTION_MOVE, hãy tìm chỉ mục cho mỗi mã con trỏ đang hoạt động được lưu vào bộ nhớ đệm, truy xuất toạ độ của con trỏ bằng các hàm getX() và getY(), sau đó so sánh các toạ độ này với toạ độ được lưu vào bộ nhớ đệm để khám phá xem con trỏ nào đã di chuyển.
Chỉ sử dụng hàm getActionIndex() với các sự kiện ACTION_POINTER_UP và ACTION_POINTER_DOWN. Đừng sử dụng hàm này với các sự kiện ACTION_MOVE, vì hàm này luôn trả về 0.
Truy xuất hành động MotionEvent
Sử dụng phương thức getActionMasked() hoặc phiên bản tương thích MotionEventCompat.getActionMasked() để truy xuất thao tác của MotionEvent. Không giống như phương thức getAction() trước đó, getActionMasked() được thiết kế để hoạt động với nhiều con trỏ. Phương thức này trả về thao tác mà không có chỉ mục con trỏ. Đối với các thao tác có chỉ mục con trỏ hợp lệ, hãy sử dụng getActionIndex() để trả về chỉ mục của các con trỏ liên kết với thao tác đó như trong đoạn mã sau:
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"";}
Hình 1. Mẫu vẽ bằng nhiều điểm chạm.
Tài nguyên khác
Để biết thêm thông tin liên quan đến sự kiện đầu vào, hãy xem các tài liệu tham khảo sau:
Nội dung và mã mẫu trên trang này phải tuân thủ các giấy phép như mô tả trong phần Giấy phép nội dung. Java và OpenJDK là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Oracle và/hoặc đơn vị liên kết của Oracle.
Cập nhật lần gần đây nhất: 2025-07-26 UTC.
[[["Dễ hiểu","easyToUnderstand","thumb-up"],["Giúp tôi giải quyết được vấn đề","solvedMyProblem","thumb-up"],["Khác","otherUp","thumb-up"]],[["Thiếu thông tin tôi cần","missingTheInformationINeed","thumb-down"],["Quá phức tạp/quá nhiều bước","tooComplicatedTooManySteps","thumb-down"],["Đã lỗi thời","outOfDate","thumb-down"],["Vấn đề về bản dịch","translationIssue","thumb-down"],["Vấn đề về mẫu/mã","samplesCodeIssue","thumb-down"],["Khác","otherDown","thumb-down"]],["Cập nhật lần gần đây nhất: 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)"]]