Flutter 布局詳解

本文主要介紹了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的作用有兩點:

  1. 在widget構(gòu)建的時候可以被同步讀取失尖;
  2. 在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)建择诈。
State LifeCycle

完整生命周期如下:

  • 創(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為diry胶背,在下一幀的時候重建(rebuild)〈龋可以看出setState并不是立即生效钳吟,它只是將widget進行了標記,真正的rebuild操作窘拯,則是等到下一幀的時候才會去進行红且。

3.3 StatefulWidget和StatelessWidget

StatefulWidget和StatelessWidget如下所示

StatefulWidget和StatelessWidget

一個StatelessWidget可以用多個不同的BuildContext構(gòu)建,而一個StatefulWidget會為每個BuildContext創(chuàng)建一個State對象涤姊。

3.3.1 StatelessWidget

對于StatelessWidget直焙,build方法會在如下三種情況下調(diào)用,

  1. widget第一次被插入到樹中砂轻;
  2. widget的父節(jié)點更改了配置(configuration);
  3. 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的兩個主要類別:

  1. 在initState中創(chuàng)建資源搔涝,在dispose中銷毀,但是不依賴于InheritedWidget或者調(diào)用setState方法和措,這類widget基本上用在一個應(yīng)用或者頁面的root庄呈;
  2. 使用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 代碼

代碼Github地址

5. 后話

筆者建了一個flutter學(xué)習(xí)相關(guān)的項目,github地址决帖,里面包含了筆者寫的關(guān)于flutter學(xué)習(xí)相關(guān)的一些文章,會定期更新蓖捶,也會上傳一些學(xué)習(xí)demo地回,歡迎大家關(guān)注。

6. 參考

  1. Layout Widgets
  2. Dealing with box constraints in Flutter
  3. Flutter樣式和布局控件簡析(一)
  4. widgets library
  5. 在Flutter中構(gòu)建布局
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊鱼,一起剝皮案震驚了整個濱河市刻像,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌并闲,老刑警劉巖细睡,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帝火,居然都是意外死亡溜徙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門犀填,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠢壹,“玉大人,你說我怎么就攤上這事九巡⊥济常” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疏日。 經(jīng)常有香客問我偿洁,道長,這世上最難降的妖魔是什么沟优? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任涕滋,我火速辦了婚禮,結(jié)果婚禮上净神,老公的妹妹穿的比我還像新娘何吝。我一直安慰自己,他們只是感情好鹃唯,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布爱榕。 她就那樣靜靜地躺著,像睡著了一般坡慌。 火紅的嫁衣襯著肌膚如雪黔酥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天洪橘,我揣著相機與錄音跪者,去河邊找鬼。 笑死熄求,一個胖子當著我的面吹牛渣玲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弟晚,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼忘衍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卿城?” 一聲冷哼從身側(cè)響起枚钓,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瑟押,沒想到半個月后搀捷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡多望,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年嫩舟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀偷。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡至壤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枢纠,到底是詐尸還是另有隱情像街,我是刑警寧澤黎棠,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站镰绎,受9級特大地震影響脓斩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜畴栖,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一随静、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吗讶,春花似錦燎猛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膜毁,卻和暖如春昭卓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘟滨。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工候醒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杂瘸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓倒淫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親败玉。 傳聞我的和親對象是個殘疾皇子敌土,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容