從零開始學Flutter--State 狀態(tài)管理

0.在Windows上搭建Flutter開發(fā)環(huán)境
1.從零開始學Flutter--Widget
[2.從零開始學Flutter--State 狀態(tài)管理]

State狀態(tài) 定義

Flutter是基于聲明式框架,我們以前開發(fā)Android或者IOS都是基于命令式框架祖很,如果是以前有過React/Vue的開發(fā)經(jīng)驗呢撞,了解Flutter狀態(tài)管理會更加簡單炸宵,如果是從原生的Android和IOS轉(zhuǎn)到Flutter,理解起來可能會有難度啄寡,不過不急拾弃,我寫這篇文章的時候也半懂不懂的讯檐,所以如果理解有出入可以留言交流羡疗,后面我有新的理解也會及時更新。

直接說人話别洪,在Flutter中我們怎么去理解State叨恨,我們在原生移動開發(fā)的時候,如果需要改變一個控件的狀態(tài)挖垛,直接調(diào)用那個控件暴露的函數(shù)就可以直接改變控件的狀態(tài)痒钝,就算是自定義控件的時候,SDK也提供了很多基礎的API讓我們操作這個View的各種狀態(tài)痢毒,但是在Flutter中送矩,好像并沒有一個控件或者View或者UI元素直接讓我們?nèi)ゲ僮鳎懊嫖覀冋f過闸准,Widget只是一個UI元素的描述,而且并不是所有的Widget都能改變狀態(tài)梢灭,所以他的子類分為StatelessWidget 和 StatefulWidget夷家,其中 StatelessWidget 的狀態(tài)是不可改變的,StatefulWidget狀態(tài)可以變化敏释,需要通過實現(xiàn)createState函數(shù)库快,接受一個State去改變狀態(tài)。

我們先不探究原理钥顽,先大致了解在Fluter怎么去管理State

Widget管理自己的狀態(tài)义屏。

在上一章介紹Widget里面,我們有寫一個簡單的例子

class PageOne extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new TextSatte();
  }
}

class TextState extends State {
  var _count = 0;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      appBar: new AppBar(
        title: Text("舉個例子"),
      ),
      body: new Stack(
        children: <Widget>[
          new Align(
            child: Text("我繼承StatefulWidget$_count"),
          ),
          new Align(
            alignment: new FractionalOffset(0.5, 1),
            child: new MaterialButton(
              onPressed: () {
                setState(() {
                  _count++;
                });
              },
              child: Text("改變狀態(tài)"),
            ),
          )
        ],
      ),
    );
  }
}

可以看到蜂大,如果 PageOne 需要自己管理狀態(tài)闽铐,那繼承 StatefulWidget,然后會需要實現(xiàn)createState函數(shù)奶浦,我們直接通過State 的setState函數(shù)就可以更改我們PageOne的狀態(tài)兄墅,比較簡單的使用

Widget管理子Widget狀態(tài)

既然是父管理子類,那父類Wiget肯定是可以改變狀態(tài)的

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => new _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

建一個ParentWidget 繼承 StatefulWidget澳叉,實現(xiàn)createState方法隙咸,返回一個State
_ParentWidgetState類繼承State沐悦,提供一個_handleTapboxChanged函數(shù),參數(shù)bool類型五督,通過setState用來改變_active的值
實現(xiàn)build方法藏否,在build里面,Container的child的對象是TapboxB充包,構(gòu)造函數(shù)傳遞兩個參數(shù)副签,_active和_handleTapboxChanged,下面來看看TapboxB里面的實現(xiàn)

TapboxB類

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

TapboxB 繼承 StatelessWidget误证,構(gòu)造函數(shù)是兩個參數(shù)active和onChanged继薛,然后提供一個_handleTap函數(shù),這個函數(shù)就是直接調(diào)用傳過來的onChanged(onChanged就是一個方法)愈捅,然后參數(shù)傳!active遏考,有代碼經(jīng)驗的應該都知道是什么意思
然后在build方法里面,設置Text元素的點擊事件為_handleTap蓝谨,這樣就可以達到改變自身狀態(tài)(子widget)的效果了

總體流程就是父類提供一個方法改變狀態(tài)灌具,然后把這個方法通過子類構(gòu)造函數(shù)傳遞到子類,然后子類需要的時候直接調(diào)用這個函數(shù)就可以到達修改狀態(tài)的效果

混合狀態(tài)管理

混合狀態(tài)管理就是子widget和父widget共同去處理state譬巫,結(jié)合上面兩種方法就可以了
ParentWidget不需要改變咖楣,TapboxB 類繼承 StatefulWidget,在事件里面調(diào)用setState函數(shù)就可以了

class TapboxC extends StatefulWidget {
  TapboxC({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;
  bool _highlight = false;

  @override
  _TapboxC createState() => _TapboxC();
}

class _TapboxC extends State<TapboxC> {

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      widget._highlight = true;
    });
  }
  void _handleTapUp(TapUpDetails details) {
    setState(() {
      widget._highlight = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new GestureDetector(
      onTapDown: _handleTapDown, // 處理按下事件
      onTapUp: _handleTapUp, // 處理抬起事件
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            widget.active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: widget._highlight
              ? new Border.all(
            color: Colors.teal[700],
            width: 10.0,
          )
              : null,
        ),
      ),
    );
  }
}

新建一個TapboxC類芦昔,繼承StatefulWidget诱贿,還是跟上面一樣,通過構(gòu)造函數(shù)咕缎,接受兩個參數(shù)珠十,用來調(diào)用父類函數(shù),改變狀態(tài)
自定義一個state凭豪,加入一個變量_highlight焙蹭,提供兩個方法_handleTapDown和_handleTapUp(方法里面通過setState函數(shù)來改變變量狀態(tài)),用來相應按下和抬起事件的處理
然后設置onTapDown和onTapUp事件嫂伞,border屬性靠_highlight變量改變孔厉,這樣就達到了混合處理狀態(tài)的模式,是不是很簡單

全局State的管理

//訂閱者回調(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;

  //保存事件訂閱者隊列帖努,key:事件名(id)撰豺,value: 對應事件的訂閱者隊列
  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ā)后該事件所有訂閱者會被調(diào)用
  void emit(eventName, [arg]) {
    var list = _emap[eventName];
    if (list == null) return;
    int len = list.length - 1;
    //反向遍歷拼余,防止訂閱者在回調(diào)中移除自身帶來的下標錯位
    for (var i = len; i > -1; --i) {
      list[i](arg);
    }
  }
}

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

看代碼,簡單的邏輯應該沒問題姿搜,開始設計單例模式寡润,然后定義一個_emap變量來保存任務隊列捆憎,提供 on 和 off 函數(shù)來添加和移除任務隊列,兩個函數(shù)都要傳一個EventCallback的回調(diào)梭纹,進行事件的監(jiān)聽回調(diào)躲惰,然后提供emit函數(shù)來觸發(fā)事件,觸發(fā)函數(shù)被調(diào)用之后变抽,實際就是遍歷循環(huán)整個_emap任務列表础拨。
其實這就是一個簡單的監(jiān)聽者模式,在需要監(jiān)聽事件的地方調(diào)用on來注冊監(jiān)聽绍载,在需要改變某個狀態(tài)的地方诡宗,調(diào)用emit函數(shù)來觸發(fā)事件,這樣在監(jiān)聽的地方就可以收到回調(diào)击儡,進行處理塔沃。

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

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

總結(jié)

Flutter的狀態(tài)管理和原生移動開發(fā)的不同阳谍,基本就是如果需要改變一個UI元素的狀態(tài)蛀柴,就需要在一個State里面進行處理,不能直接調(diào)用某個"控件"的函數(shù)進行改變矫夯,其中有的元素不需要進行狀態(tài)的改變鸽疾,我們就繼承StatelessWidget,如果需要改變就繼承StatefulWidget训貌,在StatefulWidget中自己控制自己的狀態(tài)可以調(diào)用State的setState函數(shù)就可以直接改變了制肮,父類要改變子類的狀態(tài),只需要把父類改變狀態(tài)的函數(shù)傳遞給子類递沪,讓子類去調(diào)用函數(shù)就可以了豺鼻,全局的狀態(tài)管理我們寫了一個監(jiān)聽者模式的事件總線,F(xiàn)lutter中有很多第三方的框架可以實現(xiàn)全局狀態(tài)的管理区拳,后面用到再學習拘领,如果Flutter原生的功能能滿足需求意乓,也沒必要過度的引入第三方的庫樱调,增加代碼的復雜性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末届良,一起剝皮案震驚了整個濱河市笆凌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌士葫,老刑警劉巖乞而,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慢显,居然都是意外死亡爪模,警方通過查閱死者的電腦和手機欠啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屋灌,“玉大人洁段,你說我怎么就攤上這事」补” “怎么了祠丝?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長除嘹。 經(jīng)常有香客問我写半,道長,這世上最難降的妖魔是什么尉咕? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任叠蝇,我火速辦了婚禮,結(jié)果婚禮上龙考,老公的妹妹穿的比我還像新娘蟆肆。我一直安慰自己,他們只是感情好晦款,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布炎功。 她就那樣靜靜地躺著,像睡著了一般缓溅。 火紅的嫁衣襯著肌膚如雪蛇损。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天坛怪,我揣著相機與錄音淤齐,去河邊找鬼。 笑死袜匿,一個胖子當著我的面吹牛更啄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播居灯,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祭务,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怪嫌?” 一聲冷哼從身側(cè)響起义锥,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岩灭,沒想到半個月后拌倍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年柱恤,在試婚紗的時候發(fā)現(xiàn)自己被綠了数初。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡梗顺,死狀恐怖妙真,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荚守,我是刑警寧澤珍德,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站矗漾,受9級特大地震影響锈候,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敞贡,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一泵琳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧誊役,春花似錦获列、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹏漆,卻和暖如春巩梢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艺玲。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工括蝠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饭聚。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓忌警,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秒梳。 傳聞我的和親對象是個殘疾皇子法绵,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354