Flutter核心原理Widget(2)

為什么要將build方法放在State中个束,而不是放在StatefulWidget中?

這主要是為了提高開發(fā)的靈活性扇苞。如果將build()方法在StatefulWidget中則會有兩個問題:

  • 狀態(tài)訪問不便层亿。

    試想一下,如果我們的StatefulWidget有很多狀態(tài)拔莱,而每次狀態(tài)改變都要調(diào)用build方法碗降,由于狀態(tài)是保存在State中的,如果build方法在StatefulWidget中塘秦,那么build方法和狀態(tài)分別在兩個類中讼渊,那么構(gòu)建時讀取狀態(tài)將會很不方便!試想一下尊剔,如果真的將build方法放在StatefulWidget中的話爪幻,由于構(gòu)建用戶界面過程需要依賴State,所以build方法將必須加一個State參數(shù),大概是下面這樣:

      Widget build(BuildContext context, State state){
          //state.counter
          ...
      }
    

    這樣的話就只能將State的所有狀態(tài)聲明為公開的狀態(tài)挨稿,這樣才能在State類外部訪問狀態(tài)仇轻!但是,將狀態(tài)設(shè)置為公開后叶组,狀態(tài)將不再具有私密性拯田,這就會導致對狀態(tài)的修改將會變的不可控。但如果將build()方法放在State中的話甩十,構(gòu)建過程不僅可以直接訪問狀態(tài)船庇,而且也無需公開私有狀態(tài),這會非常方便侣监。

  • 繼承StatefulWidget不便鸭轮。

    例如,F(xiàn)lutter中有一個動畫widget的基類AnimatedWidget橄霉,它繼承自StatefulWidget類窃爷。AnimatedWidget中引入了一個抽象方法build(BuildContext context),繼承自AnimatedWidget的動畫widget都要實現(xiàn)這個build方法⌒辗洌現(xiàn)在設(shè)想一下按厘,如果StatefulWidget 類中已經(jīng)有了一個build方法,正如上面所述钱慢,此時build方法需要接收一個state對象逮京,這就意味著AnimatedWidget必須將自己的State對象(記為_animatedWidgetState)提供給其子類,因為子類需要在其build方法中調(diào)用父類的build方法束莫,代碼可能如下:

    class MyAnimationWidget extends AnimatedWidget{
        @override
        Widget build(BuildContext context, State state){
          //由于子類要用到AnimatedWidget的狀態(tài)對象_animatedWidgetState懒棉,
          //所以AnimatedWidget必須通過某種方式將其狀態(tài)對象_animatedWidgetState
          //暴露給其子類   
          super.build(context, _animatedWidgetState)
        }
    }
    
    

這樣很顯然是不合理的,因為

  1. AnimatedWidget的狀態(tài)對象是AnimatedWidget內(nèi)部實現(xiàn)細節(jié)览绿,不應該暴露給外部策严。
  2. 如果要將父類狀態(tài)暴露給子類,那么必須得有一種傳遞機制饿敲,而做這一套傳遞機制是無意義的妻导,因為父子類之間狀態(tài)的傳遞和子類本身邏輯是無關(guān)的。

綜上所述诀蓉,可以發(fā)現(xiàn)栗竖,對于StatefulWidget,將build方法放在State中渠啤,可以給開發(fā)帶來很大的靈活性。

在Widget樹中獲取State對象

由于StatefulWidget的的具體邏輯都在其State中添吗,所以很多時候沥曹,我們需要獲取StatefulWidget對應的State對象來調(diào)用一些方法,比如Scaffold組件對應的狀態(tài)類ScaffoldState中就定義了打開SnackBar(路由頁底部提示條)的方法。我們有兩種方法在子widget樹中獲取父級StatefulWidget的State對象妓美。

通過Context獲取

context對象有一個ancestorStateOfType(TypeMatcher)方法僵腺,該方法可以從當前節(jié)點沿著widget樹向上查找指定類型的StatefulWidget對應的State對象。下面是實現(xiàn)打開SnackBar的示例:

Scaffold(
  appBar: AppBar(
    title: Text("子樹中獲取State對象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父級最近的Scaffold對應的ScaffoldState對象
          ScaffoldState _state = context.ancestorStateOfType(
              TypeMatcher<ScaffoldState>());
          //調(diào)用ScaffoldState的showSnackBar來彈出SnackBar
          _state.showSnackBar(
            SnackBar(
              content: Text("我是SnackBar"),
            ),
          );
        },
        child: Text("顯示SnackBar"),
      );
    }),
  ),
);

上面示例運行后壶栋,點擊”顯示SnackBar“辰如,效果如圖所示:

一般來說,如果StatefulWidget的狀態(tài)是私有的(不應該向外部暴露)贵试,那么我們代碼中就不應該去直接獲取其State對象琉兜;如果StatefulWidget的狀態(tài)是希望暴露出的(通常還有一些組件的操作方法),我們則可以去直接獲取其State對象毙玻。但是通過context.ancestorStateOfType獲取StatefulWidget的狀態(tài)的方法是通用的豌蟋,我們并不能在語法層面指定StatefulWidget的狀態(tài)是否私有,所以在Flutter開發(fā)中便有了一個默認的約定:如果StatefulWidget的狀態(tài)是希望暴露出的桑滩,應當在StatefulWidget中提供一個of靜態(tài)方法來獲取其State對象梧疲,開發(fā)者便可直接通過該方法來獲取运准;如果State不希望暴露幌氮,則不提供of方法。這個約定在Flutter SDK里隨處可見胁澳。所以该互,上面示例中的Scaffold也提供了一個of方法,我們其實是可以直接調(diào)用它的:

...//省略無關(guān)代碼
// 直接通過of靜態(tài)方法來獲取ScaffoldState 
ScaffoldState _state=Scaffold.of(context); 
_state.showSnackBar(
  SnackBar(
    content: Text("我是SnackBar"),
  ),
);

通過GlobalKey

Flutter還有一種通用的獲取State對象的方法——通過GlobalKey來獲忍蕖慢洋! 步驟分兩步:

  1. 給目標StatefulWidget添加GlobalKey

    //定義一個globalKey, 由于GlobalKey要保持全局唯一性陆盘,我們使用靜態(tài)變量存儲
    static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
    ...
    Scaffold(
        key: _globalKey , //設(shè)置key
        ...  
    )
    
    
  2. 通過GlobalKey來獲取State對象

    _globalKey.currentState.openDrawer()
    
    

GlobalKey是Flutter提供的一種在整個APP中引用element的機制普筹。如果一個widget設(shè)置了GlobalKey,那么我們便可以通過globalKey.currentWidget獲得該widget對象隘马、globalKey.currentElement來獲得widget對應的element對象太防,如果當前widget是StatefulWidget,則可以通過globalKey.currentState來獲得該widget對應的state對象酸员。

注意:使用GlobalKey開銷較大蜒车,如果有其他可選方案,應盡量避免使用它幔嗦。另外同一個GlobalKey在整個widget樹中必須是唯一的酿愧,不能重復。

Flutter SDK內(nèi)置組件庫介紹

Flutter提供了一套豐富邀泉、強大的基礎(chǔ)組件嬉挡,在基礎(chǔ)組件庫之上Flutter又提供了一套Material風格(Android默認的視覺風格)和一套Cupertino風格(iOS視覺風格)的組件庫钝鸽。要使用基礎(chǔ)組件庫,需要先導入:

import 'package:flutter/widgets.dart';

下面我們介紹一下常用的組件庞钢。

基礎(chǔ)組件

  • Text:該組件可讓您創(chuàng)建一個帶格式的文本拔恰。
  • RowColumn: 這些具有彈性空間的布局類Widget可讓您在水平(Row)和垂直(Column)方向上創(chuàng)建靈活的布局基括。其設(shè)計是基于Web開發(fā)中的Flexbox布局模型颜懊。
  • Stack: 取代線性布局 (譯者語:和Android中的FrameLayout相似),Stack允許子 widget 堆疊风皿, 你可以使用 Positioned 來定位他們相對于Stack的上下左右四條邊的位置河爹。Stacks是基于Web開發(fā)中的絕對定位(absolute positioning )布局模型設(shè)計的。
  • ContainerContainer 可讓您創(chuàng)建矩形視覺元素揪阶。container 可以裝飾一個BoxDecoration, 如 background昌抠、一個邊框、或者一個陰影鲁僚。 Container 也可以具有邊距(margins)炊苫、填充(padding)和應用于其大小的約束(constraints)。另外冰沙, Container可以使用矩陣在三維空間中對其進行變換侨艾。

Material組件

Flutter提供了一套豐富的Material組件,它可以幫助我們構(gòu)建遵循Material Design設(shè)計規(guī)范的應用程序拓挥。Material應用程序以MaterialApp 組件開始唠梨, 該組件在應用程序的根部創(chuàng)建了一些必要的組件,比如Theme組件侥啤,它用于配置應用的主題当叭。 是否使用MaterialApp完全是可選的,但是使用它是一個很好的做法盖灸。在之前的示例中蚁鳖,我們已經(jīng)使用過多個Material 組件了,如:Scaffold赁炎、AppBar醉箕、FlatButton等。要使用Material 組件徙垫,需要先引入它:

import 'package:flutter/material.dart';

Cupertino組件

Flutter也提供了一套豐富的Cupertino風格的組件讥裤,盡管目前還沒有Material 組件那么豐富,但是它仍在不斷的完善中姻报。值得一提的是在Material 組件庫中有一些組件可以根據(jù)實際運行平臺來切換表現(xiàn)風格己英,比如MaterialPageRoute,在路由切換時吴旋,如果是Android系統(tǒng)剧辐,它將會使用Android系統(tǒng)默認的頁面切換動畫(從底向上)寒亥;如果是iOS系統(tǒng),它會使用iOS系統(tǒng)默認的頁面切換動畫(從右向左)。由于在前面的示例中還沒有Cupertino組件的示例涂身,下面我們實現(xiàn)一個簡單的Cupertino組件風格的頁面:

//導入cupertino widget庫
import 'package:flutter/cupertino.dart';

class CupertinoTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text("Cupertino Demo"),
      ),
      child: Center(
        child: CupertinoButton(
            color: CupertinoColors.activeBlue,
            child: Text("Press"),
            onPressed: () {}
        ),
      ),
    );
  }
}

下面是在iPhoneX上頁面效果截圖:

總結(jié)

Flutter提供了豐富的組件威蕉,在實際的開發(fā)中你可以根據(jù)需要隨意使用它們,而不必擔心引入過多組件庫會讓你的應用安裝包變大引颈,這不是web開發(fā),dart在編譯時只會編譯你使用了的代碼。由于Material和Cupertino都是在基礎(chǔ)組件庫之上的同波,所以如果我們的應用中引入了這兩者之一,則不需要再引入flutter/widgets.dart了叠国,因為它們內(nèi)部已經(jīng)引入過了未檩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粟焊,隨后出現(xiàn)的幾起案子冤狡,更是在濱河造成了極大的恐慌,老刑警劉巖项棠,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悲雳,死亡現(xiàn)場離奇詭異,居然都是意外死亡香追,警方通過查閱死者的電腦和手機合瓢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來透典,“玉大人晴楔,你說我怎么就攤上這事∏椭洌” “怎么了税弃?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長讹语。 經(jīng)常有香客問我钙皮,道長,這世上最難降的妖魔是什么顽决? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任短条,我火速辦了婚禮,結(jié)果婚禮上才菠,老公的妹妹穿的比我還像新娘茸时。我一直安慰自己,他們只是感情好赋访,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布可都。 她就那樣靜靜地躺著缓待,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渠牲。 梳的紋絲不亂的頭發(fā)上旋炒,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音签杈,去河邊找鬼瘫镇。 笑死,一個胖子當著我的面吹牛答姥,可吹牛的內(nèi)容都是我干的铣除。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鹦付,長吁一口氣:“原來是場噩夢啊……” “哼尚粘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敲长,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤郎嫁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后潘明,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體行剂,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年钳降,在試婚紗的時候發(fā)現(xiàn)自己被綠了厚宰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡遂填,死狀恐怖铲觉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吓坚,我是刑警寧澤撵幽,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站礁击,受9級特大地震影響盐杂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哆窿,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一链烈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挚躯,春花似錦强衡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽感挥。三九已至,卻和暖如春越败,著一層夾襖步出監(jiān)牢的瞬間触幼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工眉尸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留域蜗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓噪猾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親筑累。 傳聞我的和親對象是個殘疾皇子袱蜡,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359