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)將不再具有私密性,這就會導(dǎo)致對狀態(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)細(xì)節(jié),不應(yīng)該暴露給外部。
  2. 如果要將父類狀態(tài)暴露給子類,那么必須得有一種傳遞機制,而做這一套傳遞機制是無意義的,因為父子類之間狀態(tài)的傳遞和子類本身邏輯是無關(guān)的。

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

在Widget樹中獲取State對象

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

通過Context獲取

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

Scaffold(
  appBar: AppBar(
    title: Text("子樹中獲取State對象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父級最近的Scaffold對應(yīng)的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)是私有的(不應(yīng)該向外部暴露),那么我們代碼中就不應(yīng)該去直接獲取其State對象;如果StatefulWidget的狀態(tài)是希望暴露出的(通常還有一些組件的操作方法),我們則可以去直接獲取其State對象。但是通過context.ancestorStateOfType獲取StatefulWidget的狀態(tài)的方法是通用的,我們并不能在語法層面指定StatefulWidget的狀態(tài)是否私有,所以在Flutter開發(fā)中便有了一個默認(rèn)的約定:如果StatefulWidget的狀態(tài)是希望暴露出的,應(yīng)當(dāng)在StatefulWidget中提供一個of靜態(tài)方法來獲取其State對象,開發(fā)者便可直接通過該方法來獲?。蝗绻鸖tate不希望暴露,則不提供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. 給目標(biāo)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對應(yīng)的element對象,如果當(dāng)前widget是StatefulWidget,則可以通過globalKey.currentState來獲得該widget對應(yīng)的state對象。

注意:使用GlobalKey開銷較大,如果有其他可選方案,應(yīng)盡量避免使用它。另外同一個GlobalKey在整個widget樹中必須是唯一的,不能重復(fù)。

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

Flutter提供了一套豐富、強大的基礎(chǔ)組件,在基礎(chǔ)組件庫之上Flutter又提供了一套Material風(fēng)格(Android默認(rèn)的視覺風(fēng)格)和一套Cupertino風(fēng)格(iOS視覺風(fēng)格)的組件庫。要使用基礎(chǔ)組件庫,需要先導(dǎo)入:

import 'package:flutter/widgets.dart';

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

基礎(chǔ)組件

  • Text:該組件可讓您創(chuàng)建一個帶格式的文本。
  • Row、 Column: 這些具有彈性空間的布局類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)和應(yīng)用于其大小的約束(constraints)。另外, Container可以使用矩陣在三維空間中對其進行變換。

Material組件

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

import 'package:flutter/material.dart';

Cupertino組件

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

//導(dǎo)入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ù)需要隨意使用它們,而不必?fù)?dān)心引入過多組件庫會讓你的應(yīng)用安裝包變大,這不是web開發(fā),dart在編譯時只會編譯你使用了的代碼。由于Material和Cupertino都是在基礎(chǔ)組件庫之上的,所以如果我們的應(yīng)用中引入了這兩者之一,則不需要再引入flutter/widgets.dart了,因為它們內(nèi)部已經(jīng)引入過了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容