官方对Widget的说明:
自己的理解:
官方对Element的描述:
官方对RenderObject的描述:
我们这里以Padding为例,Padding用来设置内边距
例子
StatelessWidget/StatefulWidget
的自定义Widget特点
组件Widget -> StatelessWidget/StatefulWidget -> Widget
例子
特点
渲染Widget -> Single/MultiChildRenderObjectWidget -> RenderObjectWidget -> Widget
Padding是一个Widget,并且继承自SingleChildRenderObjectWidget
继承关系如下:
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
我们之前在创建Widget时,经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在build方法中组装起来,并不是一个真正可以渲染的Widget(在之前的课程中其实有提到)。
在Padding的类中,我们找不到任何和渲染相关的代码,这是因为Padding仅仅作为一个配置信息,这个配置信息会随着我们设置的属性不同,频繁的销毁和创建。
问题:频繁的销毁和创建会不会影响Flutter的性能呢?
那么真正的渲染相关的代码在哪里执行呢?
我们来看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();
returntrue;
}());
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);
我们来看一下继承关系图:
abstractclass Element extends DiagnosticableTree implements BuildContext
在StatefulElement中,build方法也是类似,调用state的build方式时,传入的是this
Widget build() => state.build(this);
Widget只是描述了配置信息:
Element是真正保存树结构的对象:
RenderObject是真正渲染的对象:
markNeedsLayout
performLayout
markNeedsPaint
paint
等方法在我们创建Widget的时候,总是会看到一个key的参数,它又是做什么的呢?
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
,它应用于具有相同父Element的Widget进行比较,也是diff算法的核心所在;GlobalKey
,通常我们会使用GlobalKey某个Widget对应的Widget或State或ElementLocalKey有三个子类
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();
}
}