drag_grid_list 0.0.3
drag_grid_list: ^0.0.3 copied to clipboard
Reorderable list + responsive grid with drag & drop, reordering, and cross-group drag between multiple lists/grids. Long-press drag on mobile, mouse drag on desktop/web.
import 'package:drag_grid_list/drag_grid_list.dart';
import 'package:flutter/material.dart';
import 'demo_data.dart';
void main() {
runApp(const DragGridListExampleApp());
}
class DragGridListExampleApp extends StatelessWidget {
const DragGridListExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'drag_grid_list example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ExampleHome(),
);
}
}
class ExampleHome extends StatefulWidget {
const ExampleHome({super.key});
@override
State<ExampleHome> createState() => _ExampleHomeState();
}
class _ExampleHomeState extends State<ExampleHome> {
List<DemoCard> _listItems = generateDemoCards(10);
List<DemoCard> _gridItems = generateDemoCards(20);
List<DemoCard> _masonryItems = generateDemoCards(16);
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
title: const Text('drag_grid_list'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.view_list), text: 'List'),
Tab(icon: Icon(Icons.grid_view), text: 'Grid'),
Tab(icon: Icon(Icons.dashboard_customize), text: 'Masonry'),
],
),
),
body: Column(
children: [
const _DragHint(),
Expanded(
child: TabBarView(
children: [
DragGridList<DemoCard>.builder(
items: _listItems,
keyOf: (card) => ValueKey(card.id),
padding: const EdgeInsets.fromLTRB(12, 12, 12, 24),
itemSpacing: 10,
itemExtent: 72,
longPressDelay: const Duration(milliseconds: 150),
onItemsChanged: (items) => setState(() => _listItems = items),
itemBuilder: (context, card, index) => _ListTile(card: card, index: index),
),
DragGridList<DemoCard>.builder(
items: _gridItems,
layout: DragListLayout.grid,
keyOf: (card) => ValueKey(card.id),
padding: const EdgeInsets.fromLTRB(12, 12, 12, 24),
minColumnWidth: 120,
itemSpacing: 10,
rowSpacing: 10,
childAspectRatio: 1,
longPressDelay: const Duration(milliseconds: 150),
onItemsChanged: (items) => setState(() => _gridItems = items),
itemBuilder: (context, card, index) => _GridTile(card: card),
),
DragGridList<DemoCard>.builder(
items: _masonryItems,
layout: DragListLayout.masonry,
keyOf: (card) => ValueKey(card.id),
padding: const EdgeInsets.fromLTRB(12, 12, 12, 24),
minColumnWidth: 140,
itemSpacing: 10,
rowSpacing: 10,
longPressDelay: const Duration(milliseconds: 150),
onItemsChanged: (items) => setState(() => _masonryItems = items),
itemBuilder: (context, card, index) => _MasonryTile(card: card, index: index),
),
],
),
),
],
),
),
);
}
}
/// Slim, dismiss-free hint banner nudging first-time users toward the
/// long-press-to-drag gesture.
class _DragHint extends StatelessWidget {
const _DragHint();
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
color: colorScheme.secondaryContainer,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Icon(Icons.touch_app_outlined, size: 18, color: colorScheme.onSecondaryContainer),
const SizedBox(width: 8),
Expanded(
child: Text(
'Long-press an item, then drag to reorder',
style: TextStyle(fontSize: 13, color: colorScheme.onSecondaryContainer),
),
),
],
),
);
}
}
class _ListTile extends StatelessWidget {
const _ListTile({required this.card, required this.index});
final DemoCard card;
final int index;
@override
Widget build(BuildContext context) {
return Material(
elevation: 1,
borderRadius: BorderRadius.circular(14),
clipBehavior: Clip.antiAlias,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
leading: CircleAvatar(backgroundColor: card.color, foregroundColor: Colors.white, child: Text('${index + 1}')),
title: Text(card.title, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text(card.id),
trailing: Icon(Icons.drag_handle, color: Theme.of(context).colorScheme.outline),
),
);
}
}
class _MasonryTile extends StatelessWidget {
const _MasonryTile({required this.card, required this.index});
final DemoCard card;
final int index;
@override
Widget build(BuildContext context) {
return Material(
elevation: 1,
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: Ink(
height: demoMasonryHeight(card),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [card.color, card.color.withValues(alpha: 0.7)],
),
),
child: Stack(
children: [
// Scrim so the title/subtitle stay legible over any card color,
// regardless of how tall this particular tile ended up.
const Positioned(
left: 0,
right: 0,
bottom: 0,
height: 56,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black38],
),
),
),
),
const Positioned(top: 6, right: 6, child: Icon(Icons.open_with, size: 16, color: Colors.white70)),
Positioned(
left: 12,
right: 12,
bottom: 10,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
card.title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
Text(card.id, style: const TextStyle(color: Colors.white70, fontSize: 11)),
],
),
),
],
),
),
);
}
}
class _GridTile extends StatelessWidget {
const _GridTile({required this.card});
final DemoCard card;
@override
Widget build(BuildContext context) {
return Material(
elevation: 1,
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: Ink(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [card.color, card.color.withValues(alpha: 0.7)],
),
),
child: Stack(
children: [
const Positioned(top: 6, right: 6, child: Icon(Icons.open_with, size: 16, color: Colors.white70)),
Center(
child: Text(
card.title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
],
),
),
);
}
}