Skip to content

Commit

Permalink
experiment came out great
Browse files Browse the repository at this point in the history
the only gotcha is when a node is expanded while it is collapsing; all
descendants are shown twice temporarily.
  • Loading branch information
baumths committed May 3, 2024
1 parent 000fcb4 commit 8a255a7
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 1 deletion.
6 changes: 5 additions & 1 deletion example/lib/src/examples.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart';
import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'
hide AnimatedTreeView;
import 'package:path_drawing/path_drawing.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'examples/animated.dart' show AnimatedTreeView;
import 'examples/drag_and_drop.dart' show DragAndDropTreeView;
import 'examples/filterable.dart' show FilterableTreeView;
import 'examples/lazy_loading.dart' show LazyLoadingTreeView;
Expand Down Expand Up @@ -33,6 +35,7 @@ class SelectedExampleNotifier extends ValueNotifier<Example> {
}

enum Example {
animated('Animated', Icon(Icons.animation)),
dragAndDrop('Drag and Drop', Icon(Icons.move_down_rounded)),
filterable('Filterable', Icon(Icons.manage_search_rounded)),
lazyLoading('Lazy Loading', Icon(Icons.hourglass_top_rounded)),
Expand All @@ -58,6 +61,7 @@ class ExamplesView extends StatelessWidget {
child: TreeIndentGuideScope(
key: Key(selectedExample.title),
child: switch (selectedExample) {
Example.animated => const AnimatedTreeView(),
Example.dragAndDrop => const DragAndDropTreeView(),
Example.filterable => const FilterableTreeView(),
Example.lazyLoading => const LazyLoadingTreeView(),
Expand Down
192 changes: 192 additions & 0 deletions example/lib/src/examples/animated.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import 'package:flutter/material.dart';
import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'
hide SliverAnimatedTree, AnimatedTreeView;

import '../tree_data.dart' show generateTreeNodes;

class Node {
Node({required this.title}) : children = <Node>[];

final String title;
final List<Node> children;
}

class AnimatedTreeView extends StatefulWidget {
const AnimatedTreeView({super.key});

@override
State<AnimatedTreeView> createState() => _AnimatedTreeViewState();
}

class _AnimatedTreeViewState extends State<AnimatedTreeView> {
late final TreeController<Node> treeController;
late final Node root = Node(title: 'A portion of the world');

@override
void initState() {
super.initState();
generateTreeNodes(root, (Node parent, String title) {
final child = Node(title: title);
parent.children.add(child);
return child;
});

treeController = TreeController<Node>(
roots: root.children,
childrenProvider: (Node node) => node.children,
);
}

@override
void dispose() {
treeController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverAnimatedTree<Node>(
treeController: treeController,
duration: Durations.long2,
nodeBuilder: (BuildContext context, TreeEntry<Node> entry) {
return TreeIndentation(
entry: entry,
child: Row(
children: [
FolderButton(
key: Key('FolderButton#${entry.node.title}'),
isOpen: entry.isExpanded,
onPressed: () => treeController.toggleExpansion(entry.node),
),
Flexible(child: Text(entry.node.title)),
],
),
);
},
),
],
);
}
}

class SliverAnimatedTree<T extends Object> extends StatefulWidget {
const SliverAnimatedTree({
super.key,
required this.treeController,
required this.nodeBuilder,
this.duration = Durations.medium2,
this.transitionBuilder = defaultTreeTransitionBuilder,
});

final TreeController<T> treeController;
final TreeNodeBuilder<T> nodeBuilder;
final Duration duration;
final TreeTransitionBuilder transitionBuilder;

@override
State<SliverAnimatedTree<T>> createState() => _SliverAnimatedTreeState<T>();
}

class _SliverAnimatedTreeState<T extends Object>
extends State<SliverAnimatedTree<T>> {
final GlobalKey<SliverAnimatedListState> _listKey =
GlobalKey<SliverAnimatedListState>();

late Map<T, TreeEntry<T>> _nodeToEntry = <T, TreeEntry<T>>{};
List<TreeEntry<T>> _flatTree = const [];

void _createFlatTree() {
final Map<T, TreeEntry<T>> newEntries = <T, TreeEntry<T>>{};
final List<TreeEntry<T>> flatTree = <TreeEntry<T>>[];

widget.treeController.depthFirstTraversal(onTraverse: (TreeEntry<T> entry) {
flatTree.add(entry);
newEntries[entry.node] = entry;
});

_flatTree = flatTree;
_nodeToEntry = newEntries;
}

void _updateFlatTree() {
if (widget.duration == Duration.zero) {
setState(_createFlatTree);
return;
}

final Map<T, TreeEntry<T>> oldEntries = <T, TreeEntry<T>>{..._nodeToEntry};
final Map<T, TreeEntry<T>> newEntries = <T, TreeEntry<T>>{};
final List<int> indicesAnimatingIn = <int>[];
final List<TreeEntry<T>> flatTree = <TreeEntry<T>>[];

widget.treeController.depthFirstTraversal(onTraverse: (TreeEntry<T> entry) {
flatTree.add(entry);
newEntries[entry.node] = entry;

if (oldEntries.remove(entry.node) == null) {
indicesAnimatingIn.add(entry.index);
}
});

for (final TreeEntry<T> entry in oldEntries.values.toList().reversed) {
_listKey.currentState?.removeItem(
duration: widget.duration,
entry.index,
(BuildContext context, Animation<double> animation) {
return widget.transitionBuilder(
context,
widget.nodeBuilder(context, entry),
animation,
);
},
);
}

setState(() {
_flatTree = flatTree;
_nodeToEntry = newEntries;
});

for (final int index in indicesAnimatingIn) {
_listKey.currentState?.insertItem(index, duration: widget.duration);
}
}

void _rebuild() => _updateFlatTree();

@override
void initState() {
super.initState();
widget.treeController.addListener(_rebuild);
_createFlatTree();
}

@override
void dispose() {
_flatTree = const [];
_nodeToEntry = const {};
widget.treeController.removeListener(_rebuild);
super.dispose();
}

@override
Widget build(BuildContext context) {
return SliverAnimatedList(
key: _listKey,
initialItemCount: _flatTree.length,
itemBuilder: (
BuildContext context,
int index,
Animation<double> animation,
) {
return widget.transitionBuilder(
context,
widget.nodeBuilder(context, _flatTree[index]),
animation,
);
},
);
}
}

0 comments on commit 8a255a7

Please sign in to comment.