tree_view_with_drag_and_drop 1.0.3
tree_view_with_drag_and_drop: ^1.0.3 copied to clipboard
A customizable Flutter tree view widget with drag-and-drop support for reordering nodes. Supports generic data types and provides extensive customization options.
Flutter Tree Drag Drop #
A customizable Flutter tree view widget with intuitive drag-and-drop support for reordering nodes. Perfect for file explorers, organizational charts, menu systems, and any hierarchical data visualization.
Features #
✨ Generic Data Support - Use any data type with TreeNode<T>
🎯 Drag & Drop - Intuitive reordering with visual feedback
🎨 Highly Customizable - Control appearance and behavior
📂 Expand/Collapse - Show or hide child nodes
🎮 Controller API - Programmatically control the tree
⚡ Performance - Efficiently handles large trees
🔒 Circular Dependency Prevention - Built-in safety checks
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
tree_view_with_drag_and_drop: ^1.0.3
Then run:
flutter pub get
Quick Start #
Basic Usage #
import 'package:flutter/material.dart';
import 'package:tree_view_with_drag_and_drop/tree_view_with_drag_and_drop.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late List<TreeNode<String>> treeData;
@override
void initState() {
super.initState();
treeData = [
TreeNode<String>(
id: '1',
data: 'Documents',
children: [
TreeNode<String>(id: '1.1', data: 'Work'),
TreeNode<String>(id: '1.2', data: 'Personal'),
],
),
TreeNode<String>(
id: '2',
data: 'Photos',
children: [
TreeNode<String>(id: '2.1', data: 'Vacation'),
TreeNode<String>(id: '2.2', data: 'Family'),
],
),
];
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Tree View Demo')),
body: TreeView<String>(
nodes: treeData,
itemBuilder: (context, node, isExpanded) {
return ListTile(
leading: Icon(
node.hasChildren ? Icons.folder : Icons.insert_drive_file,
),
title: Text(node.data),
trailing: node.hasChildren
? Icon(isExpanded ? Icons.expand_more : Icons.chevron_right)
: null,
onTap: node.hasChildren
? () {
setState(() {
node.isExpanded = !node.isExpanded;
});
}
: null,
);
},
onReorder: (draggedNode, targetNode, position) {
print('Moved ${draggedNode.data} to $position of ${targetNode.data}');
setState(() {
// Tree is automatically updated
});
},
),
),
);
}
}
Advanced Usage #
Custom Data Types #
Use any data type with the generic TreeNode<T>:
class FileItem {
final String name;
final IconData icon;
final int size;
FileItem(this.name, this.icon, this.size);
}
final treeData = [
TreeNode<FileItem>(
id: '1',
data: FileItem('Documents', Icons.folder, 0),
children: [
TreeNode<FileItem>(
id: '1.1',
data: FileItem('report.pdf', Icons.picture_as_pdf, 1024),
),
],
),
];
TreeView<FileItem>(
nodes: treeData,
itemBuilder: (context, node, isExpanded) {
return ListTile(
leading: Icon(node.data.icon),
title: Text(node.data.name),
subtitle: node.data.size > 0
? Text('${node.data.size} bytes')
: null,
);
},
)
Customization #
Custom Appearance
TreeView<String>(
nodes: treeData,
config: TreeItemConfig(
indentPerLevel: 32.0,
dropIndicatorColor: Colors.green,
dropIndicatorThickness: 3.0,
highlightColor: Colors.green.shade50,
highlightBorderColor: Colors.green,
borderRadius: 12.0,
dragOpacity: 0.5,
),
itemBuilder: (context, node, isExpanded) {
// Your custom item widget
},
)
Custom Drag Feedback
TreeView<String>(
nodes: treeData,
itemBuilder: (context, node, isExpanded) {
// Normal item
},
feedbackBuilder: (context, node) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.purple.shade100,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.drag_indicator, color: Colors.purple),
SizedBox(width: 8),
Text(node.data, style: TextStyle(fontWeight: FontWeight.bold)),
],
),
);
},
)
Using the Controller #
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final TreeViewController<String> controller = TreeViewController<String>();
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
ElevatedButton(
onPressed: () => controller.expandAll(),
child: Text('Expand All'),
),
ElevatedButton(
onPressed: () => controller.collapseAll(),
child: Text('Collapse All'),
),
],
),
Expanded(
child: TreeView<String>(
controller: controller,
nodes: treeData,
itemBuilder: (context, node, isExpanded) {
// Your item widget
},
),
),
],
);
}
}
Callbacks #
TreeView<String>(
nodes: treeData,
itemBuilder: (context, node, isExpanded) {
// Item widget
},
onReorder: (draggedNode, targetNode, position) {
print('Node ${draggedNode.id} moved ${position} ${targetNode.id}');
// Update your data model if needed
},
onNodeExpanded: (node, isExpanded) {
print('Node ${node.id} expanded: $isExpanded');
// Save expansion state, analytics, etc.
},
)
Disable Drag & Drop #
TreeView<String>(
nodes: treeData,
enableDragAndDrop: false,
itemBuilder: (context, node, isExpanded) {
// Read-only tree view
},
)
API Reference #
TreeNode #
| Property | Type | Description |
|---|---|---|
id |
String |
Unique identifier |
data |
T |
Custom data |
children |
List<TreeNode<T>> |
Child nodes |
isExpanded |
bool |
Expansion state |
metadata |
Map<String, dynamic>? |
Additional data |
TreeView #
| Property | Type | Description |
|---|---|---|
nodes |
List<TreeNode<T>> |
Root nodes |
itemBuilder |
Widget Function(...) |
Item widget builder |
feedbackBuilder |
Widget Function(...)? |
Drag feedback builder |
config |
TreeItemConfig |
Visual configuration |
enableDragAndDrop |
bool |
Enable/disable drag & drop |
onReorder |
Function(...)? |
Reorder callback |
onNodeExpanded |
Function(...)? |
Expansion callback |
controller |
TreeViewController<T>? |
Controller instance |
TreeItemConfig #
| Property | Type | Default | Description |
|---|---|---|---|
indentPerLevel |
double |
24.0 |
Indent per level |
dropIndicatorColor |
Color |
Colors.blue |
Drop line color |
dropIndicatorThickness |
double |
2.0 |
Drop line thickness |
highlightColor |
Color |
Color(0xFFE3F2FD) |
Highlight color |
highlightBorderColor |
Color |
Colors.blue |
Highlight border |
borderRadius |
double |
8.0 |
Item border radius |
dragOpacity |
double |
0.3 |
Dragging opacity |
TreeViewController #
| Method | Description |
|---|---|
expandAll() |
Expand all nodes |
collapseAll() |
Collapse all nodes |
treeData |
Get current tree data |
updateTreeData(List<TreeNode<T>>) |
Update tree data |
Examples #
Check out the example directory for complete working examples:
- Simple Tree - Basic file explorer
- Custom Data - Using custom objects
- Styled Tree - Custom styling and themes
- Controlled Tree - Using the controller API
Tips & Best Practices #
- Unique IDs: Always use unique IDs for each node
- Performance: For large trees, consider lazy loading children
- State Management: Integrate with your state management solution
- Persistence: Save tree state (expansion, order) if needed
- Validation: Validate drops in
onReordercallback
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
If you find this package useful, please give it a ⭐️ on GitHub!
For issues and feature requests, visit the issue tracker.