效果:
说明:绿色块在向上滑动,距离顶部 103 的高度(即 AppBar 下面)时固定
代码:
class PinnedSliverPage extends StatefulWidget {
const PinnedSliverPage({Key? key}) : super(key: key);
@override
State<PinnedSliverPage> createState() => _PinnedSliverPageState();
}
class _PinnedSliverPageState extends State<PinnedSliverPage> {
@override
Widget build(BuildContext context) {
print('topBar: ${MediaQuery.of(context).padding.top}');
return Scaffold(
backgroundColor: Colors.blueGrey,
body: CustomScrollView(
physics:
AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
slivers: [
SliverAppBar(
title: Text("Pinned Sliver"),
pinned: true,
backgroundColor: Colors.orange.withOpacity(0.8),
),
SliverToBoxAdapter(
child: Container(
alignment: Alignment.center,
height: 200,
color: Colors.yellow,
child: Text("200"),
),
),
//距离顶部 103时,开始固定
SliverPinHeader(
pinnedOffset: 103,
child: Container(
alignment: Alignment.center,
color: Colors.green.withOpacity(0.8),
height: 300,
child: Text("300"),
),
),
SliverToBoxAdapter(
child: Container(
alignment: Alignment.center,
height: 1200,
color: Colors.red,
child: Text("1200"),
),
)
],
));
}
}
class SliverPinHeader extends SingleChildRenderObjectWidget {
const SliverPinHeader({
Key? key,
required this.pinnedOffset,
required Widget child,
}) : super(child: child);
final double pinnedOffset;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderSliverPinHeader(pinnedOffset: pinnedOffset);
}
@override
void updateRenderObject(BuildContext context, RenderSliverPinHeader renderObject) {
renderObject.pinnedOffset = pinnedOffset;
}
}
class RenderSliverPinHeader extends RenderSliverSingleBoxAdapter {
RenderSliverPinHeader({required double pinnedOffset}): _pinnedOffset = pinnedOffset;
late double _pinnedOffset = 0;
set pinnedOffset(double value) {
if (_pinnedOffset == value) {
return;
}
_pinnedOffset = value;
markNeedsLayout();
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData) {
child.parentData = SliverPhysicalParentData();
}
}
@override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
// 1.对子节点进行约束 SliverConstraints
child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child!.size.width;
break;
case Axis.vertical:
childExtent = child!.size.height;
break;
}
final paintedChildSize =
calculatePaintOffset(constraints, from: 0.0, to: childExtent);
final double cacheExtent =
calculateCacheOffset(constraints, from: 0.0, to: childExtent);
// 2.上报当前节点的布局信息给 viewport
geometry = SliverGeometry(
// 默认 paintExtent 大于 0时才显示当前 sliver。手动设置 visible: true 则总是可见,不受 paintExtent 影响。
// visible: paintExtent > 0.0
// 可以调整此值为 -constraints.scrollOffset,来影响 paintChild.offset。
// paintOrigin: 0
// 通常等于子组件的高度,并且不随着滚动而改变
scrollExtent: childExtent,
// 通过等于页面上实际显示的长度。滚动到顶部后,开始变小,直到等于 0。手动设置的话,须小于 maxPaintExtent
paintExtent: childExtent,
// 注:布局高度要逐渐变小,否则下一个 sliver 滚到顶部会有停顿(可尝试注释掉这段代码)。
layoutExtent: paintedChildSize,
cacheExtent: cacheExtent,
// 最大可绘制长度,常等于 childExtent。取值必须小于 constraints.remainingPaintExtent。
maxPaintExtent: childExtent,
);
// 3.设置并保存 paintOffset 到 parentData 中,待稍后确定子节点位置(offset)
// setChildParentData(child!, constraints, geometry!);
final SliverPhysicalParentData childParentData =
child!.parentData! as SliverPhysicalParentData;
final topOffset =
constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
final offset = topOffset + (geometry?.paintOrigin ?? 0);
if (offset < _pinnedOffset) {
final paintOffset = _pinnedOffset - offset;
childParentData.paintOffset = Offset(0.0, paintOffset);
} else {
childParentData.paintOffset = Offset(0.0, -constraints.scrollOffset);
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null && geometry!.visible) {
final SliverPhysicalParentData childParentData =
child!.parentData! as SliverPhysicalParentData;
print("offset: $offset, paintOffset: ${childParentData.paintOffset}");
// 4.绘制子节点
// topOffset = constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
//其中 offset = topOffset(sliver 到达顶部后等于 0) + paintOrigin(默认等于0)
//其中 paintOffset(默认等于0)
//即到达顶部后,如果两者都等于0,并且 visible 不等于 false,则 child 保持当前位置不变。
context.paintChild(child!, offset + childParentData.paintOffset);
}
}
}
标签:paintOffset,pinnedOffset,offset,childExtent,固定,child,sliver,flutter,constraints
From: https://www.cnblogs.com/lemos/p/16602957.html