Flutter是声明式编程模式,开发者就像堆积木一样来堆模型,只用关心每块积木展示的条件,不需要关心积木内部的功能实现细节。因此每一块积木都是一种状态,慢慢随着积木越来越多开发者必须要把这些状态(形态、关系)管理好,否则就会是一团麻了。
编程语言主要有三种类型:
iOS 和 Android 的原生开发模式是命令式编程模式。命令式编程要求开发者一步步描述整个构建过程,从而引导程序去构建用户界面。
状态管理器一般会关注两点:
以下是一些常见的状态管理对比
状态管理框架 | 状态更新 | 状态共享 | 说明 |
---|---|---|---|
setState | yes | no | 自带的状态管理器,适用于较小规模 widget 的暂时性状态的基础管理方法 |
StreamController | yes | no | 基于流/观察者模式的基础管理方法 |
RxDart | yes | no | 基于流/观察者模式的框架,对StreamController进行了高级封装 |
flutter_bloc | yes | yes | 底层基于 InheritedWidget,核心思想也是基于流来管理数据 |
Provider | yes | yes | 基于 InheritedWidget 和 ChangeNotifier 进行了封装,使用缓存提升性能,避免不必要的重绘 |
Fish-Redux | yes | yes | 闲鱼出品,基于前端Redux思想,似乎不再维护 |
GetX | yes | yes | 轻量级响应式状态管理解决方案,目前很受欢迎 |
系统自带的数据共享组件,例如我们在应用根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子 widget 中来获取该共享的数据。
创建方式:
class MyInheritedWidget extends InheritedWidget {
final int data;
/// 在构造方法中,我们需要传入两个参数:
/// 一个是我们希望共享的数据(在本例中数据是int型,实际业务中共享的通常是一个相对复杂的数据),
/// 另一个就是我们带界面的Widget。
MyInheritedWidget(@required this.data, Widget child) : super(child: child);
/// 获取数据方法
static MyInheritedWidget getData(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
/// 决定通知子节点中StatefulWidget的didChangeDependencies方法是否调用。
/// StatefulWidget的didChangeDependencies方法就是与InheritedWidget配合使用的。只有当InheritedWidget发生更新并且决定通知时,didChangeDependencies才会调用。
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.data != data;
}
}
使用场景:
@override
Widget build(BuildContext context) {
return Text(
'count : ${MyInheritedWidget.getData(context).data}'
);
}
RxDart、BloC、flutter_bloc 都是基于 Stream 开发,Stream 的思想是基于管道(pipe)和 生产者消费者模式。
Stream并是不Flutter的产物,而是由Dart提供的,Stream是一个抽象的接口,Dart提供了StreamController接口类可以让我们方便的使用Stream。步骤大致如下:
//创建StreamController
StreamController streamController = StreamController();
// 获取StreamSink用于发射事件
StreamSink get streamSink => streamController.sink;
// 获取Stream用于监听
Stream get streamData => streamController.stream;
//发射一个事件.
streamSink.add(index.toString());
//监听事件,返回StreamSubscription,可用于监听的取消
StreamSubscription subscription = streamData.listen((value) {
// do something
});
StreamBuilder对Stream相关逻辑的包装组件(Widget),StreamBuilder 内部已经帮我们完成了stream的订阅与取消订阅,因此在Widget中监听数据变化及时刷新界面更方便。
构造方法:
StreamBuilder({Key key, T initialData, Stream stream, @required AsyncWidgetBuilder builder })
BuildContext context
和 AsyncSnapshot snapshot
, 返回值是 Widget 组件 ; AsyncSnapshot snapshot
参数中包含有异步计算的信息;其他方法:
接收数据:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('streamBuilder')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder(
stream: streamData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Text('Result: ${snapshot.data}');
}
)
],
)
)
);
}
8种内容提供者,常用的有:
provider 实例化及取值也有三种:
实例:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
/// 1 创建ChangeNotifier,实际上就是我们的状态,它不仅存储了我们的数据模型,还包含了更改数据的方法,并暴露出它想要暴露出的数据
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// 2 ChangeNotifierProvider. 在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以获取到数据
///
/// 创建顶层共享数据。这里使用MultiProvider可以创建多个共享数据,因为实际的应用不可能只有一个数据模型
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: MaterialApp(
title: 'Flutter Demo',
home: FirstPage(),
),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider练习")),
body: Center(
/// 3 获取Provider实例
/// ctx: context,上下文,目的是知道当前树的对象
/// counterPro: ChangeNotifier对应的实例,也是我们在builder函数中主要使用的对象
/// child: 目的是进行优化,如果builder下面有一颗庞大的子树,当模型发生改变的时候,我们并不希望重新build这颗子树,那么就可以将这颗子树放到Consumer的child中,在这里直接引入即可.
child: Consumer(builder: (ctx, counterPro, child) {
return Text(
"当前计数:${counterPro.count}",
style: TextStyle(fontSize: 20, color: Colors.red),
);
}),
),
floatingActionButton: Consumer(
builder: (ctx, counterPro, child) {
return FloatingActionButton(
child: child,
onPressed: () {
counterPro.increment();
},
);
},
child: Icon(Icons.add), //! Icon放在builder外面,防止每次跟着一起刷新
),
);
}
}
GetX相比Provider更灵活,轻量,简单,api也更丰富,除了包含状态管理,还包含路由配置,另外不在依赖Context,全局随便调用。
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// GetMaterialApp重写了MaterialApp,可以配置很多,如,主题、国际化、静态路由等
return GetMaterialApp(
home: SimplePage(),
);
}
}
class SimpleController extends GetxController {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
// 刷新(GetxController通过update()更新GetBuilder)
update();
}
}
class SimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('SimplePage--build');
return GetBuilder(
init: SimpleController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(title: Text('Simple')),
body: Center(
child: Text(controller.counter.toString()),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.increment();
},
child: Icon(Icons.add),
),
);
});
}
}
class SimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Simple')),
body: Center(
child: GetBuilder(
init: SimpleController(),
// 每次执行update,builder内部都会重新构建
builder: (controller) {
return Text(controller.counter.toString());
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 找到Controller执行操作
Get.find().increment();
},
child: Icon(Icons.add),
),
);
}
}
注:GetBuilder用于小部件的刷新,不建议放到根节点上全局刷。
update()
使所有绑定了该Controller的GetBuilder都执行了刷新,可通过追加id,进行局部刷新:
Controller:
int _counter = 0;
int get counter => _counter;
String _name = "Lili";
String get firstName => _name;
void increment() {
_counter++;
_name = WordPair.random().asPascalCase;
update(['counter']);
}
void changeName() {
_counter++;
_name = WordPair.random().asPascalCase;
update(['name']);
}
GetBuilder:
GetBuilder(
id: 'counter',
builder: (ctl) => Text(ctl.counter.toString()),
),
SizedBox(
height: 50,
),
GetBuilder(
id: 'name',
builder: (ctl) => Text(ctl.firstName),
),
class SimpleController extends GetxController {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
update();
}
@override
void onInit() {
super.onInit();
print('SimpleController--onInit');
}
@override
void onReady() {
super.onReady();
print('SimpleController--onReady');
}
@override
void onClose() {
super.onClose();
print('SimpleController--onClose');
}
}
在Controller的变量后追加.obs
创建响应式数据,使得该变量变成了可观察者:
var name = '新垣结衣'.obs;
观察变量的改变,不在需要update,不在是GetBuilder了:
Obx (() => Text (controller.name));
因为是响应式,只要数据改变,界面布局自动刷新。
细心的同学会发现.obs
的底层是Rx包装,所以也可以用Rx来定义响应式变量,这里不在说了。
不同页面跨路由使用,只需要把controller存储起来即可:
class CrossOnePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// Get.put存储controller
CrossOneController controller = Get.put(CrossOneController());
...
}}
另外一个页面使用时,获取controller:
CheetahButton('打印CrossOneController的age', () {
/// Get.find获取controller
print(Get.find().age);
}),
此时需要注意一个问题:
Get.put
在build里put时,controller的生命周期和该widget保持一致的,widget销毁了这个controller也自动销毁了Get.put
作为成员变量在build外部执行时,controller的生命周期是和所属widget的引用保持一致的。我们开发过程中总喜欢一个页面对应一个controller,如果作为成员变量存放,这样的话需要额外注意内存的回收问题,可以建一个Widget,来管理controller的回收。
路由、弹框需自行进一步挖掘。