Flutter 组件集录 | FlexibleSpaceBar 组件是怎么炼成的
1. 前言: 问题引入
FlexibleSpaceBar
是一个和 SliverAppBar
共生的组件,一般不单独使用。如下所示,在滑动的过程中 FlexibleSpaceBar
的 title
区域会有缩放的效果。仔细观察会发现,这个缩放是对整个 title
组件生效的,比如 全部笔记
和 1590 项笔记
文字都有缩放效果。
上滑 | 下滑 |
---|---|
那问题来了,如何只让 全部笔记
的标题有缩放效果,下面的副标题在滑动过程中一直保持不变呢?或者我们如何监听滑动的分率,实现一些自定义的变换效果呢?这就要从 FlexibleSpaceBar
组件的源码中寻找答案,看它是否支持这种效果,如果不支持,我们该如何自己实现。
2. 探索: FlexibleSpaceBar 组件的标题如何实现缩放?
在滑动中,title
组件的内容有缩放效果是实事,这说明在组件的 构建逻辑
中必然存在缩放的变换。所以摆在我们面前的第一个问题是:
FlexibleSpaceBar 组件的标题如何
实现缩放
的?
FlexibleSpaceBar
是一个 StatefulWidget
,它依赖于 _FlexibleSpaceBarState
状态类来构建组件。所以缩放变换的逻辑也应该在 _FlexibleSpaceBarState#build
方法中:
class FlexibleSpaceBar extends StatefulWidget {
//略...
@override
State<FlexibleSpaceBar> createState() => _FlexibleSpaceBarState();
}
如下所示,在 _FlexibleSpaceBarState#build
中通过 Transform
实现对标题的缩放,缩放使用的矩阵对象是 scaleTransform
:
3. 探索: FlexibleSpaceBar 组件是如何感知滑动数据的?
从上面效果中可以看出,SliverAppBar
滑动距离和剩余空间的比值,会作为缩放数值的依据。那么摆在我们面前的第一个问题是:
FlexibleSpaceBar 组件是如何感知
滑动数据
的?
如下,是 scaleTransform
矩阵的生成过程,其中的缩放值是一个 补间值
,起始值是 widget.expandedTitleScale
,也就是初始的缩放值,默认为 1.5
。也就是说默认情况下,title
组件会被放大 1.5
倍,然后根据 t
值的大小,向 1
进行补间计算。
比如 t = 0.5
时, scaleValue
的值就在 1.5
和 1
的正中间,即 1 + (1.5-1)*0.5 = 1.25
,以此类推。现在的关键就是这个 t
是如何计算的,滑动的数据信息是谁,通过什么渠道 "贩卖的"
:
---->[_FlexibleSpaceBarState#build]----
final double scaleValue = Tween<double>(begin: widget.expandedTitleScale, end: 1.0).transform(t);
final Matrix4 scaleTransform = Matrix4.identity()
..scale(scaleValue, scaleValue, 1.0);
如下是 t
值的计算过程,可以看出滑动的数据是由 FlexibleSpaceBarSettings
组件记录的,毫无疑问,它是一个 InheritedWidget
, 子树可以通过它访问存储的信息,本质上和 Theme
一族是一样的。当 t
为 0
时,表示完全展开的状态;t
为 1
时,表示 SliverAppBar
剩余空间完全收起:
4. 探索: FlexibleSpaceBarSettings 组件在何时入树的?
现在进入最后一个问题,FlexibleSpaceBarSettings
是何时进入组件树的,其中记录的信息又是如何放入其中的。在 FlexibleSpaceBar
中有一个静态方法,如下所示,会在入参 child
组件的上层包裹住 FlexibleSpaceBarSettings
,其中持有的信息都是通过入参传递的。如下其注释中也有提及:
也就是说,觉得存入的滑动信息是 FlexibleSpaceBar#createSettings
方法的调用者。到这就好办了,想要知道方法何时被调用的,调试来帮忙。
如下可以看出,是在 _SliverAppBarDelegate
中被调用的。它最终会作为 SliverPersistentHeader
组件的代理类,用于 _SliverAppBarState
的构建逻辑中。
到这里就万事俱备了,从上滑的分析可以知道,滑动的信息从 FlexibleSpaceBarSettings
中可以获取到。另外,FlexibleSpaceBar
状态类的构建逻辑在处理变换时是比较死的,没有暴露给使用者可操作性的空间。所以需要实现一些自定义的变换效果,可以 copy
一下 FlexibleSpaceBar
的源码,来魔改一下。
5. 支持固定副标题
现在来实现一下固定副标题,效果如下,在滑动过程中 1590 项笔记
副标题一直保持不变。
上滑 | 下滑 |
---|---|
现在将源码拷贝一份,命名为 DiyFlexibleSpaceBar
,在其中定义 fixedSubtitle
构造入参,作为副标题的插槽。
final Widget? fixedSubtitle;
然后在源码组件状态类的 构建逻辑
中,通过 Column
提供一个插槽即可,如下所示:
然后使用 DiyFlexibleSpaceBar
组件,提供 fixedSubtitle
组件即可:
6. 将滑动分度值暴露出去
有时候我们期望监听到滑动的进度,从而可以在 DiyFlexibleSpaceBar
外部,处理自定义的滑动效果。比如下图中,标题的右侧有一个小风车,可以随着滑动的进度旋转:
上滑 | 下滑 |
---|---|
首先,定义一个 FractionalBuilder
的函数类型,用于回调 t
数值来返回组件 Widget
; 然后在 DiyFlexibleSpaceBar
中定义 titleIconBuilder
成员:
typedef FractionalBuilder = Widget Function(double t);
class DiyFlexibleSpaceBar extends StatefulWidget {
const DiyFlexibleSpaceBar({
/// 略...
this.titleIconBuilder,
final FractionalBuilder? titleIconBuilder;
在构建逻辑在触发 titleIconBuilder
构造组件,参数 t
就可以从这里暴露出去:
然后外界可以感知到 t
的存在,以此控制风车旋转角度即可。关于风车的绘制,参考 《Flutter 绘制集录 | 第四画 - 风车》
Flutter
提供的组件,只能满足大多数的使用场景,对于特别的需求,可能是不支持的。这时就是考验一位编程者能力的时机了,能看懂源码,并能以此进行改进,来完成需求就显得极为重要。希望大家可以在日常开发中 多做推敲
,而不是遇事就 伸手去要
。 那本文就到这里,谢谢观看 ~
转载自:https://juejin.cn/post/7164563081056485383