👐

GestureDetector使ってみた

に公開

Motivation

図形を触って動かす、みたいに自由度の高くて直感的な操作で遊べたら楽しそうだなぁ

Action

  1. そもそもどうやったら実現出来るんだ?
    ⇨画面を触っている位置が分かれば、それに合わせて都度図形の位置を更新すれば良いのか
    ⇨画面を触っている位置を取得するには?
    ⇨調べたらGestureDetectorが出てきた!

  2. どうやって実装するんだ?
    以下を参照

    https://flutter.salon/widget/gesturedetector/

    タップ、ズーム、スワイプ、ドラッグと色々出来るんだぁ💡

    公式も確認

    https://api.flutter.dev/flutter/widgets/GestureDetector-class.html

    Youtubeだと、以下のようにmaxを使用←何で?

    Positioned(
        top: _top,
        left: _left,
        child: GestureDetector(
            onPanUpdate: (details) {
                _top = max(0, _top + details.delta.dy); // Why?
                _left = max(0, _left + details.delta.dx);
                setState(() => {});
            }
        ),
        child: yourWidget(),
    ),
    
  3. いざ実装

import 'package:flutter/material.dart';

class ShapeMovePage extends StatefulWidget {
  const ShapeMovePage({super.key});

  
  _ShapeMovePageState createState() => _ShapeMovePageState();
}

class _ShapeMovePageState extends State<ShapeMovePage> {
  Offset position = Offset(0, 0);

  void _onPanUpdate(DragUpdateDetails details, Size widgetSize) {
    final double xPos = position.dx + details.delta.dx;
    final double yPos = position.dx + details.delta.dx;
    setState(() {
      position = Offset(
        xPos <= widgetSize.width ? xPos : position.dx,
        yPos <= widgetSize.height ? yPos : position.dy,
      );
    });
  }

  
  Widget build(BuildContext context) {
    final Size widgetSize = MediaQuery.of(context).size;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text('Shape move'),
      ),
      body: Stack(
        children: [
          Positioned(
            left: position.dx,
            top: position.dy,
            child: GestureDetector(
              onPanUpdate: (details) => _onPanUpdate(details, widgetSize),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
                child: Center(
                  child: Text(
                    'Drag me',
                    style: TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

これだと画面外にめり込む
⇨max(0, x or y)でpositionが画面上 or 左にいかないようにする(なるほどそれでね)
⇨Containerのwidth, height分条件を追加する

修正版

import 'package:flutter/material.dart';

class ShapeMovePage extends StatefulWidget {
  const ShapeMovePage({super.key});

  
  _ShapeMovePageState createState() => _ShapeMovePageState();
}

class _ShapeMovePageState extends State<ShapeMovePage> {
  Offset position = Offset(0, 0);
+ static const double headerMargin = 80;

-  void _onPanUpdate(DragUpdateDetails details, Size widgetSize) {
+  void _onPanUpdate(
+   DragUpdateDetails details,
+   Size widgetSize,
+   double shapeWidth,
+   shapeHeight,
  ) {  
-   final double xPos = position.dx + details.delta.dx;
-   final double yPos = position.dx + details.delta.dx;
+   final double xPos = max(0, position.dx + details.delta.dx);
+   final double yPos = max(0, position.dy + details.delta.dy);
    setState(() {
      position = Offset(
-       xPos <= widgetSize.width ? xPos : position.dx,
-       yPos <= widgetSize.height ? yPos : position.dy,
+       xPos + shapeWidth <= widgetSize.width ? xPos : position.dx,
+       yPos + shapeHeight + headerMargin <= widgetSize.height
+           ? yPos
+           : position.dy,
      );
    });
  }

  
  Widget build(BuildContext context) {
    final Size widgetSize = MediaQuery.of(context).size;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text('Shape move'),
      ),
      body: Stack(
        children: [
          Positioned(
            left: position.dx,
            top: position.dy,
            child: GestureDetector(
-             _onPanUpdate: (details) => _onPanUpdate(details, widgetSize),
+             _onPanUpdate(details, widgetSize, 100, 100),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
                child: Center(
                  child: Text(
                    'Drag me',
                    style: TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

めり込まずに動かせた!

Question

  • ヘッダー部分のサイズは、取得して使う方が変更に強くなるよねー。
  • 図形増やしていったらどうやって管理しよう?

Zaregoto

  • Markdown記法に則ってDiff手動で追加したけど、面倒だったな。Gitいつもありがとう。
  • qiitaとZennでdiffのMarkdown記法微妙に違うんかい

Discussion