使用类似于 textoverflow 的省略号处理行溢出

Handle row overflow using ellipsis similar to textoverflow

我有一行包含多个 children,我想在一行中显示该行的 children。但是,如果没有足够的空间容纳所有 children,我想在行尾显示一个省略号,就像我们有文本的 overflowmaxlines 属性一样来处理溢出问题。 row 是否有解决方法?

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

我想要这样的东西:

两种可能的方法:

这是关于 flutter 布局的较难问题之一。有两种方法,一种有点难,另一种更难。如果需要,我可以提供一些示例代码,但我会先在这里描述方法,看看你是否可以自己做。

  1. 使用CustomMultiChildLayout。在这个小部件中,您可以(几乎)完全控制 flutter 布局管道,您可以测量总可用大小,更重要的是,您可以测量每个 children 的大小,然后确定要做什么使成为。此解决方案的缺点是,您无法根据 children 的大小设置 parent (CustomMultiChildLayout) 的大小。在您的情况下,这意味着您无法动态决定小部件的高度。如果使用 SizedBox(例如 height: 120 或其他)设置固定高度听起来合理,您应该采用这种方法。

  2. 写你自己的RenderBox。在你的情况下,你应该考虑扩展 MultiChildRenderObjectWidget。基本上,您每天使用的所有这些方便的小部件,如 RowColumn,都是 RenderBox 的幕后黑手(那些从事 Flutter 工作的人已经为您实现了)。因此,如果这些不足以满足您的需求,您可以随时创造更多!


使用方法 #1 的概念证明:

采用固定高度的自定义 RowWithOverflow 小部件、溢出小部件和所有 children。

用法示例:

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

演示应用程序:

演示应用的源代码:

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;
}