Flutter 状态管理

Flutter 状态管理(StatelessWidget,StatefulWidget)前面介绍了什么是状态,Provider是官方推出的状态管理模式,本文将具体介绍Flutter的状态管理

有状态及无状态组件

无状态组件StatelessWidget)是不可变的,这意味着它们的属性不能改变,所有的值都是最终的。

有状态组件StatefulWidget)持有的状态可能在Widget生命周期中发生变化。实现一个StatefulWidget至少需要两个类:

  • StatefulWidget类,本身是不变的。
  • State类,在Widget生命周期中始终存在。

Flutter官方给出一个有状态组件的示例,点击右下角的+按钮,应用界面中间的数字会加1,如图所示。

Flutter 状态管理

这个示例有几个关键的部分,解析如下。示例代码中MyHomePage必须继承自StatefulWidget类,如下所示:

class MyHomePage extends StatefulWidget

重写createState方法,如下所示:

@override
MyHomePageState createState() => _MyHomePageState();

状态类必须继承自State,如下所示:

class _MyHomePageState extends State<MyHomePage>

定义一个普通变量_counter作为计数器变量,调用setState方法来控制这个变量的值的变化,如下所示:

int _counter = 0;


void _incrementCounter() {
  setState(() {
    // 计数器变量
    _counter++;
  });
}

完整的示例代码如下所示:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

// MyApp不需要做状态处理,所以此组件继承StatelessWidget即可
class MyApp extends StatelessWidget {
   // 这个组件是整个应用的主组件
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Flutter示例',
       theme: ThemeData(
         // 自定义主题
         primarySwatch: Colors.blue,
       ),
       home: MyHomePage(title: '无状态和有状态组件示例'),
     );
   }
 }


// 主页需要继承自StatefulWidget
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);


  // 标题
  final String title;


  // 必须重写createState方法
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
// 状态类必须继承State类,注意后面需要指定为<MyHomePage>
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0; //计数器


  void _incrementCounter() {
    // 调用State类里的setState方法来更改状态值,使得计数器加1
    setState(() {
      // 计数器变量,每次点击让其加1
      _counter++;
    });
  }


  @override
  Widget build(BuildContext context) {


    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      // 居中布局
      body: Center(


        // 垂直布局
        child: Column(
        // 主轴居中对齐
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
                '你点击右下角按钮的次数:',
            ),
            Text(
                '$_counter',               //绑定计数器的值
                          style: Theme.of(context).textTheme.display1,
                        ),
                      ],
                    ),
                  ),
                  floatingActionButton: FloatingActionButton(
                    onPressed: _incrementCounter, //点击+按钮调用自增函数
                    tooltip: '增加',
                    child: Icon(Icons.add),
                  ),
                );
            }
          }

Provider的使用

当我们的应用足够简单时,你可能并不需要状态管理。但是随着功能的增加,应用程序将会有几十个甚至上百个状态,你的应用状态变得难以维护。Flutter实际上在一开始就为我们提供了一种状态管理方式,那就是StatefulWidget。但是我们很快发现,它正是造成上述问题的罪魁祸首。这时候,我们便迫切需要一个架构来帮助我们厘清这些关系,状态管理框架应运而生。

Provider顾名思义即为提供者,用于提供数据,无论是在单个页面还是在整个应用中都有它自己的解决方案,可以很方便地管理状态。

这里我们以计数器为例来详细讲解Provider的使用方法。步骤如下所示。

步骤1:在工具目录下的pubspec.yaml文件中添加Provider的依赖。

步骤2:创建数据Model,这里的Model实际上就是我们的状态,它不仅存储了我们的数据模型,还包含了更改数据的方法,并暴露出它想要暴露出的数据,如下所示:

class Counter with ChangeNotifier {


  // 存储数据
  int _count = 0;
  // 提供外部能够访问的数据
  int get count => _count;


  // 提供更改数据的方法
  void increment(){
    _count++;
    // 通知所有听众进行刷新
    notifyListeners();
  }
}

我们要存储的数据即为_count,下划线代表私有。通过get把_count值暴露出来。并提供increment方法用于更改数据。

这里使用了mixin的方式混入了ChangeNotifier,这个类能够帮助我们自动管理所有听众。当调用notifyListeners时,它会通知所有听众进行刷新。

步骤3:创建顶层共享数据。这里使用MultiProvider可以创建多个共享数据,因为实际的应用不可能只有一个数据模型,代码如下:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 使用MultiProvider可以创建多个顶层共享数据
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
      ],
      child: MaterialApp(
        // 首页
      ),
    );
  }
}

步骤4:在子页面中获取状态,在这里我们分别编写了FirstPage及SecondPage。获取顶层数据的方法就是Provider.of<T>(context),这里的范型<T>指定为Counter。获取Counter里的值的代码如下所示:

Provider.of<Counter>(context).count

在两个页面中均添加以上代码用于获取Counter的值。同时两个页面也可以改变Counter的值,使用方法如下所示:

Provider.of<Counter>(context).increment();

上述两个页面的增加值的操作是同步的。当在FirstPage页面中点击增加值后,跳转至SecondPage页面时显示的值相同,反之,当在SecondRage中点击增加值后,跳转至FirstPage页面时显示的值相同,如图所示。
Flutter 状态管理

从执行的结果来看,第一个页面和第二个页面的数据得到了共享,改变其中一个页面的值会同时影响另一个页面。完整的代码如下所示:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';


main() {
  runApp(MyApp(),);
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 使用MultiProvider可以创建多个顶层共享数据
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
      ],
      child: MaterialApp(
        title: "Provider示例",
        home: FirstPage(),
      ),
    );
  }
}


// 第一个页面
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第一个页面"),
        actions: <Widget>[
          FlatButton(
              child: Text("下一页"),
              // 路由跳转至第二页
              onPressed: () =>
                  Navigator.push(context, MaterialPageRoute(builder: (context) {
                    return SecondPage();
                  })),
          ),
        ],
      ),
      body: Center(
        // 获取计数器中的count值
        child: Text("${Provider.of<Counter>(context).count}"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 调用数据模型中的increment方法更改数据
          Provider.of<Counter>(context).increment();
        },
        child: Icon(Icons.add),
        ),
      );
    }
  }


  // 第二个页面
  class SecondPage extends StatelessWidget {
    @override
    Widget build(BuildContext context){
      return Scaffold(
        appBar: AppBar(
          title: Text("第二个页面"),
        ),
        body: Center(
          // 获取计数器中的count值
          child: Text("${Provider.of<Counter>(context).count}"),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            // 调用数据模型中的increment方法更改数据
            Provider.of<Counter>(context).increment();
          },
          child: Icon(Icons.add),
        ),
      );
    }
  }


  /**
  * 计数器类Counter即为数据Model,实际上就是状态。
  * Counter不仅存储了数据,还包含了更改数据的方法,并暴露相关数据。
  * 使用mixin混入ChangeNotifier类,这个类能够自动管理所有听众。
  * 当调用notifyListeners时,它会通知所有听众进行刷新
  */
  class Counter with ChangeNotifier {


    // 存储数据
    int _count = 0;
    // 提供外部能够访问的数据
    int get count => _count;
    // 提供更改数据的方法
    void increment(){
      _count++;
      // 通知所有听众进行刷新
      notifyListeners();
    }
  }
赞(1)

评论 抢沙发

评论前必须登录!