Skip to content

Commit b83b63c

Browse files
authored
[two_dimensional_scrollables] TreeView (flutter#6592)
1 parent 7d0f88f commit b83b63c

38 files changed

+5812
-185
lines changed

packages/two_dimensional_scrollables/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.3.0
2+
3+
* Adds new TreeView widget and associated classes.
4+
* New example application for exploring both Trees and Tables.
5+
16
## 0.2.1
27

38
* Refactors TableSpans to use basic Span classes. Clean up for incoming TreeView.

packages/two_dimensional_scrollables/README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,30 @@ two-dimensional foundation of the Flutter framework.
55

66
## Features
77

8-
This package provides support for a TableView widget that scrolls in both the
9-
vertical and horizontal axes.
8+
This package provides support for TableView and TreeView widgets that scroll
9+
in both the vertical and horizontal axes.
1010

1111
### TableView
1212

1313
`TableView` is a subclass of `TwoDimensionalScrollView`, building its provided
1414
children lazily in a `TwoDimensionalViewport`. This widget can
1515

1616
- Scroll diagonally, or lock axes
17+
- Build infinite rows and columns
1718
- Apply decorations to rows and columns
1819
- Handle gestures & custom pointers for rows and columns
1920
- Pin rows and columns
21+
- Merge table cells
22+
23+
### TreeView
24+
25+
`TreeView` is a subclass of `TwoDimensionalScrollView`, building its provided
26+
children lazily in a `TwoDimensionalViewport`. This widget can
27+
28+
- Scroll diagonally, or lock axes
29+
- Apply decorations to tree rows
30+
- Handle gestures & custom pointers for tree rows
31+
- Animate TreeViewNodes in and out of view
2032

2133
## Getting started
2234

@@ -40,12 +52,19 @@ import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart';
4052

4153
### TableView
4254

43-
The code in `example/` shows a `TableView` of initially 400 cells, each varying
44-
in sizes with a few `TableSpanDecoration`s like background colors and borders. The
45-
`builder` constructor is called on demand for the cells that are visible in the
46-
TableView. Additional rows can be added on demand while the vertical position
47-
can jump between the first and last row using the buttons at the bottom of the
48-
screen.
55+
The code in `example/lib/table_view` has three `TableView` samples, each
56+
showcasing different features. The `TableExample` demonstrates adding and
57+
removing rows from the table, and applying `TableSpanDecoration`s. The
58+
`MergedTableExample` demonstrates pinned and merged `TableViewCell`s.
59+
Lastly, the `InfiniteTableExample` demonstrates an infinite `TableView`.
60+
61+
### TreeView
62+
63+
The code in `example/lib/tree_view` has two `TreeView` samples, each
64+
showcasing different features. The `TreeExample` demonstrates most of
65+
the default builders and animations. The `CustomTreeExample` demonstrates
66+
a highly customized tree, utilizing `TreeView.treeNodeBuilder`,
67+
`TreeView.treeRowBuilder` and `TreeView.onNodeToggle`.
4968

5069
## Changelog
5170

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# TODO(Piinks): Remove once https://github.com/flutter/flutter/pull/147202 reaches stable
2+
buildFlags:
3+
global:
4+
- "--no-tree-shake-icons"
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# TableView Example
1+
# TableView and TreeView Examples
22

3-
A sample application that utilizes the TableView API.
3+
A sample application that utilizes the TableView and TreeView APIs.

packages/two_dimensional_scrollables/example/lib/main.dart

Lines changed: 40 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -2,189 +2,70 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'package:flutter/gestures.dart';
65
import 'package:flutter/material.dart';
7-
import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart';
86

9-
// Print statements are only for illustrative purposes, not recommended for
10-
// production applications.
11-
// ignore_for_file: avoid_print
7+
import 'table_view/table_explorer.dart';
8+
import 'tree_view/tree_explorer.dart';
129

1310
void main() {
14-
runApp(const TableExampleApp());
11+
runApp(const ExampleApp());
1512
}
1613

17-
/// A sample application that utilizes the TableView API.
18-
class TableExampleApp extends StatelessWidget {
19-
/// Creates an instance of the TableView example app.
20-
const TableExampleApp({super.key});
14+
/// A sample application that utilizes the TableView and TreeView APIs.
15+
class ExampleApp extends StatelessWidget {
16+
/// Creates an instance of the example app.
17+
const ExampleApp({super.key});
2118

2219
@override
2320
Widget build(BuildContext context) {
2421
return MaterialApp(
25-
title: 'Table Example',
22+
title: '2D Scrolling Examples',
2623
theme: ThemeData(
27-
useMaterial3: true,
24+
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
25+
appBarTheme: AppBarTheme(backgroundColor: Colors.purple[50]),
2826
),
29-
home: const TableExample(),
27+
home: const ExampleHome(),
28+
routes: <String, WidgetBuilder>{
29+
'/table': (BuildContext context) => const TableExplorer(),
30+
'/tree': (BuildContext context) => const TreeExplorer(),
31+
},
3032
);
3133
}
3234
}
3335

34-
/// The class containing the TableView for the sample application.
35-
class TableExample extends StatefulWidget {
36+
/// The home page of the application, which directs to the tree or table
37+
/// explorer.
38+
class ExampleHome extends StatelessWidget {
3639
/// Creates a screen that demonstrates the TableView widget.
37-
const TableExample({super.key});
38-
39-
@override
40-
State<TableExample> createState() => _TableExampleState();
41-
}
42-
43-
class _TableExampleState extends State<TableExample> {
44-
late final ScrollController _verticalController = ScrollController();
45-
int _rowCount = 20;
40+
const ExampleHome({super.key});
4641

4742
@override
4843
Widget build(BuildContext context) {
4944
return Scaffold(
5045
appBar: AppBar(
51-
title: const Text('Table Example'),
52-
),
53-
body: Padding(
54-
padding: const EdgeInsets.symmetric(horizontal: 50),
55-
child: TableView.builder(
56-
verticalDetails:
57-
ScrollableDetails.vertical(controller: _verticalController),
58-
cellBuilder: _buildCell,
59-
columnCount: 20,
60-
columnBuilder: _buildColumnSpan,
61-
rowCount: _rowCount,
62-
rowBuilder: _buildRowSpan,
63-
),
64-
),
65-
persistentFooterButtons: <Widget>[
66-
TextButton(
67-
onPressed: () {
68-
_verticalController.jumpTo(0);
69-
},
70-
child: const Text('Jump to Top'),
71-
),
72-
TextButton(
73-
onPressed: () {
74-
_verticalController
75-
.jumpTo(_verticalController.position.maxScrollExtent);
76-
},
77-
child: const Text('Jump to Bottom'),
78-
),
79-
TextButton(
80-
onPressed: () {
81-
setState(() {
82-
_rowCount += 10;
83-
});
84-
},
85-
child: const Text('Add 10 Rows'),
86-
),
87-
],
88-
);
89-
}
90-
91-
TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) {
92-
return TableViewCell(
93-
child: Center(
94-
child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'),
46+
title: const Text('Tables & Trees'),
9547
),
96-
);
97-
}
98-
99-
TableSpan _buildColumnSpan(int index) {
100-
const TableSpanDecoration decoration = TableSpanDecoration(
101-
border: TableSpanBorder(
102-
trailing: BorderSide(),
48+
body: Center(
49+
child: Column(children: <Widget>[
50+
const Spacer(flex: 3),
51+
FilledButton(
52+
onPressed: () {
53+
// Go to table explorer
54+
Navigator.of(context).pushNamed('/table');
55+
},
56+
child: const Text('TableView Explorer'),
57+
),
58+
const Spacer(),
59+
FilledButton(
60+
onPressed: () {
61+
// Go to tree explorer
62+
Navigator.of(context).pushNamed('/tree');
63+
},
64+
child: const Text('TreeView Explorer'),
65+
),
66+
const Spacer(flex: 3),
67+
]),
10368
),
10469
);
105-
106-
switch (index % 5) {
107-
case 0:
108-
return TableSpan(
109-
foregroundDecoration: decoration,
110-
extent: const FixedTableSpanExtent(100),
111-
onEnter: (_) => print('Entered column $index'),
112-
recognizerFactories: <Type, GestureRecognizerFactory>{
113-
TapGestureRecognizer:
114-
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
115-
() => TapGestureRecognizer(),
116-
(TapGestureRecognizer t) =>
117-
t.onTap = () => print('Tap column $index'),
118-
),
119-
},
120-
);
121-
case 1:
122-
return TableSpan(
123-
foregroundDecoration: decoration,
124-
extent: const FractionalTableSpanExtent(0.5),
125-
onEnter: (_) => print('Entered column $index'),
126-
cursor: SystemMouseCursors.contextMenu,
127-
);
128-
case 2:
129-
return TableSpan(
130-
foregroundDecoration: decoration,
131-
extent: const FixedTableSpanExtent(120),
132-
onEnter: (_) => print('Entered column $index'),
133-
);
134-
case 3:
135-
return TableSpan(
136-
foregroundDecoration: decoration,
137-
extent: const FixedTableSpanExtent(145),
138-
onEnter: (_) => print('Entered column $index'),
139-
);
140-
case 4:
141-
return TableSpan(
142-
foregroundDecoration: decoration,
143-
extent: const FixedTableSpanExtent(200),
144-
onEnter: (_) => print('Entered column $index'),
145-
);
146-
}
147-
throw AssertionError(
148-
'This should be unreachable, as every index is accounted for in the switch clauses.');
149-
}
150-
151-
TableSpan _buildRowSpan(int index) {
152-
final TableSpanDecoration decoration = TableSpanDecoration(
153-
color: index.isEven ? Colors.purple[100] : null,
154-
border: const TableSpanBorder(
155-
trailing: BorderSide(
156-
width: 3,
157-
),
158-
),
159-
);
160-
161-
switch (index % 3) {
162-
case 0:
163-
return TableSpan(
164-
backgroundDecoration: decoration,
165-
extent: const FixedTableSpanExtent(50),
166-
recognizerFactories: <Type, GestureRecognizerFactory>{
167-
TapGestureRecognizer:
168-
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
169-
() => TapGestureRecognizer(),
170-
(TapGestureRecognizer t) =>
171-
t.onTap = () => print('Tap row $index'),
172-
),
173-
},
174-
);
175-
case 1:
176-
return TableSpan(
177-
backgroundDecoration: decoration,
178-
extent: const FixedTableSpanExtent(65),
179-
cursor: SystemMouseCursors.click,
180-
);
181-
case 2:
182-
return TableSpan(
183-
backgroundDecoration: decoration,
184-
extent: const FractionalTableSpanExtent(0.15),
185-
);
186-
}
187-
throw AssertionError(
188-
'This should be unreachable, as every index is accounted for in the switch clauses.');
18970
}
19071
}

0 commit comments

Comments
 (0)