前言
接著Flutter基礎(chǔ)控件篇[1],這一篇主要記錄Flutter容器控件的使用,也就是常用的布局控件(Layout)。
在學(xué)習(xí)的過程中,除了官方文檔之外,還參考了不少其他的博客資料,發(fā)現(xiàn)有挺多文章在一些細(xì)節(jié)問題上其實(shí)并沒有說明白,甚至有的實(shí)測(cè)之后證實(shí)是錯(cuò)的,有的屬性的解釋像是谷歌翻譯了一下直接就貼過來,實(shí)際含義其實(shí)都沒有說清楚,然后還被多處轉(zhuǎn)載粘貼,一處錯(cuò)誤可以在好些篇文章里同樣出現(xiàn)。
用心實(shí)在的好資料還是少數(shù)啊!
這里筆記中的內(nèi)容不一定全都對(duì),可能有遺漏或者實(shí)測(cè)場(chǎng)景不夠全面,在復(fù)雜特殊的某些場(chǎng)景可能有不符合的結(jié)果,但是里面所有內(nèi)容都是經(jīng)過代碼驗(yàn)證根據(jù)實(shí)際結(jié)果總結(jié)來的,也沒有類似機(jī)器翻譯的內(nèi)容在里面讓人看完模棱兩可啥也沒明白跟沒看一樣。沒理解的東西不往上記,沒驗(yàn)證的東西不往上記,盡量保證以后回頭來看,不會(huì)坑到自己。
正文
下面是幾個(gè)常用的容器布局控件:
Container
容器控件,可以添加一個(gè)子控件,設(shè)置寬度高度,padding,margin,背景顏色,背景裝飾等屬性,基本包含了容器控件的常用基本屬性。
基本使用:
Widget build(BuildContext context) {
return Container(
width: 200.0,
height: 150.0,
margin: EdgeInsets.fromLTRB(25, 15, 0, 0),
padding: EdgeInsets.fromLTRB(10, 15, 10, 15),
alignment: Alignment(0, 0),
decoration: BoxDecoration(boxShadow: [
//卡片陰影
BoxShadow(
color: Colors.black54,
offset: Offset(5.0, 5.0),
blurRadius: 7.0)
]),
child: Image.network(
Consts.imgUrl,
width: 200.0,
height: 150.0,
));
}
常用屬性:
width:寬度,如果設(shè)置具體值則為具體值,不設(shè)置則包裹子控件大小,設(shè)置為double.infinity一般會(huì)充滿父控件或者充滿屏幕(如果沒有其他特別的約束條件);
height:高度,使用方法基本同width;
margin:設(shè)置控件與其他控件的外邊距;
padding:設(shè)置內(nèi)邊距;
decoration:背景裝飾;
alignment:設(shè)置子控件的方位,用Alignment屬性值來表示,具體對(duì)應(yīng)的方位如下:
- Alignment topLeft = Alignment(-1.0, -1.0);
- Alignment topCenter = Alignment(0.0, -1.0);
- Alignment topRight = Alignment(1.0, -1.0);
- Alignment centerLeft = Alignment(-1.0, 0.0);
- Alignment center = Alignment(0.0, 0.0);
- Alignment centerRight = Alignment(1.0, 0.0);
- Alignment bottomLeft = Alignment(-1.0, 1.0);
- Alignment bottomCenter = Alignment(0.0, 1.0);
- Alignment bottomRight = Alignment(1.0, 1.0);
Row(橫向布局)、Column(縱向布局)
類似Android中的LinearLayout,只是拆分開來,一個(gè)橫向排列,一個(gè)縱向排列,它倆布局除了方向不同之外,其他的屬性基本含義都一樣:
基本用法:
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,//主軸方向(縱向)占空間盡量大,(如果沒有其他特別的約束條件則要么填滿屏幕,要么填滿父布局)
verticalDirection: VerticalDirection.down,//控件順序正常順序,VerticalDirection.up則倒敘
crossAxisAlignment: CrossAxisAlignment.center,//縱軸方向上控件排列規(guī)則(居中 靠邊等)
mainAxisAlignment: MainAxisAlignment.spaceEvenly,//主軸方向上控件排列規(guī)則(居中 靠邊 均勻分布等)
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
Text('verticalDirection:VerticalDirection.up'),
Text('crossAxisAlignment: CrossAxisAlignment.center'),
Text('mainAxisAlignment: MainAxisAlignment.spaceEvenly'),
Text('textBaseline: TextBaseline.ideographic'),
],
);
}
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.max,//主軸方向(橫向)占空間盡量大,(如果沒有其他特別的約束條件則要么填滿屏幕,要么填滿父布局)
verticalDirection: VerticalDirection.up,//實(shí)測(cè) up和down效果一樣,好像沒生效。
crossAxisAlignment: CrossAxisAlignment.center,//縱軸方向(縱向)上控件排列規(guī)則(居中 靠邊等)
mainAxisAlignment: MainAxisAlignment.spaceAround,////主軸方向上控件排列規(guī)則(居中 靠邊 均勻分布等)
textBaseline: TextBaseline.ideographic,
textDirection: TextDirection.rtl,//從左到右排列
children: <Widget>[
Container(
color: Colors.blue[100],
width: 70,
height: 70,
),
Container(color: Colors.blue[200], width: 70, height: 70),
Container(color: Colors.blue[300], width: 70, height: 70),
Container(color: Colors.blue[400], width: 70, height: 70),
],
);
}
這里mainAxis和crossAxis代表主軸方向和縱軸方向,Row(橫向布局)主軸方向就是橫向,縱軸方向就是縱向;Column(縱向布局)主軸方向就是縱向,縱軸方向就是橫向;
幾個(gè)常用屬性:
mainAxisSize:
- MainAxisSize.max:主軸方向占空間盡量大,(如果沒有其他特別的約束條件則要么填滿屏幕,要么填滿父布局);
- MainAxisSize.min:主軸方向占空間盡量小,基本就是包裹子控件的尺寸了
textDirection
- TextDirection.ltr,從左到右排列;
- TextDirection.rtl,從右到左排列;
textBaseline
- TextBaseline.alphabetic,使用按照排列字母字符的基準(zhǔn)線排列方式
- TextBaseline.ideographic,使用按照表意字字符的基準(zhǔn)線排列方式(比如中文);
表意字好像是說可以拆解結(jié)構(gòu)的文字類型,通過各種部首的組合可以形成各種文字,例如中文。(這里有一個(gè)視頻,簡單了解什么是表意字)
verticalDirection
- VerticalDirection.up,從下向上排列;
- VerticalDirection.down,從上向下排列;
mainAxisAlignment:
- start:將children放置在主軸的起點(diǎn);
- center:將children放置在主軸的中心;
- end:將children放置在主軸的末尾;
- spaceAround:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,但是首尾child的空白區(qū)域?yàn)?/2;
- spaceBetween:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,首尾child都靠近首尾,沒有間隙;
- spaceEvenly:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,包括首尾child;
crossAxisAlignment:
- start:將children放置在縱軸的起點(diǎn);
- center:將children放置在縱軸的中心;
- end:將children放置在縱軸的末尾;
Wrap 流式布局
Wrap包裹子控件,也可以設(shè)置橫向和縱向,它與Row和Column的區(qū)別在于:Row和Column只有一行或一列,子控件尺寸超出了就會(huì)顯示不全,但是Wrap會(huì)自動(dòng)換行。
Widget build(BuildContext context) {
return Wrap(
direction: Axis.horizontal,
//start end值受textDirection具體值的影響
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.end,//好像是runAlignment生效了 crossAxisAlignment沒有生效
runAlignment: WrapAlignment.center,//縱軸方向?qū)R方式
spacing: 15,//主軸方向間距15
runSpacing: 10,//縱軸方向間距10
textDirection: TextDirection.ltr,
children: <Widget>[
Container(color: Colors.green[100], width: 70, height: 70,),
Container(color: Colors.green[200], width: 70, height: 70),
Container(color: Colors.green[300], width: 70, height: 70),
Container(color: Colors.green[400], width: 70, height: 70),
Container(color: Colors.green[100], width: 70, height: 70,),
Container(color: Colors.green[200], width: 70, height: 70),
Container(color: Colors.green[300], width: 70, height: 70),
Container(color: Colors.green[400], width: 70, height: 70),
],
);
}
常用屬性:
direction:排列方向
- Axis.horizontal 橫向布局(主軸為橫向)
- Axis.vertical 縱向布局(主軸為縱向)
alignment:使用WrapAlignment的屬性值,但是含義和Row、Column基本一致;
spacing:主軸方向子控件間距;
runSpacing:縱軸方向的間距(主軸是橫向則縱軸是縱向,主軸是縱向則縱軸是橫向);
runAlignment:縱軸方向的對(duì)齊方式;
這里有個(gè)疑惑,crossAxisAlignment和runAlignment都表示縱軸方式的對(duì)齊方式,實(shí)測(cè)的結(jié)果似乎crossAxisAlignment并沒有起作用,只有runAlignment起作用了,這個(gè)還有待進(jìn)一步考證。
Stack 重疊布局
可以把子控件疊在一起來展示。Stack可以搭配Positioned控件一起使用,Positioned控件可以控制子控件在Stack中的具體位置(設(shè)置距離左上右下的具體參數(shù))和控件大小(設(shè)置寬高),使用場(chǎng)景會(huì)非常多。
Widget build(BuildContext context) {
return Stack(
fit:StackFit.loose,
alignment: AlignmentDirectional.center,
textDirection: TextDirection.ltr,
overflow: Overflow.clip,
children: <Widget>[
Container(width: 120,height: 120,color: Colors.cyan[200],),
Container(width: 90,height: 90,color: Colors.cyan[400],),
Container(width: 60,height: 60,color: Colors.cyan[600],),
Positioned(left: 100,top: 100,child: Container(width: 120,height: 120,color: Colors.cyan[800],),)//指定位置,超出stack范圍,測(cè)試overflow屬性
],
);
}
常用屬性:
alignment:設(shè)置子控件的方位,用AlignmentDirectional的值來表示,這個(gè)類里面的start和end受textDirection具體值的影響,TextDirection.ltr(左到右)對(duì)應(yīng)start為左邊,,TextDirection.rtl(右到左)對(duì)應(yīng)start為右邊,屬性值具體對(duì)應(yīng)的方位如下
- AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
- AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
- AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
- AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
- AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
- AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
- AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
- AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
- AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
overflow:
- Overflow.clip 當(dāng)子控件超出Stack的顯示范圍時(shí),截掉超出范圍的部分不顯示;
- Overflow.visible 當(dāng)子控件超出Stack的顯示范圍時(shí)也顯示超出范圍的部分;
fit:子控件適應(yīng)Stack控件尺寸的方式,這個(gè)屬性對(duì)于非Positioned控件內(nèi)的子控件起作用。
- StackFit.loose:使用此屬性時(shí),對(duì)于非Positioned控件內(nèi)的子控件,它的尺寸會(huì)小于Stack控件的尺寸,比如Stack的尺寸是300x600,那么子控件的尺寸寬度允許在0到300之間,高度允許在0到600之間;
- StackFit.expand:使用此屬性時(shí),對(duì)于非Positioned控件內(nèi)的子控件,它的尺寸會(huì)撐滿Stack的尺寸。比如Stack的尺寸是300x600,那么子控件的尺寸寬度會(huì)是300,高度會(huì)是600;
- StackFit.passthrough:對(duì)子控件的約束條件直接由Stack的父控件傳遞給Stack的子控件;
對(duì)應(yīng)Android中的match_parent和wrap_content
在Android中我們知道,每一個(gè)控件都可以直接設(shè)置寬高屬性為match_parent和wrap_content,選擇尺寸為充滿父布局還是包裹子控件內(nèi)容,使用非常方便。但是Flutter中沒有這樣的屬性可以直接使用,其實(shí)梳理完上面幾個(gè)常用容器控件的屬性之后,大致可以組合出與match_parent和wrap_content相同的效果,這樣,習(xí)慣了Android布局思維的同學(xué)轉(zhuǎn)過來寫Flutter控件也會(huì)順手和舒心很多,具體實(shí)現(xiàn)方式其實(shí)不止一種,以下是在stackoverflow上面找的一個(gè)答案,實(shí)測(cè)有效。
Wrap_content ,Wrap_content :
//use this as child
Wrap(
children: <Widget>[*your_child*])
Match_parent,Match_parent:
//use this as child
Container(
height: double.infinity,
width: double.infinity,child:*your_child*)
Match_parent,Wrap_content :
//use this as child
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[*your_child*],
);
Wrap_content ,Match_parent:
//use this as child
Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[your_child],
);
尾聲
最后再貼一遍筆記對(duì)應(yīng)的Demo項(xiàng)目GitHub地址,點(diǎn)這里。
如果發(fā)現(xiàn)問題,歡迎斧正!
以上。