flutter自定义控件 - StatefulWidget中的State

flutter自定义控件 - StatefulWidget中的State,第1张

对于一个StatefulWidget实例,其Widget可以有多个,但State只有一个。

问题背景

一个自定义StatefulWidget的具体实现通常由两部分组成:继承自StatefulWidget的Widget继承自State的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。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/719485.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存