Flutter - Non-scrollable Grid
Asked Answered
T

5

25

Is there a way to build a grid that's not scrollable in itself and which size is according to its children, the same way we can specify in a row or column mainAxisSize: MainAxisSize.min?

To give you the big picture -

I'm trying to create a responsive layout that depends on the device's width. It should be split into 2 parts, connected seamlessly via a column.

1) 2 big containers which sizes depend on the screen width, taking into account a small space in between them. Each container's width and height will be the same (square containers).

2) Same idea, but instead have 3 rows, each consisting of 3 smaller containers. This creates a grid. It's very important though that the grid won't be scrollable in itself and that its size will be according to its children. It should only be scrolled together with the rest of the page that's contained in a SingleChildScrollView.

Especially since each container's height needs to be the same as its width, I was thinking of going with a combination of rows, columns, and LayoutBuilder - they gives me all the capabilities I need.

However, before doing things manually, I was wondering if there's something that could work out of the box.

enter image description here

Tisiphone answered 5/10, 2019 at 12:57 Comment(0)
C
46

Something like this?

SingleChildScrollView(
  child: Column(
    children: <Widget>[
      Row(
        children: <Widget>[
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: AspectRatio(
                aspectRatio: 1.0,
                child: Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    border: Border.all(width: 3.0, color: Colors.green),
                  ),
                ),
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: AspectRatio(
                aspectRatio: 1.0,
                child: Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    border: Border.all(width: 3.0, color: Colors.green),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
      Container(
        padding: const EdgeInsets.all(10.0),
        child: GridView.builder(
          physics: NeverScrollableScrollPhysics(),
          shrinkWrap: true,
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 1.0,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
          ),
          itemCount: 21,
          itemBuilder: (context, index) {
            return Container(
              decoration: BoxDecoration(
                border: Border.all(width: 3.0),
              ),
            );
          },
        ),
      ),
    ],
  ),
)
Chaschase answered 5/10, 2019 at 14:5 Comment(3)
Thanks you very much man. I lost almost 10hrs to make this ....... Thanks again mate.Letters
@Letters I've spent 6 hours trying to figure this out lolHomologous
"...... lost almost 10 hrs", "......spent 6hrs..." smh, I love programmingCobb
B
7

For my case, I use **

NeverScrollableScrollPhysics()

**

GridView.builder(
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2,
    ),
    itemCount: listPaymentMethods.length,
    primary: false,
    physics: const NeverScrollableScrollPhysics(),
    itemBuilder: (context, index) {
      final paymentMethod = listPaymentMethods[index];
      bool isSelected = paymentMethod.code == selectedPaymentMethod?.code;
      return PaymentMethodItemWidget(
          paymentMethod: paymentMethod,
          onPaymentMethodChanged: onPaymentMethodChanged,
          isSelected: isSelected);
    });
Brandie answered 6/10, 2022 at 3:3 Comment(0)
N
5

The table widget might be an easier way to do this than GridView (janstol's answer) as it's purpose built for what you're looking for. Though this doesn't apply to your case, Table is especially useful if your widgets can be different sizes-it'll align widgets vertically and horizontally relative to the widest/tallest widget in each column/row (respectively).

Here's janstol's solution code rewritten to use Table. Note that Table requires all rows to be of the same length, so your first two element row needs to be outside of the table.

Widget bigBox = Expanded(
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: AspectRatio(
                aspectRatio: 1.0,
                child: Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    border: Border.all(width: 3.0, color: Colors.green),
                  ),
                ),
              ),
            ),
          );
          Widget smallBox = Padding(
              padding: const EdgeInsets.all(10.0),
              child: AspectRatio(
                aspectRatio: 1.0,
                child: Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    border: Border.all(width: 3.0),
                  ),
                ),
              ),
            );
          return Scaffold(
            body: SingleChildScrollView(
              child: Column(
                children: [
                  Row(
                    children: List.filled(2, bigBox),
                  ),
                  Container(
                    child: Table(
                      children: List.filled(
                          // 7 rows instead of 3 just to demonstrate the scrolling functionality
                          7,
                          TableRow(
                            children: List.filled(3, smallBox),
                          )
                      ),
                    ),
                  ),
                ],
              ),
            ),
Nolte answered 24/11, 2020 at 22:16 Comment(2)
A link to a solution is welcome, but please ensure your answer is useful without it: add context around the link so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable.Unteach
Thanks for the feedback Zahra, I've completely rewritten my answer to be much more thorough and to stand on it's own without a link.Nolte
V
5

You can create a custom widget for it

import 'package:flutter/material.dart';

class StaticGrid extends StatelessWidget {
  const StaticGrid({
    Key? key,
    this.columnCount = 2,
    this.gap,
    this.padding,
    this.columnMainAxisAlignment = MainAxisAlignment.start,
    this.columnCrossAxisAlignment = CrossAxisAlignment.center,
    this.rowMainAxisAlignment = MainAxisAlignment.start,
    this.rowCrossAxisAlignment = CrossAxisAlignment.center,
    required this.children,
  }) : super(key: key);

  final int columnCount;
  final double? gap;
  final EdgeInsets? padding;
  final MainAxisAlignment columnMainAxisAlignment;
  final CrossAxisAlignment columnCrossAxisAlignment;
  final MainAxisAlignment rowMainAxisAlignment;
  final CrossAxisAlignment rowCrossAxisAlignment;
  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: padding,
      child: Column(
        crossAxisAlignment: columnCrossAxisAlignment,
        mainAxisAlignment: columnMainAxisAlignment,
        children: _createRows(),
      ),
    );
  }

  List<Widget> _createRows() {
    final List<Widget> rows = [];
    final childrenLength = children.length;
    final rowCount = (childrenLength / columnCount).ceil();

    for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
      final List<Widget> columns = _createCells(rowIndex);
      rows.add(
        Row(
          crossAxisAlignment: rowCrossAxisAlignment,
          mainAxisAlignment: rowMainAxisAlignment,
          children: columns,
        ),
      );
      if (rowIndex != rowCount - 1) {
        rows.add(SizedBox(height: gap));
      }
    }

    return rows;
  }

  List<Widget> _createCells(int rowIndex) {
    final List<Widget> columns = [];
    final childrenLength = children.length;

    for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
      final cellIndex = rowIndex * columnCount + columnIndex;
      if (cellIndex <= childrenLength - 1) {
        columns.add(Expanded(child: children[cellIndex]));
      } else {
        columns.add(Expanded(child: Container()));
      }

      if (columnIndex != columnCount - 1) {
        columns.add(SizedBox(width: gap));
      }
    }

    return columns;
  }
}

how to use it is the same as using the column or row widget, you can define column count, gap, padding, and margin.

StaticGrid(
  gap: 16,
  padding: const EdgeInsets.all(16),
  children: [
    Text("1"),
    Text("2"),
    Text("3"),
    Text("4"),
    Text("5"),
    Text("6"),
  ],
);
Vigen answered 20/4, 2021 at 8:34 Comment(1)
Could you please add an explanation? Because in my case it is creating an overflow: "right overfloewed by 13pixels" and I'd like to figure out, how it works to modifiy it accordingly, e.g. reducing the space between the single widgets.Chacma
P
1
GridView(){
   ...
   shrinkWrap: true,  //This line prevents GridView from occupying as much space as there is.
   physics: NeverScrollableScrollPhysics(),  // This attr. disables user scroll.
   ...
}

Set these properties and you are good to go.

Purington answered 4/11, 2023 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.