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 sự kiện ACTION_MOVE bất cứ khi nào vị trí, áp suất hoặc kích thước của điểm tiếp xúc hiện tại thay đổi. Như mô tả trong phần Phát hiện cử chỉ phổ biến, tất cả các sự kiện này đều đượ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à chỉ dựa trên thao tác tiếp xúc đơn giản. Để giúp ứng dụng phân biệt giữa cử chỉ dựa trên chuyển động (chẳng hạn như thao tác vuốt) và cử chỉ không dựa trên chuyển động (chẳng hạn như thao tác nhấn một lần), Android có khái niệm về độ trễ khi chạm. Độ trễ khi 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 diễn giải là một cử chỉ dựa trên chuyển động. Để biết thêm thông tin về chủ đề này, hãy xem phần 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à một số 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 đến điểm B.
  • Hướng mà con trỏ đang di chuyển, do toạ độ X và Y xác định.
  • Lịch sử. Bạn có thể tìm thấy kích thước của nhật ký 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 trong quá khứ bằng cách sử dụng các phương thức getHistorical<Value> của sự kiện chuyển động. Nhật ký rất hữu ích khi hiển thị dấu vết của ngón tay người dùng, chẳng hạn như để 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.
  • Tốc độ của con trỏ khi di chuyển trên màn hình cảm ứng.

Hãy tham khảo các tài nguyên liên quan sau:

Vận tốc của đường đua

Bạn có thể dùng 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à 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ó xảy ra hay không. Để giúp bạn tính toán vận tốc 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 hữu ích cho các cử chỉ mà vận tốc là một phần của tiêu chí cho cử chỉ, chẳng hạn như cử chỉ vuốt nhanh.

Sau đâ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 ghi lại con trỏ

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

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

Một khung 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 view chứa khung hiển thị đó có 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 chế độ xem được lấy tiêu điểm trong ứng dụng của bạn, miễn là chế độ xem đó nằm trong cùng một hệ phân cấp view với chế độ xem đã yêu cầu thao tác chụp. Các ứng dụng khác ngừng nhận các sự kiện chuột cho đến khi chế độ chụp được giải phóng, 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 xuất hiện nữa.

Xử lý các sự kiện con trỏ được ghi lại

Sau khi một khung hiển thị thu được thành công thao tác chụp con trỏ, Android sẽ phân phối các sự kiện chuột. Khung hiển thị được lấy tiêu điểm có thể xử lý các sự kiện bằng cách thực hiện một trong các thao tác 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, khung hiển thị của bạn sẽ nhận được một MotionEvent có toạ độ con trỏ chỉ định các chuyển động tương đối như delta X hoặc delta Y, 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().

Giải phóng chế độ chụp con trỏ

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

Kotlin

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

Java

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

Hệ thống có thể lấy ảnh chụp từ 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 view chứa khung hiển thị yêu cầu chụp mất tiêu điểm.