- Flutter技术入门与实战(第2版)
- 亢少军
- 1894字
- 2023-07-10 16:29:19
2.5 状态管理
第1章中介绍了什么是状态,Provider是官方推出的状态管理模式,本节将具体介绍Flutter的状态管理。
2.5.1 有状态及无状态组件
无状态组件(StatelessWidget)是不可变的,这意味着它们的属性不能改变,所有的值都是最终的。
有状态组件(StatefulWidget)持有的状态可能在Widget生命周期中发生变化。实现一个StatefulWidget至少需要两个类:
❑StatefulWidget类,本身是不变的。
❑State类,在Widget生命周期中始终存在。
Flutter官方给出一个有状态组件的示例,点击右下角的+按钮,应用界面中间的数字会加1,如图2-7所示。
图2-7 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), ), ); } }
2.5.2 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页面时显示的值相同,如图2-8所示。
图2-8 Provider运行示例
从执行的结果来看,第一个页面和第二个页面的数据得到了共享,改变其中一个页面的值会同时影响另一个页面。完整的代码如下所示:
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(); } }