Flutter页面传值的几种方式
今天来聊聊Flutter页面传值的几种方式:
- InheritWidget
- Notification
- Eventbus
(当前Flutter版本:2.0.4)
InheritWidget
如果看过Provider的源码的同学都知道,Provider跨组件传值的原理就是根据系统提供的InheritWidget实现的,让我们来看一下这个组件。
InheritWidget是一个抽象类,我们写一个保存用户信息的类UserInfoInheritWidget
继承于InheritWidget:
class UserInfoInheritWidget extends InheritedWidget {
UserInfoBean userInfoBean;
UserInfoInheritWidget({Key key, this.userInfoBean, Widget child}) : super (child: child);
static UserInfoWidget of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<UserInfoWidget>();
}
@override
bool updateShouldNotify(UserInfoInheritWidget oldWidget) {
return oldWidget.userInfoBean != userInfoBean;
}
}
我们在这里面定义了一个静态方法:of
,并且传入了一个context
,根据context
获取当前类,拿到当前类中的UserInfoBean
,其实获取主题数据也是根据InheritWidget
这种方式获取Theme.of(context)
,关于of
方法后面重点讲一下,updateShouldNotify
是刷新机制,什么时候刷新数据
还有一个用户信息的实体:
class UserInfoBean {
String name;
String address;
UserInfoBean({this.name, this.address});
}
我们做两个页面,第一个页面显示用户信息,还有一个按钮,点击按钮跳转到第二个页面,同样也是显示用户信息:
class Page19PassByValue extends StatefulWidget {
@override
_Page19PassByValueState createState() => _Page19PassByValueState();
}
class _Page19PassByValueState extends State<Page19PassByValue> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PassByValue'),
),
body: DefaultTextStyle(
style: TextStyle(fontSize: 30, color: Colors.black),
child: Column(
children: [
Text(UserInfoWidget.of(context)!.userInfoBean.name),
Text(UserInfoWidget.of(context)!.userInfoBean.address),
SizedBox(height: 40),
TextButton(
child: Text('点击跳转'),
onPressed: (){
Navigator.of(context).push(CupertinoPageRoute(builder: (context){
return DetailPage();
}));
},
)
],
),
),
);
}
}
class DetailPage extends StatefulWidget {
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Detail'),
),
body: DefaultTextStyle(
style: TextStyle(fontSize: 30, color: Colors.black),
child: Center(
child: Column(
children: [
Text(UserInfoWidget.of(context).userInfoBean.name),
Text(UserInfoWidget.of(context).userInfoBean.address),
TextButton(
onPressed: () {
setState(() {
UserInfoWidget.of(context)!.updateBean('wf123','address123');
});
},
child: Text('点击修改'))
],
),
),
)
);
}
}
由于我们这里是跨组件传值,需要把UserInfoWidget
放在MaterialApp
的上层,并给UserInfoBean
一个初始值:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return UserInfoWidget(
userInfoBean: UserInfoBean(name: 'wf', address: 'address'),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
这样就实现了一个跨组件传值,但是还有个问题,我们给UserInfoWidget
赋值的时候是在最顶层,在真实业务场景中,如果我们把UserInfo
的赋值放在MaterialApp
上面,这时候我们还没拿到用户数据呢,所以就要有一个可以更新UserInfo
的方法,并且修改后立即刷新,我们可以借助setState
,把我们上面定义的UserInfoWidget
改个名字然后封装在StatefulWidget
中:
class _UserInfoInheritWidget extends InheritedWidget {
UserInfoBean userInfoBean;
Function update;
_UserInfoInheritWidget({Key key, this.userInfoBean, this.update, Widget child}) : super (child: child);
updateBean(String name, String address){
update(name, address);
}
@override
bool updateShouldNotify(_UserInfoInheritWidget oldWidget) {
return oldWidget.userInfoBean != userInfoBean;
}
}
class UserInfoWidget extends StatefulWidget {
UserInfoBean userInfoBean;
Widget child;
UserInfoWidget({Key key, this.userInfoBean, this.child}) : super (key: key);
static _UserInfoInheritWidget of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<_UserInfoInheritWidget>();
}
@override
State<StatefulWidget> createState() => _UserInfoState();
}
class _UserInfoState extends State <UserInfoWidget> {
_update(String name, String address){
UserInfoBean bean = UserInfoBean(name: name, address: address);
widget.userInfoBean = bean;
setState(() {});
}
@override
Widget build(BuildContext context) {
return _UserInfoInheritWidget(
child: widget.child,
userInfoBean: widget.userInfoBean,
update: _update,
);
}
}
上面把继承自InheritWidget
的类改了一个名字:_UserInfoInheritWidget
,对外只暴露用StatefulWidget
封装过的UserInfoWidget
,向_UserInfoInheritWidget
传入了包含setState
的更新数据方法,更新数据的时候通过UserInfoWidget.of(context)
获取到继承于InheritWidget
的_UserInfoInheritWidget
类,调用updateBean
方法实际上就调用了包含setState
的方法,所以做到了数据更新和页面刷新
下面重点说一下UserInfoWidget.of(context)
是如何获取到继承于InheritWidget
类的对象的,通过查看类似的方法:Theme.of(context)
发现是根据dependOnInheritedWidgetOfExactType
,于是我们也照着它的样子获取到了_UserInfoInheritWidget,点到dependOnInheritedWidgetOfExactType
源码中看一下,发现跳转到了BuildContext
中定义了这个方法:
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
复制代码
了解Widget
、Element
、RenderObject
三只之间关系的同学都知道,其实context
是Element
的一个实例,BuildContext的注释也提到了这一点:
我们可以在Element
中找到这个方法的实现:
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
_inheritedWidgets
是从哪来的,我们搜索一下在Element中发现
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
再看一下_updateInheritance
方法是什么时候调用的
@mustCallSuper
void mount(Element? parent, dynamic newSlot) {
...
...省略无关代码
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
final Key? key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();//这里调用了一次
}
还有:
@mustCallSuper
void activate() {
...
...已省略无关代码
final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
_lifecycleState = _ElementLifecycle.active;
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();//这里又调用了一次
if (_dirty)
owner!.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
从上面代码我们可以看到每个页面的Element都会通过_parent向下级传递父级信息,而我们的UserInfoWidget就保存在_parent中的_inheritedWidgets集合中:Map<Type, InheritedElement>? _inheritedWidgets;
,当_inheritedWidgets
在页面树中向下传递的时候,如果当前Widget
是InheritWidget
,在当前Widget
对应的Element
中先看_parent
传过来的_inheritedWidgets
是否为空,如果为空就新建一个集合,把自己存到这个集合中,以当前的类型作为key(这也是为什么调用of
方法中的context.dependOnInheritedWidgetOfExactType
方法为什么要传当前类型的原因),从_inheritedWidgets
集合中去取值;如果不为空直接把自己存进去,这就是of
的原理了。
Notification
上面讲的InheritWidget一般是根部组建向子级组件传值,Notification
是从子级组件向父级组件传值,下面我们来看一下它的用法
class Page19PassByValue extends StatefulWidget {
@override
_Page19PassByValueState createState() => _Page19PassByValueState();
}
class _Page19PassByValueState extends State<Page19PassByValue> {
UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PassByValue'),
),
body: Center(
child: NotificationListener<MyNotification>(
onNotification: (MyNotification data) {
userInfoBean = data.userInfoBean;
setState(() {});
///这里需要返回一个bool值,true表示阻止事件继续向上传递,false表示事件可以继续向上传递到父级组件
return true;
},
child: Builder(
///这里用了一个Builder包装了一下,为的是能取到
///NotificationListener的context
builder: (context) {
return Column(
children: [
Text(userInfoBean.name),
Text(userInfoBean.address),
Container(
child: FlatButton(
child: Text('点击传值'),
onPressed: () {
MyNotification(userInfoBean: UserInfoBean(name: 'wf123', address: 'address123')).dispatch(context);
},
),
)
],
);
},
),
),
),
);
}
}
///Notification是一个抽象类,
///使用Notification需要自定义一个class继承Notification
class MyNotification extends Notification {
UserInfoBean userInfoBean;
MyNotification({this.userInfoBean}) : super();
}
我们到源码中看一下这个dispatch
方法:
void dispatch(BuildContext target) {
// The `target` may be null if the subtree the notification is supposed to be
// dispatched in is in the process of being disposed.
target?.visitAncestorElements(visitAncestor);
}
target
就是我们传进来的context
,也就是调用了BuildContext
的visitAncestorElements
方法,并且把visitAncestor
方法作为一个参数传过去,visitAncestor
方法返回一个bool值:
@protected
@mustCallSuper
bool visitAncestor(Element element) {
if (element is StatelessElement) {
final StatelessWidget widget = element.widget;
if (widget is NotificationListener<Notification>) {
if (widget._dispatch(this, element)) // that function checks the type dynamically
return false;
}
}
return true;
}
我们进入Element内部看一下visitAncestorElements
方法的实现:
@override
void visitAncestorElements(bool visitor(Element element)) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
当有父级节点,并且visitor
方法返回true
的时候执行while
循环,visitor
是Notification
类传进来的方法,回过头再看visitor
方法的实现,当Element
向visitor
方法传递的ancestor
是NotificationListener
类的情况下,再判断widget._dispatch
方法,而widget._dispatch
方法:
final NotificationListenerCallback<T>? onNotification;
bool _dispatch(Notification notification, Element element) {
if (onNotification != null && notification is T) {
final bool result = onNotification!(notification);
return result == true; // so that null and false have the same effect
}
return false;
}
就是我们在外面写的onNotification
方法的实现,我们在外面实现的onNotification
方法返回true
(即阻止事件继续向上传递),上面的while
循环主要是为了执行我们onNotification
里面的方法.
总结一下:MyNotification
执行dispatch
方法,传递context
,根据当前context
向父级查找对应NotificationListener
,并且执行NotificationListener
里面的onNotification
方法,返回true
,则事件不再向上级传递,如果返回false
则事件继续向上一个NotificationListener
传递,并执行里面对应的方法。Notification
主要用在同一个页面中,子级向父级传值,比较轻量级,不过如果我们用了Provider
可能就就直接借助Provider
传值了。
Eventbus
Eventbus用于两个不同的页面,可以跨多级页面传值,用法也比较简单,我创建了一个EventBusUtil来创建一个单例
import 'package:event_bus/event_bus.dart';
class EventBusUtil {
static EventBus ? _instance;
static EventBus getInstance(){
if (_instance == null) {
_instance = EventBus();
}
return _instance!;
}
}
在第一个页面监听:
class Page19PassByValue extends StatefulWidget {
@override
_Page19PassByValueState createState() => _Page19PassByValueState();
}
class _Page19PassByValueState extends State<Page19PassByValue> {
UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
@override
void initState() {
super.initState();
EventBusUtil.getInstance().on<UserInfoBean>().listen((event) {
setState(() {
userInfoBean = event;
});
});
}
@override
void dispose() {
super.dispose();
//不用的时候记得关闭
EventBusUtil.getInstance().destroy();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PassByValue'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(userInfoBean.name),
Text(userInfoBean.address),
TextButton(onPressed: (){
Navigator.of(context).push(CupertinoPageRoute(builder: (_){
return EventBusDetailPage();
}));
}, child: Text('点击跳转'))
],
),
),
);
}
}
在第二个页面发送事件:
class EventBusDetailPage extends StatefulWidget {
@override
_EventBusDetailPageState createState() => _EventBusDetailPageState();
}
class _EventBusDetailPageState extends State<EventBusDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('EventBusDetail'),
),
body: Center(
child: TextButton(onPressed: (){
EventBusUtil.getInstance().fire(UserInfoBean(name: 'name EventBus', address: 'address EventBus'));
}, child: Text('点击传值')),
),
);
}
}
我们看一下EventBus
的源码,发现只有几十行代码,他的内部是创建了一个StreamController
,通过StreamController
来实现跨组件传值,我们也可以直接使用一下这个StreamController
实现页面传值:
class Page19PassByValue extends StatefulWidget {
@override
_Page19PassByValueState createState() => _Page19PassByValueState();
}
StreamController controller = StreamController();
class _Page19PassByValueState extends State<Page19PassByValue> {
//设置一个初始值
UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
@override
void initState() {
super.initState();
controller.stream.listen((event) {
setState(() {
userInfoBean = event;
});
});
}
@override
void dispose() {
super.dispose();
//页面销毁的时候记得关闭
controller.close();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PassByValue'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(userInfoBean.name),
Text(userInfoBean.address),
TextButton(onPressed: (){
Navigator.of(context).push(CupertinoPageRoute(builder: (_){
return MyStreamControllerDetail();
}));
}, child: Text('点击跳转'))
],
),
)
);
}
}
class MyStreamControllerDetail extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyStreamControllerDetailState();
}
}
class _MyStreamControllerDetailState extends State <MyStreamControllerDetail> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('StreamController'),
),
body: Center(
child: TextButton(onPressed: (){
//返回上个页面,会发现页面的数据已经变了
controller.sink.add(UserInfoBean(name: 'StreamController pass name: 123', address: 'StreamController pass address 123'));
}, child: Text('点击传值'),),
),
);
}
}
作者:wangfeng6075
链接:https://juejin.cn/post/6954701843444269093
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。