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

Thử cách sử dụng 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í tiếp xúc chạm hiện tại, áp lực hoặc kích thước thay đổi. Như mô tả trong phần 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 thông số MotionEvent của onTouchEvent().

Vì thao tác chạm dựa trên 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 sự kiện chạm thường dựa trên chuyển động nhiều hơn là dựa vào tiếp xúc đơn giản. Để giúp ứng dụng phân biệt 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 đưa vào khái niệm cảm ứng chống trượt. Độ lệch chạm đề cập đến khoảng cách tính bằng pixel mà một lần chạm của người dùng có thể di chuyển trước khi cử chỉ được hiểu 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 bài viết Quản lý các 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à ví dụ:

  • Vị trí bắt đầu và kết thúc của một 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 của con trỏ, được xác định theo toạ độ X và Y.
  • 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 phương thức MotionEvent getHistorySize(). Sau đó, bạn có thể thu được 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 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 hiển thị vệt ngón tay của 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.
  • 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 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, tốc độ thường là yếu tố quyết định theo dõi các đặc điểm của một cử chỉ hoặc quyết định xem cử chỉ đó có xuất hiện hay không. Để giúp tính toá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ỉ có vận 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 cũng như máy tính từ xa và ứng dụng ảo hoá) hưởng lợi từ việc có được quyền 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. Tính năng này cung cấp khả năng 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 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 tâm điểm đó. Vì lý do này, hãy yêu cầu thu thậ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ị. Mã ví dụ sau đây cho thấy cách yêu cầu thu thậ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 thu thập con trỏ thành công, Android sẽ gọi onPointerCaptureChange(true). Hệ thống 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 ngừng nhận sự kiện chuột cho đến khi chụp ảnh được huỷ, 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ông phải chuột như bình thường, nhưng con trỏ chuột không còn hiển thị nữa.

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

Sau khi khung hiển thị tiếp nhận thành công hoạt động thu thập con trỏ, 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 này bằng cách thực hiện một trong những tác vụ sau:

Ví dụ về mã sau đây biểu thị 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ý một 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 chuyển động tương đối, chẳng hạn như delta X hoặc Y, tương tự như toạ độ do thiết bị bi xoay phân phối. 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ể huỷ thao tác 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ể lấy khung hiển thị đã chụp khỏi khung hiển thị mà không cần bạn gọi rõ ràng releasePointerCapture(), 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 sẽ mất tiêu điểm.