Flutter頁(yè)面?zhèn)髦档膸追N方式
今天來(lái)聊聊Flutter頁(yè)面?zhèn)髦档膸追N方式:
- InheritWidget
- Notification
- Eventbus
(當(dāng)前Flutter版本:2.0.4)
InheritWidget
如果看過(guò)Provider的源碼的同學(xué)都知道,Provider跨組件傳值的原理就是根據(jù)系統(tǒng)提供的InheritWidget實(shí)現(xiàn)的,讓我們來(lái)看一下這個(gè)組件。
InheritWidget是一個(gè)抽象類,我們寫(xiě)一個(gè)保存用戶信息的類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;
}
}
我們?cè)谶@里面定義了一個(gè)靜態(tài)方法:of,并且傳入了一個(gè)context,根據(jù)context獲取當(dāng)前類,拿到當(dāng)前類中的UserInfoBean,其實(shí)獲取主題數(shù)據(jù)也是根據(jù)InheritWidget這種方式獲取Theme.of(context),關(guān)于of方法后面重點(diǎn)講一下,updateShouldNotify是刷新機(jī)制,什么時(shí)候刷新數(shù)據(jù)
還有一個(gè)用戶信息的實(shí)體:
class UserInfoBean {
String name;
String address;
UserInfoBean({this.name, this.address});
}
我們做兩個(gè)頁(yè)面,第一個(gè)頁(yè)面顯示用戶信息,還有一個(gè)按鈕,點(diǎn)擊按鈕跳轉(zhuǎn)到第二個(gè)頁(yè)面,同樣也是顯示用戶信息:
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('點(diǎn)擊跳轉(zhuǎn)'),
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('點(diǎn)擊修改'))
],
),
),
)
);
}
}
由于我們這里是跨組件傳值,需要把UserInfoWidget放在MaterialApp的上層,并給UserInfoBean一個(gè)初始值:
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'),
),
);
}
}
這樣就實(shí)現(xiàn)了一個(gè)跨組件傳值,但是還有個(gè)問(wèn)題,我們給UserInfoWidget賦值的時(shí)候是在最頂層,在真實(shí)業(yè)務(wù)場(chǎng)景中,如果我們把UserInfo的賦值放在MaterialApp上面,這時(shí)候我們還沒(méi)拿到用戶數(shù)據(jù)呢,所以就要有一個(gè)可以更新UserInfo的方法,并且修改后立即刷新,我們可以借助setState,把我們上面定義的UserInfoWidget改個(gè)名字然后封裝在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的類改了一個(gè)名字:_UserInfoInheritWidget,對(duì)外只暴露用StatefulWidget封裝過(guò)的UserInfoWidget,向_UserInfoInheritWidget傳入了包含setState的更新數(shù)據(jù)方法,更新數(shù)據(jù)的時(shí)候通過(guò)UserInfoWidget.of(context)獲取到繼承于InheritWidget的_UserInfoInheritWidget類,調(diào)用updateBean方法實(shí)際上就調(diào)用了包含setState的方法,所以做到了數(shù)據(jù)更新和頁(yè)面刷新

下面重點(diǎn)說(shuō)一下UserInfoWidget.of(context)是如何獲取到繼承于InheritWidget類的對(duì)象的,通過(guò)查看類似的方法:Theme.of(context)發(fā)現(xiàn)是根據(jù)dependOnInheritedWidgetOfExactType,于是我們也照著它的樣子獲取到了_UserInfoInheritWidget,點(diǎn)到dependOnInheritedWidgetOfExactType源碼中看一下,發(fā)現(xiàn)跳轉(zhuǎn)到了BuildContext中定義了這個(gè)方法:
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
了解Widget、Element、RenderObject三只之間關(guān)系的同學(xué)都知道,其實(shí)context是Element的一個(gè)實(shí)例,BuildContext的注釋也提到了這一點(diǎn):

我們可以在Element中找到這個(gè)方法的實(shí)現(xiàn):
@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是從哪來(lái)的,我們搜索一下在Element中發(fā)現(xiàn)
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
再看一下_updateInheritance方法是什么時(shí)候調(diào)用的
@mustCallSuper
void mount(Element? parent, dynamic newSlot) {
...
...省略無(wú)關(guān)代碼
_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();//這里調(diào)用了一次
}
還有:
@mustCallSuper
void activate() {
...
...已省略無(wú)關(guān)代碼
final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
_lifecycleState = _ElementLifecycle.active;
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();//這里又調(diào)用了一次
if (_dirty)
owner!.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
從上面代碼我們可以看到每個(gè)頁(yè)面的Element都會(huì)通過(guò)_parent向下級(jí)傳遞父級(jí)信息,而我們的UserInfoWidget就保存在_parent中的_inheritedWidgets集合中:Map<Type, InheritedElement>? _inheritedWidgets;,當(dāng)_inheritedWidgets在頁(yè)面樹(shù)中向下傳遞的時(shí)候,如果當(dāng)前Widget是InheritWidget,在當(dāng)前Widget對(duì)應(yīng)的Element中先看_parent傳過(guò)來(lái)的_inheritedWidgets是否為空,如果為空就新建一個(gè)集合,把自己存到這個(gè)集合中,以當(dāng)前的類型作為key(這也是為什么調(diào)用of方法中的context.dependOnInheritedWidgetOfExactType方法為什么要傳當(dāng)前類型的原因),從_inheritedWidgets集合中去取值;如果不為空直接把自己存進(jìn)去,這就是of的原理了。
Notification
上面講的InheritWidget一般是根部組建向子級(jí)組件傳值,Notification是從子級(jí)組件向父級(jí)組件傳值,下面我們來(lái)看一下它的用法
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(() {});
///這里需要返回一個(gè)bool值,true表示阻止事件繼續(xù)向上傳遞,false表示事件可以繼續(xù)向上傳遞到父級(jí)組件
return true;
},
child: Builder(
///這里用了一個(gè)Builder包裝了一下,為的是能取到
///NotificationListener的context
builder: (context) {
return Column(
children: [
Text(userInfoBean.name),
Text(userInfoBean.address),
Container(
child: FlatButton(
child: Text('點(diǎn)擊傳值'),
onPressed: () {
MyNotification(userInfoBean: UserInfoBean(name: 'wf123', address: 'address123')).dispatch(context);
},
),
)
],
);
},
),
),
),
);
}
}
///Notification是一個(gè)抽象類,
///使用Notification需要自定義一個(gè)class繼承Notification
class MyNotification extends Notification {
UserInfoBean userInfoBean;
MyNotification({this.userInfoBean}) : super();
}
我們到源碼中看一下這個(gè)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就是我們傳進(jìn)來(lái)的context,也就是調(diào)用了BuildContext的visitAncestorElements方法,并且把visitAncestor方法作為一個(gè)參數(shù)傳過(guò)去,visitAncestor方法返回一個(gè)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;
}
我們進(jìn)入Element內(nèi)部看一下visitAncestorElements方法的實(shí)現(xiàn):
@override
void visitAncestorElements(bool visitor(Element element)) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
當(dāng)有父級(jí)節(jié)點(diǎn),并且visitor方法返回true的時(shí)候執(zhí)行while循環(huán),visitor是Notification類傳進(jìn)來(lái)的方法,回過(guò)頭再看visitor方法的實(shí)現(xiàn),當(dāng)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;
}
就是我們?cè)谕饷鎸?xiě)的onNotification方法的實(shí)現(xiàn),我們?cè)谕饷鎸?shí)現(xiàn)的onNotification方法返回true(即阻止事件繼續(xù)向上傳遞),上面的while循環(huán)主要是為了執(zhí)行我們onNotification里面的方法.
總結(jié)一下:MyNotification執(zhí)行dispatch方法,傳遞context,根據(jù)當(dāng)前context向父級(jí)查找對(duì)應(yīng)NotificationListener,并且執(zhí)行NotificationListener里面的onNotification方法,返回true,則事件不再向上級(jí)傳遞,如果返回false則事件繼續(xù)向上一個(gè)NotificationListener傳遞,并執(zhí)行里面對(duì)應(yīng)的方法。Notification主要用在同一個(gè)頁(yè)面中,子級(jí)向父級(jí)傳值,比較輕量級(jí),不過(guò)如果我們用了Provider可能就就直接借助Provider傳值了。
Eventbus
Eventbus用于兩個(gè)不同的頁(yè)面,可以跨多級(jí)頁(yè)面?zhèn)髦?,用法也比較簡(jiǎn)單,我創(chuàng)建了一個(gè)EventBusUtil來(lái)創(chuàng)建一個(gè)單例
import 'package:event_bus/event_bus.dart';
class EventBusUtil {
static EventBus ? _instance;
static EventBus getInstance(){
if (_instance == null) {
_instance = EventBus();
}
return _instance!;
}
}
在第一個(gè)頁(yè)面監(jiān)聽(tīng):
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();
//不用的時(shí)候記得關(guān)閉
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('點(diǎn)擊跳轉(zhuǎn)'))
],
),
),
);
}
}
在第二個(gè)頁(yè)面發(fā)送事件:
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('點(diǎn)擊傳值')),
),
);
}
}
我們看一下EventBus的源碼,發(fā)現(xiàn)只有幾十行代碼,他的內(nèi)部是創(chuàng)建了一個(gè)StreamController,通過(guò)StreamController來(lái)實(shí)現(xiàn)跨組件傳值,我們也可以直接使用一下這個(gè)StreamController實(shí)現(xiàn)頁(yè)面?zhèn)髦担?br />
class Page19PassByValue extends StatefulWidget {
@override
_Page19PassByValueState createState() => _Page19PassByValueState();
}
StreamController controller = StreamController();
class _Page19PassByValueState extends State<Page19PassByValue> {
//設(shè)置一個(gè)初始值
UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
@override
void initState() {
super.initState();
controller.stream.listen((event) {
setState(() {
userInfoBean = event;
});
});
}
@override
void dispose() {
super.dispose();
//頁(yè)面銷毀的時(shí)候記得關(guān)閉
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('點(diǎn)擊跳轉(zhuǎn)'))
],
),
)
);
}
}
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: (){
//返回上個(gè)頁(yè)面,會(huì)發(fā)現(xiàn)頁(yè)面的數(shù)據(jù)已經(jīng)變了
controller.sink.add(UserInfoBean(name: 'StreamController pass name: 123', address: 'StreamController pass address 123'));
}, child: Text('點(diǎn)擊傳值'),),
),
);
}
}
到此這篇關(guān)于Flutter頁(yè)面?zhèn)髦档膸追N方式的文章就介紹到這了,更多相關(guān)Flutter頁(yè)面?zhèn)髦祪?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android動(dòng)態(tài)修改應(yīng)用圖標(biāo)與名稱的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Android動(dòng)態(tài)修改應(yīng)用圖標(biāo)與名稱的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
photoView實(shí)現(xiàn)圖片多點(diǎn)觸控效果
這篇文章主要為大家詳細(xì)介紹了photoView實(shí)現(xiàn)圖片多點(diǎn)觸控效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android自定義View構(gòu)造函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了Android自定義View構(gòu)造函數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android 實(shí)現(xiàn)高斯模糊效果且兼容低版本
這篇文章主要介紹了Android 實(shí)現(xiàn)高斯模糊效果且兼容低版本的相關(guān)資料,本文圖文并茂介紹的非常詳細(xì),需要的朋友可以參考下2016-09-09
Android實(shí)現(xiàn)帶附件的郵件發(fā)送功能
這篇文章主要介紹了Android實(shí)現(xiàn)帶附件的郵件發(fā)送功能的相關(guān)資料,android發(fā)送郵件有兩種方式,本文重點(diǎn)介紹基于JMail實(shí)現(xiàn)郵件發(fā)送功能,感興趣的小伙伴們可以參考一下2016-01-01
Android RecyclerView設(shè)置下拉刷新的實(shí)現(xiàn)方法
這篇文章主要介紹了Android RecyclerView設(shè)置下拉刷新的實(shí)現(xiàn)方法,希望通過(guò)本文通過(guò)SwipeRefreshLayout方式實(shí)現(xiàn)下拉刷新,需要的朋友可以參考下2017-10-10
Android中Glide加載庫(kù)的圖片緩存配置究極指南
這篇文章主要介紹了Android中Glide加載庫(kù)的圖片緩存配置究極指南,Glide是一款高人氣的安卓多媒體資源加載庫(kù),本文對(duì)其緩存設(shè)置和優(yōu)化作了詳細(xì)講解,需要的朋友可以參考下2016-04-04
android 控件同時(shí)監(jiān)聽(tīng)單擊和雙擊實(shí)例
這篇文章主要介紹了android 控件同時(shí)監(jiān)聽(tīng)單擊和雙擊實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08

