0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GestureDetector使ってみた

Last updated at Posted at 2025-12-09

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

  @override
  _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,
      );
    });
  }

  @override
  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});

  @override
  _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,
      );
    });
  }

  @override
  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いつもありがとう。
0
0
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?