How to limit the number of widgets to fit one row depending on a screen / parent widget width

157 views Asked by At

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

Layout of my tiles

1

There are 1 answers

0
Ivo On

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.