InheritedWidget
InheritedWidget是flutter中一个非常重要的组件,其功能是数据共享。我们只要在widget树的根或者某个widget中,使用InheritedWidget进行了数据共享,那么在其后面的子widget中都可以使用其中的共享数据。如下图:
图中,InheritedWidget包含了需要共享的数据,在InheritedWidget下面的所有子孙widget都可以使用其中的数据。
InheritedWidget的使用步骤
InheritedWidget的使用过程并不复杂,具体过程如下:
- 创建InheritedWidget的子类,并实现如下父类方法:
bool updateShouldNotify(YQCounterWidget oldWidget) - 定义需要共享的数据,比如:
final int counter; - 定义构造方法
- 定义一个类方法(of方法),用以根据context查找距离当前widget最近的InheritedWidget。
完整代码如下:
class YQSharedDataWidget extends InheritedWidget{
final int counter;//需要共享的数据
YQSharedDataWidget({this.counter,Widget child}) : super(child: child);
//用于子树中widget获取共享数据
static YQSharedDataWidget of(BuildContext context){
//沿着Element树,去找到最近的匹配的element,从element中取出Widget对象.
return context.dependOnInheritedWidgetOfExactType<YQSharedDataWidget>();
}
@override
/// 该方法决定当共享数据发生变化的时候,是否通知子树中对此有依赖的widget
/// 如果返回true,则子树中`state.didchangedependencies`会被调用
bool updateShouldNotify(YQSharedDataWidget oldWidget) {//
return oldWidget.counter != counter;
}
}
在子树widget中使用共享数据:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("InheritedWidget"),),
body: Container(
child: YQSharedDataWidget(
counter: _counter,
child: Center(child: YQUseSharedDataWidget(),),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
setState(() {
_counter++;
});
},
),
);
}
}
class YQUseSharedDataWidget extends StatefulWidget {
@override
_YQUseSharedDataWidgetState createState() => _YQUseSharedDataWidgetState();
}
class _YQUseSharedDataWidgetState extends State<YQUseSharedDataWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
int counter = YQSharedDataWidget.of(context).counter;
return Card(
color: Colors.red,
child: Text("点击次数: $counter"),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
注意,上面的代码中,如果共享数据发生变化,didChangeDependencies是会被调用的。为什么呢?两个条件:
- YQSharedDataWidget中,updateShouldNotify在数据发生变化时候返回的是true
- _YQUseSharedDataWidgetState的build方法中,依赖了YQSharedDataWidget(int counter = YQSharedDataWidget.of(context).counter;)
上述两个条件任何一个不成立,_YQUseSharedDataWidgetState的didChangeDependencies方法都不会被调用。
Provider(官方推荐)
Provider虽然是官方推荐的,但是并没有在flutter的sdk中继承。所以,使用前需要先添加依赖。
相关链接:
Provider的使用步骤
-
创建共享的数据:实现ChangeNotifier的子类,在其中设置需要共享的数据,并设置对应的setter和getter,在setter中调用notifyListeners(),这样当数据改变的时候,可以通知其对应的监听者。
-
在应用程序的顶层(传入runApp作为参数的widget)使用ChangeNotifierProvider或者MultiProvider对根widget进行包裹。
-
在其他地方使用共享的数据。使用共享数据的三种方式:
- Provider.of:当共享数据发生变化的时候,会导致依赖数据的build方法重新执行。
- Consumer:当共享数据发生变化时候,只会重新执行Consumer中的builder,而不会重新执行其所在的build方法。注意,只要数据发生变化,Consumer中的builder就会重新执行!
-
Consumer的的builder的签名如下:
Widget Function(BuildContext context, T value, Widget child) builder如果需要使用多个共享的数据,可以使用Consumer2...Consumer6,Consumer后面紧跟的数字标识bulder中接收的value的个数,如Consumer3的签名如下:
Widget Function( BuildContext context, A value, B value2, C value3, Widget child) builder其中的value,value2,value3则是共享的数据。
-
- Selector,可以控制是否执行builder()方法,优化性能。参数:
-
selector:对原有数据进行转换
-
shouldRebuild:要不要重新构建(builder要不要重新执行)
-
builder:返回需要构建的widget。
在builder的定义中,value就是共享的数据,第三个参数child则是用于性能优化。如下代码:
floatingActionButton: Consumer<YQCounterViewModel>( builder: (context, value, child){ return FloatingActionButton( child: Icon(Icons.add),//共享数据发生变化,会重新渲染 onPressed: (){ setState(() { value.counter += 1; }); }, ); }, ),在上面的代码中,如果共享的数据发生了变化,FloatingActionButton的child会因为builder执行而重新渲染,但是此处是跟数据没有关系的,根本不需重新渲染。修改如下:
floatingActionButton: Consumer<YQCounterViewModel>( builder: (context, value, child){ return FloatingActionButton( child: child,//① onPressed: (){ setState(() { value.counter += 1; }); }, ); }, child: Icon(Icons.add),//② ),上面的代码中, 在Consumer的child中生成了Icon(标识②的位置),然后在FloatingActionButton的child属性中使用了该值(标识①的位置),这样在buidler方法重新执行的时候,FloatingActionButton的Icon就不会重新渲染了。这就是Consumer的builder中的child的作用。
-
Provider完整的使用代码如下:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'view_model/counter_view_model.dart';
import 'view_model/user_view_model.dart';
import 'view_model/providers.dart';//专门管理共享数据
void main() {
runApp(MultiProvider(//在app的顶层使用MultiProvider包裹来共享数据
child: MyApp(),
providers: providers,
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider"),),
body: Container(
child: YQUseProviderDataWidget(),
),
floatingActionButton: Selector<YQCounterViewModel,YQCounterViewModel>(
selector: (context, value)=>value,//可以对value进行处理
shouldRebuild: (prev,next) => false ,//是否需要调用builder方法
builder: (context, value, child){
return FloatingActionButton(
child: child,
onPressed: (){
setState(() {
value.counter += 1;
});
},
);
},
child: Icon(Icons.add),
),
);
}
}
class YQUseProviderDataWidget extends StatefulWidget {
@override
_YQUseProviderDataWidgetState createState() => _YQUseProviderDataWidgetState();
}
class _YQUseProviderDataWidgetState extends State<YQUseProviderDataWidget> {
@override
Widget build(BuildContext context) {
//通过Provider.of来使用共享的数据
int counter = Provider.of<YQCounterViewModel>(context).counter;
return Column(
children: [
Container(
height: 100,
color: Colors.red,
child: Center(
child: Text("$counter"),
),
),
Container(
height: 100,
color: Colors.orangeAccent,
child: Center(
child: Consumer<YQUserViewModel>(//通过Consumer来使用共享的数据
builder: (ctx,userVM,child){
return Text("${userVM.user.nick}");
},
),
),
)
],
);
}
}
上面代码中,main方法里面,如果需要共享的数据只有一个,可以将之修改为如下代码:
runApp(ChangeNotifierProvider(
child: MyApp(),
create: (BuildContext context){
return YQCounterViewModel();
},
));
相关文件:
counter_view_model.dart
import 'package:flutter/material.dart';
class YQCounterViewModel extends ChangeNotifier{
int _counter = 1;
int get counter => _counter;
set counter(int value) {
_counter = value;
notifyListeners();//通知所有的监听者
}
}
user_view_model.dart
import 'package:flutter/material.dart';
import 'package:demos/models/user.dart';
class YQUserViewModel extends ChangeNotifier{
User _user;
User get user => _user;
set user(User value) {
_user = value;
notifyListeners();
}
YQUserViewModel(this._user);
}
user.dart
class User{
String nick;
int level;
String avatar;
User(this.nick, this.level, this.avatar);
}
providers.dart
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'counter_view_model.dart';
import 'user_view_model.dart';
import '../models/user.dart';
List<SingleChildWidget> providers = [
ChangeNotifierProvider(create: (ctx)=>YQCounterViewModel()),
ChangeNotifierProvider(create: (ctx)=>YQUserViewModel(User("zhangsan",99,"avatar")))
];