Flutter 中 State

state生命周期

示例代码如下:

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  @override
  void initState() {
    super.initState();
    //初始化状态  
    _counter=widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          //点击后计数器自增
          onPressed:()=>setState(()=> ++_counter,
          ),
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }

}

当我们创建一个路由,该路由中只有CounterWidgetwiget

Widget build(BuildContext context) {
  return CounterWidget();
}

此时我们运行应用打开路由界面,会log

I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build

将statefulWeight插入widget树时,首先会调用initState方法

当点击热重载按钮时

I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build

热重载时,didUpdateWidget被调用

当将CounnterWidget移除,将build方法更改

Widget build(BuildContext context) {
  //移除计数器 
  //return CounterWidget();
  //随便返回一个Text()
  return Text("xxx");
}

此时热重载

I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose

当移除时,调用deactivedispose方法

  • initState():Widget第一次插入到树中会被调用,对于每个State对象只会调用一次该回调,因此通常在其中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该方法中调用BuildContext.inheritFromWidgetOfExactType(该方法用于在Widget树中获取离当前widget最近的父级InheritFromWidget),原因是在初始化完成后,widget树中的InheritFromWidget可能也会发生变化,所以应该在build()方法或者didChangeDependencies()中调用它
  • didChangeDependencies():当State对象的依赖发生变化时被调用;例如,之前build()中包含一个InheritedWidget,之后build()中InheritdWidget发生变化,此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调
  • build():主要用于构建Widget子树,在一下场景被调用
    • initState()之后
    • didUpdateWidget()之后
    • 调用setState()
    • 调用didChangeDependencies()
    • 在State对象从树中的一个位置移除后(此时调用deactive),又重新插入到其它位置之后
  • reassemble():回调专门为开发调试提供,在热重载时被调用,在release模式下永远不会被调用
  • didUpdateWidget():在widget重新构建时,调用Widget.canUpdate检测Widget树中同一位置的新旧节点,决定是否需要更新,如果返回true调用此回调。其Widget.canUpdate会在新旧widget的key和runtimeType同时相等时返回true,
  • deactivate():当State从树中移除,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
  • dispose():当State对象从树中永久移除时调用;通常在此回调中释放资源

注意:在继承StatefulWidget重写其方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中先调用父类方法。

在Widget树中获取State对象

有时,需要获取StatefulWidget对应的State对象来调用一些方法,例如通过Scaffold组件对象的状态类ScaffoldState可以打开SnackBar。存在两种可以在子widget中获取父StatefuleWidget的State对象

通过Context获取

context对象有一个ancestorStateOfType(TypeMatcher)方法,可以再当前节点沿着widget树向上查找指定类型的StateWidget对应的State对象

Scaffold(
  appBar: AppBar(
    title: Text("子树中获取State对象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父级最近的Scaffold对应的ScaffoldState对象
          ScaffoldState _state = context.ancestorStateOfType(
              TypeMatcher<ScaffoldState>());
          //调用ScaffoldState的showSnackBar来弹出SnackBar
          _state.showSnackBar(
            SnackBar(
              content: Text("我是SnackBar"),
            ),
          );
        },
        child: Text("显示SnackBar"),
      );
    }),
  ),
);

一般来说,如果StatefulWidget状态是私有的,不应该直接去获取其State对象。但是通过context.ancestorStateOfType获取获取statefulWidget 的状态方法是通用的,并不能在语法层面指定其状态是否私有
因此,在Flutter中有一个默认的约定:

  • 如果其状态是希望暴露的,则应该在statefulWidget中提供一个of静态方法来获取其state对象,可以直接通过此方法来获取.(这个约定在Flutter的SDK中也是随处可见的)
ScaffoldState _state=Scaffold.of(context); 
_state.showSnackBar(
  SnackBar(
    content: Text("我是SnackBar"),
  ),
);

GlobalKey

  1. 给目标StatefulWidget添加GlobalKey

    //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
    static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
    ...
    Scaffold(
    key: _globalKey , //设置key
    ...
    )
  2. 通过GlobalKey获取

    _globalKey.currentState.openDrawer()
    

globalKey是在整个APP引用element的机制,如果widget设置了GlobalKey,可以通过glovalKey.currentWidget获取该widget对象,globalKey.currentElement获得widget对应element对象,如果widget是StatefulWidget,可以通过globalKey.currentState获得widget对应的state对象

使用GlobalKey开销就很大,应该尽量避免使用
同一和GlobalKey在整个widget树中必须是唯一的不能重复