Saltar al contenido principal

Flutter Drag and Drop ListView Examples

Drag and drop is a common user interface pattern that allows users to move objects from one place to another. In Flutter, this functionality can be implemented using the DragTarget and Draggable widgets. In this article, we will explore how to implement drag and drop in a ListView in Flutter.

Steps:

Follow these steps:

Step 1: Create a ListView widget

The first step is to create a ListView widget that will display the items that can be dragged and dropped. Here is an example of a simple ListView widget that displays a list of strings:

ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
)

Step 2: Wrap each item in a Draggable widget

To make each item in the ListView draggable, we need to wrap it in a Draggable widget. The Draggable widget takes two required parameters: child and feedback. The child parameter is the widget that will be dragged, and the feedback parameter is the widget that will be displayed when the item is being dragged.

Here is an example of a Draggable widget wrapped around a ListTile widget:

Draggable(
child: ListTile(title: Text('Item 1')),
feedback: ListTile(title: Text('Item 1')),
)

Step 3: Create a DragTarget widget

To define where the draggable items can be dropped, we need to create a DragTarget widget. The DragTarget widget takes a single required parameter: builder. The builder parameter is a function that returns the widget that will be displayed when a draggable item is being hovered over the DragTarget.

Here is an example of a DragTarget widget:

DragTarget(
builder: (BuildContext context, List<String> candidateData, List<dynamic> rejectedData) {
return Container(
height: 100,
color: Colors.blue,
);
},
)

Step 4: Handle the onAccept callback

When a draggable item is dropped onto a DragTarget, the onAccept callback is called. This is where we can handle the logic for what should happen when an item is dropped.

Here is an example of an onAccept callback:

void _handleAccept(String item) {
setState(() {
_items.remove(item);
});
}

Step 5: Combine all the widgets

To combine all the widgets together, we can use the ListView.builder widget. The ListView.builder widget takes a single required parameter: itemBuilder. The itemBuilder parameter is a function that returns a widget for each item in the ListView.

Here is an example of a ListView.builder widget that combines all the widgets together:

ListView.builder(
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) {
return Draggable(
child: ListTile(title: Text(_items[index])),
feedback: ListTile(title: Text(_items[index])),
childWhenDragging: Container(),
onDragCompleted: () {
setState(() {
_items.removeAt(index);
});
},
data: _items[index],
onDraggableCanceled: (Velocity velocity, Offset offset) {},
);
},
),

More Examples

In this section we look at full examples 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:

Flutter Drag Drop ListView

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';

Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
theme: ThemeData(primarySwatch: Colors.red),
home: MainPage(),
);
}
class MainPage extends StatefulWidget {

_MainPage createState() => _MainPage();
}
class _MainPage extends State<MainPage> {
late List<DragAndDropList> lists;

void initState() {
super.initState();
lists = allLists.map(buildList).toList();
}

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:

NumberLink
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 with SliverAppBar
  • Supports large lists (thousands of items) without any issues
    Here is the demo:

Here's a screenshot of the demo project:

Flutter Reorderable list

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.

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;

_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;
}

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:

NumberLink
1.Download Example
2.Follow code author

Conclusion:

In conclusion, drag and drop functionality can be implemented in a ListView widget in Flutter using the DragTarget and Draggable widgets. By following the steps outlined in this article, you can create a ListView widget that allows users to drag and drop items from one place to another.