Flutter 主要有这几种 widget:
- Stateless
- Stateful
- Inherited
Flutter 的思路大量借鉴了 React。Stateless widget 类似于没有本地 state 的 React 组件(pure component);Stateful 则是有本地 state 的。Inherited 则类似于 React 的 context(不过我没有深入过)。
Flutter 的 widget 用来描述 app 的 UI 结构。渲染 UI 时,Flutter 会递归地解析 widget 嵌套,然后构建一棵 element tree。
Stateless
Stateless widget 是一个 const 类,它的所有属性都是 final 的。如:
定义:
class DogName extends StatelessWidget {
final String name;
const DogName(this.name);
@override
Widget build(BuildContext context) {
return Text(name);
}
}
使用:
class DogApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Dog App',
home: Column(
children: [
DogName('Rocky'),
DogName('Jimmy'),
]
)
);
}
}
Stateful Widget
例子:
class ItemCounter extends StatefulWidget {
final String name;
ItemCounter({this.name});
@override
_ItemCounterState createState() => _ItemCounterState();
}
class _ItemCounterState extends State<ItemCounter> {
int count = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
count++;
});
},
child: Text('${widget.name}: $count'));
}
}
Stateful Widget 分为两部分:
- Widget object 负责:
- 保存不会变的值,比如上面的
name
- 创建 state object
- 保存不会变的值,比如上面的
- State object 负责:
- 保存会变化的值,比如上面的
count
- 构建 child widgets(通过
build
方法)
- 保存会变化的值,比如上面的
为什么 stateful widget 和 state 是分开的两个类?
You might wonder whyStatefulWidget
andState
are separate objects. In Flutter, these two types of objects have different life cycles.Widgets
are temporary objects, used to construct a presentation of the application in its current state.State
objects, on the other hand, are persistent between calls tobuild()
, allowing them to remember information.
State
是要被存进 element tree 的,而 widget 不被存入 element tree。
那为什么 build()
函数是在 state 中而不是 widget 中呢?我 不靠谱猜测 是 Flutter 运行时发现 state 中的属性有变化时,想要能快速地找到相应的 build()
函数来构建新的展示。
Flutter 的 UI 重绘机制
对于 stateful widget,当调用 setState()
时,Flutter 会把该 widget 标记为 dirty 进行重新渲染。
但假如某个 stateful widget 是一个 widget 列表构成的:
[DogName('Rocky'), DogName('Jimmy')]
如果使用代码对两个 DogName
widget 位置改变,Jimmy 跑到前头,那 Flutter 是否能正确绘制出来呢?答案是否定的。因为 Flutter 为了绘制效率,并不会销毁原先的两个 DogName widget 去重绘,而是判断变化后的 widget 的类型和 key 是否相同。由于这个场景中没有使用 key,而 widget 类型又不变,因此没有被正确绘制。你需要指定 key:
class DogName extends StatelessWidget {
final String name;
// 留意这里加了 key 参数
const DogName({Key key, this.name}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(name);
}
}
[DogName(name: 'Rocky', key: 1), DogName(name: 'Jimmy', key: 2)]
Widget Lifecycle
对于 StatefulWidget
,当框架把 state object 加进 element tree 时,会调其 initState()
函数;当 state object 不再被需要时,框架会调用它的 dispose()
。initState()
需要先调用 super.initState()
;dispose()
一般会在结尾调 super.dispose()
。
Inherited Widget
这种 widget 可以方便地将数据从 widget tree 上端传至下端:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
return Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(innerContext).color),
);
},
),
),
);
}
}
比如上面的 FrogColor
:
- 它并没有
build()
方法,不需要描述具体的子 widget 的展示,只需要提供children
变量让使用者定义子 widget - 它虽然在 widget tree 中作为一个节点,但作用仅限于存储一个
color
变量,供子节点使用 - 子节点一旦使用了
FrogColor.of()
(底层是context.dependOnInheritedWidgetOfExactType()
)后,一旦 FrogColor 中的数据发生变化(updateShouldNotify()
为 true 时),子节点就会被重绘