Flutter狀態(tài)管理及事件處理

一疤剑、什么是狀態(tài)管理?為什么要狀態(tài)管理?

一般開發(fā)一款應(yīng)用程序闷堡,頁面間極有可能需要互相進(jìn)行數(shù)據(jù)傳遞隘膘,而這里的數(shù)據(jù)傳遞也就是指頁面間的狀態(tài)同步(管理)。
頁面內(nèi)部的狀態(tài)是可以用StatefulWidget維護(hù)其狀態(tài)杠览,當(dāng)我們需要使用跨組件的狀態(tài)時(shí)弯菊,StatefulWidget 將不再是一個(gè)好的選擇。在多個(gè) Widget 之間進(jìn)行交流的時(shí)候踱阿,雖然你可以使用事件處理的方式解決(setState管钳、callback、EventBus软舌、Notification)才漆,但是當(dāng)嵌套足夠深的話,很容易就增大代碼耦合度葫隙。狀態(tài)管理就是來幫助我們理清這些關(guān)系的栽烂!

二、事件處理

1.setState

2.Function callback(類似ios Block)

widget:

class StateManageListItem extends StatelessWidget {

  final VoidCallback callback;//聲明

  const StateManageListItem({this.callback});

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: callback,//回調(diào)
      child: Container(
        
      ),
    );
  }
}

調(diào)用:

class StateManagementPage extends StatefulWidget {
  @override
  _StateManagementPageState createState() => _StateManagementPageState();
}

class _StateManagementPageState extends State<StateManagementPage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: new AppBar(
        brightness: Brightness.light,
        centerTitle: true,
        title: Text(
          '狀態(tài)管理',
          style: HSLTextStyles.textWhite16,
        ),
      ),
      body: new ListView.separated(
        itemCount: _items.length,
        physics: AlwaysScrollableScrollPhysics(),
        separatorBuilder:(BuildContext context, int index) {
          return Divider(height: 1, color: HSLColors.selago);
        },
        itemBuilder: (BuildContext context, int index) {
          return StateManageListItem(
            callback: (){
              if(index == 0){
                showToast('callBack傳值');
              }
            },
          );
        },
      ),
    );
  }
}
callBack.gif

3.事件總線-EventBus

在APP中恋脚,我們經(jīng)常會(huì)需要一個(gè)廣播機(jī)制腺办,用以跨頁面事件通知,比如一個(gè)需要登錄的APP中糟描,頁面會(huì)關(guān)注用戶登錄或注銷事件怀喉,來進(jìn)行一些狀態(tài)更新。這時(shí)候船响,一個(gè)事件總線便會(huì)非常有用躬拢,事件總線通常實(shí)現(xiàn)了訂閱者模式躲履,訂閱者模式包含發(fā)布者和訂閱者兩種角色,可以通過事件總線來觸發(fā)事件和監(jiān)聽事件

單例模式實(shí)現(xiàn)全局的事件總線

++Dart中實(shí)現(xiàn)單例模式的標(biāo)準(zhǔn)做法就是使用static變量+工廠構(gòu)造函數(shù)的方式聊闯,這樣就可以保證new EventBus()始終返回都是同一個(gè)實(shí)例工猜,讀者應(yīng)該理解并掌握這種方法。++

//訂閱者回調(diào)簽名
typedef void EventCallback(arg);

class EventBus {
  //私有構(gòu)造函數(shù)
  EventBus._internal();

  //保存單例
  static EventBus _singleton = new EventBus._internal();

  //工廠構(gòu)造函數(shù)
  factory EventBus()=> _singleton;

  //保存事件訂閱者隊(duì)列菱蔬,key:事件名(id)篷帅,value: 對應(yīng)事件的訂閱者隊(duì)列
  var _emap = new Map<Object, List<EventCallback>>();

  //添加訂閱者
  void on(eventName, EventCallback f) {
    if (eventName == null || f == null) return;
    _emap[eventName] ??= new List<EventCallback>();
    _emap[eventName].add(f);
  }

  //移除訂閱者
  void off(eventName, [EventCallback f]) {
    var list = _emap[eventName];
    if (eventName == null || list == null) return;
    if (f == null) {
      _emap[eventName] = null;
    } else {
      list.remove(f);
    }
  }

  //觸發(fā)事件,事件觸發(fā)后該事件所有訂閱者會(huì)被調(diào)用
  void emit(eventName, [arg]) {
    var list = _emap[eventName];
    if (list == null) return;
    int len = list.length - 1;
    //反向遍歷拴泌,防止訂閱者在回調(diào)中移除自身帶來的下標(biāo)錯(cuò)位 
    for (var i = len; i > -1; --i) {
      list[i](arg);
    }
  }
}

//定義一個(gè)top-level(全局)變量魏身,頁面引入該文件后可以直接使用bus
var bus = new EventBus();

使用:

//頁面A中
...
 //監(jiān)聽登錄事件
bus.on("login", (arg) {
  // do something
});

//登錄頁B中
...
//登錄成功后觸發(fā)登錄事件,頁面A中訂閱者會(huì)被調(diào)用
bus.emit("login", userInfo);
eventBus.gif

4.通知-Notification

通知(Notification)是Flutter中一個(gè)重要的機(jī)制蚪腐,在widget樹中箭昵,每一個(gè)節(jié)點(diǎn)都可以分發(fā)通知,通知會(huì)沿著當(dāng)前節(jié)點(diǎn)向上傳遞回季,所有父節(jié)點(diǎn)都可以通過NotificationListener來監(jiān)聽通知家制。Flutter中將這種由子向父的傳遞通知的機(jī)制稱為通知冒泡(Notification Bubbling)。通知冒泡和用戶觸摸事件冒泡是相似的茧跋,但有一點(diǎn)不同:通知冒泡可以中止慰丛,但用戶觸摸事件不行。

1.定義一個(gè)通知類瘾杭,要繼承自Notification類诅病;

class MyNotification extends Notification {
  MyNotification(this.msg);
  final String msg;
}

2.分發(fā)通知。

class NotificationRouteState extends State<NotificationRoute> {
  String _msg="";
  @override
  Widget build(BuildContext context) {
    //監(jiān)聽通知  
    return NotificationListener<MyNotification>(
      onNotification: (notification) {
        setState(() {
          _msg+=notification.msg+"  ";
        });
       return true;
      },
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Builder(
              builder: (context) {
                return RaisedButton(
                  //按鈕點(diǎn)擊時(shí)分發(fā)通知  
                  onPressed: () => MyNotification("Hi").dispatch(context),
                  child: Text("Send Notification"),
                );
              },
            ),
            Text(_msg)
          ],
        ),
      ),
    );
  }
}
Notification.gif

5.InheritedWidget

InheritedWidget提供了一種數(shù)據(jù)在widget樹中從上到下傳遞粥烁、共享的方式贤笆,比如我們在應(yīng)用的根widget中通過InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget中來獲取該共享的數(shù)據(jù)

import 'package:flutter/material.dart';

class InheritedWidgetPage extends StatefulWidget {
  @override
  _InheritedWidgetPageState createState() => _InheritedWidgetPageState();
}

class _InheritedWidgetPageState extends State<InheritedWidgetPage> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('數(shù)據(jù)共享InheritedWidget'),
      ),
      body: Center(
        child: ShareDataWidget( //使用ShareDataWidget
          data: count,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.only(bottom: 20.0),
                child: _TestWidget(),//子widget中依賴ShareDataWidget
              ),
              RaisedButton(
                child: Text("Increment"),
                //每點(diǎn)擊一次讨阻,將count自增芥永,然后重新build,ShareDataWidget的data將被更新
                onPressed: () => setState(() => ++count),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);

  final int data; //需要在子樹中共享的數(shù)據(jù),保存點(diǎn)擊次數(shù)

  //定義一個(gè)便捷方法钝吮,方便子樹中的widget獲取共享數(shù)據(jù)
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //該回調(diào)決定當(dāng)data發(fā)生變化時(shí)埋涧,是否通知子樹中依賴data的Widget
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,則子樹中依賴(build函數(shù)中有調(diào)用)本widget
    //的子widget的`state.didChangeDependencies`會(huì)被調(diào)用
    return old.data != data;
  }
}

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享數(shù)據(jù)
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時(shí)會(huì)被調(diào)用奇瘦。
    //如果build中沒有依賴InheritedWidget棘催,則此回調(diào)不會(huì)被調(diào)用。
    print("Dependencies change");
  }
}

![Provider.gif](https://upload-images.jianshu.io/upload_images/6243068-3683579f8be672cf.gif?imageMogr2/auto-orient/strip)

三耳标、狀態(tài)管理常用插件

1.Provider

我們往往需要管理不同頁面之間的數(shù)據(jù)共享醇坝,在頁面功能復(fù)雜,狀態(tài)達(dá)到幾十個(gè)上百個(gè)的時(shí)候次坡,我們會(huì)難以清楚的維護(hù)我們的數(shù)據(jù)狀態(tài),這時(shí)候就需要跨組件管理呼猪。

1画畅、創(chuàng)建Model

import 'package:provider/provider.dart';
class Counter with ChangeNotifier {//1
  int _count;
  Counter(this._count);

  void add() {
    _count++;
    notifyListeners();//2
  }
  get count => _count;//3
}

2.使用ChangeNotifierProvider
這里用的全局的

main() {
  runApp(ChangeNotifierProvider<Counter>.value(//1
    notifier: Counter(1),//2
    child: MyApp(),
  ));
}

3.使用Provider獲取Counter的值

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
        actions: <Widget>[
          FlatButton(
            child: Text("下一頁"),
            onPressed: () =>
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return SecondPage();
                })),
          ),
        ],
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),//1
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();//2
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

//第二個(gè)頁面也獲取到Count
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text(Provider.of<String>(context)),
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
Provider.gif

放一個(gè)之前自己學(xué)習(xí)時(shí)寫的demo,希望可以幫助新入門的老鐵們宋距,有好的建議可以提一下轴踱,我們一起進(jìn)步,奧利給O绺铩?苌摊腋!
https://github.com/Baffin-HSL/Flutter_UI

基本元素

自定義的頁面
基本功能學(xué)習(xí)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沸版,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兴蒸,更是在濱河造成了極大的恐慌视粮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橙凳,死亡現(xiàn)場離奇詭異蕾殴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)岛啸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門钓觉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坚踩,你說我怎么就攤上這事荡灾。” “怎么了瞬铸?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵批幌,是天一觀的道長。 經(jīng)常有香客問我嗓节,道長荧缘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任拦宣,我火速辦了婚禮截粗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸵隧。我一直安慰自己绸罗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布掰派。 她就那樣靜靜地躺著从诲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靡羡。 梳的紋絲不亂的頭發(fā)上系洛,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天俊性,我揣著相機(jī)與錄音,去河邊找鬼描扯。 笑死定页,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绽诚。 我是一名探鬼主播典徊,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恩够!你這毒婦竟也來了卒落?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蜂桶,失蹤者是張志新(化名)和其女友劉穎儡毕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扑媚,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腰湾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疆股。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片费坊。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖旬痹,靈堂內(nèi)的尸體忽然破棺而出附井,到底是詐尸還是另有隱情,我是刑警寧澤唱凯,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布羡忘,位于F島的核電站,受9級特大地震影響磕昼,放射性物質(zhì)發(fā)生泄漏卷雕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一票从、第九天 我趴在偏房一處隱蔽的房頂上張望漫雕。 院中可真熱鬧,春花似錦峰鄙、人聲如沸浸间。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魁蒜。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兜看,已是汗流浹背锥咸。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留细移,地道東北人搏予。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像弧轧,于是被迫代替她去往敵國和親雪侥。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容