Skip to content

Instantly share code, notes, and snippets.

@Ayu219
Created October 30, 2024 20:03
Show Gist options
  • Save Ayu219/1a350ff1f595d8d09638b6461844d2b5 to your computer and use it in GitHub Desktop.
Save Ayu219/1a350ff1f595d8d09638b6461844d2b5 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
/// Entry point of the application.
void main() {
runApp(const MyApp());
}
/// [Widget] building the [MaterialApp].
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey[900],
body: Center(
child: Dock(
items: const [
Icons.person,
Icons.message,
Icons.call,
Icons.camera,
Icons.photo,
],
builder: (e) {
return Container(
constraints: const BoxConstraints(minWidth: 48),
height: 48,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.primaries[e.hashCode % Colors.primaries.length],
),
child: Center(child: Icon(e, color: Colors.white)),
);
},
),
),
),
);
}
}
/// Dock of the reorderable [items].
class Dock<T> extends StatefulWidget {
const Dock({
super.key,
this.items = const [],
required this.builder,
});
/// Initial [T] items to put in this [Dock].
final List<T> items;
/// Builder building the provided [T] item.
final Widget Function(T) builder;
@override
State<Dock<T>> createState() => _DockState<T>();
}
/// State of the [Dock] used to manipulate the [_items].
class _DockState<T> extends State<Dock<T>> with SingleTickerProviderStateMixin {
/// [T] items being manipulated.
late final List<T> _items = widget.items.toList();
int? _draggingIndex;
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800), // Increased to slow down animation
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.93).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut), // Smooth start and end
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onDragStart() {
_controller.forward();
setState(() {
_draggingIndex = null;
});
}
void _onDragEnd() {
_controller.reverse();
setState(() {
_draggingIndex = null;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (_) => _onDragStart(),
onPanEnd: (_) => _onDragEnd(),
child: AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.black.withOpacity(0.2),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
_items.length,
(index) => _draggingIndex == index
? const SizedBox.shrink() // No placeholder, item fully disappears
: Draggable<int>(
data: index,
onDragStarted: () => setState(() {
_draggingIndex = index;
}),
onDraggableCanceled: (_, __) => _onDragEnd(),
onDragCompleted: _onDragEnd,
feedback: Material(
color: Colors.transparent,
child: Opacity(
opacity: 0.75,
child: widget.builder(_items[index]),
),
),
child: DragTarget<int>(
onAcceptWithDetails: (details) {
setState(() {
final fromIndex = details.data;
final item = _items.removeAt(fromIndex);
_items.insert(index, item);
});
},
builder: (context, candidateData, rejectedData) {
return widget.builder(_items[index]);
},
),
),
),
),
),
);
},
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment