一、InheritedWidget
1、简介
用处很简单: 帮根视图的数据、共享到子视图。例如控制主题、语言环境。
官方介绍如下:
InheritedWidget是 Flutter 中非常重要的一个功能型组件,它提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享应用主题(Theme)和 Locale (当前语言环境)信息的。
2、实例
通过实现一个简单的计算器、来直观的展示 InheritedWidget 的用法。
(1)、定义一个数据共享类。
ShareDataWidget 如下 :
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final int data; //需要在子树中共享的数据,保存点击次数
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget重新build
@override
bool updateShouldNotify(ShareDataWidget old) {
return old.data != data;
}
}
以上 data 就是我们需要共享的数据。 在真实的开发中、可以给他改成T来兼容各种业务场景。
当data发生改变的时候、就会通过代码方法 updateShouldNotify 来判断要不要通知其他 子Widget要不要更新。
(2)、定义一个测试用的子Widget
class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => __TestWidgetState();
}
class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget.of(context)!.data.toString());
}
@override //下文会详细介绍。
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}
(3)、定义我们的根视图。
InheritedWidgetTestRoute
class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();
}
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
ElevatedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}
上代码可以看见如下:
- count 就是我们需要共享的数据、在创建
ShareDataWidget的时候帮count作为共享数据传给了ShareDataWidget。 - 点击自增的是、去修改共享数据。
_TestWidget通过ShareDataWidget.of(context)!.data.toString()方式来展示共享数据、监听数据改变。并通过实现didChangeDependencies来捕捉共享状态的变化。
(4)、共享流程
二、Provider
1、介绍
InheritedWidget 是让根视图的数据共享到子视图。 而Provider就是跨组件共享数据
官方介绍如下:
在 Flutter 开发中,状态管理是一个永恒的话题。一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。对于组件私有的状态管理很好理解,但对于跨组件共享的状态,管理的方式就比较多了,如使用全局事件总线EventBus(将在下一章中介绍),它是一个观察者模式的实现,通过它就可以实现跨组件状态同步:状态持有方(发布者)负责更新、发布状态,状态使用方(观察者)监听状态改变事件来执行一些操作。
我们可以发现,通过观察者模式来实现跨组件状态共享有一些明显的缺点:
- 必须显式定义各种事件,不好管理。
- 订阅者必须需显式注册状态改变回调,也必须在组件销毁时手动去解绑回调以避免内存泄露。
在Flutter当中有没有更好的跨组件状态管理方式了呢?答案是肯定的,那怎么做的?我们想想前面介绍的InheritedWidget,它的天生特性就是能绑定InheritedWidget与依赖它的子孙组件的依赖关系,并且当InheritedWidget数据发生变化时,可以自动更新依赖的子孙组件!利用这个特性,我们可以将需要跨组件共享的状态保存在InheritedWidget中,然后在子组件中引用InheritedWidget即可,Flutter社区著名的Provider包正是基于这个思想实现的一套跨组件状态共享解决方案,接下来我们便详细介绍一下Provider的用法及原理
2、购物车实例
(1)、定义保存共享数据
// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors
import 'package:flutter/material.dart';
class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({
required this.data,
required Widget child,
}) : super(child: child);
final T data;
@override
bool updateShouldNotify(InheritedProvider<T> old) {
//在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
return true;
}
}
(2)、定义购物车数据模型
// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors
import 'dart:collection';
import 'package:flutter/material.dart';
class CartModel extends ChangeNotifier {
// 用于保存购物车中商品列表
final List<Item> _items = [];
// 禁止改变购物车里的商品信息
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
// 购物车中商品的总价
double get totalPrice =>
_items.fold(0, (value, item) => value + item.count * item.price);
// 将 [item] 添加到购物车。这是唯一一种能从外部改变购物车的方法。
void add(Item item) {
_items.add(item);
// 通知监听器(订阅者),重新构建InheritedProvider, 更新状态。
notifyListeners();
}
}
class Item {
Item(this.price, this.count);
double price; //商品单价
int count; // 商品份数
//... 省略其它属性
}
这里使用的是模型继承ChangeNotifier 是一个类似于EventBus的类。内部方法大致如下
class ChangeNotifier implements Listenable {
List listeners=[];
@override
void addListener(VoidCallback listener) {
//添加监听器
listeners.add(listener);
}
@override
void removeListener(VoidCallback listener) {
//移除监听器
listeners.remove(listener);
}
void notifyListeners() {
//通知所有监听器,触发监听器回调
listeners.forEach((item)=>item());
}
... //省略无关代码
}
(2)、构建一个状态管理的类。
// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors, unused_local_variable
import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/InheritedProvider.dart';
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({
Key? key,
required this.data,
required this.child,
});
final Widget child;
final T data;
//定义一个便捷方法,方便子树中的widget获取共享数据
static T of<T>(BuildContext context) {
// final type = _typeOf<InheritedProvider<T>>();
final provider =
context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>();
return provider!.data;
}
@override
_ChangeNotifierProviderState<T> createState() =>
_ChangeNotifierProviderState<T>();
}
class _ChangeNotifierProviderState<T extends ChangeNotifier>
extends State<ChangeNotifierProvider<T>> {
void update() {
//如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
setState(() => {});
}
@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
//当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
// 给model添加监听器
widget.data.addListener(update);
super.initState();
}
@override
void dispose() {
// 移除model的监听器
widget.data.removeListener(update);
super.dispose();
}
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
}
(4)、构建根视图
// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors
import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/CartModel.dart';
import 'package:flutter_share_test/CarProvider/ChangeNotifierProvider.dart';
import 'package:flutter_share_test/CarProvider/Consumer.dart';
class ProviderRoute extends StatefulWidget {
@override
_ProviderRouteState createState() => _ProviderRouteState();
}
class _ProviderRouteState extends State<ProviderRoute> {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ChangeNotifierProvider<CartModel>(
data: CartModel(),
child: Column(
children: <Widget>[
Builder(builder: (context) {
var cart = ChangeNotifierProvider.of<CartModel>(context);
return Text("总价: ${cart.totalPrice}");
}),
Builder(builder: (context) {
print("ElevatedButton build"); //在后面优化部分会用到
return ElevatedButton(
child: Text("添加商品"),
onPressed: () {
//给购物车中添加商品,添加后总价会更新
var cart = ChangeNotifierProvider.of<CartModel>(context);
cart.add(Item(20.0, 1));
},
);
}),
],
),
),
Consumer<CartModel>(
builder: (context, cart) => Text(" 这是一个外面的控件 ${cart!.totalPrice}")),
],
));
}
}
(5)、 优化
上面代码可以看得出来、ChangeNotifierProvider 既充当了订阅者、又是消费者,而且每个地方引用 购物车数据都要使用ChangeNotifierProvider.of<CartModel>(context); 来获取、显然有点麻烦。
所以就定义如下来专门来充当消费者。
// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors
import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/ChangeNotifierProvider.dart';
// 这是一个便捷类,会获得当前context和指定数据类型的Provider
class Consumer<T> extends StatelessWidget {
Consumer({
Key? key,
required this.builder,
}) : super(key: key);
final Widget Function(BuildContext context, T? value) builder;
@override
Widget build(BuildContext context) {
return builder(
context,
ChangeNotifierProvider.of<T>(context),
);
}
}
// 加这个类的主要原因就是、分开语义 不然 ChangeNotifierProvider 既是订阅者、也是消费者。
有了专门的消费者,在我们需要使用购物车数据的时候直接使用Consumer引用即可。
// 他就是专门定义的消费者
Consumer<CartModel>(
builder: (context, cart) => Text(" ${cart!.totalPrice}")),