Theo dõi chuyển động của con trỏ và điểm chạm

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.

Bài học này mô tả cách theo dõi chuyển động trong các sự kiện chạm.

Một onTouchEvent() mới sẽ được kích hoạt bằng một sự kiện ACTION_MOVE bất cứ khi nào vị trí của điểm tiếp xúc chạm, áp lực hoặc kích thước hiện tại thay đổi. Như mô tả trong phần Phát hiện các cử chỉ phổ biến, tất cả các sự kiện này được ghi lại trong tham số MotionEvent của onTouchEvent().

Vì thao tác chạm bằng ngón tay không phải lúc nào cũng là hình thức tương tác chính xác nhất, nên việc phát hiện các sự kiện chạm thường dựa trên chuyển động nhiều hơn là tiếp xúc đơn giản. Để giúp các ứng dụng phân biệt giữa cử chỉ dựa trên chuyển động (chẳng hạn như vuốt) và cử chỉ không chuyển động (chẳng hạn như một lần nhấn), Android bao gồm khái niệm vuốt chạm. Trượt bằng cách chạm là khoảng cách tính bằng pixel mà thao tác chạm của người dùng có thể di chuyển trước khi cử chỉ được hiểu là cử chỉ dựa trên chuyển động. Để biết thêm thông tin về chủ đề này, hãy xem bài viết Quản lý sự kiện chạm trong ViewGroup.

Có một số cách theo dõi chuyển động trong một cử chỉ, tuỳ thuộc vào nhu cầu của ứng dụng. Sau đây là các ví dụ:

  • Vị trí bắt đầu và kết thúc của con trỏ, chẳng hạn như di chuyển một đối tượng trên màn hình từ điểm A sang điểm B.
  • Hướng di chuyển của con trỏ, như được xác định bằng toạ độ X và Y.
  • Lịch sử. Bạn có thể tìm kích thước nhật ký của một cử chỉ bằng cách gọi phương thức MotionEvent getHistorySize(). Sau đó, bạn có thể lấy vị trí, kích thước, thời gian và áp lực của từng sự kiện trước đây bằng cách sử dụng phương thức getHistorical<Value> của sự kiện chuyển động. Nhật ký rất hữu ích khi kết xuất dấu vết ngón tay của người dùng, chẳng hạn như khi vẽ bằng cách chạm. Hãy xem tài liệu tham khảo MotionEvent để biết thông tin chi tiết.
  • Vận tốc của con trỏ khi con trỏ di chuyển trên màn hình cảm ứng.

Hãy tham khảo các thông tin có liên quan sau:

Theo dõi vận tốc

Bạn có thể có một cử chỉ dựa trên chuyển động dựa trên khoảng cách hoặc hướng mà con trỏ di chuyển. Tuy nhiên, vận tốc thường là một yếu tố quyết định trong việc theo dõi các đặc điểm của một cử chỉ hoặc quyết định xem cử chỉ đó có diễn ra hay không. Để giúp việc tính toán vận tốc trở nên dễ dàng hơn, Android cung cấp lớp VelocityTracker. VelocityTracker giúp bạn theo dõi tốc độ của các sự kiện chạm. Điều này rất hữu ích cho các cử chỉ trong đó tốc độ là một phần của tiêu chí cho cử chỉ, chẳng hạn như hất.

Dưới đây là ví dụ minh hoạ mục đích của các phương thức trong API VelocityTracker:

Kotlin

private const val DEBUG_TAG = "Velocity"

class MainActivity : Activity() {
    private var mVelocityTracker: VelocityTracker? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                // Reset the velocity tracker back to its initial state.
                mVelocityTracker?.clear()
                // If necessary, retrieve a new VelocityTracker object to watch
                // the velocity of a motion.
                mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                // Add a user's movement to the tracker.
                mVelocityTracker?.addMovement(event)
            }
            MotionEvent.ACTION_MOVE -> {
                mVelocityTracker?.apply {
                    val pointerId: Int = event.getPointerId(event.actionIndex)
                    addMovement(event)
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then, call getXVelocity() and
                    // getYVelocity() to retrieve the velocity for each pointer
                    // ID.
                    computeCurrentVelocity(1000)
                    // Log velocity of pixels per second. It's best practice to
                    // use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                    Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker?.recycle()
                mVelocityTracker = null
            }
        }
        return true
    }
}

Java

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = "Velocity";
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);

        switch(action) {
            case MotionEvent.ACTION_DOWN:
                if(mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                }
                else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                // Add a user's movement to the tracker.
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity() and
                // getYVelocity() to retrieve the velocity for each pointer ID.
                mVelocityTracker.computeCurrentVelocity(1000);
                // Log velocity of pixels per second. It's best practice to use
                // VelocityTrackerCompat where possible.
                Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
}

Sử dụng tính năng chụp con trỏ

Một số ứng dụng, chẳng hạn như trò chơi, máy tính từ xa và ứng dụng ảo hoá, được hưởng lợi từ việc kiểm soát con trỏ chuột. Chụp con trỏ là một tính năng có trong Android 8.0 (API cấp 26) trở lên, cung cấp chế độ kiểm soát này bằng cách phân phối tất cả các sự kiện chuột đến một khung hiển thị tập trung trong ứng dụng của bạn.

Yêu cầu chụp con trỏ

Một thành phần hiển thị trong ứng dụng của bạn chỉ có thể yêu cầu chụp con trỏ khi hệ phân cấp thành phần hiển thị chứa tiêu điểm đó. Vì lý do này, hãy yêu cầu chụp con trỏ khi có một hành động cụ thể của người dùng trên khung hiển thị, chẳng hạn như trong sự kiện onClick() hoặc trong trình xử lý sự kiện onWindowFocusChanged() của hoạt động.

Để yêu cầu chụp con trỏ, hãy gọi phương thức requestPointerCapture() trên khung hiển thị. Ví dụ về mã sau đây cho thấy cách yêu cầu chụp con trỏ khi người dùng nhấp vào một khung hiển thị:

Kotlin

fun onClick(view: View) {
    view.requestPointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

Sau khi yêu cầu chụp con trỏ thành công, Android sẽ gọi onPointerCaptureChange(true). Hệ thống sẽ phân phối các sự kiện chuột đến khung hiển thị tập trung trong ứng dụng của bạn, miễn là khung hiển thị đó nằm trong cùng hệ phân cấp khung hiển thị với khung hiển thị đã yêu cầu chụp. Các ứng dụng khác sẽ ngừng nhận các sự kiện chuột cho đến khi tính năng chụp ảnh được huỷ bỏ, bao gồm cả các sự kiện ACTION_OUTSIDE. Android phân phối các sự kiện con trỏ từ các nguồn khác ngoài chuột như bình thường, nhưng con trỏ chuột không còn hiển thị.

Xử lý các sự kiện con trỏ đã ghi

Sau khi khung hiển thị thu nạp con trỏ thành công, Android sẽ phân phối các sự kiện chuột. Khung hiển thị tập trung có thể xử lý các sự kiện bằng cách thực hiện một trong các tác vụ sau:

Ví dụ về mã sau đây cho thấy cách triển khai onCapturedPointerEvent(MotionEvent):

Kotlin

override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
    // Get the coordinates required by your app.
    val verticalOffset: Float = motionEvent.y
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true
}

Java

@Override
public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
  // Get the coordinates required by your app.
  float verticalOffset = motionEvent.getY();
  // Use the coordinates to update your view and return true if the event is
  // successfully processed.
  return true;
}

Ví dụ về mã sau đây cho thấy cách đăng ký OnCapturedPointerListener:

Kotlin

myView.setOnCapturedPointerListener { view, motionEvent ->
    // Get the coordinates required by your app.
    val horizontalOffset: Float = motionEvent.x
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    true
}

Java

myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
  @Override
  public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
    // Get the coordinates required by your app.
    float horizontalOffset = motionEvent.getX();
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true;
  }
});

Cho dù bạn sử dụng khung hiển thị tuỳ chỉnh hay đăng ký một trình nghe, thì khung hiển thị đó đều nhận được một MotionEvent có toạ độ con trỏ chỉ định các chuyển động tương đối như X hoặc Y delta, tương tự như toạ độ do thiết bị bi xoay cung cấp. Bạn có thể truy xuất toạ độ bằng cách sử dụng getX()getY().

Thả chụp con trỏ

Khung hiển thị trong ứng dụng của bạn có thể giải phóng hoạt động thu thập con trỏ bằng cách gọi releasePointerCapture(), như trong đoạn mã ví dụ sau:

Kotlin

override fun onClick(view: View) {
    view.releasePointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

Hệ thống có thể đưa ảnh chụp khỏi khung hiển thị mà bạn không cần gọi releasePointerCapture() một cách rõ ràng, thường là do hệ phân cấp khung hiển thị chứa khung hiển thị yêu cầu chụp mất tiêu điểm.