首页 > 解决方案 > 如何在渲染之前检测小部件是否会溢出其约束?

问题描述

我有一个PageView并且我想用它在列中显示无限量的小部件。我正在尝试将小部件添加到列中,直到它溢出,然后从溢出的小部件开始构建下一页。

问题示意图

到目前为止,这是我为了让某些东西起作用而拼凑起来的:

class OverflowSliverChildDelegate extends SliverChildDelegate {
  OverflowSliverChildDelegate({
    @required this.itemBuilder,
  }) : assert(itemBuilder != null),
       super();

  /// Used to build individual items to slot into pages.
  final IndexedWidgetBuilder itemBuilder;

  @override
  Widget build(BuildContext context, int index) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // todo: handle this better
        if (remainingHeight <= 0) throw Error();

        var itemIndex = 0;
        return PageView.builder(
          itemBuilder: (context, pageIndex) {
            final remainingHeight = constraints.maxHeight;
            return Column(
              // todo: move this generator to a separate method
              children: () sync* {
                // Build widgets until we run out of vertical space.
                while (remainingHeight > 0) {
                  // todo: layout widget and get its height
                  final widget = itemBuilder(context, itemIndex++);

                  // remainingHeight -= widget.height

                  yield widget;
                }
              }(),
            );
          },
        );
      },
    );
  }
}

如果我只关心文本,我可以使用 aTextPainter来布局文本并获取高度。有没有办法为任意小部件(可能有也可能没有自己的孩子)做到这一点?


更新

在查看了Offstage小部件之后,我尝试了这个,但 Flutter 断言我不会size从对象外部调用 getter。我也觉得很接近解决方案。

          while (remainingHeight > 0) {
              final widget = itemBuilder(context, itemIndex++);

              final renderObj = ConstrainedBox(
                child: widget,
                constraints: constraints.copyWith(
                  maxHeight: remainingHeight,
                ),
              ).createRenderObject(context);

              renderObj.layout(constraints);

              remainingHeight -= renderObj.size.height;

              if (remainingHeight > 0) {
                yield widget;
              }
            }

标签: flutterflutter-layout

解决方案


最后,我深入研究了 Flutter 的 sliver 列表源代码(本质上是ListView小部件的后端),我能够弄清楚它是如何决定何时有足够的孩子来填充视口和“缓存范围”的。

我已经开始了一个新的 Flutter 项目以尝试不同的方法,但我已经对其进行了一些清理并将其发布到pub.dev:https://pub.dev/packages/overflow_page_view

结果是为每个页面PageView构建一个。CustomScrollView我直接RenderSliver从 Flutter 源代码中复制了这些东西的代码,并更改了它放置子元素的部分。理想情况下,我希望找到一种将自定义传递SliverConstraintsListView或其他 sliver 小部件的方法。


至于回答“如何检测小部件在渲染之前是否会溢出其约束?”的问题:

我将 for 的代码复制RenderSliverList到我自己的类 ( NoCacheRenderSliverList) 中并开始调整。需要注意的是,我使用NeverScrollableScrollPhysics了这样我就不必担心滚动(因为我不需要它)。

首先,在performLayout方法的顶部,我将 更改targetEndScrollOffset为剩余的绘制范围,与滚动偏移或缓存范围无关。只关心这里的可见区域:

final double targetEndScrollOffset = this.constraints.remainingPaintExtent;

接下来,我需要更改嵌套bool advance()方法以在子项的底部超出视口边缘时返回 false:

  assert(child != null);
  final SliverMultiBoxAdaptorParentData childParentData =
      child.parentData as SliverMultiBoxAdaptorParentData;
  childParentData.layoutOffset = endScrollOffset;
  assert(childParentData.index == index);
  // In the original source, this is assigned directly to endScrollOffset
  final tmp = childScrollOffset(child) + paintExtentOf(child);
  if (tmp > targetEndScrollOffset) {
    return false;
  }
  endScrollOffset = tmp;
  return true;

使用上面的代码,我们最终仍然会显示一个超出视口的子项。我的最后一个更改是确保这个孩子被垃圾收集:

  // Finally count up all the remaining children and label them as garbage.
if (child != null) {
  // By commenting out this line, we ensure the final child is garbage collected.
  // child = childAfter(child);
  while (child != null) {
    trailingGarbage += 1;
    child = childAfter(child);
  }
}

这就是它的全部。如果有人有任何改进或需要澄清,我很乐意更新。


推荐阅读