GestureDetectorを使ったジェスチャーのハンドリング

概要

GestureDetectorを使うことで、共通のジェスチャーを簡単にハンドルすることができる。

またActivity.onTouchEvent()もしくはView.onTouchEvent()と組み合わせて使うため、それ以外のジェスチャーのハンドリングもシンプルに記述することが可能となっている。

ハンドルできるジェスチャー

  • シングルタップ
  • ダブルタップ
  • フリック
  • スクロール - フリックの途中も呼び出されるため、フリックとの組み合わせは注意が必要

使い方

  1. インスタンスの生成とタッチイベントのハンドリング
  2. GestureDetector.OnGestureListenerの実装
  3. ダブルタップをサポートする場合は、GestureDetector.OnDoubleTapListenerを実装

1. インスタンスの生成とハンドリング

タッチイベントをハンドリングするために、GestureDetectorの上位互換であるGestureDetectorCompatインスタンスを生成し、Activity.onTouchEvent() もしくは、View.onTouchEvent()でハンドリングする。

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

    lateinit var gestureDetector: GestureDetectorCompat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        gestureDetector = GestureDetectorCompat(this, this)
        gestureDetector.setOnDoubleTapListener(this)
    }
  • GestureDetectorCompat(context, listener)
  • gestureDetector.setOnDoubleTapListener(this) ダブルタップを有効にする場合
override fun onTouchEvent(event: MotionEvent?): Boolean {
    // GestureDetector側でハンドリングされた場合は、trueが返される
    if (gestureDetector.onTouchEvent(event)) {
        return true
    } else {
    // GestureDetector側でハンドリングされなかった場合は、それ以外のタッチジェスチャーがこちらでハンドリングできる
        return super.onTouchEvent(event)
    }
}

2. GestureDetector.OnGestureListenerの実装

以下のメソッドを全てオーバーライドする必要がある

//region GestureDetector.OnGestureListener

// MothionEvent.DONWのハンドリング
override fun onDown(e: MotionEvent): Boolean {
    Log.d(DEBUG_TAG, "onDown: $e")
    return true
}

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

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

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

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

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

//endregion

ダブルタップの検知もサポートする場合

GestureDetector.OnDoubleTapListenerを実装する

以下のメソッドをオーバーライドする

//region GestureDetector.OnDoubleTapListener

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

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

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

//endregion

イベントのハンドリング

シングルタップ

onDown: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=563.9612, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135467577, downTime=135467577, deviceId=11, source=0x1002, displayId=0 }

--- onDownされたのでactivity or viewに結果をtrueで返す

onShowPress: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=563.9612, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135467577, downTime=135467577, deviceId=11, source=0x1002, displayId=0 }

onSingleTapUp: MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=563.9612, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135467700, downTime=135467577, deviceId=11, source=0x1002, displayId=0 }

--- ダブルタップの可能性があるため一旦activity or viewにtrueが返る

最後にシングルタップの検知

onSingleTapConfirmed: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=563.9612, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135467577, downTime=135467577, deviceId=11, source=0x1002, displayId=0 }

ダブルタップの検知

onDown: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=841.9336, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136774823, downTime=136774823, deviceId=11, source=0x1002, displayId=0 }

--- onDownの結果が返る

onSingleTapUp: MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=843.9258, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136774912, downTime=136774823, deviceId=11, source=0x1002, displayId=0 }

--- onSingleToupの結果が返る

onDoubleTap: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=841.9336, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136774823, downTime=136774823, deviceId=11, source=0x1002, displayId=0 }

onDoubleTapEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=843.9258, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136774991, downTime=136774991, deviceId=11, source=0x1002, displayId=0 }

onDown: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=843.9258, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136774991, downTime=136774991, deviceId=11, source=0x1002, displayId=0 }

--- onDownの結果が返る

onDoubleTapEvent: MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=359.97803, y[0]=843.9258, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=136775082, downTime=136774991, deviceId=11, source=0x1002, displayId=0 }

--- onDoubleTapEvent action=ACTION_UPの結果が返る
  • onDoubleTapEvent()が、開始ACTION_DOWNと最後ACTION_UPの2回呼ばれる
  • onSingleTap()が呼ばれているが、onSingleTapConfirmed()は呼ばれていない

フリック

onDown: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656229, downTime=135656229, deviceId=11, source=0x1002, displayId=0 }

--- onDonwの結果が返される

onScroll: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656229, downTime=135656229, deviceId=11, source=0x1002, displayId=0 } MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1228.0225, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=135656277, downTime=135656229, deviceId=11, source=0x1002, displayId=0 }

--- onScrollの結果が返される

onScroll: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656229, downTime=135656229, deviceId=11, source=0x1002, displayId=0 } MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1189.4066, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=135656294, downTime=135656229, deviceId=11, source=0x1002, displayId=0 }

--- onScrollの結果が返される

onScroll: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656229, downTime=135656229, deviceId=11, source=0x1002, displayId=0 } MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1154.9127, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=135656310, downTime=135656229, deviceId=11, source=0x1002, displayId=0 }

--- onScrollの結果が返される

... onScrollが何度か呼び出される

--- onScrollの結果が返される

onFling: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=585.9778, y[0]=1256.9531, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656229, downTime=135656229, deviceId=11, source=0x1002, displayId=0 } MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=574.9695, y[0]=407.9297, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=135656476, downTime=135656229, deviceId=11, source=0x1002, displayId=0 }
dle the touch event successfuly

--- onFlingの結果が返される
  • フリックの途中のonScroll(指の移動)が呼び出される

SimpleOnGestureListenerを使う方法

OnGestureListenrは使用しないジェスチャーもオーバーライドする必要があるが、SimpleOnGestureListenerを使用することで、 必要な分だけオーバーライドする事ができる。

ただしこちらはクラスなのでActivityやViewに直接実装することはできない。

フリックのハンドリングのみ実装できる

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

参照

developer.android.com