Flutter的三棵树
Widget树
- Widget可以看成是对组件的描述文件或者配置文件,主要是用来描述组件的属性信息,是比较轻量级的对象;
- Widget的状态是十分不稳定的,当其状态发生变化时,就会调用build方法进行重建;
- Flutter团队对Widget做了优化,不用担心整个Widget树频繁创建、销毁所带来的性能问题;
RenderObject树
- RenderObject是渲染对象,是Flutter真正用于处理渲染操作的,用于布局与绘制等复杂的渲染操作,RenderObject对象频繁的创建与销毁会耗费大量的性能;
Element树
- Element:称之为元素,根据Widget构建而成,每一个widget内部调用
createElement()
方法,创建与之对应的Element; - Element主要负责进行协调,同时持有Widget对象与RenderObject对象,其决定了是否将RenderObject实例attach到Render Tree上,并决定是否进行Update操作;
Widget渲染机制的源码分析
- 第一步:Widget内部会创建与之对应的Element;
- Widget分为三种,分别为
StatelessWidget
,StatefulWidget
与RenderObjectWidget
,最终都继承自Widget抽象类,内部都实现了Element createElement()
,创建与之对应的Element,也就是说Widget与Element是一一对应的关系
; -
StatelessWidget
实现Element createElement()函数会创建StatelessElement
,源码如下:
@override
StatelessElement createElement() => StatelessElement(this);
-
StatefulWidget
实现Element createElement()函数会创建StatefulElement
,源码如下:
@override
StatefulElement createElement() => StatefulElement(this);
-
RenderObjectWidget
实现Element createElement()函数会创建RenderObjectElement
,源码如下:
@override
@factory
RenderObjectElement createElement();
StatelessElement
,StatefulElement
与RenderObjectElement
都是继承自Element
抽象类;第二步:FlutterSDK会调用Element的
mount
方法;下面以
StatelessWidget
为例进行探测,调试界面如下:
- 其源码的调用流程如下所示:
- 以
StatefulWidget
为例,其源码调用逻辑如下:
- 注意⚠️:StatefulElement会持有widget和state的引用;
- 以
SingleChildRenderObjectWidget
为例,其源码调用逻辑如下:
- 总结:
-
StatelessWidget
与StatelessElement
相互引用; -
StatefulWidget
与StatefulElement
相互引用,且StatefulElement
会持有state
状态的引用; -
SingleChildRenderObjectWidget
与SingleChildRenderObjectElement
相互引用,其父类RenderObjectElement
会持有RenderObject
渲染对象的引用;
-
Widget的属性 Key的应用
- Widget的构造方法会传入一个可选参数Key,如下:
const Widget({ this.key });
- 此
可选参数Key
的作用是什么,现在我们通过案例来探讨一下,测试代码如下:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
List titles = ["111","222"];
List widgets;
@override
void initState() {
// TODO: implement initState
super.initState();
widgets = [
StatefulColor(),
StatefulColor()
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Row(
children: widgets,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied),
onPressed: swapTiles,
),
);
}
void swapTiles() {
setState(() {
widgets.insert(1, widgets.removeAt(0));
});
}
}
class StatelessColor extends StatelessWidget {
Color color = ColorUtil.randomColor();
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Padding(padding: EdgeInsets.all(70.0))
);
}
}
class StatefulColor extends StatefulWidget {
StatefulColor({Key key}) : super(key: key);
@override
State createState() => StatefulColorState();
}
class StatefulColorState extends State {
Color color;
@override
void initState() {
super.initState();
color = ColorUtil.randomColor();
}
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Padding(padding: EdgeInsets.all(70.0))
);
}
}
class ColorUtil {
static Color randomColor() {
var red = Random.secure().nextInt(255);
var greed = Random.secure().nextInt(255);
var blue = Random.secure().nextInt(255);
return Color.fromARGB(255, red, greed, blue);
}
}
- 现象:当使用
StatelessColor
时,点击可以发生色块之间的交换,但使用StatefulColor
时,点击不能发生交换; - 在使用
StatelessColor
的情况下,注意颜色属性是存放在widget中的,交换之前的widget树与element树如下所示:
- 点击按钮,发生交换之后,widget树中的两个节点发生了交换,element树中的element是可以重用的,element的重用取决于当前节点处的
新widget
与对应的element原来引用的旧widget
的runtimeType与key是否相等,若相等则当前节点位置的element可重用,且会将element引用指向新的widget
,交换后的情况如下所示:
- 在使用
StatefulColor
的情况下,注意颜色属性是存放在element中的state中的,交换之前的widget树与element树如下所示:
- element的state决定了widget的颜色,点击按钮发生交换时,widget树中节点交换了,但指定节点位置的element重用了,只是widget引用发生了改变,而颜色是由element的state决定的,所以颜色没有发生变化,原理如下图所示:
- 现做如下改动,StatefulColor在初始化时,传入可选参数key,如下:
- 再点击按钮,发现颜色可发生交换,交换之前的如下所示:
- 点击交换之后,如下所示:
- element重建之后,如下所示:
- key值的一个重要作用在于:
决定element元素的重建或者重用
;
widget可选参数Key的分类
- Key是一个抽象类,其有一个工厂构造器,子类有:
- LocalKey:应用于具有相同父Element的widget进行比较,是diff算法的核心所在;
- GlobalKey:通常使用于某个widget,然后访问其widget本身与state的;
- LocalKey有三个子类:
- ValueKey:我们以特定的值作Widget的key,比如字符串,数字等;
- ObjectKey:以模型对象作为Widget的key;
- UniqueKey:可确保key为唯一的;
GlobalKey
- 先上案例代码,如下所示:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(key: homeKey),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.gesture),
onPressed: (){
},
),
);
}
}
class SFHomeContent extends StatefulWidget {
final String name = "2222";
@override
_SFHomeContentState createState() => _SFHomeContentState();
}
class _SFHomeContentState extends State {
final String message = "1111";
@override
Widget build(BuildContext context) {
return Text(message);
}
void test(){
print("_SFHomeContentState test");
}
}
- 现在要实现,在点击按钮的时候 能访问SFHomeContent的name属性,能访问_SFHomeContentState的message属性,以及调用test方法,可通过
GlobalKey
来实现; -
GlobalKey
类的定义是:abstract class GlobalKey
,易知> extends Key GlobalKey
是一个抽象类,从泛型可以看出其本质是一个State, - 通过创建一个
GlobalKey<_SFHomeContentState>
,然后传参给SFHomeContent
,修改后的代码如下:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
final GlobalKey<_SFHomeContentState> homeKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(key: homeKey),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.gesture),
onPressed: (){
print(homeKey.currentState.message);
print(homeKey.currentState.widget.name);
homeKey.currentState.test();
},
),
);
}
}
class SFHomeContent extends StatefulWidget {
final String name = "2222";
SFHomeContent({Key key}) : super(key: key);
@override
_SFHomeContentState createState() => _SFHomeContentState();
}
class _SFHomeContentState extends State {
final String message = "1111";
@override
Widget build(BuildContext context) {
return Text(message);
}
void test(){
print("_SFHomeContentState test");
}
}