Flutter でタップやドラッグ、ピンチや回転などのジェスチャー操作を処理する

Flutter

Flutter のウィジェットにはタップ時の処理などが予め用意されている場合がありますが、用意されていない場合や、ドラッグ/ピンチ/回転/指圧(筆圧?)など様々なジェスチャー操作に対応したい場合、GestureDetector ウィジェットで大体のジェスチャーに対応することができます。

今回はこのウィジェットについてまとめてみました。

※最後にドラッグと拡大縮小回転のサンプルコードを載せておきます。

スポンサーリンク

GestureDetector

タップやドラッグなどを処理する場合は GestureDetector ウィジェットを使います。

GestureDetector(
  onTap: () {
    print('onTap');
  },
  child: Container(
    color: Colors.blue,
    width: 100,
    height: 100,
  ),
},

このウィジェットは、child がある場合はchildのサイズ内でジェスチャーを検出、childが無い場合は、親ウィジェットのサイズに合わせて検出するので、親のchildに書いてもOKです。

GestureDetector で定義されている各ジェスチャーについて以下に記載します。

タップ

普通のタップです。

onTap: () {
  print('onTap');
},

onTapDown: (TapDownDetails details) {
  print('onTapDown - ${details.toString()}');
},
onTapUp: (TapUpDetails details) {
  print('onTapUp - ${details.toString()}');
},
onTapCancel: () {
  print('onTapCancel');
},

ダブルタップ

ダブルタップです。

onDoubleTap: () {
  print('onDoubleTap');
},

ロングプレス

いわゆる長押しです。

onLongPress: () {
  print('onLongPress');
},

onLongPressUp: () {
  print('onLongPressUp');
},
onLongPressStart: (LongPressStartDetails details) {
  print('onLongPressStart - ${details.toString()}');
},
onLongPressEnd: (LongPressEndDetails details) {
  print('onLongPressEnd - ${details.toString()}');
},
// 長押し中の移動ポジションも取得できます
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
  print('onLongPressMoveUpdate - ${details.toString()}');
  // details.globalPosition; 移動中のグローバルポジション
},

垂直方向のドラッグ

垂直方向のドラッグ操作です。(水平方向ならこのジェスチャーはキャンセルされます)

// ドラッグ中に呼ばれる
onVerticalDragUpdate: (DragUpdateDetails details) {
  print('onVerticalDragUpdate - ${details.toString()}');
  // details.globalPosition; //グローバル座標
  // details.localPosition; //ローカル座標
  // details.delta; //前回からの移動量
},

onVerticalDragDown: (DragDownDetails details) {
  print('onVerticalDragDown - ${details.toString()}');
},
onVerticalDragStart: (DragStartDetails details) {
  print('onVerticalDragStart - ${details.toString()}');
},
onVerticalDragEnd: (DragEndDetails details) {
  print('onVerticalDragEnd - ${details.toString()}');
},
onVerticalDragCancel: () {
  print('onVerticalDragCancel');
},

水平方向のドラッグ

水平方向のドラッグ操作です。(垂直方向ならこのジェスチャーはキャンセルされます)

// ドラッグ中に呼ばれる
onHorizo​​ntalDragUpdate: (DragUpdateDetails details) {
  print('onHorizo​​ntalDragUpdate - ${details.toString()}');
  // details.globalPosition; //グローバル座標
  // details.localPosition; //ローカル座標
  // details.delta; //前回からの移動量
},

onHorizo​​ntalDragDown: (DragDownDetails details) {
  print('onHorizo​​ntalDragDown - ${details.toString()}');
},
onHorizo​​ntalDragStart: (DragStartDetails details) {
  print('onHorizo​​ntalDragStart - ${details.toString()}');
},
onHorizo​​ntalDragEnd: (DragEndDetails details) {
  print('onHorizo​​ntalDragEnd - ${details.toString()}');
},
onHorizo​​ntalDragCancel: () {
  print('onHorizo​​ntalDragCancel');
},

パン(ドラッグ操作)

垂直や水平に依らないドラッグ操作です。(XY座標でグリグリ動かしたい時はこれ)

// ドラッグ中に呼ばれる
onPanUpdate: (DragUpdateDetails details) {
  print('onPanUpdate - ${details.toString()}');
  // details.globalPosition; //グローバル座標
  // details.localPosition; //ローカル座標
  // details.delta; //前回からの移動量
},

onPanDown: (DragDownDetails details) {
  print('onPanDown - ${details.toString()}');
},
onPanStart: (DragStartDetails details) {
  print('onPanStart - ${details.toString()}');
},
onPanEnd: (DragEndDetails details) {
  print('onPanEnd - ${details.toString()}');
},
onPanCancel: () {
  print('onPanCancel');
},

スケール(ピンチ操作)

いわゆる2本指のピンチイン/アウト(と回転)の操作です。

// スケール操作時に呼ばれる
onScaleUpdate: (ScaleUpdateDetails details) {
  print('onScaleUpdate - ${details.toString()}');
 // details.scale; //スケール
 // details.rotation; //回転
},

onScaleStart: (ScaleStartDetails details) {
  print('onScaleStart - ${details.toString()}');
},
onScaleEnd: (ScaleEndDetails details) {
  print('onScaleEnd - ${details.toString()}');
},

スケールはドラッグ操作を使用しているため、他のドラッグ操作系(水平垂直ドラッグやパン)とは併用できません。(両方書くとそれはダメよっていうエラーが出ると思います)

※タップ系はOKなので、onLongPressMoveUpdate ならドラッグ的に使えるかもしれませんね。

フォースプレス(押し込みの強さ)

タップ中の押し込みの強さです。iOSで言うところの3D Touchですね。

// フォースプレス中に呼ばれる
onForcePressUpdate: (ForcePressDetails details) {
  print('onForcePressUpdate - ${details.toString()}');
  // details.pressure; //押し込みの強さ
},

onForcePressStart: (ForcePressDetails details) {
  print('onForcePressStart - ${details.toString()}');
},
onForcePressEnd: (ForcePressDetails details) {
  print('onForcePressEnd - ${details.toString()}');
},
// 押し込みの強さが最大になった時に呼ばれる
onForcePressPeak: (ForcePressDetails details) {
  print('onForcePressPeak - ${details.toString()}');
},

onForcePressUpdate は指圧/筆圧的なものに使えると思いますし、onForcePressPeak は強く押された時に何かするみたいな処理に使えるかと思います。

サンプルコード

ドラッグ操作(Pan)と拡大縮小回転(Scale)のサンプルコードです。

緑のボックスはドラッグで移動、オレンジのボックスはピンチ操作で回転/拡大/縮小できます。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

// MyAppウィジェットクラス
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'GestureDetector'),
    );
  }
}

// MyHomePageウィジェットクラス
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

// MyHomePageステートクラス
class _MyHomePageState extends State<MyHomePage> {
  Offset _offset = Offset(10,10); //Panドラッグ時のポジション
  double _radians = 0.0; //Scaleの回転値
  double _scale = 1.0; //Scaleのスケール値

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: <Widget>[
          // Panテスト用のウィジェット
          Positioned(
            left: _offset.dx, // 移動の値(x)
            top: _offset.dy, // 移動の値(y)
            child: GestureDetector(
              // ドラッグの移動を更新
              onPanUpdate: (DragUpdateDetails details) {
                setState(() {
                  _offset = Offset(_offset.dx+details.delta.dx, _offset.dy+details.delta.dy);
                });
              },
              child: Container(
                color: Colors.green,
                width: 100,
                height: 100,
                child: Center(child:Text('Pan\nx:${_offset.dx.toInt()}\ny:${_offset.dy.toInt()}'),),
              ),
            ),
          ),
          // Scale用のウィジェット
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                GestureDetector(
                  // 回転とスケールの値を更新
                  onScaleUpdate: (ScaleUpdateDetails details) {
                    setState(() {
                      _radians = details.rotation;
                      _scale = details.scale;
                    });
                  },
                  child: Transform.rotate(
                    angle: _radians, // 回転の値
                    child: Transform.scale(
                      scale: _scale, // スケールの値
                      child: Container(
                        height: 300,
                        width: 300,
                        color: Colors.amber,
                        child: Center(child:Text('rotation:\n$_radians\nscale:\n$_scale'),),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

コメント

タイトルとURLをコピーしました