Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: How to set boundaries for a Draggable widget?

I'm trying to create a drag and drop game. I would like to make sure that the Draggable widgets don't get out of the screen when they are dragged around.

I couldn't find an answer to this specific question. Someone asked something similar about constraining draggable area Constraining Draggable area but the answer doesn't actually make use of Draggable.

To start with I tried to implement a limit on the left-hand side.

I tried to use a Listener with onPointerMove. I've associated this event with a limitBoundaries method to detect when the Draggable exits from the left side of the screen. This part is working as it does print in the console the Offset value when the Draggable is going out (position.dx < 0). I also associated a setState to this method to set the position of the draggable to Offset(0.0, position.dy) but this doesn't work.

Could anybody help me with this?

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable Test',
      home: GamePlay(),
    );
  }
}

class GamePlay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Row(
            children: [
              Container(
                width: 360,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.lightGreen,
                  border: Border.all(
                    color: Colors.green,
                    width: 2.0,
                  ),
                ),
              ),
              Container(
                width: 190,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.all(
                    color: Colors.purple,
                    width: 2.0,
                  ),
                ),
              ),
            ],
          ),
          DragObject(
              key: GlobalKey(),
              initPos: Offset(365, 0.0),
              id: 'Item 1',
              itmColor: Colors.orange),
          DragObject(
            key: GlobalKey(),
            initPos: Offset(450, 0.0),
            id: 'Item 2',
            itmColor: Colors.pink,
          ),
        ],
      ),
    );
  }
}

class DragObject extends StatefulWidget {
  final String id;
  final Offset initPos;
  final Color itmColor;

  DragObject({Key key, this.id, this.initPos, this.itmColor}) : super(key: key);

  @override
  _DragObjectState createState() => _DragObjectState();
}

class _DragObjectState extends State<DragObject> {
  GlobalKey _key;
  Offset position;
  Offset posOffset = Offset(0.0, 0.0);

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    _key = widget.key;
    position = widget.initPos;
    super.initState();
  }

  void _getRenderOffsets() {
    final RenderBox renderBoxWidget = _key.currentContext.findRenderObject();
    final offset = renderBoxWidget.localToGlobal(Offset.zero);

    posOffset = offset - position;
  }

  void _afterLayout(_) {
    _getRenderOffsets();
  }

  void limitBoundaries(PointerEvent details) {
    if (details.position.dx < 0) {
      print(details.position);
      setState(() {
        position = Offset(0.0, position.dy);
      });
    }
  }



@override
  Widget build(BuildContext context) {
    return Positioned(
      left: position.dx,
      top: position.dy,
      child: Listener(
        onPointerMove: limitBoundaries,
        child: Draggable(
          child: Container(
            width: 80,
            height: 80,
            color: widget.itmColor,
          ),
          feedback: Container(
            width: 82,
            height: 82,
            color: widget.itmColor,
          ),
          childWhenDragging: Container(),
          onDragEnd: (drag) {
            setState(() {
              position = drag.offset - posOffset;
            });
          },
        ),
      ),
    );
  }
}
like image 309
cmaxetom Avatar asked May 23 '20 08:05

cmaxetom


People also ask

What is drag target in flutter?

As the name suggests, DragTarget means a widget which provides target or destination to Draggable, it also receives the data from a Draggable.

Does flutter have drag and drop?

Flutter provides a widget called LongPressDraggable that provides the exact behavior that you need to begin a drag-and-drop interaction. A LongPressDraggable widget recognizes when a long press occurs and then displays a new widget near the user's finger.


1 Answers

Try this. I tweaked this from: Constraining Draggable area .

  ValueNotifier<List<double>> posValueListener = ValueNotifier([0.0, 0.0]);
  ValueChanged<List<double>> posValueChanged;
  double _horizontalPos = 0.0;
  double _verticalPos = 0.0;


  @override
  void initState() {
    super.initState();
  
    posValueListener.addListener(() {
      if (posValueChanged != null) {
        posValueChanged(posValueListener.value);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
   return Scaffold(
      body: Stack(
        children: <Widget>[
           _buildDraggable(),
        ]));
  }
  
  _buildDraggable() {
    return SafeArea(
      child: Container(
        margin: EdgeInsets.only(bottom: 100),
        color: Colors.green,
        child: Builder(
          builder: (context) {
            final handle = GestureDetector(
                onPanUpdate: (details) {
                  _verticalPos =
                      (_verticalPos + details.delta.dy / (context.size.height))
                          .clamp(.0, 1.0);
                  _horizontalPos =
                      (_horizontalPos + details.delta.dx / (context.size.width))
                          .clamp(.0, 1.0);
                  posValueListener.value = [_horizontalPos, _verticalPos];
                },
                child: Container(
                  child: Container(
                    margin: EdgeInsets.all(12),
                    width: 110.0,
                    height: 170.0,
                    child: Container(
                      color: Colors.black87,
                    ),
                    decoration: BoxDecoration(color: Colors.black54),
                  ),
                ));

            return ValueListenableBuilder<List<double>>(
              valueListenable: posValueListener,
              builder:
                  (BuildContext context, List<double> value, Widget child) {
                return Align(
                  alignment: Alignment(value[0] * 2 - 1, value[1] * 2 - 1),
                  child: handle,
                );
              },
            );
          },
        ),
      ),
    );
  }
like image 176
Samteewong Avatar answered Oct 29 '22 05:10

Samteewong