Draggable

Recently, I did a Flutter project, in which there was a requirement to create three task lists that could be dragged by each other. Normally, when doing Android projects, the drag controls are basically implemented by custom View. I want to see how they are implemented on Fluter. Flutter provides a very convenient Draggable control.

Constructor for Draggable

When I was developing Flutter, when I encountered a control I had never seen before, the best way to understand its function was to click open source and observe its constructor. Let’s look at the constructor for Draggable:

  const Draggable({
    Key key,
    @required this.child, // child need not explain@required this.feedback, // display the component when draggingthis.data, // The data carried by the control (typically supplied to DragTarget, described later)this.axis, // Restrict the direction of sliding (horizontal or vertical)thisChildWhenDragging, // Components displayed when multi-touch draggingthis.feedbackoffset = offset. zero, // Drag to display the position, default (0.0)this.dragAnchor = dragAnchor. Child, // The feedback position shown when the drag starts (the original child position or the touch position)this.affinity, // Allows Draggable to share vertical or horizontal sliding events (such as dragging while Scrollable)thisMaxSimultaneousDrags, // Maximum response time under multi-touchthis.ondragStarted, // Callback to start draggingthisOnDraggableCanceled, // a callback when not dragged to the DragTarget controlthis.ondragend, // Callback at the end of the dragthis.onDragCompleted, // callback when dragged to the DragTarget controlthis.ignoringFeedbackSemantics = true// control whether to display feedback}) : assert(child ! =null).assert(feedback ! =null).assert(ignoringFeedbackSemantics ! =null).assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0).super(key: key);
Copy the code

As you can see, Draggable is still very powerful, and the weiget displayed when dragging is also wrapped. We only need to pass in a Weiget to automatically display, and the callback to drag events is also very rich, where the DragTarget is another powerful control with Draggable. Let’s implement a simple Draggable:

The realization of the Draggable

Container(
        alignment: Alignment.center,
        child: Draggable(
          child: Text("I can be dragged!"),
          feedback: Text("I'm being dragged!"),),),Copy the code

Isn’t that easy? We can add some other properties, like restrictions on horizontal drag

Container(
        alignment: Alignment.center,
        child: Draggable(
          axis: Axis.vertical,
          child: Text("I can be dragged!"),
          feedback: Text("I'm being dragged!"),),),Copy the code

DragTarget

However, in general, our drag logic is to move a control to the specified position (or control). In the past, we used to judge by the coordinate after dragging. However, by observing the Draggable callback, we found that there is also a control DragTarget matching with the Draggable. Use DragTarget to assign the drag to the specified location to the system. Without further ado, add the code

Constructor for DragTarget

  const DragTarget({
    Key key,
    @required this.builder, // constructorthis.onwillAccept, // Determine whether the data meets the requirementsthis.onaccept, // Receive Data callbackthis.onLeave, // 
  }) : super(key: key);
Copy the code

The DragTarget constructor is much simpler:

Builder B.

CandidateData is the list of data that can be received when onWillAccept callback is true. RejectedData Specifies the data to be rejected when the onWillAccept callback is false. The return value of builder is the Child of DragTarget

typedef DragTargetBuilder<T> = Widget Function(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
Copy the code

OnWillAccept: Callback when Draggable is dragged to DragTarget

If true is returned, the Data Data will be added to the candidateData list and onAccept will be called. When false, Data will be added to the rejectedData list and onAccept will not be called.

OnAccept: callback to receive Draggable Data;

OnLeave: callback when Draggable leaves;

The realization of the DragTarget

  String data = "Default data";

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: 200),
      color: Colors.white,
      child: Column(
        children: [
          Draggable<String>(
            data: "Draggable data",,
            child: Text("I can be dragged!"),
            feedback: Text("I'm being dragged!"),
          ),
          DragTarget<String>(
            builder: (BuildContext context, List<dynamic> accepted, List<dynamic> rejected,) {
              return Text(data);
            },
            onAccept: (data) {
              setState(() {
                this.data = data; }); },)],),); }Copy the code

In the same way, we can apply it to more complex images. Remember my initial requirement: So we can combine DragTarget and Draggable, using the item of the list as the Draggable, using each list as the DragTarget, and adding billions of details:

  List<String> list1 = ["list1_1"."list1_2"."list1_3"];
  List<String> list2 = ["list2_1"."list2_2"];
  List<String> list3 = ["list3_1"."list3_2"];

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: 200),
      color: Colors.white,
      child: Column(
        children: [
          _createListView(list1),
          _createListView(list2),
          _createListView(list3),
        ],
      ),
    );
  }

  Widget _createListView(List<String> _items) {
    return DragTarget<String>(
      builder: (
        BuildContext context,
        List<dynamic> accepted,
        List<dynamic> rejected,
      ) {
        return ListView.builder(
          itemCount: _items.length,
          shrinkWrap: true,
          padding: EdgeInsets.all(10),
          itemBuilder: (context, index) {
            return Draggable<String>(
              onDragCompleted: () {
                // Delete data after dragging to DragTarget
                setState(() {
                  _items.removeAt(index);
                });
              },
              feedback: Material(
                child: Container(
                  height: 60,
                  width: 200,
                  color: Colors.blueAccent,
                  alignment: Alignment.center,
                  child: Text(
                    _items[index],
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              data: _items[index],
              child: Container(
                height: 50,
                width: 200,
                color: Colors.blueAccent,
                alignment: Alignment.center,
                child: Text(
                  _items[index],
                  style: TextStyle(color: Colors.white, fontSize: 20),),),); }); }, onAccept: (String data) { setState(() {// Add Draggable data to list_items.add(data); }); }); }Copy the code

Done!