100
69

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter GestureDetectorの使い方とタッチイベント検出まとめ

Last updated at Posted at 2020-05-23

1. はじめに

タッチ/ボタン入力検出用途で利用する、GestureDetectorの使い方についてのまとめ記事です。

サポート済みの入力デバイス

GestureDetectorは、PrimaryとSecondaryの2つのボタン入力をサポートしています。基本的にはPrimaryのタッチ入力のみの利用になると思います。デスクトップ環境であれば、マウスボタンでも反応します (現状、Secondaryはどこにも接続されていない?)。

Flutter EngineのAPI経由で各種プラットフォームの入力デバイスI/Fと接続することで、Flutter FrameworkのDart側で入力を検出する仕組みです。

kPrimaryStylusButton: The stylus contacts the screen.
kSecondaryMouseButton: The primary mouse button.

2. 基本的な使い方

タッチを検出したいWidgetの親WidgetにGestureDetectorを利用します。タッチ検出にはonTapプロパティを利用します。

GestureDetector(
  onTap: () {
    // シングルタップ時に呼ばれる
  },
  // タッチ検出対象のWidget
  child: Text(
    'How to use GestureDetector',
    textAlign: TextAlign.center,
    overflow: TextOverflow.ellipsis,
    style: TextStyle(fontWeight: FontWeight.bold),
  ),
)

behaviorプロパティについて

GestureDetectorのbehaviorプロパティは意外と重要です。

デフォルトはHitTestBehavior.deferToChildですが、このプロパティに設定するenum値に連動してGesture検出時の動きが変わります。子WidgetのGesture検出範囲に応じて適切に設定しましょう。

GestureDetector(
  behavior: HitTestBehavior.opaque, // こんな感じで指定
HitTestBehavior enum選択肢 内容
deferToChild 子Widgetの有効範囲のみ反応するようにする
opaque Padding領域も含めてタッチに反応するようにする
translucent 背景色が透明の場合、そのWidgetの背景部分をタッチしても反応するようにする

3. 検出可能なイベント一覧とコールバック

検出可能なイベントとプロパティ名を以下にまとめています。
コールされる順番については、後述のユースケースの部分を参照して下さい。

シングルタップ

スクリーン/ボタンの一回押し操作のイベントを検出する時に利用します。
タップ操作時の途中経過を知る必要がなければ、通常はonTapのみの利用になると思います。

GestureDetector(
  ...

  // シングルタップ開始時にコール
  // void Function(TapDownDetails) onTapDown
  onTapDown: (details) => print('onTapDown()'),

  // シングルタップ開始後、そのままタッチを動かすなどしてシングルタップがキャンセルされる時にコール
  // void Function() onTapCancel
  onTapCancel: () => print('onTapCancel()'),

  // タップが離れる時にコール
  // void Function(TapUpDetails) onTapUp
  onTapUp: (details) => print('onTapUp()'),

  // シングルタップ確定時にコール
  // void Function() onTap
  onTap: () => print('onTap()'),
)

TapUpDetailsのメンバ変数

実際に利用しそうなメンバ変数のみ紹介しておきます。globalPositionは公式サイトの説明ではスクリーン座標と書いてあり、スマホ本体のスクリーンの絶対座標かと思ってしまいますが、そうではなくてアプリ内画面の絶対座標です。Androidスマホであれば、アプリを画面分割して起動させて動作確認してみれば分かるかと思います。

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標

TapDownDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
kind 入力デバイス種類

PointerDeviceKindクラスのkindは、入力デバイスの種類を示しており、mousetouchのようなenum値が格納されています。入力デバイスを見分けたい場合には便利かもしれません。

タブルタップ

素早くタッチ2回操作ですね。
これが反応する時は、onTap系のコールバックは当然コールされません。

GestureDetector(
  ...

  // ダブルタップ確定時にコール
  // void Function() onDoubleTap
  onDoubleTap: () => print('onDoubleTap()'),
)

ロングタップ

タッチ後、そのまましばらく静止する操作です。その状態でタッチ位置を移動するとそれも検出可能です。
onLongPressEndとonLongPressUpの厳密な違いは何でしょう? (ソースコード読むか。。)

GestureDetector(
  ...

  // ロングプレス検出処理開始時にコール
  // void Function(LongPressStartDetails) onLongPressStart
  onLongPressStart: (details) => print('onLongPressStart()'),

  // ロングプレス確定時にコール
  // void Function() onLongPress
  onLongPress: () => print('onLongPress()'),

  // ロングプレス状態での移動で位置が変化した時にコール
  // void Function(LongPressMoveUpdateDetails) onLongPressMoveUpdate
  onLongPressMoveUpdate: (details) => print('onLongPressMoveUpdate()'),

  // ロングタップ状態が解除された (実質、タッチが離れた) 時にコール。onLongPressUpより先にコール
  // void Function(LongPressEndDetails) onLongPressEnd
  onLongPressEnd: (details) => print('onLongPressEnd()'),

  // ロングタップ状態が解除されて、タッチが離れた時にコール
  // void Function() onLongPressUp
  onLongPressUp: () => print('onLongPressUp()'),
)

LongPressStartDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標

LongPressEndDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
velocity 画面からタッチが離れる速度

LongPressMoveUpdateDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
offsetFromOrigin 最初のロングタップ検出位置からの相対座標
localOffsetFromOrigin

3Dタップ

iOS専用の機能です。
それ以外のプラットフォームでは利用しません (何も反応しません)。

GestureDetector(
  ...

  // 3Dタップ開始時にコール
  // void Function(ForcePressDetails) onForcePressStart
  onForcePressStart: (details) => print('onForcePressStart()'),

  // 押し込みの強さが変化した時にコール
  // void Function(ForcePressDetails) onForcePressUpdate
  onForcePressUpdate: (details) => print('onForcePressUpdate()'),

  // 押し込みの強さが最大時にコール
  // void Function(ForcePressDetails) onForcePressPeak
  onForcePressPeak: (details) => print('onForcePressPeak()'),

  // 3Dタップ終了時にコール
  // void Function(ForcePressDetails) onForcePressEnd
  onForcePressEnd: (details) => print('onForcePressEnd()'),
)

ForcePressDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
pressure 押し込みの強さ

スケール

2本指でのピンチ (イン/アウト/回転) 操作用です。ただし、1タッチ操作でも反応します。

また、後述のジェスチャーの所に記載しているonHorizontalDrag*/onVerticalDrag*/onPan*系のプロパティと同時に利用するとエラーになるため、注意して下さい。

GestureDetector(
  ...

  // スケール操作開始時にコール
  // void Function(ScaleStartDetails) onScaleStart
  onScaleStart: (details) => print('onScaleStart'),

  // スケール操作によるズーム倍率や回転率の変更時にコール
  // void Function(ScaleUpdateDetails) onScaleUpdate
  onScaleUpdate: (details) => print('onScaleUpdate'),

  // スケール操作終了時にコール
  // void Function(ScaleEndDetails) onScaleEnd
  onScaleEnd: (details) => print('onScaleEnd'),
)

ScaleStartDetailsのメンバ変数

メンバ変数 内容
focalPoint アプリ画面内の絶対座標
localFocalPoint Widget内の相対座標

ScaleUpdateDetailsのメンバ変数

メンバ変数 内容
focalPoint アプリ画面内の絶対座標
localFocalPoint Widget内の相対座標
rotation 開始時点からの回転角度
scale 開始時点からの倍率
verticalScale 開始時点からの垂直方向の倍率

ScaleEndDetailsのメンバ変数

メンバ変数 内容
velocity 画面からタッチが離れる速度(スワイプ操作の強さ検出に使えます)

ジェスチャー (水平方向)

水平方向のジェスチャー検出やドラッグ操作検出用です。

GestureDetector(
  ...

  // 初回タップ時にコール
  // void Function(DragDownDetails) onHorizontalDragDown
  onHorizontalDragDown: (details) => print('onHorizontalDragDown()'),

  // 初回タップ後にタップが離れたり、水平方向に移動した場合にコール
  // void Function() onHorizontalDragCancel
  onHorizontalDragCancel: () => print('onHorizontalDragCancel()'),

  // onHorizontalDragDownコール後の初回水平方向に移動時にコール
  // void Function() onHorizontalDragCancel
  onHorizontalDragStart: (details) => print('onHorizontalDragStart()'),

  // 水平方向に移動した時にコール
  // void Function() onHorizontalDragCancel
  onHorizontalDragUpdate: (details) => print('onHorizontalDragUpdate()'),

  // タッチを離すなど、水平方向の移動終了時にコール
  // void Function(DragEndDetails) onHorizontalDragEnd
  onHorizontalDragEnd: (details) => print('onHorizontalDragEnd()'),
)

DragDownDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標

DragStartDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
sourceTimeStamp タイムスタンプ

DragUpdateDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
primaryDelta
delta

ジェスチャー (垂直方向)

onHorizontalDrag*同様、onVerticalDragDown/onVerticalDragCancel/onVerticalDragStart/onVerticalDragUpdate/onVerticalDragEndが用意されています。

ドラッグ

前述の水平垂直お構いなしにドラッグ操作で位置を検出したい場合に利用します。

GestureDetector(
  ...

  // 初回タップ時にコール
  // void Function(DragDownDetails) onPanDown
  onPanDown: (details) => print('onPanDown()'),

  // ドラッグ操作がキャンセルされた時にコール
  // void Function() onPanCancel
  onPanCancel: () => print('onPanCancel()'),

  // ドラッグ操作が開始された時にコール
  // void Function(DragStartDetails) onPanStart
  onPanStart: (details) => print('onPanStart()'),

  // ドラッグ操作で位置が変化した時にコール
  // void Function(DragUpdateDetails) onPanUpdate
  onPanUpdate: (details) => print('onPanUpdate()'),

  // ドラッグ操作が終了した時にコール
  // void Function(DragEndDetails) onPanEnd
  onPanEnd: (details) => print('onPanEnd()'),

DragDownDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標

DragStartDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
sourceTimeStamp タイムスタンプ

DragUpdateDetailsのメンバ変数

メンバ変数 内容
globalPosition アプリ画面内の絶対座標
localPosition Widget内の相対座標
sourceTimeStamp タイムスタンプ
delta 前回からの変化量
primaryDelta 画面からタッチが離れる速度(スワイプ操作の強さ検出に使えます)

DragEndDetailsのメンバ変数

メンバ変数 内容
primaryVelocity
velocity

セカンダリーボタン

Android/iOSの通常操作ではまず使う (反応する) ことがないと思います。
無視しましょう。

GestureDetector(
  ...

  // void Function(TapDownDetails) onSecondaryTapDown
  onSecondaryTapDown: (details) => print('onSecondaryTapDown()'),

  // void Function(TapUpDetails) onSecondaryTapUp
  onSecondaryTapUp: (details) => print('onSecondaryTapUp()'),

  // void Function() onSecondaryTapCancel
  onSecondaryTapCancel: () => print('onSecondaryTapCancel'),
)

4. ユースケース毎のコール順序

以下のユースケース毎に各コールバックイベントがどのように発生するのか、その確認結果を掲載しておきます。

シングルタップ操作時

onTap系

onTapDown()
onTapUp()
onTap()

# タッチ後、指をそのまま異動
onTapDown()
onTapCancel()

ドラッグ(onPan)系

onPanDown()
onPanCancel()

スケール操作時

onScaleStart()
onScaleUpdate()
onScaleEnd()

ピンチ操作時

onPanDown()
onPanStart()
onPanUpdate()
onPanEnd()

ダブルタップ操作時

onDoubleTap()

ロングプレス操作時

# 最初はシングルタップから反応
onTapDown()
onTapCancel()

# そのあとでロングタップ判定に移行
onLongPressStart()
onLongPress()

# そのままタッチを動かす
onLongPressMoveUpdate()

# ここでタッチを離す
onLongPressEnd()
onLongPressUp()
100
69
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
100
69

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?