In this piece we look at how to drag drop items in a listview, thus reordering them.
Example 1: Flutter Drag Drop ListView
This example teaches us the implementation of drag drop in a listview. You can not only drag the individual listview items but also the whole listview.
Here is the demo screenshot:
Step 1: Create Project
Start by creating an empty flutter project.
Step 2: Install drag_and_drop_lists
Add the dependency drag_and_drop_lists
in your pubspec.yaml
and flutter pub get
or sync to fetch it:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# drag & drop listview
drag_and_drop_lists: ^0.3.1
Step 3: Create Model and Data
Start by creating the model class, which is a class to represent a single List alongside it's ListView items:
/model/draggable_list.dart
class DraggableList {
final String header;
final List<DraggableListItem> items;
const DraggableList({
required this.header,
required this.items,
});
}
class DraggableListItem {
final String title;
final String urlImage;
const DraggableListItem({
required this.title,
required this.urlImage,
});
}
Then draggable lists, which will prepare a list of lists:
/data/draggable_lists.dart
import 'package:drag_drop_listview_example/model/draggable_list.dart';
List<DraggableList> allLists = [
DraggableList(
header: 'Best Fruits',
items: [
DraggableListItem(
title: 'Orange',
urlImage:
'https://images.unsplash.com/photo-1582979512210-99b6a53386f9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
),
DraggableListItem(
title: 'Apple',
urlImage:
'https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=3367&q=80',
),
DraggableListItem(
title: 'Blueberries',
urlImage:
'https://images.unsplash.com/photo-1595231776515-ddffb1f4eb73?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80',
),
],
),
DraggableList(
header: 'Good Fruits',
items: [
DraggableListItem(
title: 'Lemon',
urlImage:
'https://images.unsplash.com/photo-1590502593747-42a996133562?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80',
),
DraggableListItem(
title: 'Melon',
urlImage:
'https://images.unsplash.com/photo-1571575173700-afb9492e6a50?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=976&q=80',
),
DraggableListItem(
title: 'Papaya',
urlImage:
'https://images.unsplash.com/photo-1617112848923-cc2234396a8d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1567&q=80',
),
],
),
DraggableList(
header: 'Disliked Fruits',
items: [
DraggableListItem(
title: 'Banana',
urlImage:
'https://images.unsplash.com/photo-1543218024-57a70143c369?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=975&q=80',
),
DraggableListItem(
title: 'Strawberries',
urlImage:
'https://images.unsplash.com/photo-1464965911861-746a04b4bca6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80',
),
DraggableListItem(
title: 'Grapefruit',
urlImage:
'https://images.unsplash.com/photo-1577234286642-fc512a5f8f11?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80',
),
],
),
];
Step 4: Create main class
main.dart
import 'package:drag_and_drop_lists/drag_and_drop_item.dart';
import 'package:drag_and_drop_lists/drag_and_drop_list.dart';
import 'package:drag_and_drop_lists/drag_and_drop_lists.dart';
import 'package:drag_drop_listview_example/data/draggable_lists.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import 'model/draggable_list.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
static final String title = 'Drag & Drop ListView';
@override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
theme: ThemeData(primarySwatch: Colors.red),
home: MainPage(),
);
}
class MainPage extends StatefulWidget {
@override
_MainPage createState() => _MainPage();
}
class _MainPage extends State<MainPage> {
late List<DragAndDropList> lists;
@override
void initState() {
super.initState();
lists = allLists.map(buildList).toList();
}
@override
Widget build(BuildContext context) {
final backgroundColor = Color.fromARGB(255, 243, 242, 248);
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
title: Text(MyApp.title),
centerTitle: true,
),
body: DragAndDropLists(
// lastItemTargetHeight: 50,
// addLastItemTargetHeightToTop: true,
// lastListTargetSize: 30,
listPadding: EdgeInsets.all(16),
listInnerDecoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.circular(10),
),
children: lists,
itemDivider: Divider(thickness: 2, height: 2, color: backgroundColor),
itemDecorationWhileDragging: BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)],
),
listDragHandle: buildDragHandle(isList: true),
itemDragHandle: buildDragHandle(),
onItemReorder: onReorderListItem,
onListReorder: onReorderList,
),
);
}
DragHandle buildDragHandle({bool isList = false}) {
final verticalAlignment = isList
? DragHandleVerticalAlignment.top
: DragHandleVerticalAlignment.center;
final color = isList ? Colors.blueGrey : Colors.black26;
return DragHandle(
verticalAlignment: verticalAlignment,
child: Container(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.menu, color: color),
),
);
}
DragAndDropList buildList(DraggableList list) => DragAndDropList(
header: Container(
padding: EdgeInsets.all(8),
child: Text(
list.header,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
),
children: list.items
.map((item) => DragAndDropItem(
child: ListTile(
leading: Image.network(
item.urlImage,
width: 40,
height: 40,
fit: BoxFit.cover,
),
title: Text(item.title),
),
))
.toList(),
);
void onReorderListItem(
int oldItemIndex,
int oldListIndex,
int newItemIndex,
int newListIndex,
) {
setState(() {
final oldListItems = lists[oldListIndex].children;
final newListItems = lists[newListIndex].children;
final movedItem = oldListItems.removeAt(oldItemIndex);
newListItems.insert(newItemIndex, movedItem);
});
}
void onReorderList(
int oldListIndex,
int newListIndex,
) {
setState(() {
final movedList = lists.removeAt(oldListIndex);
lists.insert(newListIndex, movedList);
});
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
Example 2: Flutter Reorderable list
This is another drag to reorder list in flutter. This time round we use the flutter_reorderable_list
package. This library allows us reorder lists during which we animate them.
Here are its features:
- Smooth reordering animations
- Supports different item heights
- iOS like reordering with drag handle
- Android like (long touch) reordering
- Works with slivers so it can be placed in
CustomScrollView
and used withSliverAppBar
- Supports large lists (thousands of items) without any issues
Here is the demo:
Here's a screenshot of the demo project:
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Install it
To install it, simply download this file and add to your project.
Step 3: Write Code
Here is the full code:
main.dart
import 'package:flutter/cupertino.dart' hide ReorderableList;
import 'package:flutter/material.dart' hide ReorderableList;
import 'package:flutter_reorderable_list/flutter_reorderable_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Rerderable List',
theme: ThemeData(
dividerColor: Color(0x50000000),
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Reorderable List'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class ItemData {
ItemData(this.title, this.key);
final String title;
// Each item in reorderable list needs stable and unique key
final Key key;
}
enum DraggingMode {
iOS,
Android,
}
class _MyHomePageState extends State<MyHomePage> {
late List<ItemData> _items;
_MyHomePageState() {
_items = [];
for (int i = 0; i < 500; ++i) {
String label = "List item $i";
if (i == 5) {
label += ". This item has a long label and will be wrapped.";
}
_items.add(ItemData(label, ValueKey(i)));
}
}
// Returns index of item with given key
int _indexOfKey(Key key) {
return _items.indexWhere((ItemData d) => d.key == key);
}
bool _reorderCallback(Key item, Key newPosition) {
int draggingIndex = _indexOfKey(item);
int newPositionIndex = _indexOfKey(newPosition);
// Uncomment to allow only even target reorder possition
// if (newPositionIndex % 2 == 1)
// return false;
final draggedItem = _items[draggingIndex];
setState(() {
debugPrint("Reordering $item -> $newPosition");
_items.removeAt(draggingIndex);
_items.insert(newPositionIndex, draggedItem);
});
return true;
}
void _reorderDone(Key item) {
final draggedItem = _items[_indexOfKey(item)];
debugPrint("Reordering finished for ${draggedItem.title}}");
}
//
// Reordering works by having ReorderableList widget in hierarchy
// containing ReorderableItems widgets
//
DraggingMode _draggingMode = DraggingMode.iOS;
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableList(
onReorder: this._reorderCallback,
onReorderDone: this._reorderDone,
child: CustomScrollView(
// cacheExtent: 3000,
slivers: <Widget>[
SliverAppBar(
actions: <Widget>[
PopupMenuButton<DraggingMode>(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Text("Options"),
),
initialValue: _draggingMode,
onSelected: (DraggingMode mode) {
setState(() {
_draggingMode = mode;
});
},
itemBuilder: (BuildContext context) =>
<PopupMenuItem<DraggingMode>>[
const PopupMenuItem<DraggingMode>(
value: DraggingMode.iOS,
child: Text('iOS-like dragging')),
const PopupMenuItem<DraggingMode>(
value: DraggingMode.Android,
child: Text('Android-like dragging')),
],
),
],
pinned: true,
expandedHeight: 150.0,
flexibleSpace: const FlexibleSpaceBar(
title: const Text('Demo'),
),
),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Item(
data: _items[index],
// first and last attributes affect border drawn during dragging
isFirst: index == 0,
isLast: index == _items.length - 1,
draggingMode: _draggingMode,
);
},
childCount: _items.length,
),
)),
],
),
),
);
}
}
class Item extends StatelessWidget {
Item({
required this.data,
required this.isFirst,
required this.isLast,
required this.draggingMode,
});
final ItemData data;
final bool isFirst;
final bool isLast;
final DraggingMode draggingMode;
Widget _buildChild(BuildContext context, ReorderableItemState state) {
BoxDecoration decoration;
if (state == ReorderableItemState.dragProxy ||
state == ReorderableItemState.dragProxyFinished) {
// slightly transparent background white dragging (just like on iOS)
decoration = BoxDecoration(color: Color(0xD0FFFFFF));
} else {
bool placeholder = state == ReorderableItemState.placeholder;
decoration = BoxDecoration(
border: Border(
top: isFirst && !placeholder
? Divider.createBorderSide(context) //
: BorderSide.none,
bottom: isLast && placeholder
? BorderSide.none //
: Divider.createBorderSide(context)),
color: placeholder ? null : Colors.white);
}
// For iOS dragging mode, there will be drag handle on the right that triggers
// reordering; For android mode it will be just an empty container
Widget dragHandle = draggingMode == DraggingMode.iOS
? ReorderableListener(
child: Container(
padding: EdgeInsets.only(right: 18.0, left: 18.0),
color: Color(0x08000000),
child: Center(
child: Icon(Icons.reorder, color: Color(0xFF888888)),
),
),
)
: Container();
Widget content = Container(
decoration: decoration,
child: SafeArea(
top: false,
bottom: false,
child: Opacity(
// hide content for placeholder
opacity: state == ReorderableItemState.placeholder ? 0.0 : 1.0,
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Padding(
padding:
EdgeInsets.symmetric(vertical: 14.0, horizontal: 14.0),
child: Text(data.title,
style: Theme.of(context).textTheme.subtitle1),
)),
// Triggers the reordering
dragHandle,
],
),
),
)),
);
// For android dragging mode, wrap the entire content in DelayedReorderableListener
if (draggingMode == DraggingMode.Android) {
content = DelayedReorderableListener(
child: content,
);
}
return content;
}
@override
Widget build(BuildContext context) {
return ReorderableItem(
key: data.key, //
childBuilder: _buildChild);
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |