4

I have a row with multiple children and I want to show the children of the row on a single line. However, if there is not enough room for all of the children, I want to show an ellipsis at the end of the line, just as we have the overflow and maxlines properties for text to handle the overflow problem. Is there any workaround to do it for row?

Row(
  children: [
    TagItem('Education'),
    TagItem('GPA'),
    TagItem('Books'),
    TagItem('University'),
    TagItem('Library'),
  ],
)

I want something like this:enter image description here

5
  • Can you include your snippet that you've tried so far? Commented Feb 28, 2022 at 6:24
  • @YeasinSheikh I am not even close to it. So there is no point in including the code here. In my code, there is only a row with some children. Commented Feb 28, 2022 at 6:32
  • I think that is enough to reproduce the error, check more about minimal-reproducible-example Commented Feb 28, 2022 at 6:33
  • Those guidelines are very helpful in complex situations, but for a scenario as simple as a row with some children, it is not necessary. However, I have included the code snippet. Commented Feb 28, 2022 at 6:43
  • You can measure the size of each text box and max width to control overflow, not sure about efficiency. You can read about size of the Text Widget IF every tagItem comes with same size, it will be simpler by comparing width. Commented Feb 28, 2022 at 7:24

1 Answer 1

4
+50

Two possible approaches:

This is one of the harder questions on flutter layout. There are 2 approaches, one is kinda hard, the other one is harder. If needed, I can provide some example code, but I will first describe the approaches here and see if you can do it yourself.

  1. use CustomMultiChildLayout. In this widget, you are given (almost) full control of the flutter layout pipeline, you can measure the total available size, and more importantly, you can measure the size of each of your children, and then determine what to render. The downside of this solution is, you cannot set the parent (CustomMultiChildLayout) size based on its children's size. In your case, this means you cannot dynamically decide the height of your widget. If setting fixed height with a SizedBox (say height: 120 or whatever) sounds reasonable, you should go with this approach.

  2. write your own RenderBox. In your case, you should look into extending MultiChildRenderObjectWidget. Basically, all these convenient widgets you use every day, like Row or Column, are all RenderBox under the hood (that someone working on Flutter already implemented for you). So if those aren't enough to suit your needs, you can always create more!


Proof of concept using method #1:

A custom RowWithOverflow widget that takes in a fixed height, an overflow widget, and all the children.

Example Usage:

RowWithOverflow(
  height: 100,
  overflow: Chip(label: Text('...')),
  children: [
    Chip(label: Text('Item 1')),
    Chip(label: Text('Item 2')),
    Chip(label: Text('Item 3')),
  ],
)

Demo App:

demo screenshot

Source Code for Demo App:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _count = 3;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Column(
          children: [
            Text('CustomMultiChildLayout demo:'),
            RowWithOverflow(
              height: 50,
              overflow: Chip(label: Text('...')),
              children: [
                for (int i = 0; i < _count; i++)
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 1.0),
                    child: Chip(label: Text('Item $i')),
                  ),
              ],
            ),
            ElevatedButton(
              onPressed: () => setState(() => _count += 1),
              child: Text('Add item'),
            ),
          ],
        ),
      ),
    );
  }
}

class RowWithOverflow extends StatelessWidget {
  final double height;
  final List<Widget> children;
  final Widget overflow;

  const RowWithOverflow({
    Key? key,
    required this.height,
    required this.children,
    required this.overflow,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: height,
      child: ClipRect(
        child: CustomMultiChildLayout(
          delegate: MyDelegate(children.length),
          children: [
            for (int i = 0; i < children.length; i++)
              LayoutId(
                id: i,
                child: children[i],
              ),
            LayoutId(
              id: 'overflow',
              child: overflow,
            ),
          ],
        ),
      ),
    );
  }
}

class MyDelegate extends MultiChildLayoutDelegate {
  final int _childrenCount;

  MyDelegate(this._childrenCount);

  @override
  void performLayout(Size size) {
    // Get the size of the overflow item.
    final Size overflowSize = layoutChild(
      'overflow',
      BoxConstraints.loose(size),
    );
    // Get sizes of all children.
    final List<Size> childrenSizes = [
      for (int i = 0; i < _childrenCount; i++)
        layoutChild(i, BoxConstraints.loose(size)),
    ];
    // Hide everything for now.
    positionChild('overflow', Offset(0, -2000));
    for (int i = 0; i < _childrenCount; i++) {
      positionChild(i, Offset(0, -2000));
    }

    // Carefully position each child until we run out of space.
    Offset offset = Offset.zero;
    for (int i = 0; i < _childrenCount; i++) {
      if (offset.dx + childrenSizes[i].width < size.width) {
        positionChild(i, offset);
        offset += Offset(childrenSizes[i].width, 0);
      } else {
        positionChild('overflow', offset);
        offset = Offset(0, 200);
        break;
      }
    }
  }

  @override
  bool shouldRelayout(oldDelegate) => false;
}
Sign up to request clarification or add additional context in comments.

5 Comments

thanks for the solutions. I think the first approach works for me. I went through it and found it pretty difficult to implement the performLayout of the MultiChildLayoutDelegate. I would be super glad if you include an example for this scenario. Actually, the difficult part was that there are unknown number of children and laying out those ellipsis.
@EhsanAskari Sure, I've included a proof-of-concept for method #1. You can take the source code as a starting point and modify it to suit your needs.
Thank you for the example. However, there is a problem with the solution. When positioning the overflow widget, we do not know if there is enough space for it, and if there isn't, the overflow widget will be positioned partly or fully out of visible UI.
However, I had managed to work it out myself but my code is very messy unlike yours which is very clean and organized. I will try to adapt to your coding style.
This is just a proof-of-concept, it's meant to give you an example as a starting point, it does not cover everything for you. For example, it also does not consider the case where the "overflow widget is bigger than the last widget". You should take my code as a starting point, and handle edge cases yourself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.