|
|
|
@ -23,6 +23,8 @@ class TreeViewConfig {
@@ -23,6 +23,8 @@ class TreeViewConfig {
|
|
|
|
|
final Map<String, IconData> icons; |
|
|
|
|
final bool showRefreshButton; |
|
|
|
|
final IconData refreshIcon; |
|
|
|
|
final bool draggable; // 是否可拖拽 |
|
|
|
|
final bool droppable; // 是否可作为拖放目标 |
|
|
|
|
|
|
|
|
|
const TreeViewConfig({ |
|
|
|
|
this.lazyLoad = false, |
|
|
|
@ -34,6 +36,8 @@ class TreeViewConfig {
@@ -34,6 +36,8 @@ class TreeViewConfig {
|
|
|
|
|
this.icons = const {}, |
|
|
|
|
this.showRefreshButton = false, |
|
|
|
|
this.refreshIcon = Icons.refresh, |
|
|
|
|
this.draggable = false, |
|
|
|
|
this.droppable = false, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -98,26 +102,75 @@ class _TreeViewState extends State<TreeView> {
@@ -98,26 +102,75 @@ class _TreeViewState extends State<TreeView> {
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
return Stack( |
|
|
|
|
children: [ |
|
|
|
|
ListView.builder( |
|
|
|
|
controller: _effectiveController, |
|
|
|
|
physics: const ClampingScrollPhysics(), |
|
|
|
|
itemCount: _countVisibleNodes(widget.nodes), |
|
|
|
|
itemBuilder: (context, index) { |
|
|
|
|
final node = _getVisibleNode(widget.nodes, index); |
|
|
|
|
final isSelected = _selectedIds.contains(node.id); |
|
|
|
|
DragTarget<TreeNode>( |
|
|
|
|
onWillAcceptWithDetails: (data) { |
|
|
|
|
// 检查是否允许拖放 |
|
|
|
|
if (!widget.config.droppable) return false; |
|
|
|
|
return true; |
|
|
|
|
}, |
|
|
|
|
onAcceptWithDetails: (draggedNode) { |
|
|
|
|
// 处理拖拽放置逻辑 |
|
|
|
|
// 你可以在这里实现节点移动或排序的逻辑 |
|
|
|
|
|
|
|
|
|
debugPrint('Dropped ${draggedNode.data.name}'); |
|
|
|
|
}, |
|
|
|
|
builder: (context, candidateData, rejectedData) { |
|
|
|
|
return ListView.builder( |
|
|
|
|
controller: _effectiveController, |
|
|
|
|
physics: const ClampingScrollPhysics(), |
|
|
|
|
itemCount: _countVisibleNodes(widget.nodes), |
|
|
|
|
itemBuilder: (context, index) { |
|
|
|
|
final node = _getVisibleNode(widget.nodes, index); |
|
|
|
|
final isSelected = _selectedIds.contains(node.id); |
|
|
|
|
|
|
|
|
|
return widget.nodeBuilder != null |
|
|
|
|
? widget.nodeBuilder!(context, node, isSelected, () => _handleNodeTap(node)) |
|
|
|
|
: TreeNodeWidget( |
|
|
|
|
key: ValueKey(node.id), |
|
|
|
|
node: node, |
|
|
|
|
config: widget.config, |
|
|
|
|
isSelected: isSelected, |
|
|
|
|
isChecked: _checkedIds.contains(node.id), |
|
|
|
|
onTap: () => _handleNodeTap(node), |
|
|
|
|
onDoubleTap: () => widget.onNodeDoubleTap?.call(node), |
|
|
|
|
onCheckChanged: (value) => _handleNodeCheckChanged(node, value), |
|
|
|
|
); |
|
|
|
|
return widget.nodeBuilder != null |
|
|
|
|
? widget.nodeBuilder!(context, node, isSelected, () => _handleNodeTap(node)) |
|
|
|
|
: Draggable<TreeNode>( |
|
|
|
|
data: node, |
|
|
|
|
feedback: Material( |
|
|
|
|
child: Container( |
|
|
|
|
width: 200, |
|
|
|
|
padding: const EdgeInsets.all(8), |
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
color: Theme.of(context).cardColor, |
|
|
|
|
borderRadius: BorderRadius.circular(4), |
|
|
|
|
boxShadow: [ |
|
|
|
|
BoxShadow( |
|
|
|
|
color: Colors.black.withOpacity(0.2), |
|
|
|
|
blurRadius: 4, |
|
|
|
|
offset: const Offset(0, 2), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
child: Text(node.title), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
childWhenDragging: Opacity( |
|
|
|
|
opacity: 0.5, |
|
|
|
|
child: TreeNodeWidget( |
|
|
|
|
key: ValueKey(node.id), |
|
|
|
|
node: node, |
|
|
|
|
config: widget.config, |
|
|
|
|
isSelected: isSelected, |
|
|
|
|
isChecked: _checkedIds.contains(node.id), |
|
|
|
|
onTap: () => _handleNodeTap(node), |
|
|
|
|
onDoubleTap: () => widget.onNodeDoubleTap?.call(node), |
|
|
|
|
onCheckChanged: (value) => _handleNodeCheckChanged(node, value), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
child: TreeNodeWidget( |
|
|
|
|
key: ValueKey(node.id), |
|
|
|
|
node: node, |
|
|
|
|
config: widget.config, |
|
|
|
|
isSelected: isSelected, |
|
|
|
|
isChecked: _checkedIds.contains(node.id), |
|
|
|
|
onTap: () => _handleNodeTap(node), |
|
|
|
|
onDoubleTap: () => widget.onNodeDoubleTap?.call(node), |
|
|
|
|
onCheckChanged: (value) => _handleNodeCheckChanged(node, value), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
|
|
|
|
|