2

I want to create a widget (blue background)as the picture depict:

  1. If the text count is not too much ( will not exceed the screen edge) , it will layout as a Row widget
  2. If the text count is too much ,all the text content is too long , and it will exceed the screen edge if all the text are lay out. So it will only layout the Text1 and Text2 ,and add a "..." at last , ignore the other text.

enter image description here

How to achieve this in Flutter ?(the left and right widget's size is not fixed)

ximmyxiao
  • 2,622
  • 3
  • 20
  • 35

2 Answers2

1

I used tuple but you can avoid it.

enter image description here

import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';

void main() {
  runApp(
    const MaterialApp(
      home: App(),
    ),
  );
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(5),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: const [
            Text('1st content will not exceed the edge'),
            SizedBox(height: 5),
            Example(
              list: ['text1', 'long text2'],
            ),
            SizedBox(height: 10),
            Text('2st content will not exceed the edge'),
            SizedBox(height: 5),
            Example(
              list: ['text1', 'long text2', 'text3', 'text4', 'text3'],
            ),
          ],
        ),
      ),
    );
  }
}

class Example extends StatelessWidget {
  const Example({
    super.key,
    required this.list,
  });

  final List<String> list;

  @override
  Widget build(BuildContext context) {
    const right = _SideWidget(title: 'Right');
    const left = _SideWidget(title: 'Left');

    final leftSize = measureWidget(left);
    final rightSize = measureWidget(right);
    return LayoutBuilder(builder: (context, constraints) {
      var remainingWidth =
          constraints.maxWidth - rightSize.width - leftSize.width;
      return Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          left,
          CalculatedWidget(textList: list, maxWidth: remainingWidth),
          right,
        ],
      );
    });
  }
}

class CalculatedWidget extends StatelessWidget {
  const CalculatedWidget({
    super.key,
    required this.textList,
    required this.maxWidth,
  });

  final List<String> textList;
  final double maxWidth;

  @override
  Widget build(BuildContext context) {
    const paddingSize = 5.0;
    const separaterSize = 5.0;
    final othersWidget = getWidgetAndSize('...');

    final childrenData = <Tuple2<Widget, double>>[];
    final List<Widget> children;
    late var remainTextWidgetsWidth = maxWidth;
    var isShortened = false;

    for (final text in textList) {
      final textWidget = getWidgetAndSize(text);

      if (remainTextWidgetsWidth < textWidget.item2 + separaterSize) {
        isShortened = true;
        break;
      }
      remainTextWidgetsWidth -= (textWidget.item2 + separaterSize);
      childrenData.add(textWidget);
    }

    if (isShortened) {
      children = [];
      remainTextWidgetsWidth = maxWidth - othersWidget.item2 - separaterSize;
      var it = childrenData.iterator;

      bool isStoped = false;

      while (it.moveNext() && !isStoped) {
        final isWidthEnougth =
            remainTextWidgetsWidth > it.current.item2 + separaterSize;
        if (isWidthEnougth) {
          children.add(Padding(
            padding: const EdgeInsets.only(right: separaterSize),
            child: it.current.item1,
          ));

          remainTextWidgetsWidth -= (it.current.item2 + separaterSize);
        } else {
          isStoped = true;
        }
      }
    } else {
      children = childrenData
          .map((e) => Padding(
                padding: const EdgeInsets.only(right: separaterSize),
                child: e.item1,
              ))
          .toList();
    }

    return Container(
      color: Colors.blue.shade300,
      padding:
          const EdgeInsets.fromLTRB(paddingSize, paddingSize, 0, paddingSize),
      child: Row(
        children: [
          ...children,
          if (isShortened) ...[
            othersWidget.item1,
            const SizedBox(width: paddingSize)
          ],
        ],
      ),
    );
  }

  Tuple2<Widget, double> getWidgetAndSize(String text) {
    final widget = Container(
      color: Colors.red,
      alignment: Alignment.center,
      padding: const EdgeInsets.all(5),
      constraints: const BoxConstraints(
        minWidth: 50,
      ),
      child: Text(text),
    );
    final size = measureWidget(widget);
    return Tuple2(widget, size.width);
  }
}

class _SideWidget extends StatelessWidget {
  const _SideWidget({
    required this.title,
  });

  final String title;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      margin: const EdgeInsets.all(5),
      color: Colors.green,
      child: Text(title),
    );
  }
}

the measure widget method from this answer

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

measureWidget(Widget widget,
    [BoxConstraints constraints = const BoxConstraints()]) {
  final PipelineOwner pipelineOwner = PipelineOwner();
  final _MeasurementView rootView =
      pipelineOwner.rootNode = _MeasurementView(constraints);
  final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
  final RenderObjectToWidgetElement<RenderBox> element =
      RenderObjectToWidgetAdapter<RenderBox>(
    container: rootView,
    debugShortDescription: '[root]',
    child: Directionality(
      textDirection: TextDirection.ltr,
      child: widget,
    ),
  ).attachToRenderTree(buildOwner);
  try {
    rootView.scheduleInitialLayout();
    pipelineOwner.flushLayout();
    return rootView.size;
  } finally {
    // Clean up.
    element.update(RenderObjectToWidgetAdapter<RenderBox>(container: rootView));
    buildOwner.finalizeTree();
  }
}

class _MeasurementView extends RenderBox
    with RenderObjectWithChildMixin<RenderBox> {
  final BoxConstraints boxConstraints;
  _MeasurementView(this.boxConstraints);

  @override
  void performLayout() {
    assert(child != null);
    child!.layout(boxConstraints, parentUsesSize: true);
    size = child!.size;
  }

  @override
  void debugAssertDoesMeetConstraints() => true;
}
Kherel
  • 14,882
  • 5
  • 50
  • 80
  • 1
    Thanks @Kherel, it seems the perfect answer – ximmyxiao Mar 07 '23 at 09:59
  • ,one thing may improved : if (remainTextWidgetsWidth < textWidget.item2 + separaterSize) { isShortened = true; break; } – ximmyxiao Mar 08 '23 at 02:50
  • thanks, I added to the answer – Kherel Mar 08 '23 at 06:46
  • when i remove the Directionality widget , the measurement will fail for the Text widget , and I found that this problem is also in the official example in flutter.dev. How did you found that this Directionality is necessarily?:) – ximmyxiao Mar 09 '23 at 01:06
  • It is just how the text widget works. https://github.com/flutter/flutter/blob/b44e1fb6ad18f6e61bfa272025e16985348da020/packages/flutter/lib/src/widgets/basic.dart#L2706 I have seen this before – Kherel Mar 09 '23 at 02:23
0

For Text Widgets you can use TextSpan wrapped inside a RichText:

Row(
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border.all(color: Colors.black),
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.circular(10),
          ),
          width: 200,
          height: 200,
        ),
                  Flexible(child: 
                           Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 8.0),
                    child: 
                           RichText(
                    overflow: TextOverflow.ellipsis,
                    text: const TextSpan(
                      children: [
                        TextSpan(text: "Text 1 "),
                        TextSpan(text: "Text 2 "),
                        TextSpan(text: "Text 3 "),
                        TextSpan(text: "Text 4 "),
                        TextSpan(text: "Text 5 "),
                        TextSpan(text: "Text 6 "),
                        TextSpan(text: "Text 7 "),
                        TextSpan(text: "Text 8 "),
                      ],
                      style: TextStyle(color: Colors.white, fontSize: 20,),
                    ),
                  ),
                             ),
                           ),
        Container(
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border.all(color: Colors.black),
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.circular(10),
          ),
          width: 200,
          height: 200,
        ),
      ],
    );

If the widget are mixed, you can wrap the widgets inside a row with Flexible.

Results: sample

flutter_bee
  • 150
  • 9