解码Flutter(一)

2,574 阅读4分钟

flutter热重载

flutter热重载的原理是:更新运行中的Dart虚拟机中的源代码,然后从小部件的树根开始,每个小部件的build方法都会重新运行。直到整个树被重建,并且新的框架被渲染。

例如,在 Widget build(BuildContext context) 方法中,更改小部件的title或者增加,删除小部件,都可以被重新渲染。

重新运行

如果我们在 main()方法中,修改App的根部件,由MyApp()更换为MyOtherApp()

void main() {

  runApp(const MyOtherApp());
}

此时,热重载的还是原来MyApp()的小部件,此时,我们需要重新运行程序。

hot start

当我们修改 build()方法以外的方法,热重载不会检测到,此时我们不需要重新运行,但是需要 hot start

例如,initState()或者dispose()方法,不是由build()方法调用的,这些方法不会被热重载察觉,增加一些 变量静态变量也是需要hot start的.

Build Context

当我们使用导航器、媒体查询和各种builder创建器的时候都需要BuildContext

Navigator.of(context);
MediaQuery.of(context);
ListView.builder(itemBuilder: (context, index){});

BuildContext是什么呢? Widget是用来记录UI的配置信息和属性的,但它不知道自己和其他小部件的关系

小部件关系.png

我们以StatelessWidget为例展开说明。

abstract class StatelessWidget extends Widget {
   ......
  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);
}

创建Widget的时候,会创建与之对应的Element对象。作用是跟踪管理widget的位置Element对象会维护此小部件在运行时的所需的信息

Element对象.png

Element对象是Element tree中的一部分,Element tree中的每一个Element都指定代表的widget

ElementBuildContext的一种。

abstract class Element extends DiagnosticableTree implements BuildContext {
......
}

Widget中的build()方法里面的Build Context实际上是Element对象

@override
Widget build() => widget.build(this);

当我们使用BuildContext时,实际上就是允许我的小部件找出其所在位置的东西,换句话说,为你的小部件构建方法,提供所需的前后关系

例如: Navigator.of(context)就是,找出小部件当前位置的导航器

无界限宽度/高度

示例

先来看一段代码

Column(
  children: [
    const Text('Test'),
    ListView(
      children: [],
    )
  ],
);

如上所示,在这种情况下,当我们在Column中,添加一个ListView,就会得到 Vertical viewport was given unbounded height. 的错误。

Flutter布局

Flutter为了能够高效的确定每个Widget的位置,flutter使用了单通道布局算法:向下传递约束,向上传递大小父组件向子组件传递约束,子组件向父组件传递大小,父组件决定子组件位置

当我们在Column中,添加一个ListView时,此时,Column告诉子部件不能超过我本身的大小,ListView告诉 Column,要一个有多大就多大的大小,也就是无限大

此时,如果Column限制高度,就会造成,ListView充满整个Column,导致Text小部件无法正常显示,很明显,这样的结果不是我们想要的。

总结一下,当在Column中,添加ListView时,为了避免出现无法预期的效果,Flutter官方将这种情况认定为一个无限高度的错误。

详细定义

当遇到这种无限高度或宽度的错误时,我们需要尽可能的详细定义我们的布局。

如果我们想让ListView尽可能的大且能给其他Column的子部件留出一些空间,我们可以将ListView包裹在Expanded或者Flexible小部件中。

Column(
  children: [
    const Text('Test'),
    Flexible(
      child: ListView(
        children: [],
      ),
    )
  ],
),

RenderObjects

当我们使用Widget进行布局时,会生成一个Widget TreeElement TreeWidgetElement,会共同创建RenderObject,与之对应的会有一个 Render Tree

截屏2022-05-04 下午4.44.37.png

作用

RenderObject的作用有很多,它处理布局(Layout),绘画(paint),命中测试(Hit Test)和可访问性(Accessability)。 在flutter中都是用Widget来布局的,但在屏幕显示的时候,是使用的RenderObject

Element Tree使Widget TreeRender Tree保持同步。大多数情况下Render Tree可以复用,以助于提高性能。

举个例子,如果一个Widget改变了颜色,可以重用和重新绘制相同的RenderObject。如果从树中移除Widget,相应的Element对象将会分离(detach)RenderObject也会分离(detach)

截屏2022-05-04 下午5.00.50.png

当我们在Widget Tree中,交换两个小部件时,Flutter使用Key来确定Widget何时移动到新位置,并且决定是否可以重用Element ObjectRender Object

举个例子,在构建阶段,当我们交换两个拥有唯一Key的Widget时,Element Object也会被交换,如果,在Element Tree中,有能与Key匹配的Element Object对象,就会重用该Element对象,并交换。

截屏2022-05-04 下午5.14.09.png

参考

Decoding Flutter