Flutter Gestures and Drag
Touch screens speak a language of taps, long presses, swipes, and drags. Flutter translates those physical movements into code your widgets can answer. A button reacts to taps out of the box, yet plain widgets such as Container or Text ignore touches until you wrap them with a gesture tool.
GestureDetector
Wrap any widget with GestureDetector to make it respond to touch. The widget adds no visible effect, and it listens quietly for movements.
GestureDetector(
onTap: () {
print('Tapped');
},
onDoubleTap: () {
print('Double tapped');
},
onLongPress: () {
print('Long pressed');
},
child: Container(
width: 120,
height: 120,
color: Colors.teal,
child: Center(child: Text('Touch me')),
),
)
Common Gesture Callbacks
| Callback | Triggered By |
|---|---|
| onTap | A quick touch and release |
| onDoubleTap | Two quick taps |
| onLongPress | Holding a finger down |
| onPanUpdate | Dragging in any direction |
| onHorizontalDragUpdate | Dragging left or right only |
| onScaleUpdate | Pinching with two fingers |
InkWell: Taps with Ripples
Material design shows a ripple where a finger lands. InkWell adds that effect. It needs a Material ancestor such as a Card or Scaffold.
InkWell(
onTap: () {
print('Card tapped');
},
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Tap for ripple'),
),
)
Choose InkWell for tappable cards and list rows. Choose GestureDetector when you need drags, scales, or no visual feedback.
Dragging a Widget Around
The onPanUpdate callback reports how far the finger moved since the last event. Add that distance to a stored position and rebuild.
class MovableBox extends StatefulWidget {
@override
State<MovableBox> createState() => _MovableBoxState();
}
class _MovableBoxState extends State<MovableBox> {
Offset position = Offset(100, 100);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
position += details.delta;
});
},
child: Container(width: 80, height: 80, color: Colors.orange),
),
),
],
);
}
}
Finger moves → details.delta (dx, dy)
│
▼
position = position + delta
│
▼
setState → box redraws at the new spot
Draggable and DragTarget
These two widgets build drag-and-drop. A Draggable is the item you pick up. A DragTarget is the zone where you drop it. Think of moving a file into a folder on a desktop.
┌───────────┐ ┌─────────────────┐
│ Draggable │ ── drag ──────→ │ DragTarget │
│ "Apple" │ │ "Basket" │
└───────────┘ └─────────────────┘
carries data: 'Apple' accepts and receives it
Draggable<String>(
data: 'Apple',
feedback: Material(child: Text('Apple')),
childWhenDragging: Opacity(opacity: 0.3, child: Text('Apple')),
child: Text('Apple'),
)
DragTarget<String>(
onAcceptWithDetails: (details) {
print('Dropped: ${details.data}');
},
builder: (context, candidateData, rejectedData) {
return Container(
width: 150,
height: 150,
color: candidateData.isEmpty ? Colors.grey : Colors.green,
child: Center(child: Text('Basket')),
);
},
)
The feedback widget follows the finger. The childWhenDragging widget replaces the original during the drag. The candidateData list is not empty while an item hovers over the target, which makes color feedback easy.
Swipe to Dismiss
A Dismissible removes a list row when the user swipes. Give every row a unique key and remove the matching data item inside onDismissed.
Pinch to Zoom
The InteractiveViewer widget adds pinch zoom and panning to an image or map with no extra code. Set minScale and maxScale to keep zoom within sensible limits.
How Flutter Picks a Winner
Several gesture detectors may sit on the same spot, such as a tap detector inside a scroll view. Flutter runs a contest called the gesture arena. Each detector competes, and the first clear movement wins. A fast vertical movement goes to the scroll view, while a quick touch goes to the tap handler.
Practice Ideas
Build a sticker board where users drag three emoji around the screen. Add a double tap that resets each emoji to its starting spot. Create a two-zone sorting game with Draggable fruit and two DragTarget baskets.
Giving Touch Feedback
People trust an app that reacts instantly. A tap that produces no visible change feels broken, even when the code runs correctly. Pair every gesture with a visual response: a ripple, a color change, a slight scale effect, or a sound where it fits. Keep the response under a tenth of a second, and users perceive it as immediate.
Accessibility matters here too. Users who rely on screen readers cannot see a drag. Offer a button-based alternative for every drag action, such as "Move up" and "Move down" buttons beside a reorderable list.
