The Android Developer Challenge is back! Submit your idea before December 2.

タップとポインタの動きのトラッキング

このレッスンでは、タップイベントの動きをトラッキングする方法について説明します。

新しい onTouchEvent() は、現在のタップ位置、圧力、またはサイズが変更されるたびに、ACTION_MOVE イベントでトリガーされます。一般的な操作の検出で説明しているように、このようなイベントはすべて 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
                        // 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
                    // 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 はマウスイベントの配信を開始します。フォーカスされているビューでは、次のタスクのいずれかを実施することでイベントを処理できます。

  1. カスタムビューを使用している場合は、onCapturedPointerEvent(MotionEvent) をオーバーライドします。
  2. それ以外の場合は、OnCapturedPointerListener を登録します。

次のコード例は、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 was
        // 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 was
      // 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 was
        // 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 was
        // successfully processed
        return true;
      }
    });
    

カスタムビューを使用するかリスナーを登録するかに関係なく、トラックボール デバイスによって配信される座標と同様に、X / Y デルタなどの相対的な動きを指定するポインタ座標を持つ MotionEvent をビューが受け取ります。getX()getY() を使用することで座標を取得できます。

ポインタ キャプチャの解除

次のコード例に示すように、アプリのビューは、releasePointerCapture() を呼び出すことでポインタ キャプチャを解放できます。

Kotlin

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

Java

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

システムは、releasePointerCapture() を明示的に呼び出さなくても、ビューからキャプチャを取り除くことができます。これはほとんどの場合、キャプチャをリクエストしたビューを含むビュー階層がフォーカスを失ったためです。