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 mô hình onTouchEvent() được kích hoạt bằng một ACTION_MOVE sự kiện bất cứ khi nào vị trí tiếp xúc chạm, áp suất hoặc kích thước hiện tại thay đổi. Như được mô tả trong Phát hiện các cử chỉ phổ biến, tất cả Những 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, việc phát hiện các sự kiện chạm thường dựa trên chuyển động hơn là tiếp xúc đơn giản. Để giúp ứng dụng phân biệt giữa các 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 Touch slop. Khoảng cách chạm đề cập đến khoảng cách tính bằng pixel mà người dùng có thể chạm 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 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 trên màn hình vật từ điểm A đến điểm B.
  • Hướng di chuyển của con trỏ, như được xác định bởi X và Y toạ độ.
  • Lịch sử. Bạn có thể tìm kích thước của nhật ký cử chỉ bằng cách gọi hàm MotionEvent phương thức getHistorySize(). Sau đó, bạn có thể biết được vị trí, kích thước, thời gian và áp lực của mỗi sự kiện lịch sử bằng cách sử dụng sự kiện chuyển động getHistorical<Value> . Nhật ký rất hữu ích khi hiển thị dấu vết ngón tay của người dùng, chẳng hạn như đối với thao tác 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 tài nguyên 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 con trỏ di chuyển. Tuy nhiên, tốc độ thường là yếu tố quyết định trong việc theo dõi đặc điểm của một cử chỉ hoặc quyết định liệu cử chỉ có xảy ra hay không. Để thực hiệ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. Thông tin này hữu ích dành 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ư một cử chỉ 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 quyền kiểm soát con trỏ chuột. Chụp con trỏ là một tính năng có sẵn trong Android 8.0 (API cấp 26) trở lên, cung cấp khả năng kiểm soát này bằng cách phân phối tất cả sự kiện chuột đến chế độ xem tập trung 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 khung 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ó hành động cụ thể của người dùng trên chế độ xem, chẳng hạn như trong khi onClick() hoặc trong onWindowFocusChanged() trình xử lý sự kiện của hoạt động.

Để yêu cầu thu thậ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 con trỏ thu thập khi người dùng nhấp vào một chế độ xem:

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 đưa các sự kiện chuột đến chế độ xem tập trung trong ứng dụng của bạn miễn là hệ phân cấp khung hiển thị đó ở cùng hệ phân cấp khung hiển thị với khung hiển thị đã yêu cầu chụp. Lý do khác các ứng dụng ngừng nhận sự kiện chuột cho đến khi chụp ảnh được nhả ra, bao gồm ACTION_OUTSIDE các sự kiện. Android phân phối các sự kiện con trỏ từ các nguồn không phải chuột dưới dạng 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ị nhận thành công ảnh chụp con trỏ, Android sẽ phân phối 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 nhiệm 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 biểu thị 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ị đó vẫn nhận được một MotionEvent có toạ độ con trỏ chỉ định chuyển động tương đối, chẳng hạn như X hay còn gọi là 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().

Thả chụp con trỏ

Thành phần hiển thị trong ứng dụng có thể giải phóng hoạt động chụ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ể gỡ ảnh ra khỏi chế độ xem mà không cần bạn thể hiện rõ ràng gọi releasePointerCapture(), thường là do hệ phân cấp khung hiển thị chứa khung hiển thị yêu cầu ảnh chụp sẽ mất tiêu điểm.