轉(zhuǎn)自 Q吹個大氣球Q
本文主要介紹了Flutter布局相關(guān)的內(nèi)容,對相關(guān)知識點進行了梳理牵敷,并從實際例子觸發(fā)胡岔,進一步講解該如何去進行布局。
1. 簡介
在介紹Flutter布局之前枷餐,我們得先了解Flutter中的一些布局相關(guān)的特性靶瘸。
1.1 邊界約束(box constraints)
box constraints有人也翻譯為盒約束、箱約束毛肋,我個人還是覺得邊界約束可能更直觀一些怨咪。
Flutter中的邊界約束,是指widget可以按照指定限定條件润匙,來決定自身如何占用布局空間诗眨。Flutter借鑒了很多React相關(guān)的東西,包括一些布局思想趁桃,但是它自身沒有抽離出布局樣式辽话,而是用不同的widget去實現(xiàn)不同的布局,將樣式嵌入widget中卫病,用戶可以像搭積木一樣寫布局油啤,寫法上跟React很像,只不過沒了樣式的設(shè)定蟀苛。
這樣做的好處益咬,我覺得可能是為了統(tǒng)一的渲染。加入樣式帜平,會讓布局復(fù)雜不少幽告,在渲染層面會降低很多性能梅鹦。因此,F(xiàn)lutter在大的方向上冗锁,加入不同類型的布局widget齐唆。在小的方向上,只給出很少的定制化的東西冻河,將布局限定在有限的范圍內(nèi)箍邮,在完成布局的同時,讓整個渲染能夠統(tǒng)一叨叙,加快了更新和渲染锭弊。
但是,缺點也是同樣明顯擂错,少了很多靈活性味滞,不同的布局方式都被抽離出了widget,大家需要了解的widget非常多钮呀,增加了學(xué)習(xí)成本剑鞍。
1.2 約束種類
在Flutter中,widget是由其底層的RenderBox渲染行楞,渲染邊界的約束(Constraints)由父級給出攒暇,widget在這些約束下調(diào)整自身尺寸。約束包括最小最大寬高子房,尺寸則是具體的寬高形用。
在Android中,布局的寬高限定有三種证杭,match_parent田度、wrap_content以及具體尺寸。在Flutter中解愤,也有這三種約束镇饺。
- 盡可能大的約束,例如Center送讲、ListView等奸笤;
- 跟隨內(nèi)容大小的約束,例如Transform哼鬓、Opacity等监右;
- 指定尺寸的約束,例如Image异希、Text等健盒;
但是,F(xiàn)lutter并沒有把widget處理的這么絕對,這些約束條件包含在widget里扣癣,不像Android可以在外面去指定惰帽。因此,一些widget父虑,例如Container该酗,會根據(jù)參數(shù)的不同,約束條件也不同士嚎。Container默認是盡可能大的垂涯,但是給定尺寸的話,就會優(yōu)先使用具體值航邢。不同的widget可能設(shè)置條件不同、其子widget不同骄蝇,約束條件也會不一樣膳殷。Flutter將每種widget限制在不同的約束范圍里,實際布局的時候九火,還需要綜合去考慮赚窃。
2. 分類
按照約束條件來分類,很多widget不太好區(qū)分開來岔激,官方也是根據(jù)其子元素的個數(shù)來分類勒极。
- 單個子元素(child)的布局,包括Container虑鼎、Padding等
18
種(目前是2018年5月25日辱匿,后續(xù)我想肯定會增加的,下面類似)炫彩; - 多個子元素(children)的布局匾七,包括Row、Column等
11
種江兢; - layout helper昨忆,例如ListView.Builder,在元素多的時候杉允,用這種方式更加的高效邑贴,類似Android的RecyclerView,有自動的回收機制叔磷。這種嚴格意義上不能算是一個種類拢驾,我覺得這種helper會越來越多。
2.1 優(yōu)缺點
其中日常中用的多的世澜,例如Container独旷、Padding、Center、Align嵌洼、Row案疲、Column、Stack麻养、ListView等也有上十種褐啡。
Flutter提供接近30多種不同的布局widget,還是源于其對widget的定位(在此處不再闡述鳖昌,想了解的备畦,可以翻看筆者之前文章的介紹)。對比其他移動端的開發(fā)平臺许昨,可以看出Flutter的布局widget是巨多懂盐,這也是為什么Flutter現(xiàn)在學(xué)習(xí)曲線很長的一個原因。
先來說下優(yōu)點糕档,統(tǒng)一渲染莉恼,更新效率更高。但是速那,對于普通開發(fā)者而言俐银,是不會去太在乎這些的。性能高本來就是平臺應(yīng)該提供的最基本的能力端仰,難道不是你應(yīng)該提供的嗎捶惜?
然后說下缺點吧,掌握起來還是非常費事的荔烧,布局起來也是挺蛋疼的吱七。常規(guī)的布局還好,一到復(fù)雜的布局鹤竭,覺得這個也能實現(xiàn)陪捷,那個也能實現(xiàn),最后不知道哪個可以實現(xiàn)诺擅。
2.2 個人看法
Flutter對于性能的優(yōu)化市袖,把平臺側(cè)的一些成本轉(zhuǎn)接到開發(fā)者身上,不過呢烁涌,現(xiàn)在也是Flutter的初期苍碟,可以看出,整體的設(shè)計思路還是非常好的撮执,也只有谷歌這種輪子大廠才敢這么干微峰。但是,很明顯少了些人為關(guān)懷抒钱,不同widget間約束條件穿插著蜓肆,也可以說Flutter布局控件種類的增加颜凯,是其不斷的打補丁導(dǎo)致的,后續(xù)的各種helper仗扬,也是為了填坑症概,這一塊兒Flutter顯然沒有處理的很好。
但是早芭,凡事都有個過程彼城,不能說Flutter這些地方做的不好,只是目前看起來比較混亂退个,理想的架構(gòu)設(shè)計募壕,落地下來,可能就不是那么簡單语盈,開發(fā)者的需求千差萬別舱馅,有了生態(tài),什么都好說刀荒,當然這個過程习柠,預(yù)計是會非常的緩慢。
3. widget詳解
在Flutter中照棋,我們平時自定義的widget,一般都是繼承自StatefulWidget或StatelessWidget(并不是只有這兩種)武翎,這兩種widget也是目前最常用的兩種烈炭。如果一個控件自身狀態(tài)不會去改變,創(chuàng)建了就直接顯示宝恶,不會有色值符隙、大小或者其他屬性的變化,這種widget一般都是繼承自StatelessWidget垫毙,常見的有Container霹疫、ScrollView等。如果一個控件需要動態(tài)的去改變或者相應(yīng)一些狀態(tài)综芥,例如點擊態(tài)丽蝎、色值、內(nèi)容區(qū)域等膀藐,那么一般都是繼承自StatefulWidget屠阻,常見的有CheckBox、AppBar额各、TabBar等国觉。其實單純的從名字也可以看出這兩種widget的區(qū)別,這兩種widget都是繼承自Widget類虾啦。
3.1 Widget類
Widget類在Flutter中是非常重要的麻诀,繼承自Widget類的有PreferredSizeWidget痕寓、ProxyWidget、RenderObjectWidget蝇闭、StatefulWidget呻率、StatelessWidget。我們?nèi)粘J褂玫慕^大部分widget都是繼承自Widget類丁眼,
查看Widget類源碼筷凤,內(nèi)部實現(xiàn)非常簡單,構(gòu)造函數(shù)如下
const Widget({ this.key });
final Key key;
這個key的作用苞七,注視上寫的很清楚藐守,是用來控制在widget樹中替換widget的時候使用的。其中Key類是Widget蹂风、Element以及SemanticsNode的唯一標識符卢厂,繼承自Key的還有LocalKey以及GlobalKey。
3.2 State
在說到StatefulWidget之前惠啄,先說下State慎恒。State的作用有兩點:
- 在widget構(gòu)建的時候可以被同步讀取撵渡;
- 在widget的生命周期中可能會被改變融柬。
3.2.1 State生命周期
State的生命周期有四種狀態(tài):
- created:當State對象被創(chuàng)建時候,State.initState方法會被調(diào)用趋距;
- initialized:當State對象被創(chuàng)建粒氧,但還沒有準備構(gòu)建時,State.didChangeDependencies在這個時候會被調(diào)用节腐;
- ready:State對象已經(jīng)準備好了構(gòu)建外盯,State.dispose沒有被調(diào)用的時候;
- defunct:State.dispose被調(diào)用后翼雀,State對象不能夠被構(gòu)建饱苟。
完整生命周期如下:
- 創(chuàng)建一個State對象時,會調(diào)用StatefulWidget.createState狼渊;
- 和一個BuildContext相關(guān)聯(lián)箱熬,可以認為被加載了(mounted);
- 調(diào)用initState狈邑;
- 調(diào)用didChangeDependencies坦弟;
- 經(jīng)過上述步驟,State對象被完全的初始化了官地,調(diào)用build酿傍;
- 如果有需要,會調(diào)用didUpdateWidget驱入;
- 如果處在開發(fā)模式赤炒,熱加載會調(diào)用reassemble氯析;
- 如果它的子樹(subtree)包含需要被移除的State對象,會調(diào)用deactivate莺褒;
- 調(diào)用dispose,State對象以后都不會被構(gòu)建掩缓;
- 當調(diào)用了dispose,State對象處于未加載(unmounted),已經(jīng)被dispose的State對象沒有辦法被重新加載(remount)遵岩。
3.2.2 setState
State中比較重要的一個方法是setState
你辣,當修改狀態(tài)時,widget會被更新尘执。比方說點擊CheckBox舍哄,會出現(xiàn)選中和非選中狀態(tài)之間的切換,就是通過修改狀態(tài)來達到的誊锭。
查看setState源碼表悬,在一些異常的情況下將會拋出異常:
- 傳入的為null;
- 處在defunct階段丧靡;
- created階段還沒有被加載(mounted)蟆沫;
- 參數(shù)返回一個Future對象。
檢查完一系列異常后温治,最后調(diào)用代碼如下:
_element.markNeedsBuild();
markNeedsBuild內(nèi)部饭庞,則是通過標記element為dirty,在下一幀的時候重建(rebuild)熬荆≈凵剑可以看出setState并不是立即生效,它只是將widget進行了標記惶看,真正的rebuild操作,則是等到下一幀的時候才會去進行六孵。
3.3 StatefulWidget和StatelessWidget
StatefulWidget和StatelessWidget如下所示
一個StatelessWidget可以用多個不同的BuildContext構(gòu)建纬黎,而一個StatefulWidget會為每個BuildContext創(chuàng)建一個State對象。
3.3.1 StatelessWidget
對于StatelessWidget劫窒,build方法會在如下三種情況下調(diào)用本今,
- widget第一次被插入到樹中;
- widget的父節(jié)點更改了配置(configuration)主巍;
- widget依賴的InheritedWidget改變了冠息。
class GreenFrog extends StatelessWidget {
const GreenFrog({ Key key }) : super(key: key);
@override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFF2DBD3A));
}
}
3.3.2 StatefulWidget
StatefulWidget的兩個主要類別:
- 在initState中創(chuàng)建資源,在dispose中銷毀孕索,但是不依賴于InheritedWidget或者調(diào)用setState方法逛艰,這類widget基本上用在一個應(yīng)用或者頁面的root;
- 使用setState或者依賴于InheritedWidget搞旭,這種在營業(yè)生命周期中會被重建(rebuild)很多次散怖。
class YellowBird extends StatefulWidget {
const YellowBird({ Key key }) : super(key: key);
@override
_YellowBirdState createState() => new _YellowBirdState();
}
class _YellowBirdState extends State<YellowBird> {
@override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFFFFE306));
}
}
4. 如何布局
每個頁面設(shè)計都不一樣菇绵,相同頁面可選擇的布局方式也不一樣,如果單純的說應(yīng)該如何去布局镇眷,我覺得不現(xiàn)實咬最,大家可以參考下Flutter官方的布局教程。接下來欠动,筆者永乌,通過一個簡單的頁面,來一步一步的拆解布局的流程具伍。整個過程翅雏,基本上按照拆解、組件封裝沿猜、具體布局這三步來的枚荣。
4.1 拆解
4.1.1 整體拆解
根據(jù)設(shè)計圖,可以看出整體時分行展示的啼肩,因此最外層是一個Column元素
- 第一行為標題橄妆,涉及到不對稱的布局,可以用一個Stack或者Row來進行祈坠,用Row的話害碾,則需要右邊填上一個空白的widget占位。也可能會使用AppBar赦拘,將底部陰影去掉也能實現(xiàn)相同效果慌随;
- 第二行可以看作一個Row,分兩塊布局躺同。右邊部分阁猜,涉及到疊加,會考慮Stack蹋艺;
- 第三行比較復(fù)雜剃袍,整體看,也是一行一行進行展示的捎谨,因此最外層時一個Column民效。中間的文本部分需要根據(jù)個數(shù)自動換行,因此考慮使用Wrap涛救。預(yù)習(xí)這個地方涉及到疊加畏邢,考慮Stack實現(xiàn);
- 第四行可以看作一個Row检吆,分三塊進行布局舒萎;
- 第五行可以看作一個Row,分兩塊布局蹭沛。
每一行之間的間隔逆甜,則可以考慮用Padding或者Container來設(shè)置虱肄。
通過上面這樣一步一步的分析后,基本上對大致的布局有了一個了解交煞,最外層的控件大致選對(只要能實現(xiàn)的話咏窿,就是復(fù)雜度以及效率的問題),然后一步一步的拆解每一行的元素素征,如果有重復(fù)的或者覺得可以封裝出來的部分集嵌,則進行下一步。
4.1.2 局部拆解
每一行的拆解御毅,大致也是按照這個思路來進行根欧,因此筆者在這里就不做講解了。
4.2 組件封裝
例如上面端蛆,筆者想對第四行的這種展示進行封裝凤粗,覺得今后的布局可能會用到,因此在這一步今豆,可以先把這一塊兒抽離出一個控件嫌拣。利用Row的mainAxisAlignment以及Expanded來實現(xiàn)這種效果,具體的實現(xiàn)筆者不再詳細的描述了呆躲。
經(jīng)過這一步异逐,整體的規(guī)劃設(shè)計圖已經(jīng)有了,各個組件也都有了插掂,接下來的工作就是組裝了灰瞻。
4.3 具體布局
具體布局設(shè)計到一些細節(jié)的地方,例如間隔(Padding或者Container)辅甥、居左居右居中(Align)酝润、點擊事件(GestureDetector)以及圓角(ClipRRect)等一些特殊情況,基本上就是嵌套璃弄,一層一層去實現(xiàn)要销。
在實際布局中,筆者實際使用的是Scaffold谢揪,頂部的AppBar將陰影直接去掉即可實現(xiàn)效果蕉陋,body部分則實現(xiàn)2-5行的內(nèi)容捐凭。最外層套一個Column也能實現(xiàn)拨扶,本質(zhì)上都沒什么區(qū)別,運行效果圖如下所示茁肠。
4.4 代碼
5. 后話
筆者建了一個flutter學(xué)習(xí)相關(guān)的項目患民,github地址,里面包含了筆者寫的關(guān)于flutter學(xué)習(xí)相關(guān)的一些文章垦梆,會定期更新匹颤,也會上傳一些學(xué)習(xí)demo仅孩,歡迎大家關(guān)注。