Reputation: 123
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:
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:
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:
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:
Does anyone know how to do this?
Upvotes: 3
Views: 243
Reputation: 123
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);
}
Upvotes: 1
Reputation: 679
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:
Upvotes: 1