I have list tiles that contain data that varies a lot. Inside each of them I want to fit category labels (maybe using Chip widgets) but the issue is, I need to limit the number of them to fit a specific width constrains. The number of categories inside my list of data also varies - sometimes one of the tiles will have 2 categories, sometimes 5 and sometimes 0. How do I fit them on a screen, so they can "sit" in one row only? To be more specific, I want to display only as much category labels as it fits the width of my list tile - the rest won't be shown.
Is there some simple solution that does not require measuring each label width and comparing it to the screen size of a device?
Wrap widget won't work for my problem as it cannot be limited to one row only and there is no option to limit number of its child widgets as well depending on a size constrains 😕

Here's a full working example of a solution I have used.
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Padding(
padding: EdgeInsets.all(30.0),
child: OverflowRow(children: [
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
Text('Test'),
])));
}
}
class OverflowRow extends StatefulWidget {
const OverflowRow({Key? key, required this.children}) : super(key: key);
final List<Widget> children;
@override
State<OverflowRow> createState() => _OverflowRowState();
}
class _OverflowRowState extends State<OverflowRow> {
late List<double> sizes;
late List<bool> show;
late List<int> hideList;
bool calcFinished = false;
double width = 0;
@override
void initState() {
sizes = List.generate(widget.children.length, (index) => -1);
show = List.generate(widget.children.length, (index) => true);
hideList = List.generate(widget.children.length, (index) => index);
super.initState();
}
void sizeChanged(Size size, int index) {
sizes[index] = size.width;
if (!sizes.contains(-1)) {
setState(() {
calcFinished = true;
calculate();
});
}
}
void calculate() {
var width = this.width;
for (final i in hideList) {
show[i] = width - sizes[i] > 0;
width -= sizes[i];
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
width = constraints.maxWidth;
calculate();
return Row(
children: widget.children
.mapIndexed((index, element) => element is Spacer
? element
: SizeWidget(
sizeChanged: (size) => sizeChanged(size, index),
child: element))
.whereIndexed((index, _) => show[index])
.toList());
});
}
}
class SizeWidget extends StatefulWidget {
final Widget child;
final Function(Size size)? sizeChanged;
const SizeWidget({Key? key, this.sizeChanged, required this.child})
: super(key: key);
@override
SizeWidgetState createState() => SizeWidgetState();
}
class SizeWidgetState extends State<SizeWidget> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_getSize();
});
}
_getSize() {
RenderBox renderBox = context.findRenderObject() as RenderBox;
if (widget.sizeChanged != null) widget.sizeChanged!(renderBox.size);
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
It consists of two additional widgets. A SizeWidget that is able to report its size through a callback, and an OverflowRow that uses this SizeWidget to measure the children's size. It might be more complicated than it should be so I'm also interested to see better solutions. But this should work for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With