為什么要將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) } }
這樣很顯然是不合理的,因為
-
AnimatedWidget
的狀態(tài)對象是AnimatedWidget
內(nèi)部實現(xiàn)細節(jié)览绿,不應該暴露給外部策严。 - 如果要將父類狀態(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來獲忍蕖慢洋! 步驟分兩步:
-
給目標
StatefulWidget
添加GlobalKey
。//定義一個globalKey, 由于GlobalKey要保持全局唯一性陆盘,我們使用靜態(tài)變量存儲 static GlobalKey<ScaffoldState> _globalKey= GlobalKey(); ... Scaffold( key: _globalKey , //設(shè)置key ... )
-
通過
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)建一個帶格式的文本拔恰。 -
Row
、Column
: 這些具有彈性空間的布局類Widget可讓您在水平(Row)和垂直(Column)方向上創(chuàng)建靈活的布局基括。其設(shè)計是基于Web開發(fā)中的Flexbox布局模型颜懊。 -
Stack
: 取代線性布局 (譯者語:和Android中的FrameLayout
相似),Stack
允許子 widget 堆疊风皿, 你可以使用Positioned
來定位他們相對于Stack
的上下左右四條邊的位置河爹。Stacks是基于Web開發(fā)中的絕對定位(absolute positioning )布局模型設(shè)計的。 -
Container
:Container
可讓您創(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)引入過了未檩。