skip to Main Content

I want to create a widget that takes a list of widgets (in this case always buttons) and automatically arranges them in a kind of grid depending on the width of the child.
When there are 2 Buttons in a row, each button should have 50% of the width. So it should be something like this:

enter image description here

First i tried this Code:

Widget getOrderedButtonList(
  List<Widget> buttonList,
) {
  final List<Widget> orderedButtonList = [];

  for (int index = 0; index < buttonList.length; index = index + 2) {
    orderedButtonList.add(
      Row(
        children: [
          Expanded(child: buttonList[index]),
          if (index + 1 < buttonList.length) HmipSpacer.horizontal(),
          if (index + 1 < buttonList.length) Expanded(child: buttonList[index + 1]),
        ],
      ),
    );
  }
  return Column(
    children: orderedButtonList,
  );
}

This works fine for buttons with small text, but when the text is longer i have the problem, that the text wont fit in the button like this:

enter image description here

I dont want to show the text in multiple lines (would be the easiest solution). So I need a solution where a button is placed in a new row if the text doesn’t quite fit.
I tried the flex_list library which can arrange a list of widgets depending on their width and it looked like this:

enter image description here

That’s almost what I want. The 3 buttons in a row aren’t bad either. However, the buttons now all have a different width. I want the buttons to always have the same width and as soon as that no longer fits, they are wrapped.

So in this example it should look like this:

enter image description here

Does anyone know how to do this?

2

Answers


  1. Chosen as BEST ANSWER

    The answer from @MaLa would work with a little refactoring, but I've already found another solution. I changed the performlayout method in the flex_list library a bit so that all elements in a row have the same width. If someone needs this too, here is the code:

      @override
      void performLayout() {
        final List<_RowMetrics> rows = [];
        // Determine Widths
        var child = firstChild;
        var rowWidth = 0.0;
        var rowMaxHeight = 0.0;
        var rowItemNumber = 0;
    
        final childConstraint = BoxConstraints(
          minWidth: 0,
          maxWidth: constraints.maxWidth,
          minHeight: 0,
          maxHeight: constraints.maxHeight,
        );
    
        while (child != null) {
          final size = child.getDryLayout(childConstraint);
          final parentData = child.parentData! as _FlexListParentData.._initSize = size;
          final neededWidth = size.width + horizontalSpacing;
    
          if (constraints.maxWidth - rowWidth < neededWidth ||
              constraints.maxWidth / (rowItemNumber + 1) < neededWidth) {
            // add to row to rows
            final rowSize = Size(rowWidth, rowMaxHeight);
            rows.add(_RowMetrics(rowItemNumber, rowSize));
            // reset row with first new element
            rowWidth = 0;
            rowMaxHeight = 0.0;
            rowItemNumber = 0;
          }
    
          parentData._rowIndex = rows.length;
          rowWidth += neededWidth;
          rowMaxHeight = rowMaxHeight > size.height ? rowMaxHeight : size.height;
          rowItemNumber++;
          child = parentData.nextSibling;
        }
    
        final rowSize = Size(rowWidth, rowMaxHeight);
        rows.add(_RowMetrics(rowItemNumber, rowSize));
    
        // position childs
        child = firstChild;
        var offsetX = 0.0;
        var offsetY = 0.0;
    
        for (int i = 0; i < rows.length; ++i) {
          final row = rows[i];
          while (child != null) {
            final _FlexListParentData childParentData = child.parentData! as _FlexListParentData;
    
            if (childParentData._rowIndex != i) {
              break;
            }
    
            num lastItemPadding = 0;
            if (row.childNumber > 1) {
              lastItemPadding = (horizontalSpacing * (row.childNumber - 1)) / row.childNumber;
            }
    
            final finalChildWidth = constraints.maxWidth / row.childNumber - lastItemPadding;
    
            final consts = BoxConstraints.expand(
              width: finalChildWidth,
              height: childParentData._initSize.height,
            );
            child.layout(consts);
    
            childParentData.offset = Offset(offsetX, offsetY);
            offsetX += finalChildWidth + horizontalSpacing;
    
            child = childParentData.nextSibling;
          }
    
          offsetX = 0.0;
          // don't add space to last row
          final vertSpacing = i + 1 == rows.length ? 0 : verticalSpacing;
          offsetY += row.contentRawSize.height + vertSpacing;
        }
    
        size = Size(constraints.maxWidth, offsetY);
      }
    

  2. I found this

    hasTextOverflow()

    method from How to check if Flutter Text widget was overflowed.

    In this implementation, we check does the widget fit the space or does it overflow. We also have to check does it make the previous widgets in the row overflow. If not, then we add the item into the row. If yes then we create a new row.

    This code needs some refactoring and also I have not tested how it works with paddings.

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Example',
          home: ButtonsPage(),
        );
      }
    }
    
    class ButtonsPage extends StatefulWidget {
      @override
      _ButtonsPageState createState() => _ButtonsPageState();
    }
    
    class _ButtonsPageState extends State<ButtonsPage> {
      final List<String> _buttonTexts = [
        "short 1",
        "short 2",
        "looooooooooog text 1",
        "short 3",
        "short 4",
        "short 5",
        "looooooooooog text 2",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "looooooooooog text 3",
      ];
    
      @override
      Widget build(BuildContext context) {
        const double widgetMaxWidth = 300;
        return Scaffold(
          appBar: AppBar(
            title: const Text('Test'),
          ),
          body: Center(
            child: Container(
              color: Colors.red,
              width: widgetMaxWidth,
              child: Column(children: buildRows(_buttonTexts, widgetMaxWidth)),
            ),
          ),
        );
      }
    
      List<Row> buildRows(List<String> texts, double widgetMaxWidth) {
        List<Row> rows = [];
        List<String> textsInRow = [];
        for (var text in texts) {
          // We have to check does the current widget overflow but in addition to this
          // we have to check does this cause previous widgets to overflow.
          bool overflows = hasTextOverflow(text, const TextStyle(fontSize: 20),
              maxWidth: widgetMaxWidth / (textsInRow.length + 1), maxLines: 1);
          for (var textInRow in textsInRow) {
            overflows = overflows ||
                hasTextOverflow(textInRow, const TextStyle(fontSize: 20),
                    maxWidth: widgetMaxWidth / (textsInRow.length + 1),
                    maxLines: 1);
          }
    
          if (overflows) {
            rows.add(Row(
              children: _createButtons(textsInRow),
            ));
            textsInRow = [];
          }
          textsInRow.add(text);
        }
        if (textsInRow.isNotEmpty) {
          rows.add(Row(
            children: _createButtons(textsInRow),
          ));
        }
        return rows;
      }
    
      List<Widget> _createButtons(List<String> texts) {
        List<Widget> buttons = [];
        for (var textInRow in texts) {
          buttons.add(Expanded(
            child: ElevatedButton(
              onPressed: () {},
              child: Text(
                textInRow,
                style: const TextStyle(fontSize: 20),
              ),
            ),
          ));
        }
        return buttons;
      }
    
      bool hasTextOverflow(String text, TextStyle style,
          {double minWidth = 0,
          double maxWidth = double.infinity,
          int maxLines = 2}) {
        final TextPainter textPainter = TextPainter(
          text: TextSpan(text: text, style: style),
          maxLines: maxLines,
          textDirection: TextDirection.ltr,
        )..layout(minWidth: minWidth, maxWidth: maxWidth);
        return textPainter.didExceedMaxLines;
      }
    }
    

    The output of this code looks like this:
    buttons and rows

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