一般的な操作の検出

ユーザーがタッチ スクリーンに指を 1 本以上置くと「タップ操作」が発生し、アプリはそのタップのパターンを特定の操作として解釈します。これに対応して、ジェスチャ検出には 2 つのフェーズがあります。

  1. タップイベントに関するデータを収集します。
  2. データを解釈して、アプリがサポートする操作のいずれかの基準を満たしているかどうかを確認します。

以下の関連リソースをご覧ください。

サポート ライブラリ クラス

このレッスンでは、例として GestureDetectorCompat クラスと MotionEventCompat クラスを取り上げます。これらのクラスはサポート ライブラリにあります。サポート ライブラリ クラスは、Android 1.6 以上を搭載したデバイスとの互換性を提供できる場合に使用してください。MotionEventCompat は、MotionEvent の後継クラスではありません。その代わりに、MotionEvent オブジェクトを渡すことができる静的ユーティリティ メソッドを提供します。それにより、そのイベントに関連する目的のアクションを受け取れます。

データの収集

ユーザーが画面に 1 本以上の指を置くと、タップイベントを受け取ったビューでコールバック onTouchEvent() がトリガーされます。 最終的に操作として識別されるタップイベントのシーケンス(位置、圧力、サイズ、別の指を置く動作など)ごとに、onTouchEvent() が数回発生します。

操作は、ユーザーが最初に画面に触れたときに始まり、システムがユーザーの指の位置を追跡している間継続し、最後のイベント(ユーザーの指が画面を離れる)をキャプチャしたときに終わります。このインタラクション全体を通して、onTouchEvent() に配信される MotionEvent にすべてのインタラクションの詳細が示されます。アプリは、MotionEvent によって提供されるデータを使用して、処理すべき操作が発生したかどうかを判断できます。

Activity または View のタップイベントをキャプチャする

Activity または View のタップイベントをインターセプトするには、onTouchEvent() コールバックをオーバーライドします。

次のスニペットは、getActionMasked() を使用して、ユーザーが実行したアクションを event パラメータから抽出します。これにより、処理すべき操作が発生したかどうかを判断するために必要な RAW データが得られます。

Kotlin

    class MainActivity : Activity() {
        ...
        // This example shows an Activity, but you would use the same approach if
        // you were subclassing a View.
        override fun onTouchEvent(event: MotionEvent): Boolean {

            val action: Int = MotionEventCompat.getActionMasked(event)

            return when (action) {
                MotionEvent.ACTION_DOWN -> {
                    Log.d(DEBUG_TAG, "Action was DOWN")
                    true
                }
                MotionEvent.ACTION_MOVE -> {
                    Log.d(DEBUG_TAG, "Action was MOVE")
                    true
                }
                MotionEvent.ACTION_UP -> {
                    Log.d(DEBUG_TAG, "Action was UP")
                    true
                }
                MotionEvent.ACTION_CANCEL -> {
                    Log.d(DEBUG_TAG, "Action was CANCEL")
                    true
                }
                MotionEvent.ACTION_OUTSIDE -> {
                    Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                    true
                }
                else -> super.onTouchEvent(event)
            }
        }
    }
    

Java

    public class MainActivity extends Activity {
    ...
    // This example shows an Activity, but you would use the same approach if
    // you were subclassing a View.
    @Override
    public boolean onTouchEvent(MotionEvent event){

        int action = MotionEventCompat.getActionMasked(event);

        switch(action) {
            case (MotionEvent.ACTION_DOWN) :
                Log.d(DEBUG_TAG,"Action was DOWN");
                return true;
            case (MotionEvent.ACTION_MOVE) :
                Log.d(DEBUG_TAG,"Action was MOVE");
                return true;
            case (MotionEvent.ACTION_UP) :
                Log.d(DEBUG_TAG,"Action was UP");
                return true;
            case (MotionEvent.ACTION_CANCEL) :
                Log.d(DEBUG_TAG,"Action was CANCEL");
                return true;
            case (MotionEvent.ACTION_OUTSIDE) :
                Log.d(DEBUG_TAG,"Movement occurred outside bounds " +
                        "of current screen element");
                return true;
            default :
                return super.onTouchEvent(event);
        }
    }
    

次に、このようなイベントを独自に処理して、操作が発生したかどうかを判断できます。これはカスタム操作に対して行う必要がある処理の一種です。ただし、ダブルタップ、長押し、フリングなどの一般的な操作をアプリで使用する場合は、GestureDetector クラスを利用できます。GestureDetector を使用すると、個々のタップイベントを独自に処理しなくても、一般的な操作を簡単に検出できます。これについては、操作の検出で説明しています。

単一のビューのタップイベントをキャプチャする

onTouchEvent() を使用する代わりに、setOnTouchListener() メソッドを使用して View.OnTouchListener オブジェクトを任意の View オブジェクトにアタッチできます。これにより、既存の View をサブクラス化しなくても、タップイベントをリッスンできます。例:

Kotlin

    findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
        // ... Respond to touch events
        true
    }
    

Java

    View myView = findViewById(R.id.my_view);
    myView.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            // ... Respond to touch events
            return true;
        }
    });
    

ACTION_DOWN イベントに対して false を返すリスナーの作成には注意が必要です。これを行うと、その後の連続するイベント ACTION_MOVE および ACTION_UP に対してリスナーが呼び出されません。これは、ACTION_DOWN がすべてのタップイベントの開始点であるためです。

カスタムビューを作成する場合は、上記のように onTouchEvent() をオーバーライドできます。

操作の検出

Android には、一般的な操作を検出するための GestureDetector クラスが用意されています。サポートされている操作には、onDown()onLongPress()onFling() などがあります。GestureDetector は、上記の onTouchEvent() メソッドと組み合わせて使用できます。

サポートされている操作をすべて検出する

GestureDetectorCompat オブジェクトをインスタンス化する際は、GestureDetector.OnGestureListener インターフェースを実装するクラスをパラメータの 1 つとして取ります。GestureDetector.OnGestureListener は、特定のタップイベントが発生したときにユーザーに通知します。GestureDetector オブジェクトがイベントを受け取れるようにするには、View または Activity の onTouchEvent() メソッドをオーバーライドし、観測されたすべてのイベントを Detector インスタンスに渡します。

次のスニペットで、個々の on<TouchEvent> メソッドの戻り値 true は、タップイベントが正常に処理されたことを示します。戻り値 false は、タップが正常に処理されるまでイベントがビュースタックに渡されることを示します。

次のスニペットを実行して、タッチ スクリーンを操作したときにアクションがどのようにトリガーされるか、各タップイベントに対する MotionEvent の内容は何かを把握してください。単純な操作であっても、いかに多くのデータが生成されるかを理解できます。

Kotlin

    private const val DEBUG_TAG = "Gestures"

    class MainActivity :
            Activity(),
            GestureDetector.OnGestureListener,
            GestureDetector.OnDoubleTapListener {

        private lateinit var mDetector: GestureDetectorCompat

        // Called when the activity is first created.
        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            // Instantiate the gesture detector with the
            // application context and an implementation of
            // GestureDetector.OnGestureListener
            mDetector = GestureDetectorCompat(this, this)
            // Set the gesture detector as the double tap
            // listener.
            mDetector.setOnDoubleTapListener(this)
        }

        override fun onTouchEvent(event: MotionEvent): Boolean {
            return if (mDetector.onTouchEvent(event)) {
                true
            } else {
                super.onTouchEvent(event)
            }
        }

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }

        override fun onLongPress(event: MotionEvent) {
            Log.d(DEBUG_TAG, "onLongPress: $event")
        }

        override fun onScroll(
                event1: MotionEvent,
                event2: MotionEvent,
                distanceX: Float,
                distanceY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
            return true
        }

        override fun onShowPress(event: MotionEvent) {
            Log.d(DEBUG_TAG, "onShowPress: $event")
        }

        override fun onSingleTapUp(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onSingleTapUp: $event")
            return true
        }

        override fun onDoubleTap(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDoubleTap: $event")
            return true
        }

        override fun onDoubleTapEvent(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
            return true
        }

        override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
            return true
        }

    }
    

Java

    public class MainActivity extends Activity implements
            GestureDetector.OnGestureListener,
            GestureDetector.OnDoubleTapListener{

        private static final String DEBUG_TAG = "Gestures";
        private GestureDetectorCompat mDetector;

        // Called when the activity is first created.
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // Instantiate the gesture detector with the
            // application context and an implementation of
            // GestureDetector.OnGestureListener
            mDetector = new GestureDetectorCompat(this,this);
            // Set the gesture detector as the double tap
            // listener.
            mDetector.setOnDoubleTapListener(this);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event){
            if (this.mDetector.onTouchEvent(event)) {
                return true;
            }
            return super.onTouchEvent(event);
        }

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }

        @Override
        public void onLongPress(MotionEvent event) {
            Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
        }

        @Override
        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
                float distanceY) {
            Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
            return true;
        }

        @Override
        public void onShowPress(MotionEvent event) {
            Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
        }

        @Override
        public boolean onSingleTapUp(MotionEvent event) {
            Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent event) {
            Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
            return true;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent event) {
            Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
            return true;
        }
    }
    

サポートされている操作のサブセットを検出する

少数の操作のみを処理する場合は、GestureDetector.OnGestureListener インターフェースを実装する代わりに GestureDetector.SimpleOnGestureListener を拡張できます。

GestureDetector.SimpleOnGestureListener は、すべての on<TouchEvent> メソッドに対して false を返すことにより、それらのメソッドすべての実装を提供します。これにより、必要なメソッドのみをオーバーライドできます。たとえば、以下のスニペットは、GestureDetector.SimpleOnGestureListener を拡張して onFling()onDown() をオーバーライドするクラスを作成します。

GestureDetector.OnGestureListener を使用するかどうかにかかわらず、true を返す onDown() メソッドを実装することをおすすめします。これは、すべての操作が onDown() メッセージで始まるためです。onDown() から false を返す場合、GestureDetector.SimpleOnGestureListener のデフォルトの動作と同様に、システムは残りの操作が無視されると想定し、GestureDetector.OnGestureListener の他のメソッドは呼び出されなくなります。これにより、アプリで予期しない問題が発生する可能性があります。onDown() から false を返すのは、操作全体を実際に無視する場合のみにしてください。

Kotlin

    private const val DEBUG_TAG = "Gestures"

    class MainActivity : Activity() {

        private lateinit var mDetector: GestureDetectorCompat

        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            mDetector = GestureDetectorCompat(this, MyGestureListener())
        }

        override fun onTouchEvent(event: MotionEvent): Boolean {
            mDetector.onTouchEvent(event)
            return super.onTouchEvent(event)
        }

        private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

            override fun onDown(event: MotionEvent): Boolean {
                Log.d(DEBUG_TAG, "onDown: $event")
                return true
            }

            override fun onFling(
                    event1: MotionEvent,
                    event2: MotionEvent,
                    velocityX: Float,
                    velocityY: Float
            ): Boolean {
                Log.d(DEBUG_TAG, "onFling: $event1 $event2")
                return true
            }
        }
    }
    

Java

    public class MainActivity extends Activity {

        private GestureDetectorCompat mDetector;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mDetector = new GestureDetectorCompat(this, new MyGestureListener());
        }

        @Override
        public boolean onTouchEvent(MotionEvent event){
            this.mDetector.onTouchEvent(event);
            return super.onTouchEvent(event);
        }

        class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
            private static final String DEBUG_TAG = "Gestures";

            @Override
            public boolean onDown(MotionEvent event) {
                Log.d(DEBUG_TAG,"onDown: " + event.toString());
                return true;
            }

            @Override
            public boolean onFling(MotionEvent event1, MotionEvent event2,
                    float velocityX, float velocityY) {
                Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
                return true;
            }
        }
    }