Introduction:
Implementing drag and drop functionality in Flutter allows users to interact with items in a list or grid by dragging them to a new position. The reorderables
package provides a convenient way to implement drag and drop functionality in Flutter. This tutorial will guide you through implementing drag and drop in Flutter using the reorderables
package.
Content:
Step 1: Add the dependency:
Add the reorderables
package to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
reorderables: ^0.4.0
Save the file and run flutter pub get
to install the package.
Step 2: Import the package:
Import the reorderables
package in your Dart file:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
Step 3: Create a list of items:
Create a list of items that you want to make draggable. Each item should be a widget that can be dragged and dropped.
List<String> items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];
Step 4: Create a draggable list:
Create a ReorderableWrap
widget to display the draggable list of items. Use the Reorderable
widget to make each item draggable.
ReorderableWrap(
spacing: 8.0,
runSpacing: 8.0,
padding: EdgeInsets.all(8.0),
children: items.map((item) {
return Reorderable(
key: ValueKey(item),
child: Container(
width: 100.0,
height: 100.0,
color: Colors.blue,
child: Center(
child: Text(
item,
style: TextStyle(color: Colors.white),
),
),
),
);
}).toList(),
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final String item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
),
Step 5: Run the app:
Run your Flutter app to see the draggable list in action. You should be able to drag and drop items to reorder them.
Sample Code:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
// import './column_example1.dart';
// import './column_example2.dart';
// import './nested_wrap_example.dart';
// import './row_example.dart';
// import './sliver_example.dart';
// import './table_example.dart';
// import './wrap_example.dart';
import 'dart:math' as math;
class Reorderbalepage extends StatefulWidget {
Reorderbalepage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_ReorderbalepageState createState() => _ReorderbalepageState();
}
class _ReorderbalepageState extends State<Reorderbalepage> {
int _currentIndex = 0;
final List<Widget> _examples = [
TableExample(),
WrapExample(),
NestedWrapExample(),
ColumnExample1(),
ColumnExample2(),
RowExample(),
SliverExample(),
];
final _bottomNavigationColor = Colors.blue;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: _examples[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
fixedColor: _bottomNavigationColor,
showSelectedLabels: true,
showUnselectedLabels: false,
currentIndex: _currentIndex,
// this will be set when a new tab is tapped
// type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(icon: Icon(Icons.grid_on, color: _bottomNavigationColor), tooltip: "ReorderableTable", label: "Table"),
BottomNavigationBarItem(icon: Icon(Icons.apps, color: _bottomNavigationColor), tooltip: "ReorderableWrap", label: "Wrap"),
BottomNavigationBarItem(icon: Icon(Icons.view_quilt, color: _bottomNavigationColor), tooltip: 'Nested ReroderableWrap', label: "Nested"),
BottomNavigationBarItem(icon: Icon(Icons.more_vert, color: _bottomNavigationColor), tooltip: "ReorderableColumn 1", label: "Column 1"),
BottomNavigationBarItem(icon: Icon(Icons.more_vert, color: _bottomNavigationColor), tooltip: "ReroderableColumn 2", label: "Column 2"),
BottomNavigationBarItem(icon: Icon(Icons.more_horiz, color: _bottomNavigationColor), tooltip: "ReorderableRow", label: "Row"),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_view_day, color: _bottomNavigationColor), tooltip: "ReroderableSliverList", label: "SliverList"),
],
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
class ColumnExample2 extends StatefulWidget {
@override
_ColumnExample2State createState() => _ColumnExample2State();
}
class _ColumnExample2State extends State<ColumnExample2> {
late List<Widget> _rows;
@override
void initState() {
super.initState();
_rows = List<Widget>.generate(
25,
(int index) => Container(
key: ValueKey(index),
width: double.infinity,
child: Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(vertical: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Colors.grey),
child: Center(
child: Text(
'This is row $index' " hold drag and droup",
textScaleFactor: 1.3,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
),
),
),
));
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget row = _rows.removeAt(oldIndex);
_rows.insert(newIndex, row);
});
}
Widget reorderableColumn = IntrinsicWidth(
child: ReorderableColumn(
header: Text('List-like view but supports IntrinsicWidth'),
// crossAxisAlignment: CrossAxisAlignment.start,
children: _rows,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
));
return Container(
width: double.infinity,
height: double.maxFinite,
child: Transform(
transform: Matrix4.rotationZ(0),
alignment: FractionalOffset.topLeft,
child: Material(
child: Card(child: reorderableColumn),
elevation: 6.0,
color: Colors.transparent,
borderRadius: BorderRadius.zero,
),
),
);
}
}
class TableExample extends StatefulWidget {
@override
_TableExampleState createState() => _TableExampleState();
}
class _TableExampleState extends State<TableExample> {
//The children of ReorderableTableRow must be of type ReorderableTableRow
late List<ReorderableTableRow> _itemRows;
@override
void initState() {
super.initState();
var data = [
['Alex', 'D', 'B+', 'AA', ''],
['Bob', 'AAAAA+', '', 'B', ''],
['Cindy', '', 'To Be Confirmed', '', ''],
['Duke', 'C-', '', 'Failed', ''],
['Ellenina', 'C', 'B', 'A', 'A'],
['Floral', '', 'BBB', 'A', 'A'],
['Duke', 'C-', '', 'Failed', ''],
['Floral', '', 'BBB', 'A', 'A'],
['Alex', 'D', 'B+', 'AA', ''],
];
Widget _textWithPadding(String text) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Text(text, textScaleFactor: 1.1),
);
}
_itemRows = data.map((row) {
return ReorderableTableRow(
//a key must be specified for each row
key: ObjectKey(row),
mainAxisSize: MainAxisSize.max,
decoration: BoxDecoration(border: Border.all()),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_textWithPadding('${row[0]}'),
_textWithPadding('${row[1]}'),
_textWithPadding('${row[2]}'),
_textWithPadding('${row[3]}'),
// Text('${row[4]}'),
],
);
}).toList();
}
@override
Widget build(BuildContext context) {
var headerRow = ReorderableTableRow(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text('Name', textScaleFactor: 1.5),
Text('Math', textScaleFactor: 1.5),
Text('Science', textScaleFactor: 1.5),
Text('Physics', textScaleFactor: 1.5),
Text('Sports', textScaleFactor: 1.5)
]);
void _onReorder(int oldIndex, int newIndex) {
setState(() {
ReorderableTableRow row = _itemRows.removeAt(oldIndex);
_itemRows.insert(newIndex, row);
});
}
return Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Colors.greenAccent),
child: ReorderableTable(
header: headerRow,
children: _itemRows,
borderColor: const Color(0xFFE0E0E0),
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
),
);
}
}
class WrapExample extends StatefulWidget {
@override
_WrapExampleState createState() => _WrapExampleState();
}
class _WrapExampleState extends State<WrapExample> {
final double _iconSize = 90;
late List<Widget> _tiles;
@override
void initState() {
super.initState();
_tiles = <Widget>[
Icon(Icons.filter_1, size: _iconSize),
Icon(Icons.filter_2, size: _iconSize),
Icon(Icons.filter_3, size: _iconSize),
Icon(Icons.filter_4, size: _iconSize),
Icon(Icons.filter_5, size: _iconSize),
Icon(Icons.filter_6, size: _iconSize),
Icon(Icons.filter_7, size: _iconSize),
Icon(Icons.filter_8, size: _iconSize),
Icon(Icons.filter_9, size: _iconSize),
];
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget row = _tiles.removeAt(oldIndex);
_tiles.insert(newIndex, row);
});
}
var wrap = ReorderableWrap(
spacing: 8.0,
runSpacing: 4.0,
padding: const EdgeInsets.all(8),
children: _tiles,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder started: index:$index');
});
var column = Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
wrap,
ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
iconSize: 50,
icon: Icon(Icons.add_circle),
color: Colors.deepOrange,
padding: const EdgeInsets.all(0.0),
onPressed: () {
var newTile = Icon(Icons.filter_9_plus, size: _iconSize);
setState(() {
_tiles.add(newTile);
});
},
),
IconButton(
iconSize: 50,
icon: Icon(Icons.remove_circle),
color: Colors.teal,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
_tiles.removeAt(0);
});
},
),
],
),
],
);
return SingleChildScrollView(
child: column,
);
}
}
class ColumnExample1 extends StatefulWidget {
@override
_ColumnExample1State createState() => _ColumnExample1State();
}
class _ColumnExample1State extends State<ColumnExample1> {
late List<Widget> _rows;
@override
void initState() {
super.initState();
_rows = List<ReorderableWidget>.generate(
10,
(int index) => ReorderableWidget(
reorderable: true,
key: ValueKey(index),
child: Container(
width: double.infinity, child: Align(alignment: Alignment.centerLeft, child: Text('This is row $index', textScaleFactor: 1.5))),
));
_rows += <ReorderableWidget>[
ReorderableWidget(
reorderable: false,
key: ValueKey(10),
child: Text('This row is not reorderable', textScaleFactor: 2),
)
];
_rows += List<ReorderableWidget>.generate(
40,
(int index) => ReorderableWidget(
reorderable: true,
key: ValueKey(11 + index),
child: Container(
width: double.infinity,
child: Align(alignment: Alignment.centerLeft, child: Text('This is row $index', textScaleFactor: 1.5)),
),
));
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget row = _rows.removeAt(oldIndex);
_rows.insert(newIndex, row);
});
}
return ReorderableColumn(
header: Text('THIS IS THE HEADER ROW'),
footer: Text('THIS IS THE FOOTER ROW'),
crossAxisAlignment: CrossAxisAlignment.start,
children: _rows,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
);
}
}
class NestedWrapExample extends StatefulWidget {
NestedWrapExample({
Key? key,
this.depth = 0,
this.valuePrefix = '',
this.color,
}) : super(key: key);
final int depth;
final String valuePrefix;
final Color? color;
final List<Widget> _tiles = [];
@override
State createState() => _NestedWrapExampleState();
}
class _NestedWrapExampleState extends State<NestedWrapExample> {
// List<Widget> _tiles;
Color? _color;
Color? _colorBrighter;
@override
void initState() {
super.initState();
_color = widget.color ?? Colors.primaries[widget.depth % Colors.primaries.length];
_colorBrighter = Color.lerp(_color, Colors.white, 0.6);
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
widget._tiles.insert(newIndex, widget._tiles.removeAt(oldIndex));
});
}
var wrap = ReorderableWrap(
spacing: 8.0,
runSpacing: 4.0,
padding: const EdgeInsets.all(8),
children: widget._tiles,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
);
var buttonBar = Container(
color: _colorBrighter,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
IconButton(
iconSize: 42,
icon: Icon(Icons.add_circle),
color: Colors.deepOrange,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.add(Card(
child: Container(
child: Text('${widget.valuePrefix}${widget._tiles.length}', textScaleFactor: 3 / math.sqrt(widget.depth + 1)),
padding: EdgeInsets.all((24.0 / math.sqrt(widget.depth + 1)).roundToDouble()),
),
color: _colorBrighter,
elevation: 3,
));
});
},
),
IconButton(
iconSize: 42,
icon: Icon(Icons.remove_circle),
color: Colors.teal,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.removeAt(0);
});
},
),
IconButton(
iconSize: 42,
icon: Icon(Icons.add_to_photos),
color: Colors.pink,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.add(NestedWrapExample(
depth: widget.depth + 1,
valuePrefix: '${widget.valuePrefix}${widget._tiles.length}.',
));
});
},
),
Text('Level ${widget.depth} / ${widget.valuePrefix}'),
],
));
var column = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
buttonBar,
wrap,
]);
return SingleChildScrollView(
child: Container(
child: column,
color: _color,
),
);
}
}
//main() => runApp(MaterialApp(
// home: Scaffold(
// appBar: AppBar(),
// body: NestedWrapExample(),
// ),
//));import 'package:flutter/material.dart';
class RowExample extends StatefulWidget {
@override
_RowExampleState createState() => _RowExampleState();
}
class _RowExampleState extends State<RowExample> {
late List<Widget> _columns;
@override
void initState() {
super.initState();
_columns = <Widget>[
Image.network(
'https://st.depositphotos.com/1384048/2724/v/950/depositphotos_27248023-stock-illustration-background-internet-vertical.jpg ',
key: ValueKey('river1.jpg')),
Image.network(
'https://c8.alamy.com/comp/2K4EFB4/technology-vertical-wallpaper-with-lines-simulating-internet-data-poster-or-background-of-screen-2K4EFB4.jpg',
key: ValueKey('river2.jpg')),
Image.network(
'https://c8.alamy.com/comp/2K4EFB4/technology-vertical-wallpaper-with-lines-simulating-internet-data-poster-or-background-of-screen-2K4EFB4.jpg',
key: ValueKey('river3.jpg')),
];
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget col = _columns.removeAt(oldIndex);
_columns.insert(newIndex, col);
});
}
return ReorderableRow(
crossAxisAlignment: CrossAxisAlignment.start,
children: _columns,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
);
}
}
class SliverExample extends StatefulWidget {
@override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
late List<Widget> _rows;
@override
void initState() {
super.initState();
_rows = List<Widget>.generate(
50,
(int index) => Container(
width: double.infinity, child: Align(alignment: Alignment.centerLeft, child: Text('This is sliver child $index', textScaleFactor: 2))));
}
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget row = _rows.removeAt(oldIndex);
_rows.insert(newIndex, row);
});
}
// Make sure there is a scroll controller attached to the scroll view that contains ReorderableSliverList.
// Otherwise an error will be thrown.
ScrollController _scrollController = PrimaryScrollController.maybeOf(context) ?? ScrollController();
return CustomScrollView(
// A ScrollController must be included in CustomScrollView, otherwise
// ReorderableSliverList wouldn't work
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
expandedHeight: 210.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('ReorderableSliverList'),
background: Image.network('https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Yushan'
'_main_east_peak%2BHuang_Chung_Yu%E9%BB%83%E4%B8%AD%E4%BD%91%2B'
'9030.png/640px-Yushan_main_east_peak%2BHuang_Chung_Yu%E9%BB%83'
'%E4%B8%AD%E4%BD%91%2B9030.png'),
),
),
ReorderableSliverList(
delegate: ReorderableSliverChildListDelegate(_rows),
// or use ReorderableSliverChildBuilderDelegate if needed
// delegate: ReorderableSliverChildBuilderDelegate(
// (BuildContext context, int index) => _rows[index],
// childCount: _rows.length
// ),
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder started. index:$index');
},
)
],
);
}
}
Output:
Conclusion:
By following these steps, you can easily implement drag and drop functionality in Flutter using the reorderables
package. This allows you to create interactive lists and grids that users can rearrange by dragging and dropping items.