You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SliverFillRemaining非CustomScrollView最后子组件时尺寸异常问题

Flutter: 让SliverFillRemaining作为第一个Sliver时正确占满剩余空间

我在使用CustomScrollView时遇到一个问题:布局里有一个固定高度的元素和SliverFillRemaining,希望当固定元素高度小于滚动视图时,SliverFillRemaining刚好占满剩余空间,让整个视图没法滚动。

当SliverFillRemaining放在最后一个位置时,功能完全正常:

CustomScrollView(
  physics: const ClampingScrollPhysics(),
  slivers: [
    SliverToBoxAdapter(
      child: const Placeholder(
        fallbackHeight: 250,
      ),
    ),
    SliverFillRemaining(
      hasScrollBody: false,
      child: Container(
        color: Colors.green,
      ),
    ),
  ],
)

正常效果:绿色容器占满剩余空间

但把两者顺序调换,让SliverFillRemaining放在顶部时,不管固定元素多大,它都会直接占满整个CustomScrollView的高度,把固定元素挤到视图外面:
异常效果:绿色容器占满整个视图,固定元素被挤出

试过其他相关问题的解决方案,也用过sliver_fill_remaining_box_adapter包,都解决不了这个问题。


问题原因

SliverFillRemaining的设计逻辑是:计算自己之后的所有sliver已经占用的高度,再用视图总高度减去这个值,得到自己的高度。但当它是第一个sliver时,后面的sliver还没被计算,所以它直接拿了整个视图的高度,导致后面的元素被挤走。

解决方案:自定义Sliver实现反向计算

我们可以自己写一个SliverFillRemainingBefore,让它先计算自己之后所有sliver的总高度,再用视图高度减去这个值,得到自己该占的空间:

class SliverFillRemainingBefore extends SingleChildRenderObjectWidget {
  const SliverFillRemainingBefore({
    super.key,
    super.child,
    this.hasScrollBody = false,
  });

  final bool hasScrollBody;

  @override
  RenderSliverFillRemainingBefore createRenderObject(BuildContext context) {
    return RenderSliverFillRemainingBefore(
      hasScrollBody: hasScrollBody,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderSliverFillRemainingBefore renderObject) {
    renderObject.hasScrollBody = hasScrollBody;
  }
}

class RenderSliverFillRemainingBefore extends RenderSliverSingleBoxAdapter {
  RenderSliverFillRemainingBefore({
    super.child,
    required bool hasScrollBody,
  }) : _hasScrollBody = hasScrollBody;

  bool _hasScrollBody;

  bool get hasScrollBody => _hasScrollBody;

  set hasScrollBody(bool value) {
    if (_hasScrollBody != value) {
      _hasScrollBody = value;
      markNeedsLayout();
    }
  }

  @override
  void performLayout() {
    final constraints = this.constraints;
    final viewportHeight = constraints.viewportMainAxisExtent;

    // 先算后面所有sliver的总高度
    double totalNextSliversHeight = 0;
    RenderObject? nextSliver = child;
    while (nextSliver != null) {
      if (nextSliver is RenderSliver) {
        final childConstraints = constraints.copyWith(
          remainingPaintExtent: double.infinity,
          crossAxisExtent: constraints.crossAxisExtent,
        );
        nextSliver.layout(childConstraints, parentUsesSize: true);
        totalNextSliversHeight += nextSliver.geometry!.scrollExtent;
      }
      nextSliver = nextSliver.parentData is SliverPhysicalParentData
          ? (nextSliver.parentData as SliverPhysicalParentData).nextSibling
          : null;
    }

    // 剩余空间 = 视图高度 - 后面sliver总高度
    final desiredHeight = viewportHeight - totalNextSliversHeight;
    final childConstraints = BoxConstraints(
      minHeight: hasScrollBody ? 0 : desiredHeight,
      maxHeight: desiredHeight,
      minWidth: constraints.crossAxisExtent,
      maxWidth: constraints.crossAxisExtent,
    );

    if (child != null) {
      child!.layout(childConstraints, parentUsesSize: true);
    }

    final paintExtent = desiredHeight.clamp(0.0, constraints.remainingPaintExtent);
    geometry = SliverGeometry(
      scrollExtent: desiredHeight,
      paintExtent: paintExtent,
      maxPaintExtent: paintExtent,
      hasVisualOverflow: false,
    );
  }
}

使用示例

把原来的SliverFillRemaining换成自定义的SliverFillRemainingBefore即可:

CustomScrollView(
  physics: const ClampingScrollPhysics(),
  slivers: [
    SliverFillRemainingBefore(
      hasScrollBody: false,
      child: Container(
        color: Colors.green,
      ),
    ),
    SliverToBoxAdapter(
      child: const Placeholder(
        fallbackHeight: 250,
      ),
    ),
  ],
)

这个实现会动态计算后面sliver的总高度,确保顶部的填充容器刚好占满剩余空间,整个视图不会出现滚动条。


内容的提问来源于stack exchange,提问作者Frikandel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 10:42:38