Flutter 组件集录 | AppBar 组件 - 从源码中学习

3,309 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 9 天,点击查看活动详情

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

image.png


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

image.png

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

image.png


2. AppBar 状态类中的滑动干了什么

下面问题来了,_AppBarState 要监听滑动做什么?在平时的滑动过程中似乎 AppBar 并没有什么和滑动相关的东西。想要知道干了什么,最好的方式自然是看源码中在滑动监听时做了什么处理,也就是 _handleScrollNotification 方法的逻辑:

如下所示,该方法中主要在维护状态类中的 _scrolledUnder 布尔型成员,并在该成员变化时,触发 setState 更新状态类。现在焦点在于 _scrolledUnder 在构建组件的过程中有什么用途?

image.png


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

image.png


所以它的使用方式和其他的 MaterialState 是一样的。什么? 没用过 MaterialState ,那下面来演示一下。如下所示, 通过 MaterialStatescrolledUnder可以实现滑动列表内容在 AppBar 之下时变换颜色:

标题
33.gif34.gif

实现方式如下,通过 MaterialStateColor.resolveWith 静态方法根据 states 集合中是否包含 scrolledUnder 来确定颜色。

AppBar(
  backgroundColor: MaterialStateColor.resolveWith(
    (states) => states.contains(MaterialState.scrolledUnder)
        ? Colors.deepPurpleAccent
        : Colors.blue,
  ),

另外,其他的 MaterialState 元素也可以进行类似的使用,比如按钮 presseddisabled 状态的颜色设置。像 MaterialStateColor 这种根据 MaterialState 确定信息的类型,有个顶层抽象 MaterialStateProperty, 源码中内置了 XXXColorXXXTextStyleXXXBorderSide 等以供使用,理论上来说可以自定义属性的类型。你学废了吗?

image.png

enum MaterialState {
  hovered,
  focused,
  pressed,
  dragged,
  selected,
  scrolledUnder,
  disabled,
  error,
}

接下来再仔细通过调试,看一下 _handleScrollNotification 中的逻辑。如下,在向上滑动的过程中,metrics.extentBefore 是滑动的距离,准确来说是滑动距离和 minScrollExtent 的差值,只不过默认 minScrollExtent0

---->[ScrollMetrics#extentBefore]----
double get extentBefore => math.max(pixels - minScrollExtent, 0.0);

理论上来说,可以通过增加 minScrollExtent 的值,让内容滑动到 AppBar 内部指定距离后,才触发 MaterialState 的变化。不过这个值是监听得到的,不是很好改,如果有需求的话,倒不如魔改这里的源码,比如让 metrics.extentBefore > 60 。这样滑动 60 逻辑像素后,才会添加 MaterialState.scrolledUnder 导致变色。

image.png


3. AppBar 状态类中的主题处理

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

image.png


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

image.png


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

image.png


4. AppBar 状态类构建组件的细节

对一个合成组件来说,最重要的还是构建逻辑,从其中可以看到组件在界面中表现一切本质细节。比如对于 leading 组件的处理,如果 leading 为空,并且 automaticallyImplyLeadingtrue。当拥有 Drawer 时,会将 leading 赋值为 IconButton ;如果可以返回并且编译 endDrawer 会添加返回按钮:

另外,leadingWidth 属性的作用是通过施加紧约束实现的。所以,通过源码可以看到组件属性的具体作用,这样才能对其使用更加得心应手。如果把一个组件比作一头牛,那组件的构造细节就是牛的骨头和经络,就像 庖丁解牛 :依乎天理,批大郤,导大窾,因其固然。

image.png


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

image.png

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

image.png


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

image.png


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

image.png


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

image.png

所以,一个组件的表现效果,都可以在源码的构造在中找到逻辑根源。可能有人会觉得,会用不就行了吗,为什么要研究的这么细致。良庖岁更刀,割也;族庖月更刀,折也。歌手不会唱歌,戏子不会演戏,厨子不会用刀,谬之大极。


尾声

勤小物,可治其微。有些人天天嘴上说着这个框架好,那个类库差,评头论足起来眉飞色舞,义愤填膺。写起代码来十行代码,五行警告,复制粘贴,屎山一堆。程序一报错,立刻复制贴到各大交流群,请求救命。还追着别人问有什么学习技巧,如何快速掌握知识;就是不肯花些时间去了解一些细节,主动解决问题。眼高手低,不愿脚踏实地,总想着搭建多么高大的上层建筑,其所站立的基础却满是空洞。这是病态的,也是我所厌倦见到的。

源码,是最好的老师。如果它不理你,就不断去请教他。那本文就到这里,谢谢观看 ~


更多 Flutter 内置组件介绍,欢迎关注 《Flutter 组件集录》 专栏。

  • @张风捷特烈 2022.10.24 未允禁转
  • 我的 公众号: 编程之王
  • 我的 github 主页 :  toly1994328