赞
踩
比较简单,直接上代码,主要使用CustomScrollView和SliverToBoxAdapter实现
_buildSection(String title) { return SliverToBoxAdapter( child: RepaintBoundary( child: Container( height: 50, color: Colors.brown, alignment: Alignment.center, child: Text(title), ), ) ); } _buildItem(String title) { return SliverToBoxAdapter( child: RepaintBoundary( child: Container( padding: const EdgeInsets.symmetric(horizontal: 15), height: 70, color: Colors.cyanAccent, alignment: Alignment.centerLeft, child: Text(title), ), ) ); } CustomScrollView( slivers: [ _buildSection("蜀汉五虎将"), _buildItem("关羽"), _buildItem("张飞"), _buildItem("赵云"), _buildItem("马超"), _buildItem("黄忠"), _buildSection("虎贲双雄"), _buildItem("许褚"), _buildItem("典韦"), _buildSection("五子良将"), _buildItem("张辽"), _buildItem("乐进"), _buildItem("于禁"), _buildItem("张郃"), _buildItem("徐晃"), _buildSection("八虎骑"), _buildItem("夏侯惇"), _buildItem("夏侯渊"), _buildItem("曹仁"), _buildItem("曹纯"), _buildItem("曹洪"), _buildItem("曹休"), _buildItem("夏侯尚"), _buildItem("曹真") ], )
可以使用Flutter提供的SliverPersistentHeader组件实现,在使用SliverPersistentHeader时要求我们明确指定子控件的高度,不支持吸顶上推效果,使用起来不够灵活,所以我们参考并结合SliverToBoxAdapter和SliverPersistentHeader源码,自己实现一个自适应高度的吸顶Sliver组件,并在此基础上一步步实现吸顶上推效果。
class StickySliverToBoxAdapter extends SingleChildRenderObjectWidget {
const StickySliverToBoxAdapter({
super.key,
super.child
});
@override
RenderObject createRenderObject(BuildContext context) => _StickyRenderSliverToBoxAdapter();
}
SingleChildRenderObjectWidget类要求我们自己实现createRenderObject方法,返回一个RenderObject对象,而对于一个S liver组件而言,这个RenderObject必须是RenderSilver的子类。
class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
@override
void performLayout() {
// TODO: implement performLayout
}
}
RenderSliverSingleBoxAdapter要求子类实现performLayout方法,performLayout会对widegt的布局和绘制做控制,实现吸顶效果的关键就在于performLayout方法的实现。先依次看下SliverToBoxAdapter和SliverPersistentHeader对应RenderObject的performLayout相关方法的实现。
@override void performLayout() { if (child == null) { geometry = SliverGeometry.zero; return; } final SliverConstraints constraints = this.constraints; //摆放子View,并把constraints传递给子View child!.layout(constraints.asBoxConstraints(), parentUsesSize: true); //获取子View在滑动主轴方向的尺寸 final double childExtent; switch (constraints.axis) { case Axis.horizontal: childExtent = child!.size.width; case Axis.vertical: childExtent = child!.size.height; } final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent); final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent); assert(paintedChildSize.isFinite); assert(paintedChildSize >= 0.0); //更新SliverGeometry geometry = SliverGeometry( scrollExtent: childExtent, paintExtent: paintedChildSize, cacheExtent: cacheExtent, maxPaintExtent: childExtent, hitTestExtent: paintedChildSize, hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0, ); //更新paintOffset,由滑动偏移量constraints.scrollOffset决定 setChildParentData(child!, constraints, geometry!); }
SliverPersistentHeader的performLayout方法中调用了updateGeometry方法去更新geometry,而吸顶的关键就在updateGeometry方法中,也就是paintOrigin的值。constraints.overlap的值代表前一个Sliver和当前Sliver被覆盖部分的高度。
@override double updateGeometry() { final double minExtent = this.minExtent; final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ? minExtent : constraints.remainingPaintExtent; final double maxExtent = this.maxExtent; final double paintExtent = maxExtent - _effectiveScrollOffset!; final double clampedPaintExtent = clampDouble(paintExtent, minAllowedExtent, constraints.remainingPaintExtent, ); final double layoutExtent = maxExtent - constraints.scrollOffset; final double stretchOffset = stretchConfiguration != null ? constraints.overlap.abs() : 0.0; geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), paintExtent: clampedPaintExtent, layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent), maxPaintExtent: maxExtent + stretchOffset, maxScrollObstructionExtent: minExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); return 0.0; }
直接把上面updateGeometry中设置SliverGeometry的代码拷贝到_StickyRenderSliverToBoxAdapter#performLayout实现中,maxExtent和minExtent这两个值是由SliverPersistentHeader传入的SliverPersistentHeaderDelegate对象提供的。这里可以自己去看SliverPersistentHeaderDelegate的源码,就不多废话了。我们只需要把maxExtent和minExtent这两个值都改为子控件在主轴方向的尺寸大小即可。
_buildSection(String title) { return StickySliverToBoxAdapter( child: RepaintBoundary( child: Container( height: 50, color: Colors.brown, alignment: Alignment.center, child: Text(title), ), ) ); } class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { @override void performLayout() { if (child == null) { geometry = SliverGeometry.zero; return; } final SliverConstraints constraints = this.constraints; //摆放子View,并把constraints传递给子View child!.layout(constraints.asBoxConstraints(), parentUsesSize: true); //获取子View在滑动主轴方向的尺寸 final double childExtent; switch (constraints.axis) { case Axis.horizontal: childExtent = child!.size.width; case Axis.vertical: childExtent = child!.size.height; } final double minExtent = childExtent; final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ? minExtent : constraints.remainingPaintExtent; final double maxExtent = childExtent; final double paintExtent = maxExtent; final double clampedPaintExtent = clampDouble(paintExtent, minAllowedExtent, constraints.remainingPaintExtent, ); final double layoutExtent = maxExtent - constraints.scrollOffset; geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: min(constraints.overlap, 0.0), paintExtent: clampedPaintExtent, layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent), maxPaintExtent: maxExtent, maxScrollObstructionExtent: minExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); } }
仔细看上面的效果,貌似只有第一个Sliver吸顶了,我们把分组item的背景改成透明的,再来看看效果,就知道怎么回事了 本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。