Flutter中Widget的生命周期和渲染原理

widget

FlutterWidget的生命周期

  • StatelessWidget是经过构造函数(Constructor)接收父Widget直接传入值,而后调用build方法来构建,整个过程很是简单
  • StatefulWidget须要经过State来管理其数据,而且还要监控状态的改变决定是否从新build整个Widget
  • 这里主要讨论StatefulWidget的生命周期,就是它从建立到显示再到更新最后到销毁的整个过程
  • StatefulWidget自己由两个类组成的:StatefulWidgetState
  • StatefulWidget中的相关方法主要就是
    • 执行StatefulWidget的构造函数(Constructor)来建立出StatefulWidget
    • 执行StatefulWidgetcreateState方法,来建立一个维护StatefulWidgetState对象
    • 因此咱们探讨StatefulWidget的生命周期, 最终是探讨State的生命周期
  • 那么为何Flutter在设计的时候, StatefulWidgetbuild方法要放在State中而不是自身呢
    • 首先build出来的Widget是须要依赖State中的变量(数据/自定义的状态)的
    • Flutter在运行过程当中, Widget是不断的建立和销毁的, 当咱们本身的状态改变时, 咱们只但愿刷新当前Widget, 并不但愿建立新的State

图片来源网络

上面图片大概列出了StatefulWidget的简单的函数调用过程html

constructor

调用createState建立State对象时, 执行State类的构造方法(Constructor)来建立State对象node

initState

  • initStateStatefulWidget建立完后调用的第一个方法,并且只执行一次
  • 相似于iOSviewDidLoad,因此在这里View并无完成渲染
  • 咱们能够在这个方法中执行一些数据初始化的操做,或者发送网络请求
@override
  void initState() {
    // 这里必须调用super的方法
    super.initState();
    print('4. 调用_HomeScreenState----initState');
  }
复制代码
  • 这个方法是重写父类的方法,必须调用super,由于父类中会进行一些其余操做
  • 另外一点在源码中, 会看到这个方法中有一个mustCallSuper的注解, 这里就限制了必须调用父类的方法
@protected
  @mustCallSuper
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }
复制代码

didChangeDependencies

  • didChangeDependencies在整个过程当中可能会被调用屡次, 可是也只有下面两种状况下会被调用
  1. StatefulWidget第一次建立的时候didChangeDependencies会被调用一次, 会在initState方法以后会被当即调用
  2. 从其余对象中依赖一些数据发生改变时, 好比所依赖的InheritedWidget状态发生改变时, 也会被调用

build

  • build一样也会被调用屡次
  • 在上述didChangeDependencies方法被调用以后, 会从新调用build方法, 来看一下咱们当前须要从新渲染哪些Widget
  • 当每次所依赖的状态发生改变的时候build就会被调用, 因此通常不要将比较好使的操做放在build方法中执行

didUpdateWidget

执行didUpdateWidget方法是在当父Widget触发重建时,系统会调用didUpdateWidget方法算法

dispose

  • 当前的Widget再也不使用时,会调用dispose进行销毁
  • 这时候就能够在dispose里作一些取消监听、动画的操做
  • 到这里, 也就意味着整个生命周期的过程也就结束了

setState

  • setState方法能够修改在State中定义的变量
  • 当咱们手动调用setState方法,会根据最新的状态(数据)来从新调用build方法,构建对应的Widgets
  • setState内部实际上是经过调用_element.markNeedsBuild();实现更新Widget

整个过程的代码以下:api

class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 调用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    print('2. 调用的HomeScreen---createState');
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {

  int _counter = 0;

  _HomeScreenState() {
    print('3. 调用_HomeScreenState----constructor');
  }

  @override
  void initState() {
    // 这里必须调用super的方法
    super.initState();
    print('4. 调用_HomeScreenState----initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('调用_HomeScreenState----didChangeDependencies');
  }
  
  @override
  Widget build(BuildContext context) {
    print('5. 调用_HomeScreenState----build');
    return Scaffold(
      appBar: AppBar(title: Text('生命周期', style: TextStyle(fontSize: 20))),
      body: Center(
        child: Column(
          children: <Widget>[
            Text('当前计数: $_counter', style: TextStyle(fontSize: 20),),
            RaisedButton(
              child: Text('点击增长计数', style: TextStyle(fontSize: 20),),
              onPressed: () {
                setState(() {
                  _counter++;
                });
              }
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();

    print('6. 调用_HomeScreenState---dispose');
  }
}
复制代码

打印结果以下:数组

flutter: 1. 调用HomeScreen---constructor
flutter: 2. 调用的HomeScreen---createState
flutter: 3. 调用_HomeScreenState----constructor
flutter: 4. 调用_HomeScreenState----initState
flutter: 调用_HomeScreenState----didChangeDependencies
flutter: 5. 调用_HomeScreenState----build

// 每次调用setState, 都会执行build
flutter: 5. 调用_HomeScreenState----build
flutter: 5. 调用_HomeScreenState----build
复制代码

Flutter渲染原理

Flutter中渲染过程是经过Widget, ElementRenderObject实现的, 下面是FLutter中的三种树结构微信

Flutter

Widget

这是Flutter官网对Widget的说明markdown

Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.网络

  • FlutterWidgets的灵感来自React,中心思想是使用这些Widgets来搭建本身的UI界面
  • 经过当前Widgets的配置和状态描述这个页面应该展现成什么样子
  • 当一个Widget发生改变时,Widget就会从新build它的描述,框架会和以前的描述进行对比,来决定使用最小的改变在渲染树中,从一个状态到另外一个状态
  • 从这段说明中大概意思也就是
    • Widgets只是页面描述层面的, 并不涉及渲染层面的东西, 并且若是所依赖的配置和状态发生变化的时候, 该Widgets会从新build
    • 而对于渲染对象来讲, 只会使用最小的开销从新渲染发生改变的部分而不是所有从新渲染
  • Widget Tree树结构
    • 在整个Flutter项目结构也是由不少个Widget构成的, 本质上就是一个Widget Tree
    • 在上面的相似Widget Tree结构中, 极可能会有大量的Widget在树结构中存在引用关系, 并且每一个Widget所依赖的配置和状态发生改变的时候, Widget都会从新build, Widget会被不断的销毁和重建,那么意味着这棵树很是不稳定
    • 因此Flutter Engin也不可能直接把Widget渲染到界面上, 这事极其损耗性能的, 因此在渲染层面Flutter引用了另一个树结构RenderObject Tree

RenderObject

下面是Flutter官网对RenderObject的说明app

An object in the render tree.框架

The RenderObject class hierarchy is the core of the rendering library's reason for being.

RenderObjects have a parent, and have a slot called parentData in which the parent RenderObject can store child-specific data, for example, the child position. The RenderObject class also implements the basic layout and paint protocols.

  • 每个RenderObject都是渲染树上的一个对象
  • RenderObject层是渲染库的核心, 最终Flutter Engin是把RenderObject真正渲染到界面上的
  • RenderObject Tree
    • 在渲染过程当中, 最终都会把Widget转成RenderObject, Flutter最后在解析的时候解析的也是咱们的RenderObject Tree, 可是并非每个Widget都会有一个与之对应的RenderObject
    • 由于不少的Widget都不是壳渲染的Widget, 而是相似于一个盒子的东西, 对其余Widget进行包装的做用

Element

下面是Flutter官网对Element的说明

An instantiation of a Widget at a particular location in the tree.

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

  • ElementWidget在树中具备特定位置的是实例化
  • Widget描述如何配置子树和当前页面的展现样式, 每个Element表明了在Element Tree中的特定位置
  • 若是Widget所依赖的配置和状态发生改变的时候, 和Element关联的Widget是会发生改变的, 可是Element的特定位置是不会发生改变的
  • Element Tree中的每个Element是和Widget Tree中的每个Widget一一对应的
    • Element Tree相似于HTML中的虚拟DOM, 用于判断和决定哪些RenderObject是须要更新的
    • Widget Tree所依赖的状态发生改变(更新或者从新建立Widget)的时候, Element根据拿到以前所保存的旧的Widget和新的Widget作一个对比, 判断二者的Key和类型是不是相同的, 相同的就不须要从新建立, 有须要的话, 只须要更新对应的属性便可

对象的建立过程

Widget

  • FlutterWidget有可渲染的和不可渲染的(组件Widget)
    • 组件Widget: 相似Container....等等
    • 可渲染Widget: 相似Padding.....等等
  • 下面咱们先看一下组件Widget(Container)的实现过程和继承关系
// 继承关系Container --> StatelessWidget --> Widget
class Container extends StatelessWidget {
  @override
  Widget build(BuildContext context) {}
}

// 抽象类
abstract class StatelessWidget extends Widget {
  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
}
复制代码
  • 从上面的代码能够看到, 继承关系比较简单, 并无建立RenderObject对象
  • 咱们常用StatelessWidgetStatefulWidget,这种Widget只是将其余的Widgetbuild方法中组装起来,并非一个真正能够渲染的Widget

RenderObject

这里来看一下可渲染Widget的继承关系和相关源代码, 这里以Padding为例

// 继承关系: Padding --> SingleChildRenderObjectWidget --> RenderObjectWidget --> Widget
class Padding extends SingleChildRenderObjectWidget {
  @override
  RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderPadding renderObject) {
    renderObject
      ..padding = padding
      ..textDirection = Directionality.of(context);
  }
}

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

abstract class RenderObjectWidget extends Widget {
  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
复制代码
  • Padding的类中,咱们找不到任何和渲染相关的代码,这是由于Padding仅仅做为一个配置信息,这个配置信息会随着咱们设置的属性不一样,频繁的销毁和建立
  • 因此真正的渲染相关的代码那就只能在RenderObject里面了
  • 上面代码中, 在Padding类里面有一个核心方法createRenderObject是用于建立一个RenderObject
  • 并且方法createRenderObject是来源于RenderObjectWidget这个抽象类里面的一个抽象方法
  • 抽象方法是必须被子类实现的,可是它的子类SingleChildRenderObjectWidget也是一个抽象类,因此能够不实现父类的抽象方法
  • 可是Padding不是一个抽象类,必须在这里实现对应的抽象方法,而它的实现就是下面的实现
// 这里目的是为了建立一个RenderPadding
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}
复制代码

上面这段代码中, 最终是建立了一个RenderPadding, 而这个RenderPadding又是什么呢? 下面看看他的继承关系和相关源代码

// 继承关系: RenderPadding --> RenderShiftedBox --> RenderBox --> RenderObject
class RenderPadding extends RenderShiftedBox {}

abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {}

abstract class RenderBox extends RenderObject {}
复制代码

RenderObject又是如何实现布局和渲染的呢

// 当外面修改padding时
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}

// RenderPadding类里面会调用padding属性的set方法
set padding(EdgeInsetsGeometry value) {
    if (_padding == value)
      // 若是传过来的值和以前的同样, 就不会被从新渲染, 直接return
      return;
    _padding = value;
    _markNeedResolution();
}

// 内部会调用markNeedsLayout
void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
}

// 这里是RenderObject里面的一些核心方法
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  // markNeedsLayout是RenderObject类里面的方法
  // markNeedsLayout的目的就是标记在下一帧绘制时,须要从新布局performLayout
  void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }

  // 
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
    }
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
  }
  
  // RenderObject还有一个可被子类重写的paint方法
  void paint(PaintingContext context, Offset offset) { }
}
复制代码

Element

  • 在上面介绍Widget中提到过咱们写的大量的Widget在树结构中存在引用关系,可是Widget会被不断的销毁和重建,那么意味着这棵树很是不稳定
  • 若是Widget所依赖的配置和状态发生改变的时候, 和Element关联的Widget是会发生改变的, 可是Element的特定位置是不会发生改变的
  • ElementWidget在树中具备特定位置的是实例化, 是维系整个Flutter应用程序的树形结构的稳定
  • 接下来看下Element是如何被建立和引用的, 这里仍是以ContainerPadding为例
// 在Container的父类StatelessWidget中, 实例化了其父类的一个抽象方法
// 继承关系: StatelessElement --> ComponentElement --> Element
abstract class StatelessWidget extends Widget {
  // 实例化父类的抽象方法, 并把当前Widget做为参数传入了(this)
  @override
  StatelessElement createElement() => StatelessElement(this);
}

// 在Padding的父类SingleChildRenderObjectWidget中, 实例化了其父类的一个抽象方法
// 继承关系: SingleChildRenderObjectElement --> RenderObjectElement --> Element
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  
  // 实例化父类的抽象方法, 并把当前Widget做为参数传入了(this)
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
复制代码
  • 在每一次建立Widget的时候,会建立一个对应的Element,而后将该元素插入树中
  • 上面代码SingleChildRenderObjectWidget实例化了父类的抽象方法createElement建立一个Element, 并把当前Widget(this)做为SingleChildRenderObjectElement构造方法的参数传入
  • 这也就意味着建立出来的Element保存了对当前Widget的引用
  • 在建立完一个Element以后,Framework会调用mount方法来将Element插入到树中具体的位置
  • 这是在Element类中的mount方法, 这里主要的做用就是把本身作一个挂载操做
/// Add this element to the tree in the given slot of the given parent.
  ///
  /// The framework calls this function when a newly created element is added to
  /// the tree for the first time. Use this method to initialize state that
  /// depends on having a parent. State that is independent of the parent can
  /// more easily be initialized in the constructor.
  ///
  /// This method transitions the element from the "initial" lifecycle state to
  /// the "active" lifecycle state.
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }
复制代码

StatelessElement

Container建立出来的是StatelessElement, 下面咱们探索一下StatelessElement建立完成后, framework调用mount方法的过程, 这里只留下了相关核心代码

abstract class ComponentElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    _firstBuild();
  }

  void _firstBuild() {
    rebuild();
  }
  
  @override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineWhitelistArguments);

    Widget built;
    try {
      // 这里调用的build方法, 当前类也没有实现, 因此仍是只能到调用者(子类里面找该方法的实现)
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      
    } finally {
      _dirty = false;
    }
    try {
      _child = updateChild(_child, built, slot);
      
    } catch (e, stack) {
      
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

  @protected
  Widget build();
}


abstract class Element extends DiagnosticableTree implements BuildContext {
  // 构造方法, 接收一个widget参数
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  @override
  Widget get widget => _widget;
  Widget _widget;
  
  void rebuild() {
    if (!_active || !_dirty)
      return;
      
    Element debugPreviousBuildTarget;
    
    // 这里调用的performRebuild方法, 在当前类并无实现, 只能去本身的类里面查找实现
    performRebuild();
  }

  /// Called by rebuild() after the appropriate checks have been made.
  @protected
  void performRebuild();
}


class StatelessElement extends ComponentElement {
  // 这里的widget就是以前StatelessWidget中调用createElement建立element时传过来的this(widget)
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  // 这里的build方法就是拿到当前的widget, 而且调用本身的build方法
  @override
  Widget build() => widget.build(this);
}
复制代码

上面的代码看着有点乱, 下面就理一下

  1. 这里咱们建立的是StatelessElement, 在建立完一个Element以后,Framework会调用mount方法
  2. ComponentElement类中重写了mount方法, 因此framwork会调用这里的mount方法
  3. mount方法中直接调用的_firstBuild方法(第一次构建)
  4. _firstBuild方法又是直接调用的rebuild方法(从新构建)
  5. 然而在ComponentElement类中没有重写rebuild方法, 因此仍是要调用父类的rebuild方法
  6. rebuild方法会调用performRebuild方法, 并且是调用ComponentElement内重写的performRebuild方法
  7. performRebuild方法内, 会调用build方法, 并用Widget类型的build接收返回值
  8. 而这个build方法在StatelessElement中的实现以下
  9. 也就是说, 在建立Element以后, 建立出来的elment会拿到传过来的widget, 而后调用widget本身的build方法, 这也就是为何全部的Widget建立出来以后都会调用build方法的缘由
Widget build() => widget.build(this);
复制代码

因此在StatelessElement调用mount烦恼歌发最主要的做用就是挂在以后调用_firstBuild方法, 最终经过widget调用对应widgetbuild方法构建更多的东西

RenderObjectElement

  • 下面看一下可渲染的Widget又是如何建立Element的, 这里仍是以Padding为例
  • 以前有提到Padding是继承自SingleChildRenderObjectWidget的, 而createElement方法也是在这个类中被实现的
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  // 这里是建立了一个SingleChildRenderObjectElement对象
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
复制代码
  • 上面的代码中Padding是经过父类建立了一个SingleChildRenderObjectElement对象
  • SingleChildRenderObjectElement是继承自RenderObjectElement
  • RenderObjectElement继承自Element
  • 接下来就是看一下mount方法的调用过程
/// 如下源码并不全, 这里只是拷贝了一些核心方法和相关源码
class SingleChildRenderObjectElement extends RenderObjectElement {
  // 一样构造函数接收一个widget参数
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

  @override
  SingleChildRenderObjectWidget get widget => super.widget as SingleChildRenderObjectWidget;

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
}


// RenderObjectElement类的相关实现
abstract class RenderObjectElement extends Element {
  // 构造函数接收一个widget参数
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  /// 建立一个RenderObject类型的变量
  @override
  RenderObject get renderObject => _renderObject;
  RenderObject _renderObject;


  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    // 在这里经过传过来的widget调用createRenderObject建立一个_renderObject
    _renderObject = widget.createRenderObject(this);
    _dirty = false;
  }
}
复制代码
  • 从上面的代码看SingleChildRenderObjectElement类中的mount方法核心是调用父类(RenderObjectElement)的mount方法
  • RenderObjectElement中的mount方法, 主要就是经过widget调用它的createRenderObject方法建立一个renderObject
  • 因此对于RenderObjectElement来讲, fromework调用mount方法, 其目的就是为了建立renderObject
  • 这也就意味着Element_renderObject也会有一个引用
  • 也就是说Element不但对_widget有一个引用, 对_renderObject也会有一个引用

StatefulElement

  • 上面提到StatefulWidget是由两部分构成的StatefulWidgetState
  • StatefulWidget是经过createState方法,来建立一个维护StatefulWidgetState对象
class StatefulElement extends ComponentElement {
  /// 构造函数
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    
    _state._element = this;
    
    _state._widget = widget;
  }

  State<StatefulWidget> get state => _state;
  State<StatefulWidget> _state;
}
复制代码
  • StatefulElement内定义了一个_state变量, 而且存在对_widget的引用
  • 而在StatefulElement的构造方法中, 直接经过参数widget调用其内部的createState方法, 这个是StatefulWidget中的一个抽象方法(子类必须实现), 相信这个方法都比较熟悉
class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 调用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Container()
  }
}
复制代码
  • StatefulElement建立完成以后, fromework就会调用mount方法挂载, 这个过程就和上面StatelessElement中的mount方法的调用过程基本同样了
  • 二者不一样的是:
    • StatelessElement中最后是经过widget调用widget.build(this)方法
    • StatefulElement中最后是经过_state调用_state.build(this)方法, 也就是上面_HomeScreenStatebuild方法
@override
Widget build() => _state.build(this);
复制代码

BuildContext

上面屡次提到的build方法是有参数的, 并且无论是StatelessWidget仍是State, 他们build方法的参数都是BuildContext

// StatelessWidget
abstract class StatelessWidget extends Widget {
  @protected
  Widget build(BuildContext context);
}

// State
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  @protected
  Widget build(BuildContext context);
}
复制代码

ComponentElement建立完成以后, 会调用mount方法, 最终都会调用对应的build方法

class StatelessElement extends ComponentElement {
  @override
  Widget build() => widget.build(this);
}

class StatefulElement extends ComponentElement {
  @override
  Widget build() => _state.build(this);
}
复制代码
  • 上面的build方法传入的参数都是Element, 因此本质上BuildContext就是当前的Element
  • BuildContext主要的做用就是知道我当前构建的这个Widget在这个Element Tree上面的位置信息, 以后就能够沿着这这个Tree喜好那个上查找相关的信息
  • 下面是二者的继承关系
abstract class Element extends DiagnosticableTree implements BuildContext {}
复制代码

小总结

StatelessElement

  • Widget建立出来以后, Flutter框架必定会根据这个Widget建立出一个对应的Element, 每个Widget都有一个与之对应的Element
  • Element对对当前Widget产生一个引用_widget
  • element建立完成后, fromework会调用mount方法, 最终调用_widget.build(this)方法

StatefulElement

  • Widget建立出来以后, Flutter框架必定会根据这个Widget建立出一个对应的Element, 每个Widget都有一个与之对应的Element
  • StatefulElement构造函数中会调用widget.createState()建立一个_state, 并引用_state
  • 而且会把widget赋值给_state的一个引用_widget: _state._widget = widget;, 这样在State类中就能够经过this.state拿到当前的Widget
  • element建立完成后, fromework会调用mount方法, 最终调用_state.build(this)方法

RenderObjectElement

  • Widget建立出来以后, Flutter框架必定会根据这个Widget建立出一个对应的Element, 每个Widget都有一个与之对应的Element
  • element建立完成后, fromework会调用mount方法, 在mount方法中会经过widget调用widget.createRenderObject(this)建立一个renderObject, 并赋值给_renderObject
  • 因此建立的RenderObjectElement对象也会对RenderObject产生一个引用

Widget的key

咱们以前建立的每个Widget, 在其构造方法中咱们都会看到一个参数Key, name这个Key到底有何做用又什么时候使用呢

const Scaffold({ Key key, ... })
const Container({ Key key, ... })
const Text({ Key key, ... })
复制代码

咱们先看一个示例需求代码以下: 但愿每次点击删除按钮删除数组的元素后, ListView中其余item的展现信息不变(包括颜色和字体)

class _HomeScreenState extends State<HomeScreen> {

  List<String> names = ["111111", "222222", "333333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Key Demo"),
      ),
      body: ListView(
        children: names.map((name) {
          return ListItemLess(name);
        }).toList(),
      ),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        }
      ),
    );
  }
}
复制代码

咱们吧ListViewitem分别使用StatelessWidgetStatefulWidget实现, 看看二者区别

StatelessWidget

咱们先对ListItem使用一个StatelessWidget进行实现:

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(name, style: TextStyle(fontSize: 30, color: Colors.white)),
      color: randomColor,
    );
  }
}
复制代码
  • 经过实践很明显, 每次删除第一个元素后, 虽然也能删除第一个ListItem, 剩余的每个ListItem展现的信息也是对的, 可是他们的颜色倒是每次都会发生变化
  • 这主要就是由于, 每次删除以后都会调用setState,也就会从新build,从新build出来的新的StatelessWidget`会从新生成一个新的随机颜色

StatefulWidget

如今对ListItem使用StatefulWidget实现一样的功能

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name): super();
  @override
  _ListItemFulState createState() => _ListItemFulState();
}

class _ListItemFulState extends State<ListItemFul> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(widget.name),
      color: randomColor,
    );
  }
}
复制代码
  • 咱们发现一个很奇怪的现象, 信息展现正常(删除了第一条数据),可是从颜色上看, 是删除了最后一条
  • 在咱们每次调用setState的时候, Widget都会调用一个canUpdate函数判断是否须要重建element
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
复制代码
  • 在删除第一条数据的时候,Widget对应的Element并无改变
  • 而目前是没有设置Key的, 因此Element中对应的State引用也没有发生改变
  • 在更新Widget的时候,Widget使用了没有改变的Element中的State, 也就是以前建立的三个element中的前两个
  • 这也就是为何删除以后, 从颜色上看, 删除的是最后一条

添加Key

在上面ListItemFul的基础上, 为每个ListItemFul加上一个key

class ListItemFulKey extends StatefulWidget {
  final String name;
  ListItemFulKey(this.name, {Key key}): super(key: key);

  @override
  _ListItemFulKeyState createState() => _ListItemFulKeyState();
}


// 在上面使用的时候, 传入一个不一样的key
ListItemFulKey(name, key: ValueKey(name))
复制代码
  • 最终这就是咱们想要实现的效果了
  • 上述代码中, 为每个ListItemFulKey添加了一个key值, 并且每个的Key值都是不同的
  • 在删除一个元素调用setState方法后, 会从新build的一个Widget Tree
  • Element会拿到新的Widget Tree和原来保存的旧的Widget Tree作一个diff算法
  • 根据runtimeTypekey进行比对, 和新的Widget Tree相同的会被继续复用, 不然就会调用unnmount方法删除

Key的分类

  • Key自己是一个抽象,不过它也有一个工厂构造器,建立出来一个ValueKey
  • 直接子类主要有:LocalKeyGlobalKey
    • LocalKey,它应用于具备相同父ElementWidget进行比较,也是diff算法的核心所在;
    • GlobalKey,一般咱们会使用GlobalKey某个Widget对应的WidgetStateElement
@immutable
abstract class Key {
  /// 工厂构造函数
  const factory Key(String value) = ValueKey<String>;

  @protected
  const Key.empty();
}

abstract class LocalKey extends Key {
  /// Default constructor, used by subclasses.
  const LocalKey() : super.empty();
}

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key { }
复制代码

LocalKey

LocalKey有三个子类

ValueKey

  • ValueKey是当咱们以特定的值做为key时使用,好比一个字符串、数字等等

ObjectKey

  • 若是两个学生,他们的名字同样,使用name做为他们的key就不合适了
  • 咱们能够建立出一个学生对象,使用对象来做为key

UniqueKey:

  • 若是咱们要确保key的惟一性,可使用UniqueKey
class ValueKey<T> extends LocalKey {
  const ValueKey(this.value);
}

class ObjectKey extends LocalKey {
  const ObjectKey(this.value);
}

class UniqueKey extends LocalKey {
  UniqueKey();
}
复制代码

GlobalKey

  • GlobalKey能够帮助咱们访问某个Widget的信息,包括WidgetStateElement等对象, 有点相似于React中的ref
  • 好比咱们想在HomePage中访问HomeContenet中的widget
class HomePage extends StatelessWidget {

  final GlobalKey<_HomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GlobalKey Demo"),
      ),
      body: HomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          final message = homeKey.currentState.message;
          final name = homeKey.currentState.widget.name;
          print('message = $message, name = $name');
          homeKey.currentState.newPrint();

          final currentCtx = homeKey.currentContext;
          print('currentCtx = $currentCtx');
        }
      ),
    );
  }
}

class HomeContent extends StatefulWidget {

  final String name = 'homeContent';

  HomeContent({ Key key }): super(key: key);

  @override
  _HomeContentState createState() => _HomeContentState();
}

class _HomeContentState extends State<HomeContent> {

  final String message = 'message';

  void newPrint() {
    print('new---print');
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
复制代码

参考文档


欢迎您扫一扫下面的微信公众号,订阅个人博客!

微信公众号
相关文章
相关标签/搜索