• Flutter 状态管理


    StatefulWidget


    Flutter 中的组件,按状态划分:
      StatelessWidget(无状态组件)
      StatefulWidget(状态组件)

    状态组件是包含可变状态的组件,状态的特点:
    (1)、当组件构建完成后,可同步读取
    (2)、可以在组件的生命周期中改变

    按状态作用域划分
      组件内私有状态(StatefulWidget)
      跨组件状态共享(InheritedWidget, Provider)
      全局状态(Redux, fish-redux, Mobx......)

    状态组件的组成
      StatefulWidget(组件本身不可变 @immutable)
      State(将变化的状态放到 State 中维护)
    import 'package:flutter/material.dart';
    
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('StatefulWidget'),
          ),
          body: const MyState(),
        );
      }
    }
    
    class MyState extends StatefulWidget {
      const MyState({Key? key}) : super(key: key);
    
      @override
      State<MyState> createState() => _MyStateState();
    }
    
    class _MyStateState extends State<MyState> {
      int _num = 0;
    
      // 执行加的方法
      void _increment() {
        setState(() {
          _num++;
        });
      }
    
      // 减的方法
      void _decrement() {
        setState(() {
          if (_num == 0) return;
          _num--;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Column(
            children: [
              //
              ElevatedButton(
                onPressed: _decrement,
                child: const Icon(Icons.minimize),
              ),
              // 数字
              Padding(
                padding: const EdgeInsets.all(20),
                child: Text('$_num'),
              ),
              //
              ElevatedButton(
                onPressed: _increment,
                child: const Icon(Icons.add),
              )
            ],
          ),
        );
      }
    }

    生命周期

    initState() 组件对象插入到元素树中时,通常根据后台接口的返回数据对状态进行初始化

    didChangeDependencies() 当前状态对象的依赖改变时

    build() 组件渲染时

    setState() 组件对象的内部状态变更时

    didUpdateWidget() 组件配置更新时

    deactivate() 组件对象在元素树中暂时移除时

    dispose() 组件对象在元素树中永远移除时
     
    // ignore_for_file: avoid_print
    import 'package:flutter/material.dart';
    
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('LifeCycle'),
          ),
          body: const MyState(),
        );
      }
    }
    
    class MyState extends StatefulWidget {
      const MyState({Key? key}) : super(key: key);
    
      @override
      State<MyState> createState() => _MyStateState();
    }
    
    class _MyStateState extends State<MyState> {
      int _num = 0;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        print('initState, 执行initState方法,_num: $_num');
        // 通常根据后台接口返回的数据对状态进行初始化
        _num = 1;
      }
    
      @override
      void didChangeDependencies() {
        // TODO: implement didChangeDependencies
        super.didChangeDependencies();
        print('didChangeDependencies, 当前状态对象的依赖改变了, _num: $_num');
      }
    
      @override
      void didUpdateWidget(covariant MyState oldWidget) {
        // TODO: implement didUpdateWidget
        super.didUpdateWidget(oldWidget);
        print('didUpdateWidget, 组件配置更新了,_num: $_num');
      }
    
      @override
      void deactivate() {
        // TODO: implement deactivate
        super.deactivate();
        print('deactivate, 组件对象在元素树中暂时移除了,_num: $_num');
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        print('dispose, 组件对象在元素树中永远移除了,_num: $_num');
      }
    
      // 执行加的方法
      void _increment() {
        setState(() {
          print('setState, 组件对象的内部状态变更了,_num: $_num');
          _num++;
        });
      }
    
      // 减的方法
      void _decrement() {
        setState(() {
          print('setState, 组件对象的内部状态变更了,_num: $_num');
          if (_num == 0) return;
          _num--;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print('build, 组件渲染时,_num: $_num');
        return Center(
          child: Column(
            children: [
              //
              ElevatedButton(
                onPressed: _decrement,
                child: const Icon(Icons.minimize),
              ),
              // 数字
              Padding(
                padding: const EdgeInsets.all(20),
                child: Text('$_num'),
              ),
              //
              ElevatedButton(
                onPressed: _increment,
                child: const Icon(Icons.add),
              )
            ],
          ),
        );
      }
    }

    InheritedWidget

    What:
      提供沿树向下,共享数据的功能
      即子组件可以获取父组件(InheritedWidget 的子类)的数据

    Why:
      依赖构造函数传递数据的方式不能满足业务需求。如图,组件D和组件G共享的数据是通过组件A取数据以后逐级向下透传的,也就是说数据需要逐级传递,比较麻烦,而且不是所有组件都需要相关数据
      所以,需要一个新的,更好的跨组件数据传输方案。图中,所有的组件都可以获取顶层父组件 InheritedWidget 的数据,可以直接获取,不需要依赖其它组件传递

    How:
      BuildContext.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()

    构造函数
    InheritedWidget({Key? key, required Widget child})

    InhertiedWidget抽象类包含一个常量构造器,继承该抽象类的子类可以提供一个常量构造器,在使用这些子类时可以用 const 表达式来调用

    child:Widget,表示在组件树中挂载在该子类下的组件
    key:Key?,表示要传递的数据
    import 'package:flutter/material.dart';
    
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('InheritedWidget'),
          ),
          body: const CalculatorState(),
        );
      }
    }
    
    // 组件一:挂载在 InheritedWidget 下,它提供了一个可共享的数据 num
    class CalculatorState extends StatefulWidget {
      const CalculatorState({Key? key}) : super(key: key);
    
      @override
      State<CalculatorState> createState() => _CalculatorStateState();
    }
    
    class _CalculatorStateState extends State<CalculatorState> {
      int _num = 0;
    
      // 执行加的方法
      void _increment() {
        setState(() {
          _num++;
        });
      }
    
      // 减的方法
      void _decrement() {
        setState(() {
          if (_num == 0) return;
          _num--;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        // 要调用 of 方法,of 方法中的参数 context 必须是 InheritedWidget 的后代
        // 也就是说,组件必须挂在 InheritedWidget 下面
        return ShareDataWidget(
          // 提供 ShareDataWidget 必须的两个属性 num 和 child
          num: _num,
          child: Center(
            child: Column(
              children: [
                //
                ElevatedButton(
                  onPressed: _decrement,
                  child: const Icon(Icons.minimize),
                ),
                // 数字
                const Padding(
                  padding: EdgeInsets.all(20),
                  // 跨组件访问数据
                  child: MyCounter(),
                ),
                //
                ElevatedButton(
                  onPressed: _increment,
                  child: const Icon(Icons.add),
                )
              ],
            ),
          ),
        );
      }
    }
    
    // 组件二:调用 of() 方法,获取 InheritedWidget 共享的数据 num
    class MyCounter extends StatefulWidget {
      const MyCounter({Key? key}) : super(key: key);
    
      @override
      State<MyCounter> createState() => _MyCounterState();
    }
    
    class _MyCounterState extends State<MyCounter> {
      @override
      Widget build(BuildContext context) {
        // 使用 InheritedWidget 中的共享数据
        if (ShareDataWidget.of(context)?.num == null) {
          return const Text('0');
        }
        return Text(ShareDataWidget.of(context)!.num.toString());
      }
    }
    
    // 数据共享组件
    class ShareDataWidget extends InheritedWidget {
      const ShareDataWidget({
        Key? key,
        required this.num,
        required this.child,
      }) : super(key: key, child: child);
    
      final int num;
      final Widget child;
    
      // 静态方法 of 用于调用 BuildContext.dependOnInheritedWidgetOfExactType
      // 这样做的目的是为了让类可以定义自己的回调逻辑,即便该类没有出现在 context 上下文中
      static ShareDataWidget? of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
      }
    
      @override
      bool updateShouldNotify(ShareDataWidget oldWidget) {
        return true;
      }
    }

     
    Provider

    Provider 是对 InheritedWidget 的封装,使其使用更容易使用且易于复用

    文档:https://pub.flutter-io.cn/packages/provider
    安装:flutter pub add provider
    引用:import 'package:provider/provider.dart';

    优点:
    (1)、简化资源的分配与处置
    (2)、懒加载
    (3)、创建新类时减少大量的模板代码
    (4)、支持 DevTools

    实现原理
     
    ChangeNotifier:class,可以封装应用程序的状态,用于向监听器发送通知,如果被定义为 ChangeNotifier,就可以订阅它的状态变化。对于简单的程序,一个 ChangeNotifier 就可以满足需求;对于复杂的应用,由于会有多个模型,所以可能会有多个 ChangeNotifier

    ChangeNotifierProvider:widget,用于监听 ChangeNotifier 的实例并将其暴露给子孙节点,并且当 ChangeNotifier.notifyListeners 被调用时,会重新构建。虽然数据是单独存放在 ChangeNotifier 中,但是面对组件时,直接提供数据的是 Provider
     
    如果你想提供更多状态,可以使用 MultiProvider:
    void main() {
      runApp(
        MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (context) => CartModel()),
            Provider(create: (context) => SomeOtherClass()),
          ],
          child: const MyApp(),
        ),
      );
    }

    区别于 InheritedWidget 把状态存放在根节点上,Provider 会把状态和根节点做分割处理,状态会被提取出来保存在 ChangeNotifier 组件中,使用数据时,需要把数据注册到根节点 ChangeNotifierProdiver 上,后续的逻辑基本相同。

    图中的 Producer (生产者)组件修改了数据以后,ChangeNotifierProvier (观察者)就会做两件事情,一是通知 ChangeNotifier(被观察者)更新 data,二是通知所有的 Listener(听众,即消费者)更新UI界面。也就是说,Provider 会把整个应用分成三个部分:

    (1)、数据部分

    (2)、监控数据的部分

    (3)、UI界面

    整个过程也可以类比 MVVM 的状态管理模式

    使用步骤:

    (1)、创建数据模型

    (2)、创建 Provider

    (3)、在子组件中使用数据模型

    示例:

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    // Step 1
    // 1、创建数据模型
    class LikesModel extends ChangeNotifier {
      // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中)
      int _counter = 0;
      // 模拟一个私有的getter
      int get counter => _counter;
    
      void incrementCounter() {
        // 累加
        _counter++;
    
        // 通知 UI 更新(监听了这个模型的组件重新构建)
        notifyListeners();
      }
    }
    
    // Step 2
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // 2、创建 Provider (注册数据模型)
        return ChangeNotifierProvider(
          create: (BuildContext context) => LikesModel(),
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Provider'),
            ),
            body: const MyHomePage(),
          ),
        );
      }
    }
    
    // Step 3
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          padding: const EdgeInsets.all(10),
          child: Column(
            children: [
              // 3、在子组件中使用数据模型
              Text(
                '点赞了:${context.watch<LikesModel>().counter} 次',
                style: const TextStyle(
                  color: Colors.red,
                  fontSize: 26,
                ),
              ),
              TextButton(
                onPressed: () => context.read<LikesModel>().incrementCounter(),
                child: const Icon(Icons.thumb_up),
              ),
            ],
          ),
        );
      }
    }

    读取值
     
    可以使用 BuildContext 上的扩展属性(由 provider 注入)
    (1)、context.watch<T>(),widget 能够监听到 T 类型的 provider 发生的改变
    (2)、context.read<T>(),直接返回 T,不会监听改变
    (3)、context.select<T, R>(R cb(T value)),允许 widget 只监听 T 上的一部分内容的改变

    需要注意的是,context.read<T>() 方法不会在值变化时让 widget 重新构建,并且不能在 StatelessWidget.build 和 State.build 内调用。换句话说,它可以在除了这两个方法以外的任意位置调用
    Text(
      // 此处如果使用 context.read 来读取 counter,将监听不到状态更改,视图不会更新
      '点赞了:${context.watch<LikesModel>().counter} 次',
      style: const TextStyle(
        color: Colors.red,
        fontSize: 26,
      ),
    ),
    这些方法会从传入的 BuildContext 关联的 widget 开始,向上查找 widget 树,并返回查找到的层级最近的 T 类型的 provider(未找到时将抛出错误)。例如,把 Step3 中的 MyHomePage 直接作为 Step2 中的 body 组件,就会报如下错误
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    // Step 1
    // 1、创建数据模型
    class LikesModel extends ChangeNotifier {
      // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中)
      int _counter = 0;
      // 模拟一个私有的getter
      int get counter => _counter;
    
      void incrementCounter() {
        // 累加
        _counter++;
    
        // 通知 UI 更新(监听了这个模型的组件重新构建)
        notifyListeners();
      }
    }
    
    // Step 2
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // 2、创建 Provider (注册数据模型)
        return ChangeNotifierProvider(
          create: (BuildContext context) => LikesModel(),
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Provider'),
            ),
            // todo: 注意看这里,会产生异常
            body: Container(
              padding: const EdgeInsets.all(10),
              child: Column(
                children: [
                  // 3、在子组件中使用数据模型
                  Text(
                    '点赞了:${context.watch<LikesModel>().counter} 次',
                    style: const TextStyle(
                      color: Colors.red,
                      fontSize: 26,
                    ),
                  ),
                  TextButton(
                    onPressed: () => context.read<LikesModel>().incrementCounter(),
                    child: const Icon(Icons.thumb_up),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    ProviderNotFoundException (Error: Could not find the correct Provider<LikesModel> above this Home Widget
     
    大概意思是说,Home组件的 BuildContext 不包含 Provider<LikesModel>,产生这个错误的原因是使用的 BuildContext 是当前正在读取数据的 provider 的祖先元素,当你创建好一个 provider 以后立即读取其数据就会报这个错。
    解决方法:
    (1)、将上下文传递给与构建方法分离。参考示例中的 Step2、Step3 的写法,拆成两个组件
    (2)、将上下文包装在 Builder 中
     
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    // Step 1
    // 1、创建数据模型
    class LikesModel extends ChangeNotifier {
      // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中)
      int _counter = 0;
      // 模拟一个私有的getter
      int get counter => _counter;
    
      void incrementCounter() {
        // 累加
        _counter++;
    
        // 通知 UI 更新(监听了这个模型的组件重新构建)
        notifyListeners();
      }
    }
    
    // Step 2
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // 2、创建 Provider (注册数据模型)
        return ChangeNotifierProvider(
          create: (BuildContext context) => LikesModel(),
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Provider'),
            ),
            // todo: 修改点:将上下文包装在 Builder 中
            body: Builder(builder: (BuildContext context) {
              return Container(
                padding: const EdgeInsets.all(10),
                child: Column(
                  children: [
                    // 3、在子组件中使用数据模型
                    Text(
                      '点赞了:${context.watch<LikesModel>().counter} 次',
                      style: const TextStyle(
                        color: Colors.red,
                        fontSize: 26,
                      ),
                    ),
                    TextButton(
                      onPressed: () =>
                          context.read<LikesModel>().incrementCounter(),
                      child: const Icon(Icons.thumb_up),
                    ),
                  ],
                ),
              );
            }),
          ),
        );
      }
    }
    DataTable

    DataTable 是 Flutter 中的表格

    columns:声明表头列表
      DataColumn:表头单元格

    rows:声明数据列表
      DataRow:一行数据
      DataCell:数据单元格
    import 'package:flutter/material.dart';
    
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('DataTable'),
          ),
          body: ListView(
            children: const [UserTable()],
          ),
        );
      }
    }
    
    class UserTable extends StatefulWidget {
      const UserTable({Key? key}) : super(key: key);
    
      @override
      State<UserTable> createState() => _UserTableState();
    }
    
    class _UserTableState extends State<UserTable> {
      @override
      Widget build(BuildContext context) {
        return Container(
          padding: const EdgeInsets.symmetric(horizontal: 1),
          child: SingleChildScrollView(
            child: DataTable(
              // 表头
              columns: const [
                // 表头单元格
                DataColumn(label: Text('姓名')),
                DataColumn(label: Text('年龄')),
                DataColumn(label: Text('性别')),
                DataColumn(label: Text('简介')),
              ],
              // 表格主体
              rows: const [
                //
                DataRow(cells: [
                  //
                  DataCell(Text('张三')),
                  DataCell(Text('18')),
                  DataCell(Text('')),
                  DataCell(Text('一首张三的歌')),
                ]),
                DataRow(cells: [
                  DataCell(Text('二丫')),
                  DataCell(Text('22')),
                  DataCell(Text('')),
                  DataCell(Text('没有简介')),
                ]),
              ],
            ),
          ),
        );
      }
    }

    import 'package:flutter/material.dart';
    
    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('DataTable'),
          ),
          body: const UserTable(),
        );
      }
    }
    
    class User {
      String name;
      int age;
      String gender;
      String intro;
      bool selected;
    
      User(this.name, this.age, this.gender, this.intro, {this.selected = false});
    }
    
    class UserTable extends StatefulWidget {
      const UserTable({Key? key}) : super(key: key);
    
      @override
      State<UserTable> createState() => _UserTableState();
    }
    
    class _UserTableState extends State<UserTable> {
      List<User> data = [
        User('美国队长', 200, '', '最会甩锅的男人'),
        User('钢铁侠', 72, '', 'i love you more than 4000'),
        User('蜘蛛侠', 18, '', '彼得帕克和玛莉简拍照时被五彩毒蜘蛛咬了', selected: true),
        User('黑寡妇', 36, '', '把你的洗澡水给杜兰特喝一点吧'),
      ];
    
      bool _sortAscending = true;
      List<DataRow> _getUserRows() {
        List<DataRow> dataRows = [];
        for (int i = 0; i < data.length; i++) {
          dataRows.add(
            DataRow(
              selected: data[i].selected,
              onSelectChanged: (selected) {
                // ignore: avoid_print
                setState(() {
                  data[i].selected = selected!;
                });
              },
              cells: [
                DataCell(Text(data[i].name)),
                DataCell(Text('${data[i].age}')),
                DataCell(Text(data[i].gender)),
                DataCell(Text(data[i].intro)),
              ],
            ),
          );
        }
        return dataRows;
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          padding: const EdgeInsets.symmetric(horizontal: 1),
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            // 表格
            child: DataTable(
              // 需要排序的列
              sortColumnIndex: 1,
              // 排序方式,false:降序,true:升序
              sortAscending: _sortAscending,
              // 行高
              dataRowHeight: 100,
              // 每一行的首位两列的表格边缘和父容器间的外边距
              horizontalMargin: 10,
              // 单元格的水平外边距
              columnSpacing: 100,
              // 表头
              columns: [
                const DataColumn(label: Text('姓名')),
                DataColumn(
                  label: const Text('年龄'),
                  // 该列是否表示数值,如是,则该列内容右对齐
                  numeric: true,
                  // 排序:ascending false:降序,true:升序
                  onSort: (int columnIndex, bool ascending) {
                    setState(() {
                      _sortAscending = ascending;
                      if (ascending) {
                        // compareTo:负数表示a小于b,正数表示a大于b
                        data.sort((a, b) => a.age.compareTo(b.age));
                      } else {
                        data.sort((a, b) => b.age.compareTo(a.age));
                      }
                    });
                  },
                ),
                const DataColumn(label: Text('性别')),
                const DataColumn(label: Text('介绍')),
              ],
              // 表格主体
              rows: _getUserRows(),
            ),
          ),
        );
      }
    }

  • 相关阅读:
    git--简单操作
    flask--简记
    Python--进阶处理9
    Python--比较两个字典部分value是否相等
    Python--进阶处理8
    Python--进阶处理7
    Python--进阶处理6
    Python--进阶处理5
    java7连接数据库 网页 添加学生信息测试
    使用类的静态字段和构造函数,可以跟踪某个类所创建对象的个数。请写一个类,在任何时候都可以向它查询“你已经创建了多少个对象?”
  • 原文地址:https://www.cnblogs.com/rogerwu/p/16293189.html
Copyright © 2020-2023  润新知