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
相关产品推荐
相关产品推荐

