在 《全面认识 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