Widget其实是Element的配置项,Flutter中真正代表屏幕上显示元素的类是Element 。
Tips:Element本身并不处理laying out, painting, 和hit testing这些操作,这些操作是由RenderObject来实现的
Element生命周期
Initial
Element一般并不是直接调用的,而是通过调用Widget.createElement方法来初始化元素,通过阅读Widget源码Element createElement(),我们可以知道createElement方法返回一个Elment类对象。
由Element Class源码中,我们可以得知,完成了createElement后Element的状态为initial。
_ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial;
复制代码
Tips:_debugLifecycleState内部属性用于判断当前Element所处的状态。
Active
当RenderObject调用mount方法时,会将新Element添加到Parent的render树中,具体实现:
RenderObject的mount方法先是执行Element的mount方法,根据Element类的mount方法assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());,我们可以得知此时Element的状态已经被改为active。
因为Element本身不进行layout,所以Elment本身的mount方法只有生命周期的改变,那么layout是在哪里进行的呢?我们可以打开RenderObjectElement的源码,看到mount方法,通过调用attachRenderObject方法将render object 添加到 render tree中,在此,实现了Element的Layout。
@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;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
}
复制代码
当前状态:active,并且此时已经在屏幕中展示
Inactive
当Parent Rebuilt的时候,Widget将会调用update方法,Flutter会根据runtimeType和key两个属性来判断新旧Widget是否相同,若是相同,则会调用Element.update方法进行更新;若不相同,旧的Element将会从Render Tree中移除,新的Element将会填充进(这一块涉及到Widget的更新、插入、移除操作,这里只是粗略一讲)。那么问题来了,我们能够手动改变runtimeType么?目前来看是不行的,官网也有一句话,
所以要想改变runtimeType就必须unmounting element。
若Ancestor元素需要将element从tree中移除,Ancestor将会调用deactivateChild方法,此方法将会将element的render object从render tree中移除,并且将该element推入到Ancestor的inactive elements list,在这list中的element将会被调用deactivate方法
当前状态:inactive,并且此时已经从屏幕中移除,此时好像该element还存在于内存中
Defunct
当动画最后一帧结束后,所有inactive的element将会被卸载(unmounted)
void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(depth != null);
assert(!_active);
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._unregister(this);
}
assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; }());
}
复制代码
当前状态:defunct
如果在动画最后一帧结束前该元素被再合并到tree中,框架将会将其从inactive’s list中移除,重新调用该element的activate方法,并且将该element的render object重新推到render tree中
void activate() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(owner != null);
assert(depth != null);
assert(!_active);
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true;
// We unregistered our dependencies in deactivate, but never cleared the list.
// Since we're going to be reused, let's clear our list now.
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
if (_dirty)
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
复制代码
用处
了解了Element的生命周期,那么我们是否能够使用该生命周期,在某些特殊的场景下实现例如组件移除触发的特殊需求呢?
答案当然是可以的,但是有一点需要注意的是,需要借用RenderObjecgtElement或者ComponentElement类来实现,因为这两个类继承自Element类,有人问了,那我直接继承Element类是否可以,这当然也是可以的,但是有一点,正如我最开始说的,Element本身不处理laying out, painting, 和hit testing这些操作,所以这些操作需要你自己实现,而使用RenderObjectElement或者ComponentElement则可以借用其来进行布局渲染等等。
我们都是通过使用Widget来实现功能的,就算我们追本溯源也是继承Widget,那本文讲述的Element又在哪里触发呢?其实上文已经有写了,通过使用Widget的createElement返回Element,那么,所以若是我们想要使用该生命周期,需要创建一个新的类继承自RenderObjectElment或者ComponentElement,使用mount、unmount、activate等方法触发特定的生命周期。
以下代码是我写的一个Demo
class MyWidget extends Widget {
@override
MyElement createElement() => MyElement(this);
}
class MyElement extends ComponentElement {
MyElement(Widget widget) : super(widget);
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
print('触发mount生命周期');
}
@override
void deactivate() {
super.deactivate();
print('触发deactivate生命周期');
}
@override
void unmount() {
super.unmount();
print('触发unmount生命周期');
}
@override
Widget build() {
// TODO: implement build
return Text('测试');
}
}
复制代码
总结
本文讲述了一些特殊名词,例如:Render Tree、RenderObject,这些如果有下篇的话会在下篇中进行详细讲解。