Phát hiện cử chỉ phổ biến

Thử cách Compose
Jetpack Compose là bộ công cụ giao diện người dùng được đề xuất cho Android. Tìm hiểu cách sử dụng thao tác chạm và nhập trong Compose.

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 dưới dạng một cử chỉ. Có 2 giai đoạn phát hiện cử chỉ:

  1. Thu thập dữ liệu sự kiện chạm.
  2. Diễn giải dữ liệu để xác định xem dữ liệu đó có đáp ứng các tiêu chí cho cử chỉ mà ứng dụng của bạn hỗ trợ hay không.

Các lớp AndroidX

Các ví dụ trong tài liệu này sử dụng các lớp GestureDetectorCompatMotionEventCompat. 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 đó, nó 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 hành động đượ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á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à việ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ỉ bắt đầu khi người dùng chạm vào màn hình lần đầu tiên, tiếp tục khi hệ thống theo dõi vị trí của ngón tay hoặc các ngón tay của người dùng và kết thúc bằng cách ghi lại sự kiện cuối cùng khi 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 gửi đến 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ể sử dụng dữ liệu do MotionEvent cung cấp để xác định xem có xảy ra cử chỉ mà ứng dụng quan tâm hay không.

Ghi lại các sự kiện chạm cho một Hoạt động hoặc Khung hiển thị

Để 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 hành động mà người dùng thực hiện từ tham số event. Thao tác này cung cấp cho bạn dữ liệu thô cần thiết để xác định xem có xảy ra cử chỉ mà bạn quan tâm 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);
    }
}

Đoạn 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ữ, cũng như kéo:

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

Đối với cử chỉ tuỳ chỉnh, sau đó, bạn có thể tự xử lý các sự kiện này để xác định xem chúng có đại diện cho một cử chỉ mà bạn cần xử lý hay không. Tuy nhiên, nếu ứng dụng của bạn sử dụng các cử chỉ thông thường, chẳng hạn như nhấn đúp, chạm và giữ, hất, v.v., 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ỉ thông thường mà không cần tự xử lý các sự kiện chạm riêng lẻ. Điều này sẽ được thảo luận thêm trong phần Phát hiện cử chỉ.

Ghi lại sự kiện chạm cho một khung hiển thị

Để thay thế 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(). Điều này giúp bạn có thể theo dõi các sự kiện chạm mà không cần phân lớp phụ View hiện có, như minh hoạ 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 cẩn thận khi 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 các sự kiện ACTION_MOVEACTION_UP tiếp theo. Điều này là do ACTION_DOWN là điểm xuất phát cho tất cả các sự kiện chạm.

Nếu đang tạo một chế độ xem 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ỉ thông thường. Một số cử chỉ mà tính năng này hỗ trợ bao gồm onDown(), onLongPress()onFling(). Bạn có thể sử dụng GestureDetector cùng với phương thức onTouchEvent() đã mô tả trước đó.

Phát hiện tất cả cử chỉ được hỗ trợ

Khi bạn tạo một đối tượng GestureDetectorCompat, một trong các tham số mà đối tượng này nhận là một lớp triển khai giao diện GestureDetector.OnGestureListener. GestureDetector.OnGestureListener 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ủa bạn có thể nhận được các sự kiện, hãy ghi đè phương thức onTouchEvent() của khung hiển thị hoặc hoạt động và truyền tất cả các sự kiện đã quan sát được đến thực thể của trình phát hiện.

Trong đoạn mã sau, giá trị trả về 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ề 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 thao tác chạm được xử lý thành công.

Nếu chạy đoạn mã sau trong một ứng dụng kiểm thử, bạn có thể cảm nhận được cách các thao tác được kích hoạt khi bạn tương tác với màn hình cảm ứng và nội dung của MotionEvent cho từng sự kiện chạm. Sau đó, bạn sẽ thấy lượng dữ liệu được tạo cho các lượt 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 một cách triển khai cho tất cả cá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 đè những 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 và ghi đè onFling()onDown().

Cho dù bạn sử dụng GestureDetector.OnGestureListener hay GestureDetector.SimpleOnGestureListener, bạn nên triển khai một phương thức onDown() trả về true. Điều này là do mọi 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 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 vấn đề 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ộ 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;
        }
    }
}

Tài nguyên khác