2

I am trying to create a game in Flutter where items are dragged and dropped into targets. As soon as an item is dropped on the target it will be repositioned at the target.

This works up untill the next item being dropped on the original target. The onAccept is not triggered anymore because the first item is 'on top' of the target.

What is the best way to work around this?

I basically rewrote this question/answer : How to update Draggable child when entering DragTarget in Flutter?

Part of Main:

child: Stack(
              children: <Widget>[
                MyDragTarget(Offset(50, 500), draggableController, 'target 1'),
                MyDragTarget(Offset(250, 500), draggableController, 'target 2'),
                MyDraggable(
                  Offset(50, 100),
                  draggableController,
                  'ball 1',
                ),
                MyDraggable(
                  Offset(150, 100),
                  draggableController,
                  'ball 2',
                ),
                MyDraggable(
                  Offset(250, 100),
                  draggableController,
                  'ball 3',
                ),

              ],
            ),

Draggable

class MyDraggable<T> extends StatefulWidget {
  final Offset initPos;
  final MyDraggableController<T> controller;
  final T data;

  MyDraggable(this.initPos, this.controller, this.data, {Key key})
      : super(key: key);

  @override
  _MyDraggableState createState() =>
      _MyDraggableState<T>(this.initPos, this.controller, this.data);
}

class _MyDraggableState<T> extends State<MyDraggable> {
  MyDraggableController<T> controller;
  T data;
  Offset position = Offset(0.0, 0.0);

  _MyDraggableState(this.position, this.controller, this.data);



  @override
  void initState() {

    this.controller.subscribeToOnTargetCallback(onTargetCallbackHandler);

    super.initState();

    position = widget.initPos;
  }

  void onTargetCallbackHandler(T data, Offset targetPosition) {
    debugPrint("dropped inside target: " + data.toString());

    if (this.data == data) {
      debugPrint("DRAGGABLE is ACCEPTED " +
          this.data.toString() +
          " " +
          this.isOnTarget.toString());

      setState(() {
        this.position = targetPosition;
      });
    } else {
      debugPrint("DRAGGABLE is NOT ACCEPTED " +
          this.data.toString() +
          " " +
          this.isOnTarget.toString());
      if (this.position == targetPosition) {
        debugPrint(this.data.toString() + " is occupying this spot!");
      }
      setState(() {});
    }
  }

  @override
  void dispose() {
    this.controller.unSubscribeFromOnTargetCallback(onTargetCallbackHandler);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
        left: position.dx,
        top: position.dy,
        child: Draggable<T>(
          data: this.data,
          child: CirkleWidget(this.data,0.5),
          feedback: CirkleWidget(this.data,1.2),
          childWhenDragging: new Container(),
          onDraggableCanceled: (v, f) => setState(
                () {
                  this.isOnTarget = false;
                  this.position = widget.initPos;


                },
              ),
        ));
  }
}

DragTarget

class MyDragTarget<T> extends StatefulWidget {

  final Offset inPos;
  final MyDraggableController<T> controller;
  final T data;

  MyDragTarget(this.inPos, this.controller, this.data, {Key key})
      : super(key: key);

  @override
  _MyDragTargetState createState() =>
      _MyDragTargetState(this.inPos, this.controller, this.data);
}

class _MyDragTargetState<T> extends State<MyDragTarget> {
  Offset position = Offset(0.0, 0.0);
  MyDraggableController<T> controller;
  T data;
  T currentBall;




  _MyDragTargetState(this.position, this.controller, this.data);

  @override
  void initState() {
    position = widget.inPos;
    data = widget.data;
    //this.controller.subscribeToOnTargetCallback(onTargetCallbackHandler);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint(position.toString());

    return Positioned(
      left: position.dx-10,
      top: position.dy-10,
      child: DragTarget<T>(

        builder: (context, list, list2) {
          return Container(
            decoration: BoxDecoration(
                color: Colors.blueGrey,
                borderRadius: BorderRadius.circular(50.0)),
            height: 120,
            width: 120,

            child: Center(
              child: Text(data.toString().toUpperCase()),
            ),
          );
        },

        onWillAccept: (item){
          debugPrint("will accept");
          return true;
        },

        onAccept: (item) {
          debugPrint('TARGET accepted $item');
          //this.draggableController.onTarget(true, item);
          //debugPrint("set currentball from "+ currentBall.toString() + " to" + item.toString());
          //currentBall = item;

          this.controller.onDropped(item,this.position);
          return true;
        },
      ),
    );
  }
}

controller

class MyDraggableController<T> {
  List<Function(T,Offset)> _targetUpdateCallbacks = new List<Function(T,Offset)>();

  //List<Function( )> _targetMoveCallbacks =  new List<Function( )>();

  MyDraggableController();

  void onDropped(T draggableData,Offset targetPosition) {
    debugPrint("dropped" + draggableData.toString());
    _targetUpdateCallbacks.forEach((f) {
      f(draggableData,targetPosition);
    });
  }

  void subscribeToOnTargetCallback(Function(T,Offset) f) {
    _targetUpdateCallbacks.add(f);
  }

  void unSubscribeFromOnTargetCallback(Function(T,Offset) f) {
    _targetUpdateCallbacks.remove(f);
  }
}

jarno punt
  • 23
  • 9

2 Answers2

1

Make the overlaying Draggable widget a child of the DragTarget.

class _MyDragTargetState<T> extends State<MyDragTarget> {
  ...

  @override
  Widget build(BuildContext context) {
    debugPrint(position.toString());

    return Positioned(
      left: position.dx-10,
      top: position.dy-10,
      child: DragTarget<T>(

        builder: (context, list, list2) {
          return Stack(
            children: [
              Container(
                decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(50.0)
                ),
                height: 120,
                width: 120,

                child: Center(
                  child: Text(data.toString().toUpperCase()
                ),
              ),

              MyDraggable(...) // Put your Draggable widgets here onwards
            ]
          );
        },
  ...
}
Tox
  • 135
  • 1
  • 10
0

Use Provider architecture.

Constants.HOME_SCREEN: (BuildContext context) => ChangeNotifierProvider(
            builder: (context) => Data(), child: HomePage())

Initialize getters and setters for the variables and inside setter functions notifyListeners() will be called so that the Widgets listening for these variables can rebuild.

removeLastItem() method is created which will remove the last item from the list of Draggable.

removeLastItem() {
    items.removeLast();
    notifyListeners();
  }

The best example is by Manik Gupta. Follow this link. https://medium.com/flutterdevs/draggable-and-drag-target-in-flutter-2513ea7c09f2

class DragTargetWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DragTarget(onWillAccept: (data) {
      return true;
    }, onAccept: (CardItem data) {
      if (Provider.of<Data>(context).itemsList.length >= 1) {
        Provider.of<Data>(context).removeLastItem();
        Provider.of<Data>(context).changeSuccessDrop(true);
        Provider.of<Data>(context).changeAcceptedData(data);
      }