Draggable
Draggable
is a widget that makes child widgets draggable with mouse interactions. It internally uses Transform
and GestureDetector
to implement drag functionality.
Overview
The Draggable widget enables user interaction to move widgets around on the screen. It provides drag start, progress, and end events for fine-grained control.
When to Use
- Drag and Drop Interfaces: When moving files, cards, or items
- Interactive Diagrams: When freely positioning nodes or elements
- Widget Repositioning: When users customize UI element positions
- Game Elements: When moving puzzle pieces or game objects
- Visual Editors: When placing elements in design tools
Basic Usage
import { Draggable, Container } from '@meursyphus/flitter';
Draggable({
onDragUpdate: ({ delta, movement }) => {
console.log('Dragging:', { delta, movement });
},
onDragStart: () => {
console.log('Drag started');
},
onDragEnd: () => {
console.log('Drag ended');
},
child: Container({
width: 100,
height: 100,
color: 'blue'
})
})
Props
Property | Type | Description |
---|---|---|
child | Widget? | Child widget to make draggable |
onDragUpdate | (event: { delta: Offset, movement: Offset }) => void? | Callback called during dragging |
onDragStart | () => void? | Callback called when drag starts |
onDragEnd | () => void? | Callback called when drag ends |
feedback | Widget? | Feedback widget to show during dragging |
Event Object
delta
: Movement amount relative to previous framemovement
: Total movement amount relative to drag start point
Real-world Examples
1. Basic Drag Box
import { Draggable, Container, Text } from '@meursyphus/flitter';
Draggable({
onDragUpdate: ({ movement }) => {
console.log(`Move: x=${movement.x}, y=${movement.y}`);
},
child: Container({
width: 120,
height: 80,
color: '#3498db',
child: Text('Drag me', {
style: { color: 'white', textAlign: 'center' }
})
})
})
2. State Management Integration
import { StatefulWidget, State, Draggable, Container, Transform } from '@meursyphus/flitter';
class DraggableBox extends StatefulWidget {
createState() {
return new DraggableBoxState();
}
}
class DraggableBoxState extends State<DraggableBox> {
position = { x: 0, y: 0 };
isDragging = false;
handleDragStart = () => {
this.setState(() => {
this.isDragging = true;
});
}
handleDragUpdate = ({ movement }) => {
this.setState(() => {
this.position = { x: movement.x, y: movement.y };
});
}
handleDragEnd = () => {
this.setState(() => {
this.isDragging = false;
});
}
build() {
return Transform.translate({
offset: this.position,
child: Draggable({
onDragStart: this.handleDragStart,
onDragUpdate: this.handleDragUpdate,
onDragEnd: this.handleDragEnd,
child: Container({
width: 100,
height: 100,
color: this.isDragging ? '#e74c3c' : '#2ecc71'
})
})
});
}
}
3. Bounded Dragging
import { Draggable, Container } from '@meursyphus/flitter';
class BoundedDraggable extends StatefulWidget {
createState() {
return new BoundedDraggableState();
}
}
class BoundedDraggableState extends State<BoundedDraggable> {
position = { x: 0, y: 0 };
bounds = { minX: -100, maxX: 100, minY: -50, maxY: 50 };
handleDragUpdate = ({ movement }) => {
const clampedX = Math.max(this.bounds.minX,
Math.min(this.bounds.maxX, movement.x));
const clampedY = Math.max(this.bounds.minY,
Math.min(this.bounds.maxY, movement.y));
this.setState(() => {
this.position = { x: clampedX, y: clampedY };
});
}
build() {
return Transform.translate({
offset: this.position,
child: Draggable({
onDragUpdate: this.handleDragUpdate,
child: Container({
width: 60,
height: 60,
color: '#9b59b6'
})
})
});
}
}
4. Drag Feedback Widget
import { Draggable, Container, Text, Opacity } from '@meursyphus/flitter';
Draggable({
child: Container({
width: 100,
height: 100,
color: '#1abc9c',
child: Text('Original')
}),
feedback: Opacity({
opacity: 0.7,
child: Container({
width: 100,
height: 100,
color: '#16a085',
child: Text('Dragging', {
style: { color: 'white' }
})
})
})
})
5. Multiple Drag Objects
import { Column, Draggable, Container, Text, EdgeInsets } from '@meursyphus/flitter';
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12'];
const items = colors.map((color, index) =>
Draggable({
onDragUpdate: ({ movement }) => {
console.log(`Item ${index + 1} moved:`, movement);
},
child: Container({
width: 80,
height: 60,
color,
margin: EdgeInsets.all(5),
child: Text(`${index + 1}`, {
style: { color: 'white', textAlign: 'center' }
})
})
})
);
Column({
children: items
})
6. Drag State Visualization
import { Draggable, Container, Text, Column } from '@meursyphus/flitter';
class DragVisualizer extends StatefulWidget {
createState() {
return new DragVisualizerState();
}
}
class DragVisualizerState extends State<DragVisualizer> {
status = 'Waiting';
position = { x: 0, y: 0 };
build() {
return Column({
children: [
Text(`Status: ${this.status}`),
Text(`Position: (${this.position.x.toFixed(1)}, ${this.position.y.toFixed(1)})`),
Draggable({
onDragStart: () => {
this.setState(() => {
this.status = 'Dragging';
});
},
onDragUpdate: ({ movement }) => {
this.setState(() => {
this.position = movement;
});
},
onDragEnd: () => {
this.setState(() => {
this.status = 'Completed';
});
},
child: Container({
width: 100,
height: 100,
color: '#8e44ad'
})
})
]
});
}
}
Important Notes
-
Performance: Drag events occur frequently, so avoid heavy computations in
onDragUpdate
. -
State Updates: Proper
setState
calls are needed when changing state during dragging. -
Coordinate System:
movement
is relative to drag start point,delta
is relative to previous frame. -
Transform Relationship: Draggable uses Transform internally, so be careful when applying additional transforms.
-
Drop Targets: Currently Draggable only supports dragging; drop target functionality must be implemented separately.
Related Widgets
- Transform: Widget transformation and positioning
- GestureDetector: Mouse/touch event detection
- Container: Draggable area definition
- ZIndex: Layer order adjustment during dragging