追蹤觸控和指標移動位置

試用 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用觸控和輸入功能。

本課程將說明如何追蹤觸控事件的動作。

每當目前的觸控聯絡人位置、壓力或大小有所變更,就會透過 ACTION_MOVE 事件觸發新的 onTouchEvent()。如「偵測常用手勢」一節所述,所有這類事件都會記錄在 onTouchEvent()MotionEvent 參數中。

由於手指觸控不一定是最精確的互動形式,因此偵測觸控事件的偵測依據通常是動作,而非簡單接觸。為協助應用程式區分動作式手勢 (例如滑動) 和非移動手勢 (例如輕觸一下),Android 也採用觸控滑動的概念。觸控手勢是指使用者輕觸時,在系統將手勢視為以動作為主的手勢前,使用者輕觸可進行的距離 (以像素為單位)。如要進一步瞭解這個主題,請參閱「在 ViewGroup 中管理觸控事件」。

有幾種方法可以追蹤手勢中的動作,視應用程式的需求而定。範例如下:

  • 指標的開始和結束位置,例如將螢幕上的物件從 A 點移動到 B 點。
  • 指標的行進方向,由 X 和 Y 座標決定。
  • 。如要查看手勢歷史記錄的大小,請呼叫 MotionEvent 方法 getHistorySize()。接著,您可以使用動作事件的 getHistorical<Value> 方法,取得每個歷史事件的位置、大小、時間和壓力。當您轉譯使用者手指軌跡 (例如觸控繪圖) 時,「記錄」功能非常實用。詳情請參閱 MotionEvent 參考資料。
  • 指標在觸控螢幕上移動時的速度。

請參閱下列相關資源:

追蹤速度

您可以採用以指標移動的距離或方向為基礎的動作手勢。不過,速度通常是在追蹤手勢特性或判斷手勢是否發生時的決定因素。為了簡化速率計算作業,Android 提供 VelocityTracker 類別。VelocityTracker 可協助您追蹤觸控事件的速度。如果手勢是速度是手勢條件的一部分 (例如快速滑過),這就非常實用。

以下範例說明 VelocityTracker API 中方法的用途:

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;
    }
}

使用指標擷取功能

某些應用程式 (例如遊戲、遠端桌面和虛擬化用戶端) 可以藉由控制滑鼠遊標而受益。指標擷取是 Android 8.0 (API 級別 26) 及以上版本提供的功能,可透過將所有滑鼠事件傳送至應用程式中的焦點檢視畫面,藉此提供這個控制項。

要求指標擷取

應用程式中的檢視畫面只有在包含其焦點的檢視區塊階層時,才能要求擷取指標。因此,當檢視畫面有特定的使用者動作時 (例如在 onClick() 事件或活動的 onWindowFocusChanged() 事件處理常式期間),要求指標就會擷取要求指標。

如要要求指標擷取,請在檢視畫面中呼叫 requestPointerCapture() 方法。以下程式碼範例說明如何在使用者點選檢視畫面時要求指標擷取:

Kotlin

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

Java

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

擷取指標的要求成功後,Android 就會呼叫 onPointerCaptureChange(true)。只要與要求擷取的檢視區塊位於相同的檢視區塊階層,系統才會在應用程式中將滑鼠事件傳送至應用程式中的聚焦檢視區塊。其他應用程式在擷取發布前會停止接收滑鼠事件,包括 ACTION_OUTSIDE 事件。Android 會照常傳送來自滑鼠以外來源的指標事件,但不會再顯示滑鼠遊標。

處理擷取的指標事件

檢視畫面成功擷取指標擷取後,Android 就會傳送滑鼠事件。您的聚焦檢視畫面可以執行下列其中一項工作來處理事件:

以下程式碼範例說明如何實作 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;
}

以下程式碼範例說明如何註冊 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;
  }
});

無論您使用自訂檢視區塊或註冊事件監聽器,檢視畫面都會收到 MotionEvent,其中包含用於指定相對動作 (例如 X 或 Y 差異遷移) 的指標座標,與軌跡球裝置提供的座標類似。您可以使用 getX()getY() 擷取座標。

版本指標擷取

應用程式中的檢視畫面可呼叫 releasePointerCapture() 來釋出指標擷取,如以下程式碼範例所示:

Kotlin

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

Java

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

系統可以在您未明確呼叫 releasePointerCapture() 的情況下,從檢視畫面中移除擷取內容,原因通常是因為包含要求擷取的檢視區塊階層所含的檢視區塊階層失去焦點。