Flutter - Using LongPressDraggable Examples

This tutorial shows you how to create a widget that can be dragged starting from a long press using LongPressDraggable in Flutter.

If you need to make a widget draggable after the user performs a long press on it, Flutter has a widget for that purpose. LongPressDraggable is a widget that allows its child to be dragged starting from a long press. What you need to do is wrap the draggable widget as the child of LongPressDraggable widget. This tutorial shows you how to use it, including how to set the drag axis, set the child to show when dragging, add data to the draggable, as well as handle events.

Using LongPressDraggable

Below is the constructor of LongPressDraggable.

  const LongPressDraggable({
    Key key,
    @required Widget child,
    @required Widget feedback,
    T data,
    Axis axis,
    Widget childWhenDragging,
    Offset feedbackOffset = Offset.zero,
    DragAnchor dragAnchor = DragAnchor.child,
    int maxSimultaneousDrags,
    VoidCallback onDragStarted,
    DraggableCanceledCallback onDraggableCanceled,
    DragEndCallback onDragEnd,
    VoidCallback onDragCompleted,
    this.hapticFeedbackOnStart = true,
    bool ignoringFeedbackSemantics = true,
  })

There are two arguments you have to pass, child and feedback. child is the widget that can be dragged starting from a long press. You also need to define the widget to be shown under the pointer during a drag as feedback.

Besides the draggable widget, usually there's a DragTarget widget which is an area where any draggable widget can be dropped. In this tutorial, I'm not going to explain DragTarget in detail, but only show a basic example instead. Below is the constructor of DragTarget.

    const DragTarget({
    Key key,
    @required this.builder,
    this.onWillAccept,
    this.onAccept,
    this.onAcceptWithDetails,
    this.onLeave,
    this.onMove,
  })

And below is a function that returns a DragTarget widget.

  Widget _buildDragTarget() {
    return DragTarget<int>(
      builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
        return Container(
          width: 250,
          height: 100,
          color: Colors.grey,
        );
      },
      onAcceptWithDetails: (DragTargetDetails<int> dragTargetDetails) {
        print('onAcceptWithDetails');
        print('Data: ${dragTargetDetails.data}');
        print('Offset: ${dragTargetDetails.offset}');
      },
    );
  }

The LongPressDraggable has a generic type parameter. You need to define the type of the data held by the draggable widget. The example below shows the basic usage of LongPressDraggable that only passes the required arguments when calling the constructor. There is also a DragTarget widget created by calling the function above.

  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
      ),
      _buildDragTarget(),
    ],
  )

Output:

Flutter - LongPressDraggable

Set Drag Axis

By default, the user can perform drag in any direction. To restrict the movement of the draggable, you can pass axis agument.

  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        axis: Axis.vertical,
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
      ),
      _buildDragTarget(),
    ],
  )

Output:

Flutter - LongPressDraggable - Axis - vertical

Set Data

It's quite common that a draggable contains data that's used when it's accepted by a DragTarget. For that purpose, you can pass data argument. The value type must comply with the generic type defined when calling the constructor.

  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        data: 1,
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
      ),
      _buildDragTarget(),
    ],
  )

Set Child When Dragging

You can set the widget to be shown on the original position when a drag is under way by passing a Widget as childWhenDragging argument.

  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
        childWhenDragging: const Text('Woolha.com', style: const TextStyle(color: Colors.grey, fontSize: 36)),
      ),
      _buildDragTarget(),
    ],
  )

Output:

Flutter - LongPressDraggable - Axis - Child When Dragging

Set Drag Anchor

You can set where the draggable should be anchored during a drag by passing dragAnchor argument. There are two possible values:

  • child: Display the feedback anchored at the position of the original child.
  • pointer: Display the feedback anchored at the position of the touch that started the drag.

The default value is child. The example below sets the dragAnchor to pointer.

  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        dragAnchor: DragAnchor.pointer,
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
      ),
      _buildDragTarget(),
    ],
  )

Output:

Flutter - Drag Anchor - Pointer

Handle Events

You can add callback functions to be called when certain events occur.

When a drag is started, you can pass a VoidCallback, which has no arguments and returns no data, as onDragStarted argument.

A drag is completed when it's dropped and accepted by a DragTarget. To catch that event, you can pass a VoidCallback as onDragCompleted argument.

When a drag is not accepted by a DragTarget, you can handle it by passing DraggableCanceledCallback as onDraggableCanceled argument. It returns void and accepts two parameters whose type are Velocity and Offset.

Alternatively, you can pass a callback function that will be called when a drag ends either accepted by a DragTarget or not. It also returns void and accepts a parameter whose type is DraggableDetails, which has the following properties.

  • bool wasAccepted: Whether a DragTarget accepts the draggable.
  • Velocity velocity: The velocity at which the pointer was moving when the specific pointer event occurred on the draggable.
  • Offset offset: The global position when the specific pointer event occurred on the draggable.
  Column(
    children: <Widget>[
      LongPressDraggable<int>(
        child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
        feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
        onDragStarted: () {
          print('onDragStarted');
        },
        onDragCompleted: () {
          print('onDragCompleted');
        },
        onDraggableCanceled: (Velocity velocity, Offset offset) {
          print('onDraggableCanceled');
          print('velocity: $velocity}');
          print('offset: $offset');
        },
        onDragEnd: (DraggableDetails details) {
          print('onDragEnd');
          print('wasAccepted: ${details.wasAccepted}');
          print('velocity: ${details.velocity}');
          print('offset: ${details.offset}');
        },
      ),
      _buildDragTarget(),
    ],
  )
 

LongPressDraggable Parameters

  • Key key: The widget's key, used to control how a widget is replaced with another widget.
  • Widget child *: The widget below this widget in the tree.
  • Widget feedback *: The widget to show under the pointer when a drag is under way.
  • T data: The data that will be dropped by this draggable.
  • Axis axis: The axis to restrict the movement of this draggable.
  • Widget childWhenDragging: The widget to display instead of child when one or more drags are under way.
  • Offset feedbackOffset: Used to set the hit test target point for the purposes of finding a drag target. Defaults to Offset.zero
  • DragAnchor dragAnchor: Where this widget should be anchored during a drag. Defaults to DragAnchor.child.
  • int maxSimultaneousDrags: How many simultaneous drags to support.
  • VoidCallback onDragStarted: Called when the draggable starts being dragged.
  • DraggableCanceledCallback onDraggableCanceled: Called when the draggable is dropped without being accepted by a DragTarget.
  • DragEndCallback onDragEnd,: Called when the draggable is dropped.
  • VoidCallback onDragCompleted: Called when the draggable is dropped and accepted by a DragTarget.
  • bool hapticFeedbackOnStart: Whether to trigger haptic feedback on start. Defaults to true.
  • bool ignoringFeedbackSemantics: Whether the semantics of the feedback widget is ignored. Defaults to true.

*: required

Full Code

  import 'package:flutter/material.dart';
  import 'package:flutter/rendering.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        home: _LongPressDraggableExample(),
      );
    }
  }
  
  class _LongPressDraggableExample extends StatelessWidget {
  
    Widget _buildDragTarget() {
      return DragTarget<int>(
        builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
          return Container(
            width: 250,
            height: 100,
            color: Colors.grey,
          );
        },
        onAccept: (int data) {
          print('onAccept');
        },
        onAcceptWithDetails: (DragTargetDetails<int> dragTargetDetails) {
          print('onAcceptWithDetails');
          print('Data: ${dragTargetDetails.data}');
          print('Offset: ${dragTargetDetails.offset}');
        },
      );
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Woolha.com Flutter Tutorial'),
        ),
        body: Column(
          children: <Widget>[
            LongPressDraggable<int>(
              data: 1,
              axis: Axis.vertical,
              child: const Text('Woolha.com', style: const TextStyle(color: Colors.teal, fontSize: 36)),
              feedback: const Text('Woolha.com', style: const TextStyle(color: Colors.yellow, fontSize: 36)),
              childWhenDragging: const Text('Woolha.com', style: const TextStyle(color: Colors.grey, fontSize: 36)),
              dragAnchor: DragAnchor.pointer,
              onDragStarted: () {
                print('onDragStarted');
              },
              onDragCompleted: () {
                print('onDragCompleted');
              },
              onDraggableCanceled: (Velocity velocity, Offset offset) {
                print('onDraggableCanceled');
                print('velocity: $velocity}');
                print('offset: $offset');
              },
              onDragEnd: (DraggableDetails details) {
                print('onDragEnd');
                print('wasAccepted: ${details.wasAccepted}');
                print('velocity: ${details.velocity}');
                print('offset: ${details.offset}');
              },
            ),
            _buildDragTarget(),
          ],
        ),
      );
    }
  }

Summary

To make a widget draggable after the user performs a long press, you can wrap it as the child of LongPressDraggable. By doing so, you are also required to define a widget to be shown under the pointer during a drag. Flutter also makes it easy for us to set the drag axis, the child to be displayed on the original position during a drag, and the drag anchor by passing optional arguments. You can also pass several callback functions that will be called when certain events occur.