This feels like a basic problem and I somehow made it work, but I'm not happy with my solution. So I hope someone can jump in and suggest a more elegant one.
Goal
I have a column-based layout derived from the
The code
The page in the above screenshot is rendered with the code below:
lib/trees/controller_usage.dart
import 'package:flutter/material.dart';
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
class ControllerUsage extends StatefulWidget {
@override
_ControllerUsageState createState() => _ControllerUsageState();
}
class _ControllerUsageState extends State<ControllerUsage> {
final Key _key = ValueKey(22);
final TreeController _controller = TreeController(allNodesExpanded: true);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 530,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
flex: 2,
child: SizedBox( // tight constraints
// height: 450,
width: 350,
child: buildTree(),
),
),
//TODO:
// - place a row of btns at the bottom
// const Spacer(),
Container(
color: Colors.amber,
child: Row(
children: [
ElevatedButton(
child: const Text("Unfold All"),
onPressed: () => setState(() {
_controller.expandAll();
}),
),
ElevatedButton(
child: const Text("Fold All"),
onPressed: () => setState(() {
_controller.collapseAll();
}),
),
ElevatedButton(
child: const Text("Unfold 22"),
onPressed: () => setState(() {
_controller.expandNode(_key);
}),
),
ElevatedButton(
child: const Text("Fold 22"),
onPressed: () => setState(() {
_controller.collapseNode(_key);
}),
),
],
),
),
],
),
);
}
Widget buildTree() {
return Expanded(
child: Container(
color: Colors.blueAccent,
child: TreeView(
treeController: _controller,
nodes: [
TreeNode(content: const Text("node 11")),
TreeNode(
content: const Icon(Icons.audiotrack),
children: [
TreeNode(content: const Text("node 21")),
TreeNode(
content: const Text("node 22"),
key: _key,
children: [
TreeNode(
content: const Icon(Icons.sentiment_very_satisfied),
),
],
),
TreeNode(
content: const Text("node 23"),
),
],
),
],
),
),
);
}
}
lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:url_launcher/url_launcher.dart';
import 'trees/controller_usage.dart';
import 'trees/tree_from_json.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('flutter_simple_treeview Demo'),
actions: [
TextButton(
child: const Text(
"Source Code",
style: TextStyle(color: Colors.white),
),
onPressed: () async => await launch(
'https://github.com/google/flutter.widgets/tree/master/packages/flutter_simple_treeview/example')),
],
bottom: const TabBar(
tabs: [
Tab(text: "Tree Controller Usage"),
Tab(text: "Tree From JSON"),
],
),
),
body: TabBarView(
children: [
buildBodyFrame(ControllerUsage()),
buildBodyFrame(TreeFromJson()),
],
),
),
),
);
}
/// Adds scrolling and padding to the [content].
Widget buildBodyFrame(Widget content) {
return Container(
color: Colors.green,
child: SingleChildScrollView(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.all(10),
child: content,
),
),
),
);
}
}
Implementation details and questions
To have the widgets fill the screen, I ended up using a SizedBox
inside the SingleChildScrollView
as a constraint. Other failed attempts:
- Placing the
SizedBox
with the same height constraint around theSingleChildScroolView
gives me a blank screen. - Placing the
SizedBox
inside theSingleChildScroolView
without a height constraint gives me a blank screen. - Placing an
Expanded
inside theSingleChildScroolView
instead of theSizedBox
gives me a blank screen as well.
Then I got a bad feeling about that magic nunber height constraint because then I'd have to adapt to different screens this way. I wish I could simply "expand" to the screen size without magic numbers or MediaQuery
. Is this possible?
To make the TreeView
"push" the button Row to the bottom, I had to use a Flexible
around the TreeView
. This feels goofy as well because it seems to break the code symmetry.
Another problem I found with the SingleChildScrollView
:
As I add more TreeNode
s to the TreeView
, I expect that the scroll can work automatically. But in reality, I get the offshoot errors as shown below.
I'd appreciate it if someone can suggest an improvement.
CodePudding user response:
In buildBodyFrame
, remove SingleChildScrollView
and add it to buildTree
then use LayoutBuilder
, to calculate remain size in column for tree.
App class:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 1,
child: Scaffold(
appBar: AppBar(
title: const Text('flutter_simple_treeview Demo'),
actions: [
TextButton(
child: const Text(
"Source Code",
style: TextStyle(color: Colors.white),
),
onPressed: () async {}),
],
bottom: const TabBar(
tabs: [
Tab(text: "Tree Controller Usage"),
// Tab(text: "Tree From JSON"),
],
),
),
body: TabBarView(
children: [
buildBodyFrame(ControllerUsage()),
// buildBodyFrame(TreeFromJson()),
],
),
),
),
);
}
/// Adds scrolling and padding to the [content].
Widget buildBodyFrame(Widget content) {
return Container(
padding: const EdgeInsets.all(10),
color: Colors.green,
child: content,
);
}
}
ControllerUsage class :
class ControllerUsage extends StatefulWidget {
@override
_ControllerUsageState createState() => _ControllerUsageState();
}
class _ControllerUsageState extends State<ControllerUsage> {
final Key _key = ValueKey(22);
final TreeController _controller = TreeController(allNodesExpanded: true);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(child: LayoutBuilder(
builder: (context, constraints) {
return Container(
height: constraints.maxHeight,
width: double.infinity,
child: buildTree());
},
)),
Container(
color: Colors.amber,
child: Row(
children: [
ElevatedButton(
child: const Text("Unfold All"),
onPressed: () => setState(() {
_controller.expandAll();
}),
),
ElevatedButton(
child: const Text("Fold All"),
onPressed: () => setState(() {
_controller.collapseAll();
}),
),
ElevatedButton(
child: const Text("Unfold 22"),
onPressed: () => setState(() {
_controller.expandNode(_key);
}),
),
ElevatedButton(
child: const Text("Fold 22"),
onPressed: () => setState(() {
_controller.collapseNode(_key);
}),
),
],
),
),
],
);
}
Widget buildTree() {
return SingleChildScrollView(
child: Container(
color: Colors.blueAccent,
child: TreeView(
treeController: _controller,
nodes: [
TreeNode(content: const Text("node 11")),
TreeNode(
content: const Icon(Icons.audiotrack),
children: [
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(content: const Text("node 21")),
TreeNode(
content: const Text("node 22"),
key: _key,
children: [
TreeNode(
content: const Icon(Icons.sentiment_very_satisfied),
),
],
),
TreeNode(
content: const Text("node 23"),
),
],
),
],
),
),
);
}
}