日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter入门三部曲(3) - 数据传递/状态管理 | 掘金技术征文

發布時間:2024/4/17 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter入门三部曲(3) - 数据传递/状态管理 | 掘金技术征文 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Flutter數據傳遞 分為兩種方式。一種是沿著數的方向從上向下傳遞狀態。另一種是 從下往上傳遞狀態值。

沿著樹的方向,向下傳遞狀態

按照Widgets Tree的方向,從上往子樹和節點上傳遞狀態。

InheritedWidget & ValueNotifier

InheritedWidget

這個既熟悉又陌生類可以幫助我們在Flutter中沿著樹向下傳遞信息。 我們經常通過這樣的方式,通過BuildContext,可以拿到Theme和MediaQuery

//得到狀態欄的高度 var statusBarHeight = MediaQuery.of(context).padding.top; //復制合并出新的主題 var copyTheme =Theme.of(context).copyWith(primaryColor: Colors.blue); 復制代碼

看到of的靜態方法,第一反應是去通過這個context去構建新的類。然后從這個類中,去調用獲取狀態的方法。(Android開發的同學應該很熟悉的套路,類似Picasso、Glide)。但事實上是這樣嗎?

MediaQuery

通過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返回的也是它)
MediaQuery狀態保存的原理
  • 繼承InheritedWidget

  • 通過build方法中返回

  • MaterialApp的_MaterialAppState中的build方法

  • WidgetsApp的_WidgetsAppState中的build方法

    • 獲取 最后就是最上面看到的那段代碼,通過context.inheritFromWidgetOfExactType來獲取。 然后在子樹的任何地方,都可以通過這樣的方式來進行獲取。

    定義一個AppState

    了解了MediaQuery的存放方式,我們可以實現自己的狀態管理,這樣在子組件中,就可以同步獲取到狀態值。

    0.先定義一個AppState
    //0. 定義一個變量來存儲 class AppState {bool isLoading;AppState({this.isLoading = true});factory AppState.loading() => AppState(isLoading: true);@overrideString toString() {return 'AppState{isLoading: $isLoading}';} }復制代碼
    1. 繼承InheritedWidget
    //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@overridebool updateShouldNotify(_InheritedStateContainer oldWidget) =>data != oldWidget.data; } 復制代碼
    2. 創建外層的Widget

    創建外層的Widget,并且提供靜態方法of,來得到我們的AppState

    /* 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.inheritFromWidgetOfExactTypereturn (context.inheritFromWidgetOfExactType(_InheritedStateContainer)as _InheritedStateContainer).data;}@override_AppStateContainerState createState() => _AppStateContainerState(); }class _AppStateContainerState extends State<AppStateContainer> {//2. 在build方法內返回我們的InheritedWidget//這樣App的層級就是 AppStateContainer->_InheritedStateContainer-> real app@overrideWidget build(BuildContext context) {return _InheritedStateContainer(data: widget.state,child: widget.child,);} } 復制代碼
    3. 使用
    • 包括在最外層
    class MyInheritedApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {//因為是AppState,所以他的范圍是全生命周期的,所以可以直接包裹在最外層return AppStateContainer(//初始化一個loadingstate: 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了@overridevoid didChangeDependencies() {super.didChangeDependencies();print('didChangeDependencies');if(appState==null){appState= AppStateContainer.of(context);}}@overrideWidget 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),);}));} } 復制代碼
    運行效果1-當前頁面

    點擊按鈕更改狀態。

    4. 在另外一個頁面修改AppState

    因為上面代碼是在一個頁面內的情況,我們要確定是否全局的狀態是保持一致的。所以 讓我們再改一下代碼,點擊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;@overridevoid didChangeDependencies() {super.didChangeDependencies();if(state ==null){state = AppStateContainer.of(context);}}@overrideWidget 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.);} } 復制代碼
    運行效果2-另外一個頁面內修改狀態

    在push的頁面修改AppState的狀態,回到初始的頁面,看狀態是否發生變化。

    小結和思考

    通過分析MediaQuery,我們了解到了InheritedWidget的用法,并且通過自定義的AppState等操作熟悉了整體狀態控制的流程。 我們可以繼續思考下面幾個問題

    • 為什么AppState能在整個App周期中,維持狀態呢? 因為我們將其包裹在了最外層。 由此思考,每個頁面可能也有自己的狀態,維護頁面的狀態,可以將其包裹在頁面的層級的最外層,這樣它就變成了PageScope的狀態了。

    • 限制-like a EventBus 當我們改變state并關閉頁面后,因為didChangeDependencies方法和build方法的執行,我們打開這個頁面時,總能拿到最新的state。所以我們的頁面能夠同步狀態成功。 那如果是像EventBus一樣,push出一個狀態,我們需要去進行一個耗時操作,然后才能發生的改變我們能監聽和處理嗎?

    ValueNotifier

    繼承至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.@overrideT get value => _value;T _value;set value(T newValue) {if (_value == newValue)return;_value = newValue;notifyListeners();}@overrideString toString() => '${describeIdentity(this)}($value)'; } 復制代碼

    源碼看到,只要改變值value值,相當于調用set方法,都會notifyListeners

    修改代碼

    AppState添加成員
    //定義一個變量來存儲 class AppState {//...忽略重復代碼。添加成員變量ValueNotifier<bool> canListenLoading = ValueNotifier(false); } 復制代碼
    _MyHomeInheritedPageState 中添加監聽
    class _MyHomeInheritedPageState extends State<MyInheritedHomePage> {//...忽略重復代碼。添加成員變量@overridevoid didChangeDependencies() {super.didChangeDependencies();print('didChangeDependencies');if (appState == null) {print('state == null');appState = AppStateContainer.of(context);//在這里添加監聽事件appState.canListenLoading.addListener(listener);}}@overridevoid dispose() {print('dispose');if (appState != null) {//在這里移除監聽事件appState.canListenLoading.removeListener(listener);}super.dispose();}@overridevoid 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;@overrideWidget 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來做這個事情。但是其基于的原理,應該也是上方的內容。


    從下往上傳遞分發狀態值

    Notification

    我們知道,我們可以通過NotificationListener的方式來監聽ScrollNotification頁面的滾動情況。Flutter中就是通過這樣的方式,通過來從子組件往父組件的BuildContext中發布數據,完成數據傳遞的。 下面我們簡單的來實現一個我們自己的。

    • 代碼
    //0.自定義一個Notification。 class MyNotification extends Notification {}class _MyHomePageState extends State<MyHomePage> {@overridevoid didChangeDependencies() {super.didChangeDependencies();}@overrideWidget 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:真的強烈推薦)


    從 0 到 1:我的 Flutter 技術實踐 | 掘金技術征文,征文活動正在進行中

    總結

    以上是生活随笔為你收集整理的Flutter入门三部曲(3) - 数据传递/状态管理 | 掘金技术征文的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。