Skip to content

Commit 9b6813f

Browse files
authored
Reland TreeSliver (#149839)
Reland of flutter/flutter#147171 It was reverted for a failing unit test that had passed in presubmit. Hopefully this sticks better. Also fixed leaks in flutter/flutter@37b10ad
1 parent d86448f commit 9b6813f

File tree

11 files changed

+3332
-0
lines changed

11 files changed

+3332
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code sample for [TreeSliver].
8+
9+
void main() => runApp(const TreeSliverExampleApp());
10+
11+
class TreeSliverExampleApp extends StatelessWidget {
12+
const TreeSliverExampleApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return const MaterialApp(
17+
home: TreeSliverExample(),
18+
);
19+
}
20+
}
21+
22+
class TreeSliverExample extends StatefulWidget {
23+
const TreeSliverExample({super.key});
24+
25+
@override
26+
State<TreeSliverExample> createState() => _TreeSliverExampleState();
27+
}
28+
29+
class _TreeSliverExampleState extends State<TreeSliverExample> {
30+
TreeSliverNode<String>? _selectedNode;
31+
final TreeSliverController controller = TreeSliverController();
32+
final List<TreeSliverNode<String>> _tree = <TreeSliverNode<String>>[
33+
TreeSliverNode<String>('First'),
34+
TreeSliverNode<String>(
35+
'Second',
36+
children: <TreeSliverNode<String>>[
37+
TreeSliverNode<String>(
38+
'alpha',
39+
children: <TreeSliverNode<String>>[
40+
TreeSliverNode<String>('uno'),
41+
TreeSliverNode<String>('dos'),
42+
TreeSliverNode<String>('tres'),
43+
],
44+
),
45+
TreeSliverNode<String>('beta'),
46+
TreeSliverNode<String>('kappa'),
47+
],
48+
),
49+
TreeSliverNode<String>(
50+
'Third',
51+
expanded: true,
52+
children: <TreeSliverNode<String>>[
53+
TreeSliverNode<String>('gamma'),
54+
TreeSliverNode<String>('delta'),
55+
TreeSliverNode<String>('epsilon'),
56+
],
57+
),
58+
TreeSliverNode<String>('Fourth'),
59+
];
60+
61+
@override
62+
Widget build(BuildContext context) {
63+
return Scaffold(
64+
appBar: AppBar(
65+
title: const Text('TreeSliver Demo'),
66+
),
67+
body: CustomScrollView(
68+
slivers: <Widget>[
69+
TreeSliver<String>(
70+
tree: _tree,
71+
controller: controller,
72+
treeNodeBuilder: (
73+
BuildContext context,
74+
TreeSliverNode<Object?> node,
75+
AnimationStyle animationStyle,
76+
) {
77+
Widget child = GestureDetector(
78+
behavior: HitTestBehavior.translucent,
79+
onTap: () {
80+
setState(() {
81+
controller.toggleNode(node);
82+
_selectedNode = node as TreeSliverNode<String>;
83+
});
84+
},
85+
child: TreeSliver.defaultTreeNodeBuilder(
86+
context,
87+
node,
88+
animationStyle,
89+
),
90+
);
91+
if (_selectedNode == node as TreeSliverNode<String>) {
92+
child = ColoredBox(
93+
color: Colors.purple[100]!,
94+
child: child,
95+
);
96+
}
97+
return child;
98+
},
99+
),
100+
],
101+
),
102+
);
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter/rendering.dart';
7+
8+
/// Flutter code sample for [TreeSliver].
9+
10+
void main() => runApp(const TreeSliverExampleApp());
11+
12+
class TreeSliverExampleApp extends StatelessWidget {
13+
const TreeSliverExampleApp({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return const MaterialApp(
18+
home: TreeSliverExample(),
19+
);
20+
}
21+
}
22+
23+
class TreeSliverExample extends StatefulWidget {
24+
const TreeSliverExample({super.key});
25+
26+
@override
27+
State<TreeSliverExample> createState() => _TreeSliverExampleState();
28+
}
29+
30+
class _TreeSliverExampleState extends State<TreeSliverExample> {
31+
TreeSliverNode<String>? _selectedNode;
32+
final List<TreeSliverNode<String>> tree = <TreeSliverNode<String>>[
33+
TreeSliverNode<String>('README.md'),
34+
TreeSliverNode<String>('analysis_options.yaml'),
35+
TreeSliverNode<String>(
36+
'lib',
37+
children: <TreeSliverNode<String>>[
38+
TreeSliverNode<String>(
39+
'src',
40+
children: <TreeSliverNode<String>>[
41+
TreeSliverNode<String>(
42+
'widgets',
43+
children: <TreeSliverNode<String>>[
44+
TreeSliverNode<String>('about.dart.dart'),
45+
TreeSliverNode<String>('app.dart'),
46+
TreeSliverNode<String>('basic.dart'),
47+
TreeSliverNode<String>('constants.dart'),
48+
],
49+
),
50+
],
51+
),
52+
TreeSliverNode<String>('widgets.dart'),
53+
],
54+
),
55+
TreeSliverNode<String>('pubspec.lock'),
56+
TreeSliverNode<String>('pubspec.yaml'),
57+
TreeSliverNode<String>(
58+
'test',
59+
children: <TreeSliverNode<String>>[
60+
TreeSliverNode<String>(
61+
'widgets',
62+
children: <TreeSliverNode<String>>[
63+
TreeSliverNode<String>('about_test.dart'),
64+
TreeSliverNode<String>('app_test.dart'),
65+
TreeSliverNode<String>('basic_test.dart'),
66+
TreeSliverNode<String>('constants_test.dart'),
67+
],
68+
),
69+
],
70+
),
71+
];
72+
73+
Widget _treeNodeBuilder(
74+
BuildContext context,
75+
TreeSliverNode<Object?> node,
76+
AnimationStyle toggleAnimationStyle,
77+
) {
78+
final bool isParentNode = node.children.isNotEmpty;
79+
final BorderSide border = BorderSide(
80+
width: 2,
81+
color: Colors.purple[300]!,
82+
);
83+
return TreeSliver.wrapChildToToggleNode(
84+
node: node,
85+
child: Row(
86+
children: <Widget>[
87+
// Custom indentation
88+
SizedBox(width: 10.0 * node.depth! + 8.0),
89+
DecoratedBox(
90+
decoration: BoxDecoration(
91+
border: node.parent != null
92+
? Border(left: border, bottom: border)
93+
: null,
94+
),
95+
child: const SizedBox(height: 50.0, width: 20.0),
96+
),
97+
// Leading icon for parent nodes
98+
if (isParentNode)
99+
DecoratedBox(
100+
decoration: BoxDecoration(border: Border.all()),
101+
child: SizedBox.square(
102+
dimension: 20.0,
103+
child: Icon(
104+
node.isExpanded ? Icons.remove : Icons.add,
105+
size: 14,
106+
),
107+
),
108+
),
109+
// Spacer
110+
const SizedBox(width: 8.0),
111+
// Content
112+
Text(node.content.toString()),
113+
],
114+
),
115+
);
116+
}
117+
118+
Widget _getTree() {
119+
return DecoratedSliver(
120+
decoration: BoxDecoration( border: Border.all()),
121+
sliver: TreeSliver<String>(
122+
tree: tree,
123+
onNodeToggle: (TreeSliverNode<Object?> node) {
124+
setState(() {
125+
_selectedNode = node as TreeSliverNode<String>;
126+
});
127+
},
128+
treeNodeBuilder: _treeNodeBuilder,
129+
treeRowExtentBuilder: (
130+
TreeSliverNode<Object?> node,
131+
SliverLayoutDimensions layoutDimensions,
132+
) {
133+
// This gives more space to parent nodes.
134+
return node.children.isNotEmpty ? 60.0 : 50.0;
135+
},
136+
// No internal indentation, the custom treeNodeBuilder applies its
137+
// own indentation to decorate in the indented space.
138+
indentation: TreeSliverIndentationType.none,
139+
),
140+
);
141+
}
142+
143+
@override
144+
Widget build(BuildContext context) {
145+
// This example is assumes the full screen is available.
146+
final Size screenSize = MediaQuery.sizeOf(context);
147+
final List<Widget> selectedChildren = <Widget>[];
148+
if (_selectedNode != null) {
149+
selectedChildren.addAll(<Widget>[
150+
const Spacer(),
151+
Icon(
152+
_selectedNode!.children.isEmpty
153+
? Icons.file_open_outlined
154+
: Icons.folder_outlined,
155+
),
156+
const SizedBox(height: 16.0),
157+
Text(_selectedNode!.content),
158+
const Spacer(),
159+
]);
160+
}
161+
return Scaffold(
162+
body: Row(children: <Widget>[
163+
SizedBox(
164+
width: screenSize.width / 2,
165+
height: double.infinity,
166+
child: CustomScrollView(
167+
slivers: <Widget>[
168+
_getTree(),
169+
],
170+
),
171+
),
172+
DecoratedBox(
173+
decoration: BoxDecoration(
174+
border: Border.all(),
175+
),
176+
child: SizedBox(
177+
width: screenSize.width / 2,
178+
height: double.infinity,
179+
child: Center(
180+
child: Column(
181+
children: selectedChildren,
182+
),
183+
),
184+
),
185+
),
186+
]),
187+
);
188+
}
189+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_api_samples/widgets/sliver/sliver_tree.0.dart'
6+
as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Can toggle nodes in TreeSliver', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const example.TreeSliverExampleApp(),
13+
);
14+
expect(find.text('Second'), findsOneWidget);
15+
expect(find.text('alpha'), findsNothing);
16+
// Toggle tree node.
17+
await tester.tap(find.text('Second'));
18+
await tester.pumpAndSettle();
19+
expect(find.text('alpha'), findsOneWidget);
20+
});
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_api_samples/widgets/sliver/sliver_tree.1.dart'
6+
as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Can toggle nodes in TreeSliver', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const example.TreeSliverExampleApp(),
13+
);
14+
expect(find.text('lib'), findsOneWidget);
15+
expect(find.text('src'), findsNothing);
16+
// Toggle tree node.
17+
await tester.tap(find.text('lib'));
18+
await tester.pumpAndSettle();
19+
expect(find.text('src'), findsOneWidget);
20+
});
21+
}

packages/flutter/lib/rendering.dart

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export 'src/rendering/sliver_list.dart';
6767
export 'src/rendering/sliver_multi_box_adaptor.dart';
6868
export 'src/rendering/sliver_padding.dart';
6969
export 'src/rendering/sliver_persistent_header.dart';
70+
export 'src/rendering/sliver_tree.dart';
7071
export 'src/rendering/stack.dart';
7172
export 'src/rendering/table.dart';
7273
export 'src/rendering/table_border.dart';

packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
88
import 'box.dart';
99
import 'object.dart';
1010
import 'sliver.dart';
11+
import 'sliver_fixed_extent_list.dart';
1112

1213
/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
1314
///

0 commit comments

Comments
 (0)