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

2022-11-01 11:36:35 浏览数 (1)

在 《全面认识 AppBar 组件 - 使用篇》 中,我们已经详细分析了 AppBar 在使用中的细节。本文将从源码的角度来分析 AppBar 的源码实现,一方面有利于进一步认识 AppBar 内部的更多细节,另一方面源码中对组件封装中的处理方式,也有很多值得我们学习的地方。


1. 为什么 AppBar 需要是 StatefulWidget ?

AppBar 的源码中可以看出,它继承自 StatefulWidget,其实从表现上来看 AppBar 并没有需要更改自身内部状态的需求,那它为什么要继承自 StatefulWidget 呢? AppBar 用于构建组件的状态类是 _AppBarState,所以只有通过源码,才能看出所以然。

代码语言:javascript复制
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 ,那下面来演示一下。如下所示, 通过 MaterialStatescrolledUnder可以实现滑动列表内容在 AppBar 之下时变换颜色:

标题

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

代码语言:javascript复制
AppBar(
  backgroundColor: MaterialStateColor.resolveWith(
    (states) => states.contains(MaterialState.scrolledUnder)
        ? Colors.deepPurpleAccent
        : Colors.blue,
  ),

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

代码语言:javascript复制
enum MaterialState {
  hovered,
  focused,
  pressed,
  dragged,
  selected,
  scrolledUnder,
  disabled,
  error,
}

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

代码语言:javascript复制
---->[ScrollMetrics#extentBefore]----
double get extentBefore => math.max(pixels - minScrollExtent, 0.0);

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


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

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


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


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


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

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

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


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

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


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


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


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

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


尾声

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

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


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

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

0 人点赞