フラッタ:カスタムペイントでドラッグ&ドロップ図形



イントロ


この記事では、Flutter CustomPaintウィジェットを使用してユーザーインタラクティブドラッグ&ドロップを実装する方法を共有したいと思います.があるno packages need to be installed 私たちは、フラッタとダートが我々を提供するものだけを使うつもりです.では、ダイビングをしましょう!

1 .形状オブジェクトデータを使用した状態ウィジェット


class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isDown = false;
  double x = 0.0;
  double y = 0.0;
  int? targetId;
  Map<int, Map<String, double>> pathList = {
    1: {"x": 100, "y": 100, "r": 50, "color": 0},
    2: {"x": 200, "y": 200, "r": 50, "color": 1},
    3: {"x": 300, "y": 300, "r": 50, "color": 2},
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container(child: Text('dummy text'))
        );
  }
}

まず、我々は必要StatefulWidget ユーザーの相互作用に反応する.
  • アイダウン→ ユーザータッチまたはクリックする場合はtrue
  • X , Y→ ユーザーアクションのコーディネート
  • 指標→ ユーザーが現在指している形状オブジェクトのID
  • パスリスト→ キャンバスで表示される図形データのリスト.この例では、我々は円でテストするだけです、したがって、形データのxとyは円の中心の座標です、Rは半径です、色はプリセットカラーリストのインデックスです
  • 2 .ウィジェット


    @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: GestureDetector(
              child: Container(
                  width: MediaQuery.of(context).size.width,
                  height: MediaQuery.of(context).size.height,
                  color: Colors.grey,
                  child: CustomPaint(
                    foregroundPainter:
                        ShapePainter(down: isDown, x: x, y: y, pathList: pathList),
                    size: Size(MediaQuery.of(context).size.width,
                        MediaQuery.of(context).size.height),
                  )),
            ) // This trailing comma makes auto-formatting nicer for build methods.
            );
      }
    
    さて、ウィジェットツリーを埋める必要があります.
  • 自噴検出器→ それは、我々が検出しようとしているユーザー相互作用に関連するすべての装置をラップしなければなりません
  • カスタムペイント→ キャンバス機能を提供するウィジェット
  • 形容詞→ ペイントとペイントを行う
  • 3 .ユーザインタラクションのキャプチャ


    // util function
    bool isInObject(Map<String, double> data, double dx, double dy) {
      Path _tempPath = Path()
        ..addOval(Rect.fromCircle(
            center: Offset(data['x']!, data['y']!), radius: data['r']!));
      return _tempPath.contains(Offset(dx, dy));
    }
    // event handler
    void _down(DragStartDetails details) {
        setState(() {
          isDown = true;
          x = details.localPosition.dx;
          y = details.localPosition.dy;
        });
      }
    
      void _up() {
        setState(() {
          isDown = false;
          targetId = null;
        });
      }
    
      void _move(DragUpdateDetails details) {
        if (isDown) {
          setState(() {
            x += details.delta.dx;
            y += details.delta.dy;
            targetId ??= pathList.keys
                .firstWhereOrNull((_id) => isInObject(pathList[_id]!, x, y));
            if (targetId != null) {
              pathList = {
                ...pathList,
                targetId!: {...pathList[targetId!]!, 'x': x, 'y': y}
              };
            }
          });
        }
      }
    
    // map event handler to pan event
    ...
    body: GestureDetector(
              onPanStart: (details) {
                _down(details);
              },
              onPanEnd: (details) {
                _up();
              },
              onPanUpdate: (details) {
                _move(details);
              },
    ...
    
    次にイベントハンドラを登録します.インGestureDetector パンイベントはドラッグ&ドロップです.
  • オンパンスタート→ 初期ユーザーアクションの座標を設定する
  • オンパンアップデート→ IsDownがtrueの場合、Updateユーザーアクションの座標&ターゲット項目と更新状態の検索
  • オンパンエンド→ isdownをfalseに設定する
  • カスタムペイント&カスタムペイント


    class ShapePainter extends CustomPainter {
      final colors = [Colors.red, Colors.yellow, Colors.lightBlue];
      Path path = Path();
      Paint _paint = Paint()
        ..color = Colors.red
        ..strokeWidth = 5
        ..strokeCap = StrokeCap.round;
    
      final bool down;
      final double x;
      final double y;
      Map<int, Map<String, double>> pathList;
      ShapePainter({
        required this.down,
        required this.x,
        required this.y,
        required this.pathList,
      });
      @override
      void paint(Canvas canvas, Size size) {
        for (var pathData in pathList.values) {
          _paint = _paint..color = colors[pathData['color']! as int];
          path = Path()
            ..addOval(Rect.fromCircle(
                center: Offset(pathData['x']!, pathData['y']!),
                radius: pathData['r']!));
          canvas.drawPath(path, _paint);
        }
      }
    
      @override
      bool shouldRepaint(ShapePainter oldDelegate) => down;
    }
    
    ShapePainterはペイントメソッドを所有している人です.多くの論理がありません、ちょうど上のウィジェットがそれを与えたデータを描いてください.注意shouldRepaint ユーザーのタッチやマウスがダウンしているときには常に更新されます.

    結論


    私は一般的にキャンバスに興味があります.私はこれがFlutterのカスタム画家を使用してドラッグ&ドロップを実装したい方を助けることができることを願っています.
    乾杯!