skip to Main Content

Gridview.count inside Column and wrapped by SingleChildScrollView crops button text due to shrinkWrap: true. Without shrinkWrap: true, flutter throws an error, shown bellow.

I’ve already tried changing physics: BouncingScrollPhysics() property to NeverScrollableScrollPhysics() inside Gridview.count but it throws the same error:

The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
Viewports expand in the scrolling direction to fill their container. In this case, a vertical
viewport was given an unlimited amount of vertical space in which to expand. This situation
typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because
there will always be enough vertical space for the children. In this case, consider using a Column
or Wrap instead. Otherwise, consider using a CustomScrollView to concatenate arbitrary slivers into
a single scrollable.

It’s pretty self-explanatory, but I’ve already tried creating a layout with rows and columns, but I cannot make them to get the same height and width of the biggest choice button (which is what I want). That’s why I tried using Gridview.count.

Obs.: Of course, I cannot give a fixed height to the Column because the text size inside the buttons is variable.

Here is the code snippet (look how text choice 4 is cropped):

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    super.key,
    required this.title,
  });

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _controller = ScrollController();

  List<Widget> choiceButtons = [
    OutlinedButton(
      onPressed: () {},
      child: Text('choice 1: this size this size this size'),
    ),
    OutlinedButton(
      onPressed: () {},
      child: Text(
          'choice 2: this size this size this size this size this size this size'),
    ),
    OutlinedButton(
      onPressed: () {},
      child: Text('choice 3: this size'),
    ),
    OutlinedButton(
      onPressed: () {},
      child: Text(
          'choice 4: this size this size this size this size this size this size this size this size this size this size this size '),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SafeArea(
        child: Scrollbar(
          child: SingleChildScrollView(
            controller: _controller,
            child: SizedBox(
              width: 400,
              child: Column(
                children: [
                  Text('test'),
                  Center(
                    child: GridView.count(
                      physics: BouncingScrollPhysics(),
                      scrollDirection: Axis.vertical,
                      shrinkWrap: true,
                      crossAxisCount: 2,
                      childAspectRatio: 1 / .4,
                      mainAxisSpacing: 2,
                      children: List.generate(choiceButtons.length, (index) {
                        return Padding(
                          padding: const EdgeInsets.all(10.0),
                          child: choiceButtons[index],
                        );
                      }),
                    ),
                  ),
                  Text('test'),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}


What could I do?

2

Answers


  1. You probably have to manually layout children in the render layer in this case.

    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'dart:math';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorSchemeSeed: Colors.blue,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      final String title;
    
      const MyHomePage({
        super.key,
        required this.title,
      });
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final _controller = ScrollController();
    
      List<Widget> choiceButtons = [
        OutlinedButton(
          onPressed: () {},
          child: Text('choice 1: this size this size this size'),
        ),
        OutlinedButton(
          onPressed: () {},
          child: Text('choice 2: this size this size this size this size this size this size'),
        ),
        OutlinedButton(
          onPressed: () {},
          child: Text('choice 3: this size'),
        ),
        OutlinedButton(
          onPressed: () {},
          child: Text(
              'choice 4: this size this size this size this size this size this size this size this size this size this size this size '),
        ),
      ];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: SafeArea(
            child: Scrollbar(
              child: SingleChildScrollView(
                controller: _controller,
                child: SizedBox(
                  width: 400,
                  child: Column(
                    children: [
                      Text('test'),
                      Center(
                        child: MyGridLayout(
                          children: List.generate(choiceButtons.length, (index) {
                            return Padding(
                              padding: const EdgeInsets.all(10.0),
                              child: choiceButtons[index],
                            );
                          }),
                        ),
                      ),
                      Text('test'),
                    ],
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class MyGridLayout extends MultiChildRenderObjectWidget {
      const MyGridLayout({
        super.key,
        super.children,
      });
    
      @override
      RenderObject createRenderObject(BuildContext context) {
        return MyGridLayoutRenderObject();
      }
    }
    
    class MyGridLayoutRenderObject extends RenderBox
        with
            ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
            RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
      @override
      void setupParentData(RenderBox child) {
        if (child.parentData is! MultiChildLayoutParentData) {
          child.parentData = MultiChildLayoutParentData();
        }
      }
    
      @override
      void performLayout() {
        double maxHeight = 0;
        RenderBox? child = firstChild;
        while (child != null) {
          child.layout(
              BoxConstraints(maxWidth: constraints.maxWidth / 2, maxHeight: constraints.maxHeight / 2),
              parentUsesSize: true);
          maxHeight = max(maxHeight, child.size.height);
          child = childAfter(child);
        }
        int column = 0;
        int row = 0;
        child = firstChild;
        while (child != null) {
          final childParentData = child.parentData as MultiChildLayoutParentData;
          if (column == 2) {
            column = 0;
            row++;
          }
          childParentData.offset = Offset(
            column * constraints.maxWidth / 2,
            row * maxHeight + (maxHeight - child.size.height) / 2,
          );
          column++;
          child = childAfter(child);
        }
        size = Size(constraints.maxWidth, maxHeight * (row + 1));
      }
    
      @override
      void paint(PaintingContext context, Offset offset) {
        defaultPaint(context, offset);
      }
    }
    

    Or use Boxy library if you want to reduce some boilerplate

    import 'package:boxy/boxy.dart';
    
    class MyBoxyGridLayout extends StatelessWidget {
      const MyBoxyGridLayout({
        super.key,
        required this.children,
      });
    
      final List<Widget> children;
    
      @override
      Widget build(BuildContext context) {
        return CustomBoxy(
          delegate: MyGridLayoutDelegate(),
          children: children,
        );
      }
    }
    
    class MyGridLayoutDelegate extends BoxyDelegate {
      @override
      Size layout() {
        double maxHeight = 0;
        for (BoxyChild child in children) {
          child.layout(
              BoxConstraints(maxWidth: constraints.maxWidth / 2, maxHeight: constraints.maxHeight / 2));
          maxHeight = max(maxHeight, child.size.height);
        }
        int column = 0;
        int row = 0;
        for (BoxyChild child in children) {
          if (column == 2) {
            column = 0;
            row++;
          }
          child.position(Offset(
            column * constraints.maxWidth / 2,
            row * maxHeight + (maxHeight - child.size.height) / 2,
          ));
          column++;
        }
        return Size(constraints.maxWidth, maxHeight * (row + 1));
      }
    }
    
    Login or Signup to reply.
  2. Just remove childAspectRatio: 1 / .4, and its working.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search