天天看点

Flutter移动应用:状态管理

原文链接https://siques.cn/p/41

这个章节我们会先介绍一下 Flutter 的 StatelessWidget … 还有 StatefulWidget …

然后会用一个非常简单的例子来理解一下 Flutter 应用的状态管理 … 状态就是小部件里的数据 … 小部件可以自己管理需要的数据 … 这些数据也可以通过小部件的构造函数从它的父辈那里传递过来 …

我们还会学一下,使用 InheritedWidget … 还有 ScopedModel,更有效的去把数据传递给需要的小部件 …

小部件

StatelessWidget:无变化状态的小部件

StatelessWidget,这种小部件里面不包含可以变化的状态,State 指的就是状态,状态可以想成是小部件里的数据 … 也就是 StatelessWidget 小部件一但被创建以后 … 它里面的状态一般不会有什么变化 …

Flutter移动应用:状态管理

下面再把 count 的值输出到控制台上检查一下 … 打开调试控制台 …

按一下界面上的这个漂浮按钮 … 你会发现,每次按这个按钮的时候,count 的值都会加上 1 … 不过在界面上这个 Chip 里面显示的 count 的值没有发生改变 …

我们创建的小部件的状态会根据用户行为发生变化 … 这个时候我们需要考虑使用另一种小部件,StatefulWidget …

StatefulWidget:带变化状态的小部件

StatefulWidget,这种类型的小部件里面可以有一些能够变化的状态 … 也就是 StaefulWidget 构建的用户界面可以动态的发生变化 … 比如在我们这个例子里面 … 点了这个漂浮按钮,界面上显示的这个数字要动态的发生变化 … 这种情况我们就可以使用一个 StaefulWidget …

下面可以把我们这个小部件改造成一个 StatefulWidget … 让它继承一下 StatefulWidget … 要注意的是 StaefulWidget 小部件本身也是 immutable ,不可改变的 …

小部件需要的可以变化的那些状态要单独放在一个 State 对象里面 … 这个 State 对象可以使用小部件里的 createState 这个方法来创建 .

Flutter移动应用:状态管理

然后到模拟器上再试一下 … 按一下这个动作按钮 … count 的值会加上 1,这个变化是在 setState 方法里面完成的,所以每次有变化,小部件都会使用新的状态被重建 … 重建以后,界面上会显示小部件变化之后的状态 …

状态管理(由父辈管理状态)

在我们这个 StateManagementDemo 小部件里面 … 小部件的状态是它自己管理的 … 它需要的所有的状态都在这个小部件自己这里 … 有时候这个状态可能在小部件的父辈那里 … 把 State 从父辈那里传递过来,可以通过小部件的构造函数 …

Flutter移动应用:状态管理

使用这个 Counter 的时候,现在需要提供一个 count 属性 … 这个属性的值可以使用上面定义的这个 _count …

现在 Counter 小部件里面需要的数据是从它爸爸,也就是 StateManagementDemo 那里传递过来的 …

从父辈那里传递个回调

下面我们再改造一下这个 Counter 小部件 … 把这个 Chip 换成一个 ActionChip ,这样可以给它一个 onPressed 属性,设置一下点按这个 Chip 要执行的动作 … 我想在按它的时候也可以让 _count 的值加上 1 …

如果在这个 Counter 小部件里面设置它的这个 count ,界面上显示的数字是不会有什么变化的 … 因为这个数字其实是 StateManagementDemo 里面的 _count 的的值 …

Flutter移动应用:状态管理

按一下界面上的这个 ActionChip … 执行的就是它爸爸传递过来的一个回调 … 做的事儿就是让小部件的 _count 的值加上 1 …

再按一下漂浮动作按钮 … 同样可以增加 _count 的值 …

状态树

Flutter移动应用:状态管理

现在,在 Counter 里面需要的数据是通过小部件的构造函数,从 StateManagementDemo 那里直接传递过来的 … 假设在这两个小部件之间还有一个小部件 … 比如可能有一个 CounterWrapper … 在这个 CounterWrapper 里面使用了 Counter …

这样,这个 Counter 需要的数据要先从 StateManagementDemo 传递给 CounterWrapper … 然后再由这个 CounterWrapper 传递给 Counter … 需要一级一级往下传 …

Flutter移动应用:状态管理

InheritedWidget

InheritedWidget:直接把数据传递给需要的小部件

在我们这个示例里面,Counter 小部件需要的数据是从 StateManagementDemo 传递给 CounterWrapper,又从 CounterWrapper 传递给了 Counter … 现在我们需要一种方法,可以把数据直接传递给需要的小部件 … 也就是 Counter 需要的数据可以不通过 CounterWrapper,而是直接从 StateManagementDemo 那里传递过来 …

可以试一下 Flutter 的 InheritedWidget … 用法就是可以去创建一个 InheritedWidget,在这个小部件里面设置其它小部件需要的数据,然后把这个 InheritedWidget 放在小部件树的某个地方,这样在树下面的小部件都可以直接访问到在 InheritedWidget 小部件里的数据了 …

Flutter移动应用:状态管理

ScopedModel

ScopedModel:安装与基本用法介绍

ScopedModel 也可以把数据直接交给需要的小部件 … 它是一个第三方的包,所以要使用它得先去安装一下 … 打开项目下面的 pubspec.yaml …

Flutter移动应用:状态管理

使用 ScopedModel … 我们得先去创建一个 Model … 在里面添加需要的数据 … 然后把 ScopedModel 小部件放到 Widget Tree 的某个位置上 … 设置一下它的 model … 这样在它下面的小部件都可以直接访问到它设置的 model 里的东西 …

使用 ScopedModel 传递数据

先创建一个 Model … 添加一个类 … 名字可以是 CounterModel … 它要继承一下 Model … 再导入需要的包 … 就是之前我们安装的这个 scoped_model …

里添加一个 int _count … 让它先等于 0 … 再添加一个 getter 方法, 名字叫 count … 让它返回 _count 的值 … 在使用了这个 model 的小部件里面,可以使用这个 getter 方法获取到 _count 的值 …

Flutter移动应用:状态管理

按一下界面上的 ActionChip ,会让 CounterModel 的 _count 的值增加 1 ,有变化就会重建这个部件显示出变化之后的样子 …

同样,按一下漂浮动作按钮,也可以让 CounterModel 里的 _count 的值加上 1 …

最终代码

import 'package:flutter/material.dart';
import "package:scoped_model/scoped_model.dart";

class StateManagementDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel(
        model: CounterModel(),
        child: Scaffold(
            appBar: AppBar(
              title: Text('StateManagementDemo'),
              elevation: 0.0,
            ),
            body: CouterWrapper(),
            floatingActionButton: ScopedModelDescendant<CounterModel>(
              rebuildOnChange: false,
              builder: (context, _, model) => FloatingActionButton(
                child: Icon(Icons.add),
                onPressed: model.increaseCount,
              ),
            )));
  }
}

class CouterWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Counter(),
    );
  }
}

class Counter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CounterModel>(
        builder: (context, _, model) => ActionChip(
              label: Text('${model.count}'),
              onPressed: model.increaseCount,
            ));
  }
}

class CounterProvider extends InheritedWidget {
  final int count;
  final VoidCallback increaseCount;
  final Widget child;

  // 构造函数
  CounterProvider({this.count, this.increaseCount, this.child})
      : super(child: child);

  static CounterProvider of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType();

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}

class CounterModel extends Model {
  int _count = 0;
  int get count => _count;

  void increaseCount() {
    _count += 1;
    notifyListeners();
  }
}