2024年 第21篇
关键组件
关键内容 TLDR
实践过程
先看效果
CustomScrollView
, SliverOverlapAbsorber
, SliverPersistentHeader
SliverOverlapAbsorber
包住 pinned
为 true 的组件 可以被CustomScrollView
忽略高度。
以下的全部内容都为了阐述上面这句话。初阶 Flutter 开发知道这句话或许可以节省数天研究时间。
pinned
为 true 的组件:SliverPersistentHeader
和 SliverAppBar
被 CustomScrollView
忽略高度可以使的其他的 Sliver
组件与 pinned
为 true
的组件有重叠效果。
先进行定义 TopBar:顶部栏, TabBar:吸顶的标签栏。对上说效果进行说明:刚进入页面时,TopBar 下是透明的,一般业务上用来显示 banner 或者重要的广告。向上滑动页面,TabBar 滑动到顶部时吸顶 并且位于 TopBar 以下。代码的结构如下
CustomScrollView(
controller: _controller,
slivers: <Widget>[
/// 顶部栏,固定在顶部
SliverOverlapAbsorber(
handle: SliverOverlapAbsorberHandle(),
sliver: SliverAppBar(
pinned: true:
...
)
),
/// 比如 Banner,金刚区等
SliverToBoxAdapter(
child: Image.asset(
"./assets/images/sky.webp",
fit: BoxFit.cover,
),
),
/// 其他一些 Sliver 组件
...
///吸顶的标签栏
SliverPersistentHeader(
pinned: true,
delegate: TabBarDelegate()
),
/// 列表
SliverList(),
],
)
CutomScrollView
会忽略 SliverOverlapAbsorber
组件高度,使得之后的组件计算开始布局的位置从SliverOverlapAbsorber
的上边缘开始计算。这样当 TopBar 透明时可以看到完整的 Banner 大图 。如何使得 TabBar 吸顶并且位于 TopBar 下面呢,可以使用 SliverAppBar
作为 TopBar, 使用 SliverPersistentHeader
作为 TabBar。将两个组件中的 pinned
属性都设置为 true
之后,当页面上滑时当 TabBar 滑到 TopBar 下方时便会固定住不再向上滑动。
换个便于记忆的简单说法是,在 CustomScrollView
中,如果想要忽略高度用 SliverOverlapAbsorber
,想要吸顶就用 SliverPersistentHeader
。
「 TopBar 底部用到大图,为什么不用 SliverAppBar 的 flexibleSpace 属性?」
使用 SliverAppBar 的特性需要修改属性较多,而且把 顶部 Banner 与 SliverAppBar 混在一起并不优雅。如果只能用 flexbleSpace 实现可以如此
SliverAppBar(
pinned: true,
expandedHeight: 200,
title: TopBar()
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Banner(),
),
)
「 TabBar 怎么吸顶的?」
TabBar 之所以能吸顶是使用的 SliverPersistentHeader
中使用了 RenderSliverPinnedPersistentHeader
。
class RenderSliverPinnedPersistentHeader{
void performLayout() {
...
geometry = SliverGeometry(
scrollExtent: maxExtent,
paintOrigin: constraints.overlap,
paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
layoutExtent: layoutExtent,
maxPaintExtent: maxExtent + stretchOffset,
maxScrollObstructionExtent: minExtent,
cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
);
}
...
}
吸顶关键是设置 paintOrigin
属性。paintOrigin
注释如下
/// The visual location of the first visible part of this sliver relative to
/// its layout position.
官方的注释说的比较严谨且抽象,我尝试解释下 直译是 sliver 的可见位置相对于布局位置。布局位置可以理解为 sliver 占据的空间,可见位置就是其绘制的位置。如果 paintOrigin
为 0,那么可见位置就是布局位置,就像普通的不吸顶的 sliver 一样。
paintOrigin: constraints.overlap
意为从距离布局位置顶部 constraints.overlap
的距离开始绘制。constraints.overlap
是两个页面重叠的距离,如果当前吸顶组件的上面没有其他可吸顶组件,则这个值就是 0,如何之前有其他的吸顶组件,比如 TopBar 也是吸顶组件,这个值就与 TopBar 的高度相同,如是 TabBar 可以悬停在 TopBar 下面。有时学习为了解释一个概念,又会引入了另外一个新概念,导致复杂度成倍增加,有时甚至会引入几十个甚至几百个新概念,尽管如此,坚持下去,才能真正理解知识,而不是原地空转。
有个 SliverPhysicalParentData.paintOffset
属性 与 paintOrigin 类似的概念也对 sliver 组件的绘制位置有影响。在 不吸顶的组件(如 SliverToBoxAdapter
)中 paintOffset
为 SliverConstraint.scrollOffset
在 可以悬停吸顶的组件 SliverPersistentHeader
中 paintOffset
是 Offset(0, 0)
final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
childParentData.paintOffset = Offset(0, 0);
「 为什么 SliverOverlapAbsorber 高度会被忽略?」
SliverOverlapAbsorber
实际渲染的是 RenderSliverOverlapAbsorber
。在 RenderSliverOverlapAbsorber.performLayout
中设定高度代码如下
child!.layout(constraints, parentUsesSize: true);
final SliverGeometry childLayoutGeometry = child!.geometry!;
geometry = childLayoutGeometry.copyWith(
scrollExtent: childLayoutGeometry.scrollExtent - childLayoutGeometry.maxScrollObstructionExtent,
layoutExtent: math.max(0, childLayoutGeometry.paintExtent - childLayoutGeometry.maxScrollObstructionExtent),
);
可以看到 SliverOverlapAbsorber
的高度依赖于 child
的信息 scrollExtent - maxScrollObstructionExtent
。
child
是什么?当 SliverOverlapAbsorber
中包含的 SliverAppBar
属性 pinned
设置为 true
时,child
实际为 RenderSliverPinnedPersistentHeader
void performLayout() {
...
geometry = SliverGeometry(
scrollExtent: maxExtent,
paintOrigin: constraints.overlap,
paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
layoutExtent: layoutExtent,
maxPaintExtent: maxExtent + stretchOffset,
maxScrollObstructionExtent: minExtent,
cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
);
}
可以看到 scrollExtent
是 maxExtent
, maxScrollObstructionExtent
是 minExtent
,当 SliverAppBar
不设置 expandedHeight
时 maxExtent
与 minExtent
相等,最终 SliverOverlapAbsorber
中的 geometry
中 scrollExtent
和 layoutExtent
为 0。从而 CustomScrollView 忽略了 SliverOverlapAbsorber
高度。注意 只有 SliverOverlapAbsorber 包含的 SliverAppBar
组件 pinned=true
时才会忽略 SliverAppBar 的高度,并且忽略的部分依据 maxExtent
和 minExtent
相等时才完全忽略。
重要内容总结为一句话和一些 限定语
SliverOverlapAbsorber
可以让CustomScrollView
忽略 其包含的固定组件高度SliverOverlapAbsorber
包含的SliverAppBar
需要设置为pinned=true
SliverAppBar
的maxExtent
和minExtent
相等时 整个SliverAppBar
的高度都会被忽略
记住了组件的作用在用到时就可以快速完成大部分工作了,再了解了组件原理就可以更好的完成工作了,当效果偏离预想时也可以纠偏找到正确的思路。
「三翼鸟数字化技术平台-商城」负责搭建门店数字化转型工具,包括:海尔智家体验店小程序、三翼鸟工作台APP、商家中心等产品形态,通过数字化工具,实现门店的用户上平台、交互上平台、交易上平台、交付上平台,从而助力海尔专卖店的零售转型,并实现三翼鸟店的场景创新。
_________________ END__________________