当前位置:   article > 正文

Flutter 组件 | Builder 构造器与 BuildContext 认知

flutter builder中context属于谁的

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: v-green

highlight:

1. Builder 组件引言

这篇文章来讲一个只有 10 行代码 的组件: Builder ,下面是它的全部代码。虽然非常简单,但通过它,仍然能让你了解很多知识。

```dart typedef WidgetBuilder = Widget Function(BuildContext context);

class Builder extends StatelessWidget { const Builder({ Key key, @required this.builder, }) : assert(builder != null), super(key: key);

final WidgetBuilder builder;

@override Widget build(BuildContext context) => builder(context); } ```

可以看出 Flutter 源码中有不少组件是依赖于 Builder 组件实现的。这个小东西何德何能呢?通过本文,你会对它,甚至对 Flutter 有一个更全面的认识。

image-20201231082557130


2. Builder 组件的价值何在

通过源码可以看出,Builder 是一个继承自 Stateless 的组件,需要实现 build 抽象方法,通过 BuildContext 对象,来构建 Widget 对象。而 Builder#build 只是使用了构造传入的 builder 函数,并将 当前的 BuildContext 作为回调传递出去。也就是将构建组件的逻辑交由外界处理,自身只是将 context 对象回调出去而已。

dart typedef WidgetBuilder = Widget Function(BuildContext context);

所以 Builder 组件唯一的价值,就是回调当前的 context ,让用户根据这个 context 进行组件的构建。感觉上它完全是打酱油的,那这到底有什么用呢?这时所有的关注焦点应该被集中到了 BuildContext 上,且听我慢慢道来。


3. 认识 BuildContext 对象

为了更好地认识 BuildContext,我们来做个调试。断点如下,MyApp 是代码层的顶部组件,我们可以看一下在 build 方法中,回调过来的这个 context 对象到底是个什么东西。

image-20201231085648205

从下面调试结果可以看出:

【1】. 其类型为 StatelessElement ,也就是说此时它的本质是一个 Element 。
【2】. 其持有一个 Widget 对象 _widget,类型是 MyApp 即当前组件。
【3】. 其有一个父亲 _parent 属性,类型为 RenderOnjectToWidgetElement
【4】. 其有一个孩子 _child 属性,此时为 null。

image-20201231092013020

其实一直所说的 Element 树其实就在这里。根元素是 RenderOnjectToWidgetElement 类型对象,它的父节点为 null ,而这里的持有 MyAppStatelessElement 便是第二元素。

image-20201231092654996


BuildContext 是一个抽象类,也就是说它无法直接构造对象。 而在 Flutter 框架层,它有且仅有一个实现类 ---- Element ,所以两者之间的关系应该非常明确了。在 Flutter 使用中,你所见到的每个 BuildContext 对象,它的本质都是 Element 对象。 更形象点说,Element 就像是工程师眼中的电视机,BuildContext 就像一般用户眼中的电视机。工程师 需要将这个对象的内在功能、逻辑进行完整的实现;而对于用户来说,只需要按按开关,点点按键即可,不需要在意实现细节,只需要有对电视机的抽象认知即可。可以说用户认知中的电视机,并非是真实的电视机,只是电视机功能的抽象,我们只是知道它能干什么,所有的显示、按键都是内部提供的功能接口。 对于 Element 对象而言,BuildContext 就是暴露给用户 的功能接口。而用户,便是使用 Flutter 框架的我们。我们在使用时,不需要了解电视机( Element )内部做了什么,只需要知道如何使用(BuildContext )即可。这也是为什么抽象出来 BuildContext 而非直接使用 Element 的原因。

image-20201231093502380


4. BuildContext 的作用

BuildContext 对象有什么用呢,下面是 BuildContext 的所有信息,可见,除了四个成员变量之外,其他的都是抽象方法。值得注意的是 BuildContext 中并没有树状结构,也就是说它只是一种抽象,内部的结构、逻辑完全交于实现类来完成,抽象只是负责暴露给用户需要的接口功能。里面的方法很多,稍微瞄一眼,可以看到基本上都是在 找东西

image-20201231123155820

我们会经常使用 Navigator.of(context).push来用于路由的跳转。 其实 Navigator.of(context) 是一个静态方法,用于返回 NavigatorState,而路由的方法都是定义在 NavigatorState 中的。这里 BuildContext 的作用就是获取相关状态类 XXXState。核心方法是 findAncestorStateOfType,获取上层第一个某类型组件对应的 State 对象。

dart static NavigatorState of( BuildContext context, { bool rootNavigator = false, }) { // Handles the case where the input context is a navigator element. NavigatorState? navigator; if (context is StatefulElement && context.state is NavigatorState) { navigator = context.state as NavigatorState; } if (rootNavigator) { navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator; } else { navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>(); } return navigator!; }

还有 MediaQuery 这种 InheritedWidget,通过 .of(context) 可以获取特定的数据。比如下面通过 MediaQuery.of(context) 可以获取 MediaQueryData 数据,从而拿到 屏幕尺寸、设备分辨率等数据信息,这里核心方法 BuildContext 的 dependOnInheritedWidgetOfExactType

dart static MediaQueryData of(BuildContext context) { assert(context != null); assert(debugCheckHasMediaQuery(context)); return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data; }

BuildContext 可以在所有父辈节点中获取特定的对象,在本文中讲到这就差不多了,后面会为 BuildContext 专门写一篇来详细讨论这些接口作用。


5.Builder 的作用

经过上面对 BuildContext 的认识后,再来看这里,当前的 context 之上只有根节点,所以用这个 context 开做 MediaQuery ,将会报错,因为MediaQuery 是在 MaterialApp 内部包含的,这时 context 中是找不到的,所以想要使用 MediaQuery,那就必需将 context 下移到 MaterialApp 构建之后。最简单的方案是抽离组件,来让 context 下移,如果不抽离组件,那么 Builder 组件就可以大显神威。

image-20201231131829018

需要注意的是,Builder 中回调的 上下文 并非是 context 的直接孩子,也就是说,并非仅是下移了一层。这要取决于组件的复杂程度。比如 MaterialApp 的内部实现比较复杂,可以看出,Builder 中回调的 ctx 的深度是 87,就说明在其之上还有这么多的父亲节点。

image-20201231132254856

所以,你认为的 Flutter 中的树,和真实的 Flutter 中的树是完全不同的。这时,也怕某些连门都没入,却别有用心的人开始扬言:“ 你们看,一个 MaterialApp 组件就这么复杂,性能担忧啊,Flutter 真是太嵌套地狱了,真可怕,早晚要凉”。我只想说,别拿你的脑子跟电脑比,就算是几千个元素节点,在树状结构下,形成树或找在树中出某个元素来也只是探囊取物。 现在相当于我打开电视机的外壳,让你瞟一眼内部是什么,千万不要用自己的无知来感叹这个世界的复杂

image-20201231133344783

一直往下翻,你会看到有一个持有 MediaQuery 的元素,这就说明当前的 ctx 可以上溯寻到该祖先节点。也就说明使用 Builder 回调的上下文,是可以使用 MediaQuery.of(ctx) 获取到媒体信息的。

image-20201231191832339

到这里,你应该对上下文的层级有了一定的认识。我们所使用的 XXX.of(context),都是在该上下文之上去寻找某些对象,Theme.ofScaffold.ofNavigator.ofProvider.ofBloc.of 都是这样的,如果你的上下文太靠前,是找不到的。所有 Builder 组件就是做这个事的,回调一个较下层的上下文以供使用。这一点本质上和提取出一个 Widget 没什么两样,如果很简单的东西,不想提出一个组件来处理,那 Builder 就是一个很好的帮手。当你通过 XXX.of(context) 没拿到想要的东西,现在你应该明白该怎么做了。

image-20201231193245999

比如下面的示例,ScaffoldBuilderDemo#build 下,这是想在 floatingActionButton 单击时弹出 SnackBar ,而 showSnackBar 是需要 ScaffoldState 对象触发的,当前的 context 上层还没有对应的 元素。这时有两种方案:1,抽离组件,在下层组件的上下文中触发。 2, 使用 Builder 回调下层的上下文。由于这里东西很少,没必要新建个组件,使用 Builder 就很轻轻方便。

dart import 'package:flutter/material.dart'; class BuilderDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 200, child: Scaffold( appBar: AppBar( title: Text('Builder'), ), floatingActionButton: Builder( builder: (ctx) => FloatingActionButton( onPressed: () { Scaffold.of(ctx).showSnackBar(SnackBar(content: Text('hello builder'))); }, child: Icon(Icons.add), ), ), ), ); } }


6.Builder 组件在源码中的应用

也许你并没有注意到,MaterialApp 本身有一个 builder 属性。MaterialApp 内部使用了 WidgetsApp

image-20201231194222536

_WidgetsAppState#build 中可以看到如果 builder 属性非空,会使用 Builder 组件。在使用 MaterialApp 组件时,可以通过 builder 属性,实现和 Builder 组件一样的效果,不过追其本质也还是 Builder 组件的功劳。

image-20201231194530212


IconTheme 中的 merge 方法里也使用了 Builder 组件,这是为了在没有上下文的时候拿到上下文,这样就不需要在 merge 方法中传入上下文了,这也是上下文无中生有的使用方式。同样的使用,也可以在源码的Text.mergeListTile.merge 中看到。

image-20201231195829723


在 Provider 相关的类中,你也可以看到一个 TransitionBuilder 类型的 builder 属性,其实它们的作用也是 Builder 赋予的,其作用也就不言而喻了,当你了解 Builder ,源码看到这里时,你就会很有亲切感。

image-20201231200535163

image-20201231201921349

Builder 组件本身难吗?10 行的源码组件肯定不难,难的是你对它存在价值的思考,以及去发现更深层东西的兴趣和能力。通过本文,你应该能对 Flutter 增加了一丢丢的新的认知,那么本文就到此结束,谢谢观看。


@张风捷特烈 2021.01.02 未允禁转 我的公众号:编程之王 联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328 ~ END ~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/412329
推荐阅读
相关标签
  

闽ICP备14008679号