Cử chỉ chạm xảy ra khi người dùng đặt một hoặc nhiều ngón tay lên màn hình cảm ứng và ứng dụng của bạn diễn giải mẫu thao tác chạm này là một cử chỉ. Có 2 giai đoạn phát hiện cử chỉ:
- Thu thập dữ liệu sự kiện chạm.
- Diễn giải dữ liệu để xác định xem dữ liệu đó có đáp ứng các tiêu chí về các cử chỉ mà ứng dụng của bạn hỗ trợ hay không.
Lớp AndroidX
Các ví dụ trong tài liệu này sử dụng các lớp
GestureDetectorCompat
và
MotionEventCompat
. Các lớp này nằm trong Thư viện AndroidX. Sử dụng các lớp AndroidX nếu có thể để cung cấp khả năng tương thích với các thiết bị cũ.
MotionEventCompat
không thay thế cho lớp MotionEvent
. Thay vào đó, thư viện này cung cấp các phương thức tiện ích tĩnh mà bạn truyền đối tượng MotionEvent
để nhận thao tác liên kết với sự kiện đó.
Thu thập dữ liệu
Khi người dùng đặt một hoặc nhiều ngón tay lên màn hình, thao tác này sẽ kích hoạt lệnh gọi lại onTouchEvent()
trên khung hiển thị nhận các sự kiện chạm. Đối với mỗi chuỗi sự kiện chạm (chẳng hạn như vị trí, áp lực, kích thước và thao tác thêm một ngón tay khác) được xác định là một cử chỉ, onTouchEvent()
sẽ được kích hoạt nhiều lần.
Cử chỉ này bắt đầu khi người dùng nhấn vào màn hình lần đầu tiên, tiếp tục khi hệ thống theo dõi vị trí ngón tay của người dùng và kết thúc bằng cách chụp lại sự kiện cuối cùng của ngón tay cuối cùng của người dùng rời khỏi màn hình.
Trong suốt quá trình tương tác này, MotionEvent
được cung cấp cho onTouchEvent()
sẽ cung cấp thông tin chi tiết về mọi lượt tương tác. Ứng dụng của bạn có thể dùng dữ liệu do MotionEvent
cung cấp để xác định xem cử chỉ mà ứng dụng quan tâm có xảy ra hay không.
Ghi lại các sự kiện chạm cho một Hoạt động hoặc Chế độ xem
Để chặn các sự kiện chạm trong Activity
hoặc View
, hãy ghi đè lệnh gọi lại onTouchEvent()
.
Đoạn mã sau đây sử dụng getAction()
để trích xuất thao tác mà người dùng thực hiện từ tham số event
.
Thao tác này cung cấp dữ liệu thô cần thiết để xác định xem một cử chỉ bạn quan tâm có xảy ra hay không.
Kotlin
class MainActivity : Activity() { ... // This example shows an Activity. You can use the same approach if you are // subclassing a View. override fun onTouchEvent(event: MotionEvent): Boolean { return when (event.action) { MotionEvent.ACTION_DOWN -> { Log.d(DEBUG_TAG, "Action was DOWN") true } MotionEvent.ACTION_MOVE -> { Log.d(DEBUG_TAG, "Action was MOVE") true } MotionEvent.ACTION_UP -> { Log.d(DEBUG_TAG, "Action was UP") true } MotionEvent.ACTION_CANCEL -> { Log.d(DEBUG_TAG, "Action was CANCEL") true } MotionEvent.ACTION_OUTSIDE -> { Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element") true } else -> super.onTouchEvent(event) } } }
Java
public class MainActivity extends Activity { ... // This example shows an Activity. You can use the same approach if you are // subclassing a View. @Override public boolean onTouchEvent(MotionEvent event){ switch(event.getAction()) { case (MotionEvent.ACTION_DOWN) : Log.d(DEBUG_TAG,"Action was DOWN"); return true; case (MotionEvent.ACTION_MOVE) : Log.d(DEBUG_TAG,"Action was MOVE"); return true; case (MotionEvent.ACTION_UP) : Log.d(DEBUG_TAG,"Action was UP"); return true; case (MotionEvent.ACTION_CANCEL) : Log.d(DEBUG_TAG,"Action was CANCEL"); return true; case (MotionEvent.ACTION_OUTSIDE) : Log.d(DEBUG_TAG,"Movement occurred outside bounds of current screen element"); return true; default : return super.onTouchEvent(event); } }
Mã này tạo ra các thông báo như sau trong Logcat khi người dùng nhấn, chạm và giữ và kéo:
GESTURES D Action was DOWN GESTURES D Action was UP GESTURES D Action was MOVE
Đối với các cử chỉ tuỳ chỉnh, bạn có thể tự xử lý các sự kiện này để xác định xem các sự kiện đó có đại diện cho một cử chỉ bạn cần xử lý hay không. Tuy nhiên, nếu ứng dụng dùng các cử chỉ phổ biến, chẳng hạn như nhấn đúp, chạm và giữ, hất, v.v., thì bạn có thể tận dụng lớp GestureDetector
. GestureDetector
giúp bạn dễ dàng phát hiện các cử chỉ phổ biến mà không cần tự xử lý các sự kiện chạm riêng lẻ. Bạn sẽ thảo luận thêm về vấn đề này trong bài viết Phát hiện cử chỉ.
Ghi lại các sự kiện chạm cho một khung hiển thị
Thay vì onTouchEvent()
, bạn có thể đính kèm đối tượng View.OnTouchListener
vào bất kỳ đối tượng View
nào bằng phương thức setOnTouchListener()
. Nhờ vậy, bạn có thể theo dõi các sự kiện chạm mà không cần phân lớp con View
hiện có, như trong ví dụ sau:
Kotlin
findViewById<View>(R.id.my_view).setOnTouchListener { v, event -> // Respond to touch events. true }
Java
View myView = findViewById(R.id.my_view); myView.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // Respond to touch events. return true; } });
Hãy lưu ý việc tạo một trình nghe trả về false
cho sự kiện ACTION_DOWN
.
Nếu bạn làm như vậy, trình nghe sẽ không được gọi cho chuỗi sự kiện ACTION_MOVE
và ACTION_UP
tiếp theo. Lý do là ACTION_DOWN
là điểm bắt đầu cho tất cả các sự kiện chạm.
Nếu đang tạo thành phần hiển thị tuỳ chỉnh, bạn có thể ghi đè onTouchEvent()
, như mô tả ở trên.
Phát hiện cử chỉ
Android cung cấp lớp GestureDetector
để phát hiện các cử chỉ phổ biến. Một số cử chỉ mà ứng dụng hỗ trợ bao gồm onDown()
, onLongPress()
và onFling()
.
Bạn có thể sử dụng GestureDetector
cùng với phương thức onTouchEvent()
được mô tả ở trên.
Phát hiện tất cả cử chỉ được hỗ trợ
Khi bạn tạo thực thể cho một đối tượng GestureDetectorCompat
, một trong các tham số cần có là một lớp triển khai giao diện GestureDetector.OnGestureListener
. GestureDetector.OnGestureListener
sẽ thông báo cho người dùng khi một sự kiện chạm cụ thể xảy ra. Để đối tượng GestureDetector
có thể nhận sự kiện, hãy ghi đè phương thức onTouchEvent()
của khung hiển thị hoặc hoạt động và chuyển tất cả sự kiện đã quan sát được sang thực thể của trình phát hiện.
Trong đoạn mã sau, giá trị trả về là true
từ các phương thức on<TouchEvent>
riêng lẻ cho biết sự kiện chạm đã được xử lý. Giá trị trả về của false
sẽ truyền các sự kiện xuống thông qua ngăn xếp khung hiển thị cho đến khi xử lý thành công thao tác chạm.
Nếu chạy đoạn mã sau trong một ứng dụng kiểm thử, bạn có thể nắm được cách các hành động được kích hoạt khi tương tác với màn hình cảm ứng, cũng như nội dung của MotionEvent
đối với mỗi sự kiện chạm. Sau đó, bạn sẽ thấy lượng dữ liệu được tạo cho các tương tác đơn giản.
Kotlin
private const val DEBUG_TAG = "Gestures" class MainActivity : Activity(), GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { private lateinit var mDetector: GestureDetectorCompat // Called when the activity is first created. public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Instantiate the gesture detector with the // application context and an implementation of // GestureDetector.OnGestureListener. mDetector = GestureDetectorCompat(this, this) // Set the gesture detector as the double-tap // listener. mDetector.setOnDoubleTapListener(this) } override fun onTouchEvent(event: MotionEvent): Boolean { return if (mDetector.onTouchEvent(event)) { true } else { super.onTouchEvent(event) } } override fun onDown(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onDown: $event") return true } override fun onFling( event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { Log.d(DEBUG_TAG, "onFling: $event1 $event2") return true } override fun onLongPress(event: MotionEvent) { Log.d(DEBUG_TAG, "onLongPress: $event") } override fun onScroll( event1: MotionEvent, event2: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { Log.d(DEBUG_TAG, "onScroll: $event1 $event2") return true } override fun onShowPress(event: MotionEvent) { Log.d(DEBUG_TAG, "onShowPress: $event") } override fun onSingleTapUp(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onSingleTapUp: $event") return true } override fun onDoubleTap(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onDoubleTap: $event") return true } override fun onDoubleTapEvent(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onDoubleTapEvent: $event") return true } override fun onSingleTapConfirmed(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event") return true } }
Java
public class MainActivity extends Activity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener{ private static final String DEBUG_TAG = "Gestures"; private GestureDetectorCompat mDetector; // Called when the activity is first created. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Instantiate the gesture detector with the // application context and an implementation of // GestureDetector.OnGestureListener. mDetector = new GestureDetectorCompat(this,this); // Set the gesture detector as the double-tap // listener. mDetector.setOnDoubleTapListener(this); } @Override public boolean onTouchEvent(MotionEvent event){ if (this.mDetector.onTouchEvent(event)) { return true; } return super.onTouchEvent(event); } @Override public boolean onDown(MotionEvent event) { Log.d(DEBUG_TAG,"onDown: " + event.toString()); return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString()); return true; } @Override public void onLongPress(MotionEvent event) { Log.d(DEBUG_TAG, "onLongPress: " + event.toString()); } @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString()); return true; } @Override public void onShowPress(MotionEvent event) { Log.d(DEBUG_TAG, "onShowPress: " + event.toString()); } @Override public boolean onSingleTapUp(MotionEvent event) { Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString()); return true; } @Override public boolean onDoubleTap(MotionEvent event) { Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString()); return true; } @Override public boolean onDoubleTapEvent(MotionEvent event) { Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString()); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent event) { Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString()); return true; } }
Phát hiện một số cử chỉ được hỗ trợ
Nếu chỉ muốn xử lý một vài cử chỉ, bạn có thể mở rộng GestureDetector.SimpleOnGestureListener
thay vì triển khai giao diện GestureDetector.OnGestureListener
.
GestureDetector.SimpleOnGestureListener
cung cấp phương thức triển khai cho tất cả phương thức on<TouchEvent>
bằng cách trả về false
cho tất cả các phương thức đó. Điều này cho phép bạn chỉ ghi đè các phương thức mà bạn quan tâm. Ví dụ: đoạn mã sau đây tạo một lớp mở rộng
GestureDetector.SimpleOnGestureListener
, đồng thời ghi đè
onFling()
và onDown()
.
Cho dù sử dụng GestureDetector.OnGestureListener
hay GestureDetector.SimpleOnGestureListener
, tốt nhất bạn nên triển khai phương thức onDown()
trả về true
. Nguyên nhân là vì tất cả các cử chỉ đều bắt đầu bằng thông báo onDown()
. Nếu bạn trả về false
từ onDown()
, như GestureDetector.SimpleOnGestureListener
thực hiện theo mặc định, hệ thống sẽ giả định rằng bạn muốn bỏ qua phần còn lại của cử chỉ và các phương thức khác của GestureDetector.OnGestureListener
sẽ không được gọi. Điều này có thể gây ra các sự cố không mong muốn trong ứng dụng của bạn. Chỉ trả về false
từ onDown()
nếu bạn thực sự muốn bỏ qua toàn bộ một cử chỉ.
Kotlin
private const val DEBUG_TAG = "Gestures" class MainActivity : Activity() { private lateinit var mDetector: GestureDetectorCompat public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mDetector = GestureDetectorCompat(this, MyGestureListener()) } override fun onTouchEvent(event: MotionEvent): Boolean { mDetector.onTouchEvent(event) return super.onTouchEvent(event) } private class MyGestureListener : GestureDetector.SimpleOnGestureListener() { override fun onDown(event: MotionEvent): Boolean { Log.d(DEBUG_TAG, "onDown: $event") return true } override fun onFling( event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { Log.d(DEBUG_TAG, "onFling: $event1 $event2") return true } } }
Java
public class MainActivity extends Activity { private GestureDetectorCompat mDetector; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDetector = new GestureDetectorCompat(this, new MyGestureListener()); } @Override public boolean onTouchEvent(MotionEvent event){ if (this.mDetector.onTouchEvent(event)) { return true; } return super.onTouchEvent(event); } class MyGestureListener extends GestureDetector.SimpleOnGestureListener { private static final String DEBUG_TAG = "Gestures"; @Override public boolean onDown(MotionEvent event) { Log.d(DEBUG_TAG,"onDown: " + event.toString()); return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString()); return true; } } }