Home > Blockchain >  flutter: Make a column layout expand to fill the screen
flutter: Make a column layout expand to fill the screen

Time:09-04

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 enter image description here

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 the SingleChildScroolView gives me a blank screen.
  • Placing the SizedBox inside the SingleChildScroolView without a height constraint gives me a blank screen.
  • Placing an Expanded inside the SingleChildScroolView instead of the SizedBox 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 TreeNodes to the TreeView, I expect that the scroll can work automatically. But in reality, I get the offshoot errors as shown below.

enter image description here

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"),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

enter image description here

  • Related