Flutter 四 之Flutter的佈局Widget
Flutter將佈局組件分成了 單子佈局組件(Single-child layout widgets) 和 多子佈局組件(Multi-child layout widgets)
一. 單子佈局組件
單子佈局組件的含義是其只有一個子組件,可以通過設置一些屬性設置該子組件所在的位置信息等。
比較常用的單子佈局組件有:Align、Center、Padding、Container。
1.1. Align組件
1.1.1. Align介紹
看到
Align
這個詞,我們就知道它有我們的對齊方式有關。
在其他端的開發中(iOS、Android、前端)Align通常只是一個屬性而已,但是Flutter中Align也是一個組件。
我們可以通過源碼來看一下Align有哪些屬性:
const Align({
Key key,
this.alignment: Alignment.center, // 對齊方式,默認居中對齊
this.widthFactor, // 寬度因子,不設置的情況,會儘可能大
this.heightFactor, // 高度因子,不設置的情況,會儘可能大
Widget child // 要佈局的子Widget
})
這裏我們特別解釋一下widthFactor
和heightFactor
作用:
- 因爲子組件在父組件中的對齊方式必須有一個前提,就是父組件得知道自己的範圍(寬度和高度);
- 如果
widthFactor
和heightFactor
不設置,那麼默認Align會儘可能的大(儘可能佔據自己所在的父組件); - 我們也可以對他們進行設置,比如widthFactor設置爲3,那麼相對於Align的寬度是子組件跨度的3倍;
1.1.2. Align演練
我們簡單演練一下Align
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Align(
child: Icon(Icons.pets, size: 36, color: Colors.red),
alignment: Alignment.bottomRight,
widthFactor: 3,
heightFactor: 3,
);
}
}
1.2. Center組件
1.2.1. Center介紹
Center組件我們在前面已經用過很多次了。
事實上Center組件繼承自Align,只是將alignment設置爲Alignment.center。
源碼分析:
class Center extends Align {
const Center({
Key key,
double widthFactor,
double heightFactor,
Widget child
}) : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
1.2.2. Center演練
我們將上面的代碼Align換成Center
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Icon(Icons.pets, size: 36, color: Colors.red),
widthFactor: 3,
heightFactor: 3,
);
}
}
1.3. Padding組件
1.3.1. Padding介紹
Padding組件在其他端也是一個屬性而已,但是在Flutter中是一個Widget,但是Flutter中沒有Margin這樣一個Widget,這是因爲外邊距也可以通過Padding來完成。
Padding通常用於設置子Widget到父Widget的邊距(你可以稱之爲是父組件的內邊距或子Widget的外邊距)。
源碼分析:
const Padding({
Key key,
@requiredthis.padding, // EdgeInsetsGeometry類型(抽象類),使用EdgeInsets
Widget child,
})
1.3.2. Padding演練
代碼演練:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(20),
child: Text(
"莫聽穿林打葉聲,何妨吟嘯且徐行。竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
style: TextStyle(
color: Colors.redAccent,
fontSize: 18
),
),
);
}
}
1.4. Container組件
Container組件類似於其他Android中的View,iOS中的UIView。
如果你需要一個視圖,有一個背景顏色、圖像、有固定的尺寸、需要一個邊框、圓角等效果,那麼就可以使用Container組件。
1.4.1. Container介紹
Container在開發中被使用的頻率是非常高的,特別是我們經常會將其作爲容器組件。
下面我們來看一下Container有哪些屬性:
Container({
this.alignment,
this.padding, //容器內補白,屬於decoration的裝飾範圍
Color color, // 背景色
Decoration decoration, // 背景裝飾
Decoration foregroundDecoration, //前景裝飾
double width,//容器的寬度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制條件
this.margin,//容器外補白,不屬於decoration的裝飾範圍
this.transform, //變換
this.child,
})
大多數屬性在介紹其它容器時都已經介紹過了,不再贅述,但有兩點需要說明:
- 容器的大小可以通過
width
、height
屬性來指定,也可以通過constraints
來指定,如果同時存在時,width
、height
優先。實際上Container內部會根據width
、height
來生成一個constraints
; color
和decoration
是互斥的,實際上,當指定color時,Container內會自動創建一個decoration;decoration
屬性稍後我們詳細學習;
1.4.2. Container演練
簡單進行一個演示:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Color.fromRGBO(3, 3, 255, .5),
width: 100,
height: 100,
child: Icon(Icons.pets, size: 32, color: Colors.white),
),
);
}
}
1.4.3. BoxDecoration
Container有一個非常重要的屬性 decoration
:
- 他對應的類型是Decoration類型,但是它是一個抽象類。
- 在開發中,我們經常使用它的實現類BoxDecoration來進行實例化。
BoxDecoration常見屬性:
const BoxDecoration({
this.color, // 顏色,會和Container中的color屬性衝突
this.image, // 背景圖片
this.border, // 邊框,對應類型是Border類型,裏面每一個邊框使用BorderSide
this.borderRadius, // 圓角效果
this.boxShadow, // 陰影效果
this.gradient, // 漸變效果
this.backgroundBlendMode, // 背景混合
this.shape = BoxShape.rectangle, // 形變
})
部分效果演示:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
// color: Color.fromRGBO(3, 3, 255, .5),
width: 150,
height: 150,
child: Icon(Icons.pets, size: 32, color: Colors.white),
decoration: BoxDecoration(
color: Colors.amber, // 背景顏色
border: Border.all(
color: Colors.redAccent,
width: 3,
style: BorderStyle.solid
), // 這裏也可以使用Border.all統一設置
// top: BorderSide(
// color: Colors.redAccent,
// width: 3,
// style: BorderStyle.solid
// ),
borderRadius: BorderRadius.circular(20), // 這裏也可以使用.only分別設置
boxShadow: [
BoxShadow(
offset: Offset(5, 5),
color: Colors.purple,
blurRadius: 5
)
],
// shape: BoxShape.circle, // 會和borderRadius衝突
gradient: LinearGradient(
colors: [
Colors.green,
Colors.red
]
)
),
),
);
}
}
1.4.4. 實現圓角圖像
上一個章節我們提到可以通過 Container+BoxDecoration
來實現圓角圖像。
實現代碼如下:
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
)
),
),
);
}
}
二. 多子佈局組件
在開發中,我們經常需要將多個Widget放在一起進行佈局,比如水平方向、垂直方向排列,甚至有時候需要他們進行層疊,比如圖片上面放一段文字等;
這個時候我們需要使用多子佈局組件(Multi-child layout widgets)。
比較常用的多子佈局組件是Row、Column、Stack,我們來學習一下他們的使用。
2.1. Flex組件
事實上,我們即將學習的Row組件和Column組件都繼承自Flex組件。
- Flex組件和Row、Column屬性主要的區別就是多一個direction。
- 當direction的值爲Axis.horizontal的時候,則是Row。
- 當direction的值爲Axis.vertical的時候,則是Column。
在學習Row和Column之前,我們先學習主軸和交叉軸的概念。
因爲Row是一行排布,Column是一列排布,那麼它們都存在兩個方向,並且兩個Widget排列的方向應該是對立的。
它們之中都有主軸(MainAxis)和交叉軸(CrossAxis)的概念:
- 對於Row來說,主軸(MainAxis)和交叉軸(CrossAxis)分別是下圖
- 對於Column來說,主軸(MainAxis)和交叉軸(CrossAxis)分別是下圖
2.1. Row組件
2.1.1. Row介紹
Row組件用於將所有的子Widget排成一行,實際上這種佈局應該是借鑑於Web的Flex佈局。
如果熟悉Flex佈局,會發現非常簡單。
從源碼中查看Row的屬性:
Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, // 主軸對齊方式
MainAxisSize mainAxisSize = MainAxisSize.max, // 水平方向儘可能大
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, // 交叉處對齊方式
TextDirection textDirection, // 水平方向子widget的佈局順序(默認爲系統當前Locale環境的文本方向(如中文、英語都是從左往右,而阿拉伯語是從右往左))
VerticalDirection verticalDirection = VerticalDirection.down, // 表示Row縱軸(垂直)的對齊方向
TextBaseline textBaseline, // 如果上面是baseline對齊方式,那麼選擇什麼模式(有兩種可選)
List<Widget> children = const <Widget>[],
})
部分屬性詳細解析:(不過文字是真的難描述,後續推出視頻學習較差)
mainAxisSize:
-
表示Row在主軸(水平)方向佔用的空間,默認是
MainAxisSize.max
,表示儘可能多的佔用水平方向的空間,此時無論子widgets實際佔用多少水平空間,Row的寬度始終等於水平方向的最大寬度 -
而
MainAxisSize.min
表示儘可能少的佔用水平空間,當子widgets沒有佔滿水平剩餘空間,則Row的實際寬度等於所有子widgets佔用的的水平空間;
mainAxisAlignment:表示子Widgets在Row所佔用的水平空間內對齊方式
- 如果mainAxisSize值爲
MainAxisSize.min
,則此屬性無意義,因爲子widgets的寬度等於Row的寬度 - 只有當mainAxisSize的值爲
MainAxisSize.max
時,此屬性纔有意義 MainAxisAlignment.start
表示沿textDirection的初始方向對齊,- 如textDirection取值爲
TextDirection.ltr
時,則MainAxisAlignment.start
表示左對齊,textDirection取值爲TextDirection.rtl
時表示從右對齊。 - 而
MainAxisAlignment.end
和MainAxisAlignment.start
正好相反; - MainAxisAlignment.center表示居中對齊。
crossAxisAlignment:表示子Widgets在縱軸方向的對齊方式
- Row的高度等於子Widgets中最高的子元素高度
- 它的取值和MainAxisAlignment一樣(包含
start
、end
、center
三個值) - 不同的是crossAxisAlignment的參考系是verticalDirection,即verticalDirection值爲
VerticalDirection.down
時crossAxisAlignment.start
指頂部對齊,verticalDirection值爲VerticalDirection.up
時,crossAxisAlignment.start
指底部對齊;而crossAxisAlignment.end
和crossAxisAlignment.start
正好相反;
2.1.2. Row演練
我們來對部分屬性進行簡單的代碼演練,其他一些屬性大家自己學習一下
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(color: Colors.red, width: 60, height: 60),
Container(color: Colors.blue, width: 80, height: 80),
Container(color: Colors.green, width: 70, height: 70),
Container(color: Colors.orange, width: 100, height: 100),
],
);
}
}
2.1.3. mainAxisSize
默認情況下,Row會儘可能佔據多的寬度,讓子Widget在其中進行排布,這是因爲mainAxisSize屬性默認值是MainAxisSize.max。
我們來看一下,如果這個值被修改爲MainAxisSize.max會什麼變化:
2.1.4. TextBaseline
關於TextBaseline的取值解析
ideographic: 以漢字爲基線
alphabetic: 以字母爲基線
line-height(行高): 是指bottom到top的距離
text-height(文本的高度): 文字的高度
行距: (行高 - 文本的高度) / 2
2.1.5. Expanded
如果我們希望紅色和黃色的Container Widget不要設置固定的寬度,而是佔據剩餘的部分,這個時候應該如何處理呢?
這個時候我們可以使用 Expanded
來包裹 Container Widget,並且將它的寬度不設置值;
- flex屬性,彈性係數,Row會根據兩個Expanded的彈性係數來決定它們佔據剩下空間的比例
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 1,
child: Container(color: Colors.red, height: 60),
),
Container(color: Colors.blue, width: 80, height: 80),
Container(color: Colors.green, width: 70, height: 70),
Expanded(
flex: 1,
child: Container(color: Colors.orange, height: 100),
)
],
);
}
}
2.2. Column組件
Column組件用於將所有的子Widget排成一列,學會了前面的Row後,Column只是和row的方向不同而已。
2.2.1. Column介紹
我們直接看它的源碼:我們發現和Row屬性是一致的,不再解釋
Column({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
2.2.2. Column演練
我們直接將Row的代碼中Row改爲Column,查看代碼運行效果:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 1,
child: Container(color: Colors.red, width: 60),
),
Container(color: Colors.blue, width: 80, height: 80),
Container(color: Colors.green, width: 70, height: 70),
Expanded(
flex: 1,
child: Container(color: Colors.orange, width: 100),
)
],
);
}
}
2.3. Stack組件
在開發中,我們多個組件很有可能需要重疊顯示,比如在一張圖片上顯示文字或者一個按鈕等。
在Android中可以使用Frame來實現,在Web端可以使用絕對定位,在Flutter中我們需要使用層疊佈局Stack。
2.3.1. Stack介紹
我們還是通過源碼來看一下Stack有哪些屬性:
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
參數解析:
- alignment:此參數決定如何去對齊沒有定位(沒有使用Positioned)或部分定位的子widget。所謂部分定位,在這裏特指沒有在某一個軸上定位. left、right爲橫軸,top、bottom爲縱軸,只要包含某個軸上的一個定位屬性就算在該軸上有定位。
- textDirection:和Row、Wrap的textDirection功能一樣,都用於決定alignment對齊的參考系即:textDirection的值爲
TextDirection.ltr
,則alignment的start
代表左,end
代表右;textDirection的值爲TextDirection.rtl
,則alignment的start
代表右,end
代表左。 - fit:此參數用於決定 沒有定位 的子widget如何去適應Stack的大小。
StackFit.loose
表示使用子widget的大小,StackFit.expand
表示擴伸到Stack的大小。 - overflow:此屬性決定如何顯示超出Stack顯示空間的子widget,值爲
Overflow.clip
時,超出部分會被剪裁(隱藏),值爲Overflow.visible
時則不會。
2.3.2. Stack演練
Stack會經常和Positioned一起來使用,Positioned可以決定組件在Stack中的位置,用於實現類似於Web中的絕對定位效果。
我們來看一個簡單的演練:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.purple,
width: 300,
height: 300,
),
Positioned(
left: 20,
top: 20,
child: Icon(Icons.favorite, size: 50, color: Colors.white)
),
Positioned(
bottom: 20,
right: 20,
child: Text("你好啊,李銀河", style: TextStyle(fontSize: 20, color: Colors.white)),
)
],
);
}
}
注意: Positioned組件只能在Stack中使用。