Flutter 组件集录 | AppBar 组件 - 从源码中学习
在 《全面认识 AppBar 组件 - 使用篇》 中,我们已经详细分析了 AppBar 在使用中的细节。本文将从源码的角度来分析 AppBar 的源码实现,一方面有利于进一步认识 AppBar 内部的更多细节,另一方面源码中对组件封装中的处理方式,也有很多值得我们学习的地方。
1. 为什么 AppBar 需要是 StatefulWidget ?
从 AppBar 的源码中可以看出,它继承自 StatefulWidget,其实从表现上来看 AppBar 并没有需要更改自身内部状态的需求,那它为什么要继承自 StatefulWidget 呢? AppBar 用于构建组件的状态类是 _AppBarState,所以只有通过源码,才能看出所以然。
class AppBar extends StatefulWidget implements PreferredSizeWidget {
// 略成员定义...
@override
State<AppBar> createState() => _AppBarState();
}
如下所示,在 _AppBarState 类中,会覆写 didChangeDependencies 回调,通过上下文,从上级节点中获取 ScrollNotificationObserverState 对象,进行监听触发 _handleScrollNotification 方法。覆写 dispose 回调,移除监听。由于需要感知组件状态类生命周期的回调事件,所以 AppBar 需要是 StatefulWidget 。

_AppBarState 中需要处理滑动相关的监听的通知,如果不查阅源码,肯定不知道还有这回事。另外,反过来,我们也能学到:如何在一个状态类中,监听到滑动通知的事件。 ScrollNotificationObserver 是在构建 Scaffold 组件时被嵌套进去的:

通过 of 静态方法,可以让子树沿上下文,查找到 ScrollNotificationObserverState 状态类。有很多朋友都问过如何获组件的状态类对象,其实这里已经给出方案了:通过上下文,可以获取状态类,至于其中的 of 方法然后实现的,可以自己研究一下。源码中处处都蕴含着可以学习的知识,多看多思考是没有坏处的。

2. AppBar 状态类中的滑动干了什么
下面问题来了,_AppBarState 要监听滑动做什么?在平时的滑动过程中似乎 AppBar 并没有什么和滑动相关的东西。想要知道干了什么,最好的方式自然是看源码中在滑动监听时做了什么处理,也就是 _handleScrollNotification 方法的逻辑:
如下所示,该方法中主要在维护状态类中的 _scrolledUnder 布尔型成员,并在该成员变化时,触发 setState 更新状态类。现在焦点在于 _scrolledUnder 在构建组件的过程中有什么用途?

下面来到 build 方法中,可见 _scrolledUnder 唯一的的用途是决定是非为 states 集合添加 MaterialState.scrolledUnder 元素。 scrolledUnder 是在 Flutter 2.5 中添加的新特性,作为 MaterialState 枚举中的一员。

所以它的使用方式和其他的 MaterialState 是一样的。什么? 没用过 MaterialState ,那下面来演示一下。如下所示, 通过 MaterialState 的 scrolledUnder可以实现滑动列表内容在 AppBar 之下时变换颜色:
| 标题 | |
|---|---|
![]() | ![]() |
实现方式如下,通过 MaterialStateColor.resolveWith 静态方法根据 states 集合中是否包含 scrolledUnder 来确定颜色。
AppBar(
backgroundColor: MaterialStateColor.resolveWith(
(states) => states.contains(MaterialState.scrolledUnder)
? Colors.deepPurpleAccent
: Colors.blue,
),
另外,其他的 MaterialState 元素也可以进行类似的使用,比如按钮 pressed 、disabled 状态的颜色设置。像 MaterialStateColor 这种根据 MaterialState 确定信息的类型,有个顶层抽象 MaterialStateProperty, 源码中内置了 XXXColor、XXXTextStyle、XXXBorderSide 等以供使用,理论上来说可以自定义属性的类型。你学废了吗?

enum MaterialState {
hovered,
focused,
pressed,
dragged,
selected,
scrolledUnder,
disabled,
error,
}
接下来再仔细通过调试,看一下 _handleScrollNotification 中的逻辑。如下,在向上滑动的过程中,metrics.extentBefore 是滑动的距离,准确来说是滑动距离和 minScrollExtent 的差值,只不过默认 minScrollExtent 为 0 。
---->[ScrollMetrics#extentBefore]----
double get extentBefore => math.max(pixels - minScrollExtent, 0.0);
理论上来说,可以通过增加 minScrollExtent 的值,让内容滑动到 AppBar 内部指定距离后,才触发 MaterialState 的变化。不过这个值是监听得到的,不是很好改,如果有需求的话,倒不如魔改这里的源码,比如让 metrics.extentBefore > 60 。这样滑动 60 逻辑像素后,才会添加 MaterialState.scrolledUnder 导致变色。

3. AppBar 状态类中的主题处理
在 _AppBarState#build 方法中,在一开始可以看到关于主题数据的处理。主要通过 Theme 和 AppBarTheme 两个主题来处理默认属性。比如 toolbarHeight 属性为空时,会优先使用 appBarTheme 的 toolbarHeight ,其次是 kToolbarHeight 。

从中我们可以学习到,通过 主题 来控制子树默认属性的小技巧。甚至可以自定义一些主题,包含默认数据,提供给子树节点使用。其实主题本质上介绍一种使用 InheritedWidget 实现的子树间数据共享的方式。

对于框架内置的组件,需要响应主题的变化是非常重要的。但为了适配主题,也就需要更多的代码逻辑处理,在很多内置组件的源码中,都可以看到各种 Theme 为变量提供默认值的场景。在阅读源码的过程中,这部分的处理看起来比较繁琐,如果不是研究主题对组件表现的作用,可以随便扫略一下,了解即可。

4. AppBar 状态类构建组件的细节
对一个合成组件来说,最重要的还是构建逻辑,从其中可以看到组件在界面中表现一切本质细节。比如对于 leading 组件的处理,如果 leading 为空,并且 automaticallyImplyLeading 为 true。当拥有 Drawer 时,会将 leading 赋值为 IconButton ;如果可以返回并且编译 endDrawer 会添加返回按钮:
另外,leadingWidth 属性的作用是通过施加紧约束实现的。所以,通过源码可以看到组件属性的具体作用,这样才能对其使用更加得心应手。如果把一个组件比作一头牛,那组件的构造细节就是牛的骨头和经络,就像 庖丁解牛 :依乎天理,批大郤,导大窾,因其固然。

对于 AppBar 的标题栏结构而言,主要是使用 NavigationToolbar 组件实现的,如下所示, leading 、title 、actions 都是为构造 NavigationToolbar 准备的。

另外,很多人都知道 iOS 和 android 平台中 AppBar 的标题表现不一致。本质原因如下, NavigationToolbar 的 centerMiddle 属性会根据平台来判定是否将标题居中,在 iOS/macOS 平台中,当 actions 为空或长度小于 2 时,标题会居中。如果不看源码,很少人都不知道有这个小细节。

另外 AppBar 的 bottom 属性,本质上就是通过 Column 标题栏和底栏数值排列,并没有什么神奇的东西。其中标题栏在使用能指定宽度,是依靠 ConstrainedBox 组件施加了在高度上的紧约束。

AppBar 的 flexibleSpace 属性,在构建逻辑中会通过 Stack 叠放在整个 appBar 之下。这就是为什么将 flexibleSpace 设置为图片,就能当 AppBar 主题背景图的原因。

AppBar 状态类构建时的顶层会使用 AnnotatedRegion和 Materal 进行包裹,分别处理 状态栏样式 和 Materal 相关的属性。从中可以学到,如果不想使用 AppBar,我们也可以直接使用 AnnotatedRegion 来控制该界面中的状态栏样式。

所以,一个组件的表现效果,都可以在源码的构造在中找到逻辑根源。可能有人会觉得,会用不就行了吗,为什么要研究的这么细致。良庖岁更刀,割也;族庖月更刀,折也。歌手不会唱歌,戏子不会演戏,厨子不会用刀,谬之大极。
尾声
勤小物,可治其微。有些人天天嘴上说着这个框架好,那个类库差,评头论足起来眉飞色舞,义愤填膺。写起代码来十行代码,五行警告,复制粘贴,屎山一堆。程序一报错,立刻复制贴到各大交流群,请求救命。还追着别人问有什么学习技巧,如何快速掌握知识;就是不肯花些时间去了解一些细节,主动解决问题。眼高手低,不愿脚踏实地,总想着搭建多么高大的上层建筑,其所站立的基础却满是空洞。这是病态的,也是我所厌倦见到的。
源码,是最好的老师。如果它不理你,就不断去请教他。那本文就到这里,谢谢观看 ~
更多 Flutter 内置组件介绍,欢迎关注 《Flutter 组件集录》 专栏。
@张风捷特烈 2022.10.24 未允禁转我的 公众号: 编程之王我的 github 主页: toly1994328
转载自:https://juejin.cn/post/7158228658044272671

