官方对Widget的说明:vue
本身的理解:web
官方对Element的描述:算法
官方对RenderObject的描述:数据结构
咱们这里以Padding为例,Padding用来设置内边距app
Padding是一个Widget,而且继承自SingleChildRenderObjectWidget框架
继承关系以下:less
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
复制代码
咱们以前在建立Widget时,常用StatelessWidget和StatefulWidget,这种Widget只是将其余的Widget在build方法中组装起来,并非一个真正能够渲染的Widget(在以前的课程中其实有提到)。dom
在Padding的类中,咱们找不到任何和渲染相关的代码,这是由于Padding仅仅做为一个配置信息,这个配置信息会随着咱们设置的属性不一样,频繁的销毁和建立。编辑器
问题:频繁的销毁和建立会不会影响Flutter的性能呢?ide
那么真正的渲染相关的代码在哪里执行呢?
咱们来看Padding里面的代码,有一个很是重要的方法:
@override
RenderPadding createRenderObject(BuildContext context) { return RenderPadding( padding: padding, textDirection: Directionality.of(context), ); } 复制代码
上面的代码建立了什么呢?RenderPadding
RenderPadding的继承关系是什么呢?
RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject
复制代码
咱们来具体查看一下RenderPadding的源代码:
set padding(EdgeInsetsGeometry value) {
assert(value != null); assert(value.isNonNegative); if (_padding == value) return; _padding = value; _markNeedResolution(); } 复制代码
咱们来思考一个问题:
Element何时建立?
在每一次建立Widget的时候,会建立一个对应的Element,而后将该元素插入树中。
在SingleChildRenderObjectWidget中,咱们能够找到以下代码:
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this); 复制代码
在建立完一个Element以后,Framework会调用mount方法来将Element插入到树中具体的位置:
在调用mount方法时,会同时使用Widget来建立RenderObject,而且保持对RenderObject的引用:
@override
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); assert(() { _debugUpdateRenderObjectOwner(); return true; }()); assert(_slot == newSlot); attachRenderObject(newSlot); _dirty = false; } 复制代码
可是,若是你去看相似于Text这种组合类的Widget,它也会执行mount方法,可是mount方法中并无调用createRenderObject这样的方法。
@override
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_active); _firstBuild(); assert(_child != null); } void _firstBuild() { rebuild(); } 复制代码
若是是一个StatefulWidget,则建立出来的是一个StatefulElement
咱们来看一下StatefulElement的构造器:
StatefulElement(StatefulWidget widget) : _state = widget.createState(), ....省略代码 _state._widget = widget; 复制代码
而调用build的时候,本质上调用的是_state中的build方法:
Widget build() => state.build(this);
复制代码
在StatelessElement中,咱们发现是将this传入,因此本质上BuildContext就是当前的Element
Widget build() => widget.build(this);
复制代码
咱们来看一下继承关系图:
abstract class Element extends DiagnosticableTree implements BuildContext 复制代码
在StatefulElement中,build方法也是相似,调用state的build方式时,传入的是this
Widget build() => state.build(this);
复制代码
Widget只是描述了配置信息:
Element是真正保存树结构的对象:
RenderObject是真正渲染的对象:
markNeedsLayout
performLayout
markNeedsPaint
paint
等方法
在咱们建立Widget的时候,老是会看到一个key的参数,它又是作什么的呢?
咱们一块儿来作一个key的案例需求
home界面的基本代码:
class _HYHomePageState extends State<HYHomePage> {
List<String> names = ["aaa", "bbb", "ccc"]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Test Key"), ), body: ListView( children: names.map((name) { return ListItemLess(name); }).toList(), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.delete), onPressed: () { setState(() { names.removeAt(0); }); } ), ); } } 复制代码
注意:待会儿咱们会修改返回的ListItem为ListItemLess或者ListItemFul
咱们先对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), color: randomColor, ); } } 复制代码
它的实现效果是每删除一个,全部的颜色都会发现一次变化
咱们对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, ); } } 复制代码
咱们发现一个很奇怪的现象,颜色不变化,可是数据向上移动了
咱们使用一个随机的key
ListItemFul的修改以下:
class ListItemFul extends StatefulWidget {
final String name; ListItemFul(this.name, {Key key}): super(key: key); @override _ListItemFulState createState() => _ListItemFulState(); } 复制代码
home界面代码修改以下:
body: ListView(
children: names.map((name) { return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),); }).toList(), ), 复制代码
这一次咱们发现,每次删除都会出现随机颜色的现象:
// Widget类中的代码
static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } 复制代码
此次,咱们将name做为key来看一下结果:
body: ListView(
children: names.map((name) { return ListItemFul(name, key: ValueKey(name)); }).toList(), ), 复制代码
咱们理想中的效果:
Key自己是一个抽象,不过它也有一个工厂构造器,建立出来一个ValueKey
直接子类主要有:LocalKey和GlobalKey
LocalKey有三个子类
ValueKey:
ObjectKey:
UniqueKey
GlobalKey能够帮助咱们访问某个Widget的信息,包括Widget或State或Element等对象
咱们来看下面的例子:我但愿能够在HYHomePage中直接访问HYHomeContent中的内容
class HYHomePage extends StatelessWidget {
final GlobalKey<_HYHomeContentState> homeKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表测试"), ), body: HYHomeContent(key: homeKey), floatingActionButton: FloatingActionButton( child: Icon(Icons.data_usage), onPressed: () { print("${homeKey.currentState.value}"); print("${homeKey.currentState.widget.name}"); print("${homeKey.currentContext}"); }, ), ); } } class HYHomeContent extends StatefulWidget { final String name = "123"; HYHomeContent({Key key}): super(key: key); @override _HYHomeContentState createState() => _HYHomeContentState(); } class _HYHomeContentState extends State<HYHomeContent> { final String value = "abc"; @override Widget build(BuildContext context) { return Container(); } } 复制代码
备注:全部内容首发于公众号,以后除了Flutter也会更新其余技术文章,TypeScript、React、Node、uniapp、mpvue、数据结构与算法等等,也会更新一些本身的学习心得等,欢迎你们关注