Reputation: 12497
I have a list of Widgets that I want to display in non-scrollable horizontal layout.
In the example below, I have 5 Widgets, 2 of them are fully displayed and the other 3 do not fit, hence the '+3' label.
I do not mind if there is some empty space after the label, but the important thing is that we only display Widgets that fully fit in the row.
I guess the main problem is how can I test that a Widget fits based on it's width into a row?
I thought about VisibilityDetector
, but I will ended up rendering all widgets, then removing the ones that are not 100% visible, and the entire logic seems quite flawed for this use case.
Any ideas? Thanks
Upvotes: 2
Views: 1103
Reputation: 566
try my version:
class Home extends StatefulWidget {
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// widget
List<GlobalKey> keys = [];
var widgets = List.generate(25, (index) {
GlobalKey globalKey = GlobalKey();
keys.add(globalKey);
return FilterChip(
key: globalKey,
label: Text("asdasasd"),
);
});
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: Column(
children: [
OverRow(
keys: keys,
widgets: widgets,
),
],
),
);
}
}
class OverRow extends StatefulWidget {
OverRow({Key key, this.keys, this.widgets}) : super(key: key);
List<GlobalKey> keys = [];
List<Widget> widgets = [];
@override
State<OverRow> createState() => _OverRowState();
}
class _OverRowState extends State<OverRow> {
int overIndex = -1;
@override
void initState() {
// TODO: implement initState
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
for (var i = 0; i < widget.keys.length; i++) {
final box = widget.keys[i].currentContext.findRenderObject() as RenderBox;
final pos = box.localToGlobal(Offset.zero);
var over = pos.dx + box.size.width > MediaQuery.of(context).size.width;
if (over) {
overIndex = i;
setState(() {});
return;
}
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Row(
children: overIndex == -1
? widget.widgets
: [
...widget.widgets.take(overIndex).toList(),
Container(
child: Text("+${widget.widgets.skip(overIndex).length}"),
)
]);
}
}
Upvotes: 1
Reputation: 321
EDIT: Included dynamic width based in label length
Try this, with defined width of item container and the +label or with dynamic calculating the width based on font size.
Change useFixedWidth to true or false if you want dynamic or static width.
Example in a Stateless widget:
class MyWidget extends StatelessWidget {
final double itemWidth = 100; //maxwidth if item container
final double labelWidth = 40; //maxWidth of +label
List<String> items = List.generate(
10, (index) => 'Name ${index + 1}'); //replace with a real list of items
final bool useFixedWidth =
false; // define if want to calculate dynamic with of each item
final double letterWidth =
12; // defined width of a letter || change depending on font size
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
int totalItems = items.length;
int maxDisplayItems = 0;
List<double> dynamicItemsWidth = []; //if want to use dynamic width
if (useFixedWidth) {
//if want to use fixed width
maxDisplayItems =
(((constraints.maxWidth - labelWidth) / itemWidth).floor()).clamp(
0, totalItems); //check how many items fit including the +label
} else {
//if want to calculate based on string length
dynamicItemsWidth = items
.map((e) => e.length * letterWidth)
.toList(); //calculate individual item width
double _acumWidth = 0.0;
for (var width in dynamicItemsWidth) {
_acumWidth = _acumWidth + width;
if (_acumWidth < (constraints.maxWidth - labelWidth)) {
maxDisplayItems++;
}
}
}
bool showPlusSign =
maxDisplayItems < totalItems; //check if all items can be shown
return Row(children: [
Row(
children: List.generate(maxDisplayItems, (index) {
return SizedBox(
height: itemWidth,
width: useFixedWidth ? itemWidth : dynamicItemsWidth[index],
child: Container(
//color: Colors.red,
//width: itemWidth,
child: Center(child: Text('Name ${index + 1}'))));
}).toList()),
if (showPlusSign)
SizedBox(
width: labelWidth,
height: itemWidth,
child: Center(child: Text('+${totalItems - maxDisplayItems}')))
]);
});
}
}
shows this dynamic layout for 10 items:
Upvotes: 1