问题背景对于一个StatefulWidget实例,其Widget可以有多个,但State只有一个。
一个自定义StatefulWidget的具体实现通常由两部分组成:继承自StatefulWidget的Widget
和继承自State
.
当第一次构造StatefulWidget实例时(即使用Widget的构造函数构造实例),其主要构造过程如下(createState直接构造并返回State实例):
[log] Widget: 构造函数
[log] Widget: createState
[log] State: 构造函数
[log] State: initState
[log] State: build
当其父控件因状态改变等原因再次构造该StatefulWidget实例时(即再次使用Widget的构造函数构造实例),其构造过程如下:
[log] Widget: 构造函数
[log] State: didUpdateWidget
[log] State: build
可以看到,此时Widget中没有触发createState
函数,即一个StatefulWidget实例的State只有一个
。
因此当有StatefulWidget实现如下时:
class _Widget extends StatefulWidget {
final _State _state;
_Widget(double width, double height, {Key? key})
: _state = _State(width, height), super(key: key) {
log('Widget: 构造函数 ${width}dp x ${height}dp');
}
@override
State<StatefulWidget> createState() {
log('Widget: createState');
return _state;
}
}
class _State extends State<_Widget> {
final double _width;
final double _height;
_State(this._width, this._height) {
log('State: 构造函数 ${_width}dp x ${_height}dp');
}
...
}
其第一次构造过程如下:
[log] State: 构造函数 30.0dp x 20.0dp
[log] Widget: 构造函数 30.0dp x 20.0dp
[log] Widget: createState
[log] State: initState
[log] State: build 30.0dp x 20.0dp
父控件再次构造该StatefulWidget实例时,过程如下:
[log] State: 构造函数 100.0dp x 50.0dp
[log] Widget: 构造函数 100.0dp x 50.0dp
[log] State: didUpdateWidget
[log] State: build 30.0dp x 20.0dp
因此就出现了明明使用了新的参数(宽高)去构建StatefulWidget实例,但其在build时使用的参数依然是旧参数这种问题。
解决方案 常规方案一种常规的解决思路是:把Widget同时视为参数集,将必要的随Widget改变而改变的参数定义在Widget中,然后在State中通过widget.参数名
来使用参数。示例如下:
class _Widget extends StatefulWidget {
final double width;
final double height;
_Widget(this.width, this.height, {Key? key}) : super(key: key) {
log('Widget: 构造函数');
}
@override
State<StatefulWidget> createState() {
log('Widget: createState');
return _State();
}
}
class _State extends State<_Widget> {
...
@override
Widget build(BuildContext context) {
log('State: build');
return Container(
width: widget.width,
height: widget.height,
color: Colors.greenAccent,
);
}
...
}
当StatefulWidget实例再次构造时,其过程如下:
[log] Widget: 构造函数
[log] State: didUpdateWidget
[log] oldWidget: 30.0dp x 20.0dp
[log] Widget: 100.0dp x 50.0dp
[log] State: build
每当Widget实例改变时,在State实例中通过widget
所获得的实例就会随之改变,因此在build时就能够使用新的Widget实例中的参数。
实际上,这种方案在新建flutter项目时就可以看到:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
...
);
}
另辟蹊径(不推荐)
从上面的描述中可以看到当Widget再次构造时,State并不会再次构造。实际上,我们可以通过设定key值的方式来实现当Widget再次构造的同时State也重新构造。
当Widget再次构造时,flutter会检查
新Widget实例的key值是否
与旧的Widget实例的key值相同
,若相同将会使用已有的State实例,否则将会创建新的State实例
(实际上,在未设置key时,新旧Widget实例的key值都为null,它们是相等的,所以State的实例保持不变)。因此我们可以通过设置不同的key值(如UniqueKey()
)来促使State实例的重新构造。示例如下:
class _Widget extends StatefulWidget {
final _State _state;
_Widget(double width, double height)
: _state = _State(width, height), super(key: UniqueKey()) {
log('Widget: 构造函数 ${width}dp x ${height}dp');
}
@override
State<StatefulWidget> createState() {
log('Widget: createState');
return _state;
}
}
class _State extends State<_Widget> {
final double _width;
final double _height;
_State(this._width, this._height) {
log('State: 构造函数 ${_width}dp x ${_height}dp');
}
...
}
其第一次构造过程如下:
[log] State: 构造函数 30.0dp x 20.0dp
[log] Widget: 构造函数 30.0dp x 20.0dp
[log] Widget: createState
[log] State: initState
[log] State: build 30.0dp x 20.0dp
其再次构造过程如下:
[log] State: 构造函数 100.0dp x 50.0dp
[log] Widget: 构造函数 100.0dp x 50.0dp
[log] Widget: createState
[log] State: initState
[log] State: build 100.0dp x 50.0dp
[log] State: dispose 30.0dp x 20.0dp
该方案不推荐的原因在于,若key值设置的不合适,将违背Widget和State的设计是为了提高布局效率的初衷。
StatefulWidget的状态改变通常是通过State的setState
方法实现的,但在上述问题的常规解决方案下,可以发现,我们似乎拿不到当前这个Widget所绑定的State实例(因为再次构造Widget实例时,createState并不会被调用),因此我们还需找到这个State实例。欢迎阅读:flutter自定义控件 - StatefulWidget获取当前State。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)