在之前的文章中,我們一起學習了建構視圖的基本元素,文本Text、圖檔Image和按鈕,用于展示一組連續視圖元素的ListView,以及處理多重嵌套的可滾動視圖的CustomScrollView,等等。
在Flutter中,一個完整的界面通常就是由這些小型、單用途的基本控件元素依據特定的布局規則堆砌而成的。那麼今天,我們就一起來了解下,在Flutter中,搭建出一個漂亮的布局,我們需要了解哪些布局規則,以及這些規則與其他平台類似概念的差别在哪裡。
我們已經知道,在Flutter中一切皆Widget,那麼布局也不例外。但與基本控件元素不同,布局類的Widget并不會直接呈現視覺内容,而是作為承載其他子Widget的容器。
這些布局類的Widget,内部都會包含一個或多個子控件,并且都提供了擺放子控件的不同布局方式,可以實作子控件的對齊、嵌套、層疊和縮放等。而我們要做的就是,通過各種定制化的參數,将其内部的子Widget按照自己的布局規則放置在特定的位置上,最終形成一個漂亮的布局。
Flutter提供了31種布局Widget,對布局控件的劃分非常詳細,一些相同(或相似)的視覺效果可以通過多種布局控件實作,是以布局類型相比原生iOS、Android平台多了不少。比如Android布局一般就隻有FrameLayout、LinearLayout、RelativeLayout、GridLayout和TableLayout這5種,而iOS的布局更少,隻有Frame布局和自動布局兩種。
今天,我着重介紹幾類在開發Flutter應用時,最常用也最具有代表性的布局Widget,包括單子Widget布局、多子Widget布局、層疊Widget布局。掌握這些典型的Widget,你就基本掌握了建構一個界面精美的APP所需要的全部布局方式了。接下來,我們就先從單子Widget聊起吧。
單子Widget布局:Container、Padding和Center
單子Widget布局類容器比較簡單,一般用來對其唯一的子Widget進行樣式包裝,比如限制大小、添加背景色樣式、内間距、旋轉變換等。這一類布局Widget,包括Container、Padding與Center三種。
Container,是一種允許在其内部添加其他控件的控件,也是UI架構中的一個常見概念。
在Flutter中,Container本身可以單獨作為控件存在(比如單獨設定背景色、寬高),也可以作為其他控件的父級存在:Container可以定義布局過程中子Widget如何擺放,以及如何展示。與其他架構不同的是,Flutter的Container僅能包含一個子Widget。
是以,對于多個子Widget的布局場景,我們通常會這樣處理:先用一個根Widget去包含這些子Widget,然後把這個根Widget放到Container中,再由Container設定它的對齊alignment、邊距padding等基礎屬性和樣式屬性。
接下來,我通過一個示例,與你示範如何定義一個Container。
在這個示例中,我将一段較長的文字,包裝在一個紅色背景、圓角邊框、固定寬高的Container中,并分别設定了Container的外邊距(距離其父Widget的邊距)和内邊距(距離其子Widget的邊距):
Container(
child: Text("Container(容器)在UI架構中是一個很常見的概念,Flutter也不例外!~~"),
width: 180,
height: 240,
margin: EdgeInsets.all(44),//外邊距
padding: EdgeInsets.all(18),//内邊距
alignment: Alignment.center,//子Widget居中對齊
decoration: BoxDecoration(
color: Colors.red,//背景色
borderRadius: BorderRadius.circular(10),//圓角邊框
),
);
複制

如果我們隻需要将子Widget設定間距,則可以使用另一個單子容器控件Padding進行内容填充:
Padding(
child: Text("Container(容器)在UI架構中是一個很常見的概念,Flutter也不例外!~~"),
padding: EdgeInsets.all(44),
);
複制
在需要設定内容間距時,我們可以通過EdgeInsets的不同構造函數,分别制定四個方向的不同補白方式,如均使用同樣數值留白(EdgeInsets.all),隻設定左留白(EdgeInsets.only)或對稱方向留白(EdgeInsets.symmetric)等。
接下來,我們再來看看單子Widget布局容器中另一個常用的容器Center。正如它的名字一樣,Center會将對其子Widget居中排列。
比如,我可以把一個Text包在Center裡,實作居中展示:
Center(
child: Text("Center"),
);
複制
需要注意的是,為了實作居中布局,Center所占據的空間一定要比其子Widget要大才行,這也是顯而易見的:如果Center要和其子Widget一樣大,自然就不需要居中,也沒空間居中了。是以Center通常會結合Container一起使用。
現在,我們結合Container,一起看看Center的具體使用方法吧。
Container(
child: Center(
child: Text("Container(容器)在UI架構中是一個很常見的概念,Flutter也不例外!~~~"),
),
height: 240,
width: 180,
padding: EdgeInsets.all(18),
margin: EdgeInsets.all(44),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
);
複制
可以看到,我們通過Center容器實作了Container容器中 alignment: Alignment.center 的效果。
事實上,為了達到這一效果,Container容器與Center容器底層都依賴了同一個容器Align,通過它實作子Widget的對齊方式。
接下來,我們再來看看多子Widget布局的三種方式,即Row、Column與Expanded。
多子Widget布局:Row、Column和Expanded
對于擁有多個子Widget的布局類容器而言,其布局行為無非就是兩種規則的抽象:水準方向上應該如何布局、垂直方向上應該如何布局。
如同Android的LinearLayout、前端的Flex布局一樣,Flutter中也有類似的概念,即将子Widget按行水準排列的Row,按列垂直排列的Column,以及負責配置設定這些子Widget在布局方向中剩餘空間的Expanded。
Row與Column的使用方法很簡單,我們隻需要将各個子Widget按序加入到Children數組即可。在下面的代碼中,我把四個分别設定了不同顔色和寬高的Container加到Row與Column中:
//Row的用法示範
Row(
children: <Widget>[
Container(color: Colors.red, height: 80, width: 60),
Container(color: Colors.yellow, height: 80, width: 60),
Container(color: Colors.blue, height: 180, width: 100),
Container(color: Colors.green, height: 80, width: 60),
],
);
//Column的用法示範
Column(
children: <Widget>[
Container(color: Colors.red, height: 80, width: 60),
Container(color: Colors.yellow, height: 80, width: 60),
Container(color: Colors.blue, height: 180, width: 100),
Container(color: Colors.green, height: 80, width: 60),
],
);
複制
Row的顯示效果如下:
Column的顯示效果如下:
可以看到,單純使用Row和Column控件,在子Widget的尺寸較小時,無法将容器填滿,視覺樣式比較難看。對于這樣的場景,我們可以通過Expanded控件,來制定配置設定規則填滿容器的剩餘空間。
比如,我們希望Row元件(或者Column元件)中的綠色容器與黃色容器均分剩下的空間,于是就可以設定他們的彈性系數參數flex都為1,這兩個Expanded會按照其flex的比例(即1:1)來分割剩餘的Row橫向(Column縱向)空間:
Row(
children: <Widget>[
Expanded(flex: 1, child: Container(color: Colors.red, height: 80, width: 60)),
Container(color: Colors.yellow, height: 80, width: 60),
Container(color: Colors.blue, height: 180, width: 100),
Expanded(flex: 1, child: Container(color: Colors.green, height: 80, width: 60)),
],
);
複制
于Row和Column而言,Flutter提供了依據坐标軸的布局對齊行為,即根據布局方向劃分出主軸和交叉軸:主軸,表示容器依次擺放子Widget的方向;交叉軸,則是與主軸垂直的另一個方向。
比如,Row的主軸是橫向,交叉軸是縱向;Column的主軸是縱向,交叉軸是橫向。
我們可以根據主軸和交叉軸,設定子Widget在這兩個方向上的對齊規則mainAxisAlignment與crossAxisAlignment。比如,對于Row而言,主軸方向start表示靠左對齊、center表示橫向居中對齊,end表示靠右對齊,spaceEvenly表示按固定間距對齊;而交叉軸方向start則表示靠上對齊,center表示縱向居中對齊,end表示靠下對齊。
下圖展示了在Row中設定不同方向的對齊規則後的呈現效果:
Row的主軸對齊方式
Row的縱軸對齊方式:
Column的對齊方式也是類似的,這裡不做過多展開。
需要注意的是,對于主軸而言,Flutter預設是讓父容器決定其長度,即盡可能大。
在上例中,Row的寬度為螢幕寬度,Column的高度為螢幕高度。主軸長度大于所有子Widget的總長度,意味着容器在主軸方向的空間比子Widget要大,這也是我們能通過主軸對齊方式設定子Widget布局效果的原因。
如果想讓容器與子Widget在主軸上完全比對,我們可以通過設定Row的mainAxisSize參數為MainAxisSize.min,由所有子Widget來決定主軸方向的容器長度,即主軸方向的長度盡可能小。
Row(
mainAxisSize: MainAxisSize.min,//讓容器寬度與所有子Widget的寬度總和一緻
mainAxisAlignment: MainAxisAlignment.spaceEvenly,//由于容器與子Widget一樣寬,是以這行設定排列間距的代碼并未起作用
children: <Widget>[
Container(color: Colors.red, height: 80, width: 60),
Container(color: Colors.yellow, height: 80, width: 60),
Container(color: Colors.blue, height: 180, width: 100),
Container(color: Colors.green, height: 80, width: 60)
],
);
複制
可以看到,我們設定了主軸大小為MainAxisSize.min之後,Row的寬度變得和其子Widget一樣大,是以再設定主軸的對齊方式也就不起作用了。
層疊Widget布局:Stack與Positioned
有些時候,我們需要讓一個控件疊加在另一個控件的上面,比如在一張圖檔上放置一段文字,又或是在圖檔的某個區域放置一個按鈕。這時候,我們就需要用到層疊布局容器Stack了。
Stack容器與前端中的絕對定位、iOS中的Frame布局非常類似,子Widget之間允許疊加,還可以根據父容器上下左右四個角的位置來确定自己的位置。
Stack提供了層疊布局的容器,而Positioned則提供了設定子Widget位置的能力。接下來,我們通過一個例子來看一下Stack和Position的用法吧。
在這個例子中,我先在Stack中放置了一塊300x300的黃色畫布,随後在(18,18)處放置了一個50x50的綠色控件,然後在(18,70)處放置了一個文本控件。
Stack(
children: <Widget>[
Container(
height: 300,
width: 300,
color: Colors.yellow,
),
Positioned(
left: 18,
top: 18,
child: Container(height: 50, width: 50, color: Colors.green),
),
Positioned(
left: 18,
top: 70,
child: Text("文本控件"),
)
],
);
複制
運作一下,可以看到,這三個子Widget都按照我們預定的規則疊加在一起了。
Stack控件允許其子Widget按照建立的先後順序進行層疊擺放,而Position控件則用來控制這些子Widget的擺放位置。需要注意的是,Positioned控件隻能在Stack中使用,在其他容器中使用會報錯。
總結
Flutter的布局容器強大而豐富,可以将小型、單用途的基本視覺元素快速封裝成控件。
單子容器包括Container、Padding和Center。其中,Container内部提供了間距、背景樣式等基礎屬性,為子Widget的擺放方式,及展現樣式都提供了定制能力。而Padding與Center提供的功能,則正如其名一樣簡潔,就是對齊與居中。
多子Widget布局有Row和Column,使用Expanded控件使用容器内部的剩餘空間。
層疊布局Stack,以及與之搭配使用的,定位子Widget位置的Positioned容器,通過它們,實作多控件堆放的布局效果。
以上