Flutter数据传递 分为两种方式。一种是沿着数的方向从上向下传递状态。另外一种是 从下往上传递状态值。react
按照Widgets Tree的方向,从上往子树和节点上传递状态。bash
这个既熟悉又陌生类能够帮助咱们在Flutter中沿着树向下传递信息。 咱们常常经过这样的方式,经过BuildContext
,能够拿到Theme
和MediaQuery
markdown
//获得状态栏的高度
var statusBarHeight = MediaQuery.of(context).padding.top;
//复制合并出新的主题
var copyTheme =Theme.of(context).copyWith(primaryColor: Colors.blue);
复制代码
看到of
的静态方法,第一反应是去经过这个context
去构建新的类。而后从这个类中,去调用获取状态的方法。(Android开发的同窗应该很熟悉的套路,相似Picasso、Glide)。但事实上是这样吗?网络
context.inheritFromWidgetOfExactType
static MediaQueryData of(BuildContext context, { bool nullOk: false }) {
assert(context != null);
assert(nullOk != null);
final MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
if (query != null)
return query.data;
if (nullOk)
return null;
throw new FlutterError(
'MediaQuery.of() called with a context that does not contain a MediaQuery.\n'
'No MediaQuery ancestor could be found starting from the context that was passed '
'to MediaQuery.of(). This can happen because you do not have a WidgetsApp or '
'MaterialApp widget (those widgets introduce a MediaQuery), or it can happen '
'if the context you use comes from a widget above those widgets.\n'
'The context used was:\n'
' $context'
);
}
复制代码
context.inheritFromWidgetOfExactType
来查到MediaQuery
。 MediaQuery
是咱们存在在BuildContext
中的属性。MediaQuery
存储在的BuildContext
中的位置是在WidgetsApp
.(由于其实MaterialApp
返回的也是它)继承InheritedWidget
架构
经过build
方法中返回app
MaterialApp
的_MaterialAppState
中的build
方法 框架
WidgetsApp
的_WidgetsAppState
中的build
方法 less
context.inheritFromWidgetOfExactType
来获取。 而后在子树的任何地方,均可以经过这样的方式来进行获取。了解了MediaQuery
的存放方式,咱们能够实现本身的状态管理,这样在子组件中,就能够同步获取到状态值。ide
//0. 定义一个变量来存储
class AppState {
bool isLoading;
AppState({this.isLoading = true});
factory AppState.loading() => AppState(isLoading: true);
@override
String toString() {
return 'AppState{isLoading: $isLoading}';
}
}
复制代码
//1. 模仿MediaQuery。简单的让这个持有咱们想要保存的data
class _InheritedStateContainer extends InheritedWidget {
final AppState data;
//咱们知道InheritedWidget老是包裹的一层,因此它必有child
_InheritedStateContainer(
{Key key, @required this.data, @required Widget child})
: super(key: key, child: child);
//参考MediaQuery,这个方法一般都是这样实现的。若是新的值和旧的值不相等,就须要notify
@override
bool updateShouldNotify(_InheritedStateContainer oldWidget) =>
data != oldWidget.data;
}
复制代码
建立外层的Widget
,而且提供静态方法of
,来获得咱们的AppState
post
/* 1. 从MediaQuery模仿的套路,咱们知道,咱们须要一个StatefulWidget做为外层的组件, 将咱们的继承于InheritateWidget的组件build出去 */
class AppStateContainer extends StatefulWidget {
//这个state是咱们须要的状态
final AppState state;
//这个child的是必须的,来显示咱们正常的控件
final Widget child;
AppStateContainer({this.state, @required this.child});
//4.模仿MediaQuery,提供一个of方法,来获得咱们的State.
static AppState of(BuildContext context) {
//这个方法内,调用 context.inheritFromWidgetOfExactType
return (context.inheritFromWidgetOfExactType(_InheritedStateContainer)
as _InheritedStateContainer)
.data;
}
@override
_AppStateContainerState createState() => _AppStateContainerState();
}
class _AppStateContainerState extends State<AppStateContainer> {
//2. 在build方法内返回咱们的InheritedWidget
//这样App的层级就是 AppStateContainer->_InheritedStateContainer-> real app
@override
Widget build(BuildContext context) {
return _InheritedStateContainer(
data: widget.state,
child: widget.child,
);
}
}
复制代码
class MyInheritedApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
//由于是AppState,因此他的范围是全生命周期的,因此能够直接包裹在最外层
return AppStateContainer(
//初始化一个loading
state: AppState.loading(),
child: new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
复制代码
didChangeDependencies
中查询它。因此咱们也class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState() {}
AppState appState;
//在didChangeDependencies方法中,就能够查到对应的state了
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies');
if(appState==null){
appState= AppStateContainer.of(context);
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
//根据isLoading来判断,显示一个loading,或者是正常的图
child: appState.isLoading
? CircularProgressIndicator()
: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'appState.isLoading = ${appState.isLoading}',
),
],
),
),
floatingActionButton: new Builder(builder: (context) {
return new FloatingActionButton(
onPressed: () {
//点击按钮进行切换
//由于是全局的状态,在其余页面改变,也会致使这里发生变化
appState.isLoading = !appState.isLoading;
//setState触发页面刷新
setState(() {});
},
tooltip: 'Increment',
child: new Icon(Icons.swap_horiz),
);
}));
}
}
复制代码
点击按钮更改状态。
由于上面代码是在一个页面内的状况,咱们要肯定是否全局的状态是保持一致的。因此 让咱们再改一下代码,点击push出新的页面,在新页面内改变appState的状态,看看就页面会不会发生变化。 代码修改以下:
//修改floatingButton的点击事件
floatingActionButton: new Builder(builder: (context) {
return new FloatingActionButton(
onPressed: () {
//push出一个先的页面
Navigator.of(context).push(
new MaterialPageRoute<Null>(builder: (BuildContext context) {
return MyHomePage(
title: 'Second State Change Page');
}));
//注释掉原来的代码
// appState.isLoading = !appState.isLoading;
// setState(() {});
},
tooltip: 'Increment',
child: new Icon(Icons.swap_horiz),
);
})
复制代码
MyHomePage
基本上和上面的代码一致。一样让他修改appState
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _changeState() {
setState(() {
state.isLoading = !state.isLoading;
});
}
AppState state;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if(state ==null){
state = AppStateContainer.of(context);
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'appState.isLoading = ${state.isLoading}',
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _changeState,
tooltip: 'ChangeState',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
复制代码
在push的页面修改AppState的状态,回到初始的页面,看状态是否发生变化。
经过分析MediaQuery
,咱们了解到了InheritedWidget
的用法,而且经过自定义的AppState
等操做熟悉了总体状态控制的流程。 咱们能够继续思考下面几个问题
为何AppState
能在整个App周期中,维持状态呢? 由于咱们将其包裹在了最外层。 由此思考,每一个页面可能也有本身的状态,维护页面的状态,能够将其包裹在页面的层级的最外层,这样它就变成了PageScope
的状态了。
限制-like a EventBus 当咱们改变state
并关闭页面后,由于didChangeDependencies
方法和build
方法的执行,咱们打开这个页面时,总能拿到最新的state
。因此咱们的页面可以同步状态成功。 那若是是像EventBus同样,push出一个状态,咱们须要去进行一个耗时操做,而后才能发生的改变咱们能监听和处理吗?
继承至ChangeNotifier
。能够注册监听事件。当值发生改变时,会给监听则发送监听。
/// A [ChangeNotifier] that holds a single value.
///
/// When [value] is replaced, this class notifies its listeners.
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value);
/// The current value stored in this notifier.
///
/// When the value is replaced, this class notifies its listeners.
@override
T get value => _value;
T _value;
set value(T newValue) {
if (_value == newValue)
return;
_value = newValue;
notifyListeners();
}
@override
String toString() => '${describeIdentity(this)}($value)';
}
复制代码
源码看到,只要改变值value
值,至关于调用set
方法,都会notifyListeners
//定义一个变量来存储
class AppState {
//...忽略重复代码。添加成员变量
ValueNotifier<bool> canListenLoading = ValueNotifier(false);
}
复制代码
class _MyHomeInheritedPageState extends State<MyInheritedHomePage> {
//...忽略重复代码。添加成员变量
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies');
if (appState == null) {
print('state == null');
appState = AppStateContainer.of(context);
//在这里添加监听事件
appState.canListenLoading.addListener(listener);
}
}
@override
void dispose() {
print('dispose');
if (appState != null) {
//在这里移除监听事件
appState.canListenLoading.removeListener(listener);
}
super.dispose();
}
@override
void initState() {
print('initState');
//初始化监听的回调。回调用做的就是延迟5s后,将result修改为 "From delay"
listener = () {
Future.delayed(Duration(seconds: 5)).then((value) {
result = "From delay";
setState(() {});
});
};
super.initState();
}
//添加成员变量。 result参数和 listener回调
String result = "";
VoidCallback listener;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: appState.isLoading
? CircularProgressIndicator()
: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'appState.isLoading = ${appState.isLoading}',
),
//新增,result的显示在屏幕上
new Text(
'${result}',
),
],
),
),
//...忽略重复代码
}
}
复制代码
运行结果和咱们预想的同样。
canListenLoading
的value。这样会触发上一个页面已经注册的监听事件(4s后改变值)。这样就感受能够实现一个相似EventBus的功能了~~
这边文章,主要说的是,利用Flutter自身的框架来实现,状态管理和消息传递的内容。
InheritedWidget
来保存状态context.inheritFromWidgetOfExactType
来获取属性ValueNotifer
来实现属性监听。Key 保存Widget
的状态,咱们能够经过给对应Widget
的key
,来保存状态,并经过Key来拿到状态。 好比是 ObjectKey
能够在列表中标记惟一的Key
,来保存状态,让动画识别。 GlobalKey
,则能够保存一个状态,其余地方均可以获取。
InheritedWidget
能够持有一个状态,共它的子树来获取。 这样子树自己能够不直接传入这个字段(这样能够避免多级的Widget时,要一层一层向下传递状态) 还能够作不一样Widget
中间的状态同步
ChangeNofier
继承这里类,咱们就能够实现Flutter
中的观察者模式,对属性变化作观察。
另外,咱们还能够经过第三方库
,好比说 Redux
和ScopeModel
Rx
来作这个事情。可是其基于的原理,应该也是上方的内容。
咱们知道,咱们能够经过NotificationListener
的方式来监听ScrollNotification
页面的滚动状况。Flutter中就是经过这样的方式,经过来从子组件往父组件的BuildContext
中发布数据,完成数据传递的。 下面咱们简单的来实现一个咱们本身的。
//0.自定义一个Notification。
class MyNotification extends Notification {}
class _MyHomePageState extends State<MyHomePage> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
//2.在Scaffold的层级进行事件的监听。建立`NotificationListener`,并在`onNotification`就能够获得咱们的事件了。
return NotificationListener(
onNotification: (event) {
if (event is MyNotification) {
print("event= Scaffold MyNotification");
}
},
child: new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
//3.注意,这里是监听不到事件的。这里须要监听到事件,须要在body本身的`BuildContext`发送事件才行!!!!
body: new NotificationListener<MyNotification>(
onNotification: (event) {
//接受不到事件,由于`context`不一样
print("body event=" + event.toString());
},
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'appState.isLoading = ',
),
new Text(
'appState.canListenLoading.value',
),
],
),
)),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: () {
//1.建立事件,并经过发送到对应的`BuildContext`中。注意,这里的`context`是`Scaffold`的`BuildContext`
new MyNotification().dispatch(context);
},
tooltip: 'ChangeState',
child: new Icon(Icons.add),
);
})));
}
}
复制代码
咱们能够经过Notification的继承类,将其发布到对应的BuildContext
中,来实现数据传递。
经过这边Flutter数据传递的介绍,咱们能够大概搭建本身的Flutter App的数据流结构。 相似闲鱼的界面的架构设计。
从上往下: 经过自定义不一样Scope
的InheritedWidget
来hold住不一样Scope
的数据,这样对应的Scope
下的子组件都能获得对应的数据,和获得对应的更新。
从下往上: 经过自定义的Notification
类。在子组件中经过Notification(data).dispatch(context)
这样的方式发布,在对应的Context
上,在经过NotificationListener
进行捕获和监听。
经过三遍文章,对Flutter
文档中一些细节作了必要的入门补充。 尚未介绍相关的 手势
,网络请求
,Channel和Native通讯
,还有动画
等内容。请结合文档学习。
在丰富了理论知识以后,下一编开始,咱们将进行Flutter
的实战分析。
Build reactive mobile apps in Flutter — companion article
set-up-inherited-widget-app-state
深刻了解Flutter界面开发(强烈推荐) (ps:真的强烈推荐)