09_flutter吸顶上推组件2.0
之前实现过一个吸顶上推的效果,但是只能在ViewPort最顶部进行吸顶,并且在当前RenderObject中去干涉了其他RenderObject的绘制,这不是一个好的解决方案,本文将使用自定义自绘组件的方式,同时挂载header和sliver作为一个RenderObject的子级,统一处理header和sliver的布局和绘制,通过改变header的起始绘制位置,来实现吸顶上推效果。
一.先上效果

二.挂载header组件
定义Widget,通过header传入要吸顶的部分(RenderBox),通过sliver传入内容部分(RenderSliver)。
dart
class SliverStickyBar extends RenderObjectWidget {
final Widget? header;
final Widget? sliver;
const SliverStickyBar({
super.key,
this.header,
this.sliver
});
@override
RenderObjectElement createElement() {
throw UnimplementedError();
}
@override
RenderObject createRenderObject(BuildContext context) {
throw UnimplementedError();
}
}
///使用
CustomScrollView(
slivers: [
SliverPrototypeBuilder(
prototype: Padding(
padding: const EdgeInsets.only(bottom: 48),
child: Image.asset(
"assets/user.jpeg",
fit: BoxFit.fitWidth,
),
),
sliverBuilder: (context, mainAxisChildExtent, crossAxisChildExtent) {
return SliverAppBar(
pinned: true,
collapsedHeight: kToolbarHeight,
expandedHeight: mainAxisChildExtent - MediaQuery.paddingOf(context).top,
centerTitle: true,
title: Builder(builder: (context) {
final FlexibleSpaceBarSettings? settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
bool isScrolledUnder = settings?.isScrolledUnder ?? false;
Color? titleColor = isScrolledUnder ? Theme.of(context).appBarTheme.foregroundColor:Colors.white;
return Text(
"首页",
style: TextStyle(
color: titleColor
),
);
}),
backgroundColor: MaterialStateColor.resolveWith((states) {
if(states.contains(MaterialState.scrolledUnder)) {
return Theme.of(context).appBarTheme.backgroundColor ?? Colors.white;
}
return Colors.transparent;
}),
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: InkResponse(
onTap: () {
debugPrint("click");
},
child: Padding(
padding: const EdgeInsets.only(bottom: 48),
child: Image.asset(
"assets/user.jpeg",
fit: BoxFit.fitWidth,
),
),
),
),
bottom: const TabBar(
tabs: [
Tab(
text: "tab1",
),
Tab(
text: "tab2",
)
],
)
);
}
),
SliverStickyBar(
header: InkWell(
onTap: () {
debugPrint("Section1 click");
},
child: Container(
height: 44,
color: Colors.red,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: const Text("Section1", style: TextStyle(color: Colors.black),),
),
),
sliver: SliverPadding(
padding: const EdgeInsets.all(5),
sliver: SliverList.list(
children: List<Widget>.generate(10, (index) {
return InkWell(
onTap: () {
debugPrint("click content item $index");
},
child: Container(
height: 100,
color: Colors.red,
margin: const EdgeInsets.all(5),
alignment: Alignment.center,
child: Text(
"item $index",
style: const TextStyle(
color: Colors.white
),
),
),
);
})
),
),
),
SliverStickyBar(
header: InkWell(
onTap: () {
debugPrint("Section2 click");
},
child: Container(
height: 44,
color: Colors.green,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: const Text("Section2", style: TextStyle(color: Colors.white),),
),
),
sliver: SliverPadding(
padding: const EdgeInsets.all(5),
sliver: SliverList.list(
children: List<Widget>.generate(6, (index) {
return InkWell(
onTap: () {
debugPrint("click content item $index");
},
child: Container(
height: 100,
color: Colors.green,
margin: const EdgeInsets.all(5),
alignment: Alignment.center,
child: Text(
"item $index",
style: const TextStyle(
color: Colors.white
),
),
),
);
})
),
),
),
SliverStickyBar(
header: InkWell(
onTap: () {
debugPrint("Section3 click");
},
child: Container(
height: 44,
color: Colors.blue,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: const Text("Section3", style: TextStyle(color: Colors.white),),
),
),
sliver: SliverPadding(
padding: const EdgeInsets.all(5),
sliver: SliverList.list(
children: List<Widget>.generate(10, (index) {
return InkWell(
onTap: () {
debugPrint("click content item $index");
},
child: Container(
height: 100,
color: Colors.blue,
margin: const EdgeInsets.all(5),
alignment: Alignment.center,
child: Text(
"item $index",
style: const TextStyle(
color: Colors.white
),
),
),
);
})
),
)
),
],
)
定义RenderObject,提供一个header属性,由Element来初始化挂载header和sliver对应的RenderObject,sliver对应的RenderObject会被挂载到child属性上。
dart
class _RenderSliverStickyBar extends RenderSliver with RenderSliverHelpers {
_RenderSliverStickyBar({
RenderSliver? child,
RenderObject? header
}) {
this.header = header as RenderBox?;
this.child = child;
}
RenderBox? _header;
RenderBox? get header => _header;
set header(RenderBox? value) {
if (_header != null) {
dropChild(_header!);
}
_header = value;
if (_header != null) {
adoptChild(_header!);
}
}
RenderSliver? _child;
RenderSliver? get child => _child;
set child(RenderSliver? value) {
if (_child != null) {
dropChild(_child!);
}
_child = value;
if (_child != null) {
adoptChild(_child!);
}
}
double get headerExtent {
if (header == null) {
return 0.0;
}
assert(header!.hasSize);
switch (constraints.axis) {
case Axis.vertical:
return header!.size.height;
case Axis.horizontal:
return header!.size.width;
}
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData) {
child.parentData = SliverPhysicalParentData();
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_header != null) {
_header!.attach(owner);
}
if (_child != null) {
_child!.attach(owner);
}
}
@override
void detach() {
super.detach();
if (_header != null) {
_header!.detach();
}
if (_child != null) {
_child!.detach();
}
}
@override
void redepthChildren() {
if (_header != null) {
redepthChild(_header!);
}
if (_child != null) {
redepthChild(_child!);
}
}
@override
void visitChildren(RenderObjectVisitor visitor) {
if (_header != null) {
visitor(_header!);
}
if (_child != null) {
visitor(_child!);
}
}
@override
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
final childParentData = child.parentData as SliverPhysicalParentData;
childParentData.applyPaintTransform(transform);
}
@override
void performLayout() {
if(header == null && child == null) {
geometry = SliverGeometry.zero;
return;
}
if (header != null) {
header!.layout(
constraints.asBoxConstraints(),
parentUsesSize: true,
);
}
}
}
定义Element,初始化SliverStickyBar传入的header和sliver对应的RenderObject
dart
class _SliverStickyBarRenderObjectElement extends RenderObjectElement {
_SliverStickyBarRenderObjectElement(SliverStickyBar super.widget);
@override
SliverStickyBar get widget => super.widget as SliverStickyBar;
Element? _header;
static final Object _headerSlot = Object();
Element? _child;
@override
void visitChildren(ElementVisitor visitor) {
if (_header != null) {
visitor(_header!);
}
if (_child != null) {
visitor(_child!);
}
}
@override
void forgetChild(Element child) {
super.forgetChild(child);
if (child == _header) {
_header = null;
}
if (child == _child) {
_child = null;
}
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_header = updateChild(_header, widget.header, _headerSlot);
_child = updateChild(_child, widget.sliver, null);
}
@override
void update(SliverStickyBar newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_header = updateChild(_header, widget.header, _headerSlot);
_child = updateChild(_child, widget.sliver, null);
}
@override
void insertRenderObjectChild(RenderObject child, Object? slot) {
final renderObject = this.renderObject as _RenderSliverStickyBar;
if (slot == _headerSlot) {
renderObject.header = child as RenderBox?;
} else {
renderObject.child = child as RenderSliver?;
}
assert(renderObject == this.renderObject);
}
@override
void moveRenderObjectChild(RenderObject child, Object? slot, Object? newSlot) {
assert(false);
}
@override
void removeRenderObjectChild(RenderObject child, Object? slot) {
final renderObject = this.renderObject as _RenderSliverStickyBar;
if (renderObject.header == child) {
renderObject.header = null;
} else {
renderObject.child = null;
}
assert(renderObject == this.renderObject);
}
}
把Widget、RenderObject、Element关联上
dart
class SliverStickyBar extends RenderObjectWidget {
final Widget? header;
final Widget? sliver;
const SliverStickyBar({
super.key,
this.header,
this.sliver
});
@override
RenderObjectElement createElement() => _SliverStickyBarRenderObjectElement(this);
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverStickyBar();
}
此时,header并不会被绘制出来,和我们直接使用SliverList没有任何区别,因此,之后我们需要修改_RenderSliverStickyBar重新对header和sliver进行布局和绘制。
三.确定应该传给child的SliverConstraints,先不考虑吸顶
-
确定
scrollOffsetscrollOffset是当前Sliver划出屏幕的偏移量大小,取值不能小于0。
所以,传递给child约束的
scrollOffset为:dartmath.max(0, constraints.scrollOffset - headerExtent); -
确定
cacheOriginViewport会按照cachExtent的值分成两份与渲染区域,一份在上边,一份在下边
cacheOrigin是当前Sliver在预渲染区域中的起点,取值范围[-Viewport.cachExtent, 0]。
所以,传递给child约束的
cacheOrigin为:dartmath.min(0, constraints.cacheOrigin + headerExtent); -
确定
remainingPaintExtentremainingPaintExtent是当前Sliver在Viewport中的最大可绘制的大小,取值范围[0, constraints.viewportMainAxisExtent]。
所以传递给child约束的
remainingPaintExtent为:dartdouble headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent); double remainingPaintExtent = constraints.remainingPaintExtent - headerPaintExtent; -
确定
remainingCacheExtentremainingCacheExtent是当前Sliver预渲染区域大小,取值范围为[constraints.viewportMainAxisExtent, constraints.viewportMainAxisExtent + 2*Viewport.cachExtent]。
所以传递给child约束的
remainingCacheExtent为:dartdouble headerCacheExtent = calculateCacheOffset(constraints, from: 0.0, to: headerExtent); double remainingCacheExtent = constraints.remainingCacheExtent - headerCacheExtent; -
由此可确定child的layout过程,得到child的布局信息
childLayoutGeometrydartvoid performLayout() { ... if(child != null) { final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent); final double headerCacheExtent = calculateCacheOffset(constraints, from: 0.0, to: headerExtent); child!.layout(constraints.copyWith( scrollOffset: math.max(0, constraints.scrollOffset - headerExtent), cacheOrigin: math.min(0, constraints.cacheOrigin + headerExtent), remainingPaintExtent: constraints.remainingPaintExtent - headerPaintExtent, remainingCacheExtent: constraints.remainingCacheExtent - headerCacheExtent ), parentUsesSize: true); final childLayoutGeometry = child!.geometry!; if (childLayoutGeometry.scrollOffsetCorrection != null) { geometry = SliverGeometry( scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection, ); return; } ... } ... }
四.根据child的布局信息确定自身的布局信息,先不考虑吸顶
-
确定
scrollExtentscrollExtent是当前Sliver主轴方向上的固有高度,不会随滚动发生变化。所以自身布局信息的
scrollExtent等于child布局信息的scrollExtent加上header在主轴方向上的尺寸,为:dartchildLayoutGeometry.scrollExtent + headerExtent; -
确定
maxPaintExtentmaxPaintExtent是当前Sliver在主轴方向上的最大可绘制大小。取值不能大于constraints.remainingPaintExtent。所以自身布局信息的
maxPaintExtent等于child布局信息的maxPaintExtent加上header在主轴方向上的尺寸,为:dartmath.min(childLayoutGeometry.maxPaintExtent + headerExtent, constraints.remainingPaintExtent); -
确定
paintExtentpaintExtent是当前Sliver在可视区域内主轴方向上的绘制大小。滚动到顶部后,开始变小,直到等于 0,并且不能大于maxPaintExtent。所以自身布局信息的
paintExtent等于child布局信息的paintExtent加上header在可见区域内的绘制大小,为:dartmath.min(childLayoutGeometry.paintExtent + headerPaintExtent, maxPaintExtent); -
确定
layoutExtentlayoutExtent是当前Sliver的top到下一个Sliver的top之间的距离,取值不能大于paintExtent。所以自身布局信息的
layoutExtent等于child布局信息的layoutExtent加上header在可见区域内的绘制大小,为:dartmath.min(childLayoutGeometry.layoutExtent + headerPaintExtent, paintExtent); -
确定
cacheExtentcacheExtent是当前Sliver在预渲染区域中占用的大小,取值不能大于constraints.remainingCacheExtent。所以自身布局信息的
cacheExtent等于child布局信息的cacheExtent加上header在预渲染区域内占用的大小,为:dartmath.min(childLayoutGeometry.cacheExtent + headerCacheExtent, constraints.remainingCacheExtent); -
确定
hitTestExtenthitTestExtent是当前Sliver点击范围在主轴方向的高度。所以自身布局信息的
hitTestExtent等于child布局信息的hitTestExtent加上header在可见区域内的绘制大小,为:dartchildLayoutGeometry.hitTestExtent + headerPaintExtent; -
由此可确定自身的布局信息
geometry。dartvoid performLayout() { ... final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent); final double headerCacheExtent = calculateCacheOffset(constraints, from: 0.0, to: headerExtent); if(child == null) { geometry = SliverGeometry( scrollExtent: headerExtent, maxPaintExtent: headerExtent, paintExtent: headerPaintExtent, cacheExtent: headerCacheExtent, hitTestExtent: headerPaintExtent ); } else { ... final double maxPaintExtent = math.min(childLayoutGeometry.maxPaintExtent + headerExtent, constraints.remainingPaintExtent); final double paintExtent = math.min(childLayoutGeometry.paintExtent + headerPaintExtent, maxPaintExtent); geometry = SliverGeometry( scrollExtent: childLayoutGeometry.scrollExtent + headerExtent, maxPaintExtent: maxPaintExtent, paintExtent: paintExtent, layoutExtent: math.min(childLayoutGeometry.layoutExtent + headerPaintExtent, paintExtent), cacheExtent: math.min(childLayoutGeometry.cacheExtent + headerCacheExtent, constraints.remainingCacheExtent), hitTestExtent: childLayoutGeometry.hitTestExtent + headerPaintExtent, hasVisualOverflow: childLayoutGeometry.hasVisualOverflow, ); } ... }
五.确定paintOffset并保存到 parentData 中,待paint时使用,先不考虑吸顶。
-
确定child相对于
ViewPort起始位置的paintOffset主轴方向为
down或right时,以down为例,最后的paintOffset需要在原有基础上加上headerPaintExtent:
主轴方向为
up或left时,以up为例,最后的paintOffset与原来的一样:
dartvoid setChildParentData(RenderObject child, SliverConstraints constraints, SliverGeometry geometry) { final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent); switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { case AxisDirection.up: childParentData.paintOffset = Offset.zero; case AxisDirection.right: childParentData.paintOffset = Offset(headerPaintExtent, 0.0); case AxisDirection.down: childParentData.paintOffset = Offset(0.0, headerPaintExtent); case AxisDirection.left: childParentData.paintOffset = Offset.zero; } } -
确定
header相对于ViewPort起始位置的paintOffset主轴方向为
down或right时,以down为例,当前Sliver滚动出ViewPort多少,header的绘制起始位置就在原有基础上减多少,所以为-constraints.scrollOffset。主轴方向为
up或left时,以up为例,header绘制的起始位置需要在原有基础上加上:paintExtent + scrollOffset - headerExtent
dartvoid setHeaderParentData(RenderObject header, SliverConstraints constraints, SliverGeometry geometry) { final SliverPhysicalParentData headerParentData = header.parentData! as SliverPhysicalParentData; switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { case AxisDirection.up: headerParentData.paintOffset = Offset(0.0, geometry.paintExtent + constraints.scrollOffset - headerExtent); case AxisDirection.right: headerParentData.paintOffset = Offset(-constraints.scrollOffset, 0.0); case AxisDirection.down: headerParentData.paintOffset = Offset(0.0, -constraints.scrollOffset); case AxisDirection.left: headerParentData.paintOffset = Offset(geometry.paintExtent + constraints.scrollOffset - headerExtent, 0.0); } } -
最后调用
setChildParentData和setHeaderParentData将paintOffset并保存到 parentData 中。dartvoid performLayout() { ... final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent); final double headerCacheExtent = calculateCacheOffset(constraints, from: 0.0, to: headerExtent); if(child == null) { geometry = SliverGeometry( scrollExtent: headerExtent, maxPaintExtent: headerExtent, paintExtent: headerPaintExtent, cacheExtent: headerCacheExtent, hitTestExtent: headerPaintExtent ); } else { ... setChildParentData(child!, constraints, geometry!); } if(header != null) { setHeaderParentData(header!, constraints, geometry!); } }
六.绘制child和header
-
重写
paint方法dart@override void paint(PaintingContext context, Offset offset) { if (geometry!.visible) { if (child != null && child!.geometry!.visible) { final childParentData = child!.parentData as SliverPhysicalParentData; context.paintChild(child!, offset + childParentData.paintOffset); } if(header != null) { final headerParentData = header!.parentData as SliverPhysicalParentData; context.paintChild(header!, offset + headerParentData.paintOffset); } } }
七.实现吸顶上推。
-
确定吸顶情况下,
header的起始绘制位置。以AxisDirection.down为例:
所以,吸顶情况下,header的起始位置需要在原有基础上加上:
dartmath.min(constraints.overlap, geometry.scrollExtent - headerExtent - constraints.scrollOffset)所以有:
dartvoid setHeaderParentData(RenderObject header, SliverConstraints constraints, SliverGeometry geometry) { final SliverPhysicalParentData headerParentData = header.parentData! as SliverPhysicalParentData; ///不吸顶时 // final double headerOrigin = -constraints.scrollOffset; final double headerOrigin = math.min(constraints.overlap, geometry.scrollExtent - headerExtent - constraints.scrollOffset); switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { case AxisDirection.up: headerParentData.paintOffset = Offset(0.0, geometry.paintExtent - headerOrigin - headerExtent); case AxisDirection.right: headerParentData.paintOffset = Offset(headerOrigin, 0.0); case AxisDirection.down: headerParentData.paintOffset = Offset(0.0, headerOrigin); case AxisDirection.left: headerParentData.paintOffset = Offset(geometry.paintExtent - headerOrigin - headerExtent, 0.0); } } -
确定传给child约束的
overlapoverlap是上一个 sliver 覆盖当前 sliver 的大小dartchild!.layout(constraints.copyWith( ... overlap: math.min(headerExtent, constraints.scrollOffset) + constraints.overlap, ), parentUsesSize: true); -
确定自身布局信息的
maxScrollObstructionExtentdartgeometry = SliverGeometry( ... maxScrollObstructionExtent: headerExtent ); -
至此,吸顶以及上推效果完成。
八.修复header和child的点击事件
dart
@override
double childMainAxisPosition(covariant RenderObject child) {
if (child == this.child) {
return calculatePaintOffset(constraints, from: 0.0, to: headerExtent);
}
if(child == header) {
final double headerOrigin = math.min(constraints.overlap, geometry!.scrollExtent - headerExtent - constraints.scrollOffset);
return headerOrigin;
}
return 0.0;
}
@override
bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
assert(geometry!.hitTestExtent > 0.0);
if(header != null && mainAxisPosition <= (constraints.overlap + headerExtent)) {
final didHitHeader = hitTestBoxChild(
BoxHitTestResult.wrap(SliverHitTestResult.wrap(result)),
header!,
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
);
return didHitHeader;
} else if (child != null) {
return child!.hitTest(
result,
mainAxisPosition: mainAxisPosition - childMainAxisPosition(child!),
crossAxisPosition: crossAxisPosition
);
}
return false;
}
九.完整代码
dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
class SliverStickyBar extends RenderObjectWidget {
final Widget? header;
final Widget? sliver;
const SliverStickyBar({
super.key,
this.header,
this.sliver
});
@override
RenderObjectElement createElement() => _SliverStickyBarRenderObjectElement(this);
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverStickyBar();
}
class _SliverStickyBarRenderObjectElement extends RenderObjectElement {
_SliverStickyBarRenderObjectElement(SliverStickyBar super.widget);
@override
SliverStickyBar get widget => super.widget as SliverStickyBar;
Element? _header;
static final Object _headerSlot = Object();
Element? _child;
@override
void visitChildren(ElementVisitor visitor) {
if (_header != null) {
visitor(_header!);
}
if (_child != null) {
visitor(_child!);
}
}
@override
void forgetChild(Element child) {
super.forgetChild(child);
if (child == _header) {
_header = null;
}
if (child == _child) {
_child = null;
}
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_header = updateChild(_header, widget.header, _headerSlot);
_child = updateChild(_child, widget.sliver, null);
}
@override
void update(SliverStickyBar newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_header = updateChild(_header, widget.header, _headerSlot);
_child = updateChild(_child, widget.sliver, null);
}
@override
void insertRenderObjectChild(RenderObject child, Object? slot) {
final renderObject = this.renderObject as _RenderSliverStickyBar;
if (slot == _headerSlot) {
renderObject.header = child as RenderBox?;
} else {
renderObject.child = child as RenderSliver?;
}
assert(renderObject == this.renderObject);
}
@override
void moveRenderObjectChild(RenderObject child, Object? slot, Object? newSlot) {
assert(false);
}
@override
void removeRenderObjectChild(RenderObject child, Object? slot) {
final renderObject = this.renderObject as _RenderSliverStickyBar;
if (renderObject.header == child) {
renderObject.header = null;
} else {
renderObject.child = null;
}
assert(renderObject == this.renderObject);
}
}
class _RenderSliverStickyBar extends RenderSliver with RenderSliverHelpers {
_RenderSliverStickyBar({
RenderSliver? child,
RenderObject? header
}) {
this.header = header as RenderBox?;
this.child = child;
}
RenderBox? _header;
RenderBox? get header => _header;
set header(RenderBox? value) {
if (_header != null) {
dropChild(_header!);
}
_header = value;
if (_header != null) {
adoptChild(_header!);
}
}
RenderSliver? _child;
RenderSliver? get child => _child;
set child(RenderSliver? value) {
if (_child != null) {
dropChild(_child!);
}
_child = value;
if (_child != null) {
adoptChild(_child!);
}
}
double get headerExtent {
if (header == null) {
return 0.0;
}
assert(header!.hasSize);
switch (constraints.axis) {
case Axis.vertical:
return header!.size.height;
case Axis.horizontal:
return header!.size.width;
}
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData) {
child.parentData = SliverPhysicalParentData();
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_header != null) {
_header!.attach(owner);
}
if (_child != null) {
_child!.attach(owner);
}
}
@override
void detach() {
super.detach();
if (_header != null) {
_header!.detach();
}
if (_child != null) {
_child!.detach();
}
}
@override
void redepthChildren() {
if (_header != null) {
redepthChild(_header!);
}
if (_child != null) {
redepthChild(_child!);
}
}
@override
void visitChildren(RenderObjectVisitor visitor) {
if (_header != null) {
visitor(_header!);
}
if (_child != null) {
visitor(_child!);
}
}
@override
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
final childParentData = child.parentData as SliverPhysicalParentData;
childParentData.applyPaintTransform(transform);
}
@override
void performLayout() {
if(header == null && child == null) {
geometry = SliverGeometry.zero;
return;
}
if (header != null) {
header!.layout(
constraints.asBoxConstraints(),
parentUsesSize: true,
);
}
final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent);
final double headerCacheExtent = calculateCacheOffset(constraints, from: 0.0, to: headerExtent);
if(child == null) {
geometry = SliverGeometry(
scrollExtent: headerExtent,
maxPaintExtent: headerExtent,
paintExtent: headerPaintExtent,
cacheExtent: headerCacheExtent,
hitTestExtent: headerPaintExtent
);
} else {
child!.layout(constraints.copyWith(
scrollOffset: math.max(0, constraints.scrollOffset - headerExtent),
cacheOrigin: math.min(0, constraints.cacheOrigin + headerExtent),
remainingPaintExtent: constraints.remainingPaintExtent - headerPaintExtent,
remainingCacheExtent: constraints.remainingCacheExtent - headerCacheExtent,
overlap: math.min(headerExtent, constraints.scrollOffset) + constraints.overlap,
), parentUsesSize: true);
final childLayoutGeometry = child!.geometry!;
if (childLayoutGeometry.scrollOffsetCorrection != null) {
geometry = SliverGeometry(
scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
);
return;
}
final double maxPaintExtent = math.min(childLayoutGeometry.maxPaintExtent + headerExtent, constraints.remainingPaintExtent);
final double paintExtent = math.min(childLayoutGeometry.paintExtent + headerPaintExtent, maxPaintExtent);
geometry = SliverGeometry(
scrollExtent: childLayoutGeometry.scrollExtent + headerExtent,
maxPaintExtent: maxPaintExtent,
paintExtent: paintExtent,
layoutExtent: math.min(childLayoutGeometry.layoutExtent + headerPaintExtent, paintExtent),
cacheExtent: math.min(childLayoutGeometry.cacheExtent + headerCacheExtent, constraints.remainingCacheExtent),
hitTestExtent: childLayoutGeometry.hitTestExtent + headerPaintExtent,
hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
maxScrollObstructionExtent: headerExtent
);
setChildParentData(child!, constraints, geometry!);
}
if(header != null) {
setHeaderParentData(header!, constraints, geometry!);
}
}
void setChildParentData(RenderObject child, SliverConstraints constraints, SliverGeometry geometry) {
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
final double headerPaintExtent = calculatePaintOffset(constraints, from: 0.0, to: headerExtent);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
childParentData.paintOffset = Offset.zero;
case AxisDirection.right:
childParentData.paintOffset = Offset(headerPaintExtent, 0.0);
case AxisDirection.down:
childParentData.paintOffset = Offset(0.0, headerPaintExtent);
case AxisDirection.left:
childParentData.paintOffset = Offset.zero;
}
}
void setHeaderParentData(RenderObject header, SliverConstraints constraints, SliverGeometry geometry) {
final SliverPhysicalParentData headerParentData = header.parentData! as SliverPhysicalParentData;
///不吸顶时
// final double headerOrigin = -constraints.scrollOffset;
final double headerOrigin = math.min(constraints.overlap, geometry.scrollExtent - headerExtent - constraints.scrollOffset);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
headerParentData.paintOffset = Offset(0.0, geometry.paintExtent - headerOrigin - headerExtent);
case AxisDirection.right:
headerParentData.paintOffset = Offset(headerOrigin, 0.0);
case AxisDirection.down:
headerParentData.paintOffset = Offset(0.0, headerOrigin);
case AxisDirection.left:
headerParentData.paintOffset = Offset(geometry.paintExtent - headerOrigin - headerExtent, 0.0);
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (geometry!.visible) {
if (child != null && child!.geometry!.visible) {
final childParentData = child!.parentData as SliverPhysicalParentData;
context.paintChild(child!, offset + childParentData.paintOffset);
}
if(header != null) {
final headerParentData = header!.parentData as SliverPhysicalParentData;
context.paintChild(header!, offset + headerParentData.paintOffset);
}
}
}
@override
double childMainAxisPosition(covariant RenderObject child) {
if (child == this.child) {
return calculatePaintOffset(constraints, from: 0.0, to: headerExtent);
}
if(child == header) {
final double headerOrigin = math.min(constraints.overlap, geometry!.scrollExtent - headerExtent - constraints.scrollOffset);
return headerOrigin;
}
return 0.0;
}
@override
bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
assert(geometry!.hitTestExtent > 0.0);
if(header != null && mainAxisPosition <= (constraints.overlap + headerExtent)) {
final didHitHeader = hitTestBoxChild(
BoxHitTestResult.wrap(SliverHitTestResult.wrap(result)),
header!,
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
);
return didHitHeader;
} else if (child != null) {
return child!.hitTest(
result,
mainAxisPosition: mainAxisPosition - childMainAxisPosition(child!),
crossAxisPosition: crossAxisPosition
);
}
return false;
}
}