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:
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:
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:
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:
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 aDragTarget
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 toOffset.zero
DragAnchor dragAnchor
: Where this widget should be anchored during a drag. Defaults toDragAnchor.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 aDragTarget
.DragEndCallback onDragEnd,
: Called when the draggable is dropped.VoidCallback onDragCompleted
: Called when the draggable is dropped and accepted by aDragTarget
.bool hapticFeedbackOnStart
: Whether to trigger haptic feedback on start. Defaults totrue
.bool ignoringFeedbackSemantics
: Whether the semantics of the feedback widget is ignored. Defaults totrue
.
*: 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.