Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
How to create a reorderble tree view in Flutter? There are several packages to create tree view, but I want to make it reorderble.
This
is what I want to implement.
I have no idea where to start. Can you give me just some of basic ideas?
–
try this package,
reorderables
, ReorderableWrap can be used:
class _WrapExampleState extends State<WrapExample> {
final double _iconSize = 90;
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.start,
children: <Widget>[
wrap,
ButtonBar(
alignment: MainAxisAlignment.start,
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,
Here is a full example to get the idea;
Using expandable and reorderables packages;
import 'dart:math' as math;
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expandable Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
home: MyHomePage(),
class MyHomePage extends StatefulWidget {
@override
State createState() {
return MyHomePageState();
class MyHomePageState extends State<MyHomePage> {
List<Widget> myList = [
ItemBuilder(title: 'Category 0'),
ItemBuilder(title: 'Category 1'),
ItemBuilder(title: 'Category 2'),
@override
Widget build(BuildContext context) {
ScrollController _scrollController =
PrimaryScrollController.of(context) ?? ScrollController();
return Scaffold(
appBar: AppBar(
title: Text("Expandable Demo"),
body: ExpandableTheme(
data: const ExpandableThemeData(
iconColor: Colors.blue,
useInkWell: true,
child: CustomScrollView(
// A ScrollController must be included in CustomScrollView, otherwise
// ReorderableSliverList wouldn't work
controller: _scrollController,
slivers: <Widget>[
ReorderingItems(
list: myList,
const loremIpsum =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
class ItemBuilder extends StatelessWidget {
const ItemBuilder({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
buildItem(String label) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text(label),
buildList() {
// ScrollController _scrollController =
// PrimaryScrollController.of(context) ?? ScrollController();
return Column(
children: <Widget>[
for (var i in [1, 2, 3, 4]) buildItem("Item ${i}"),
return ExpandableNotifier(
child: Padding(
padding: const EdgeInsets.all(10),
child: ScrollOnExpand(
child: Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: <Widget>[
ExpandablePanel(
theme: const ExpandableThemeData(
headerAlignment: ExpandablePanelHeaderAlignment.center,
tapBodyToExpand: true,
tapBodyToCollapse: true,
hasIcon: false,
header: Container(
color: Colors.indigoAccent,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: [
ExpandableIcon(
theme: const ExpandableThemeData(
expandIcon: Icons.arrow_right,
collapseIcon: Icons.arrow_drop_down,
iconColor: Colors.white,
iconSize: 28.0,
iconRotationAngle: math.pi / 2,
iconPadding: EdgeInsets.only(right: 5),
hasIcon: false,
Expanded(
child: Text(
title,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.white),
collapsed: Container(),
expanded: buildList(),
class ReorderingItems extends StatefulWidget {
const ReorderingItems({Key? key, required this.list}) : super(key: key);
final List<Widget> list;
@override
State<ReorderingItems> createState() => _ReorderingItemsState();
class _ReorderingItemsState extends State<ReorderingItems> {
late List<Widget> _rows;
@override
void initState() {
super.initState();
_rows = widget.list;
@override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
Widget row = _rows.removeAt(oldIndex);
_rows.insert(newIndex, row);
return ReorderableSliverList(
delegate: ReorderableSliverChildListDelegate(_rows),
onReorder: _onReorder,
–
–
I write simple code to give you a basic idea of how to use Drag&Drop API in a flutter. You don't need to use any third-party packages.
Here are the official docs: https://api.flutter.dev/flutter/widgets/Draggable-class.html
Use Draggable
widget that gives us the ability to drag and move widgets around.
Use DragTarget
widget that receives data when a Draggable
widget is dropped on it.
Example code:
import 'package:flutter/material.dart';
void main() => runApp(const DragAndDropApp());
class DragAndDropApp extends StatelessWidget {
const DragAndDropApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Drag & Drop Sample')),
body: const DragAndDropExample(),
class DragAndDropExample extends StatefulWidget {
const DragAndDropExample({super.key});
@override
State<DragAndDropExample> createState() => _MyStatefulWidgetState();
class _MyStatefulWidgetState extends State<DragAndDropExample> {
final _mapList = {
'Blogs': {
0: 'Yesterday',
1: '2 Days ago',
'Home': {},
'News': {},
'About': {},
_reorder(Object? data) {
debugPrint('Reorder: $data');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _mapList.length,
itemBuilder: (context, index) => Column(
children: [
// Topic
_draggbleAndDragTargetListTile(
data: {'topic': index},
title: _mapList[index]!.keys.first,
key: '$index',
// Subtopics list
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(left: 32),
itemCount: _mapList[index]!.values.first.length,
itemBuilder: (context, subindex) => Column(
children: [
// Subtopic divider
_dragTargetDivider(),
// Subtopic
_draggbleAndDragTargetListTile(
data: {'topic': index, 'subtopic': subindex},
title: _mapList[index]!.values.first[subindex],
key: '${index}_$subindex',
// Topic with Subtopics end divider
_dragTargetDivider(),
Widget _dragTargetDivider() {
return DragTarget(
builder: (context, acceptedItems, rejectedItems) => Divider(
thickness: acceptedItems.isNotEmpty ? 3 : null,
color: acceptedItems.isNotEmpty ? Colors.blue : null,
onAccept: _reorder,
Widget _draggbleAndDragTargetListTile({
required Map<String, dynamic> data,
required String title,
required String key,
return Draggable(
data: data,
feedback: Text(title),
dragAnchorStrategy: pointerDragAnchorStrategy,
childWhenDragging: ListTile(
tileColor: Colors.grey,
title: Text(title),
child: DragTarget(
builder: (context, acceptedItems, rejectedItems) {
return ListTile(
key: Key(key),
title: Text(title),
tileColor: acceptedItems.isNotEmpty ? Colors.blue : null,
onAccept: _reorder,
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.