Widget 分類(lèi)
widget 其實(shí)是Element 的配置文件,而Element是右RenderObject 渲染的壤靶!如果想知道的更詳細(xì)請(qǐng)看我的這篇《Flutter之HolleWold》
根據(jù)Widget是否需要包含子節(jié)點(diǎn)將Widget分為了三類(lèi),分別對(duì)應(yīng)三種Element缸濒,如下:
【注】:Flutter中的很多Widget是直接繼承自StatelessWidget或StatefulWidget族沃,然后在build()方法中構(gòu)建真正的RenderObjectWidget茉兰,如Text,它其實(shí)是繼承自StatelessWidget歌逢,然后在build()方法中通過(guò)RichText來(lái)構(gòu)建其子樹(shù)巾钉,而RichText才是繼承自LeafRenderObjectWidget。所以為了方便敘述秘案,我們也可以直接說(shuō)Text屬于LeafRenderObjectWidget(其它widget也可以這么描述)砰苍,這才是本質(zhì)。讀到這里我們也會(huì)發(fā)現(xiàn)阱高,其實(shí)StatelessWidget和StatefulWidget就是兩個(gè)用于組合Widget的基類(lèi)赚导,它們本身并不關(guān)聯(lián)最終的渲染對(duì)象(RenderObjectWidget)。
布局類(lèi)組件就是指直接或間接繼承(包含)MultiChildRenderObjectWidget的Widget赤惊,它們一般都會(huì)有一個(gè)children屬性用于接收子Widget吼旧。我們看一下繼承關(guān)系 Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget 。
整個(gè)Widget 樹(shù)的關(guān)系圖如下:
Flutter 布局分類(lèi)
線性布局:Row(X 軸 橫向)未舟,Column(Y軸 縱向)
彈性布局:Flex(Row 和 Column 的基類(lèi))圈暗,Expanded(靈活布局),Spacer(特殊的Expanded)
流式布局:Wrap(Row和Column + 折行功能)裕膀,F(xiàn)low(類(lèi)似于UICollectionview)
層疊布局:Stack(Z 軸)员串,Positioned(類(lèi)似于Masonry,相對(duì)布局)
相對(duì)布局:Align
Row可以在水平方向排列其子widget昼扛。定義如下:
Row({
...
TextDirection textDirection,
MainAxisSize mainAxisSize = MainAxisSize.max,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
VerticalDirection verticalDirection = VerticalDirection.down,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
List<Widget> children = const <Widget>[],
})
- textDirection:表示水平方向子widget的布局順序(是從左往右還是從右往左)寸齐,默認(rèn)為系統(tǒng)當(dāng)前Locale環(huán)境的文本方向(如中文、英語(yǔ)都是從左往右,而阿拉伯語(yǔ)是從右往左)渺鹦。
- mainAxisSize:表示Row在主軸(水平)方向占用的空間扰法,默認(rèn)是MainAxisSize.max,表示盡可能多的占用水平方向的空間海铆,此時(shí)無(wú)論子widgets實(shí)際占用多少水平空間迹恐,Row的寬度始終等于水平方向的最大寬度;而MainAxisSize.min表示盡可能少的占用水平空間卧斟,當(dāng)子widgets沒(méi)有占滿水平剩余空間,則Row的實(shí)際寬度等于所有子widgets占用的的水平空間憎茂。
- mainAxisAlignment:表示子Widgets在Row所占用的水平空間內(nèi)對(duì)齊方式珍语,如果mainAxisSize值為MainAxisSize.min,則此屬性無(wú)意義竖幔,因?yàn)樽觲idgets的寬度等于Row的寬度板乙。只有當(dāng)mainAxisSize的值為MainAxisSize.max時(shí),此屬性才有意義拳氢,MainAxisAlignment.start表示沿textDirection的初始方向?qū)R募逞,如textDirection取值為T(mén)extDirection.ltr時(shí),則MainAxisAlignment.start表示左對(duì)齊馋评,textDirection取值為T(mén)extDirection.rtl時(shí)表示從右對(duì)齊放接。而MainAxisAlignment.end和MainAxisAlignment.start正好相反。
- MainAxisAlignment.center表示居中對(duì)齊留特。讀者可以這么理解:textDirection是mainAxisAlignment的參考系纠脾。
- verticalDirection:表示Row縱軸(垂直)的對(duì)齊方向,默認(rèn)是VerticalDirection.down蜕青,表示從上到下苟蹈。
- crossAxisAlignment:表示子Widgets在縱軸方向的對(duì)齊方式,Row的高度等于子Widgets中最高的子元素高度右核,它的取值和MainAxisAlignment一樣(包含start慧脱、end、 center三個(gè)值)贺喝,不同的是crossAxisAlignment的參考系是verticalDirection菱鸥,即verticalDirection值為VerticalDirection.down時(shí)crossAxisAlignment.start指頂部對(duì)齊,verticalDirection值為VerticalDirection.up時(shí)搜变,crossAxisAlignment.start指底部對(duì)齊采缚;而crossAxisAlignment.end和crossAxisAlignment.start正好相反。
- children :子Widgets數(shù)組挠他。
Column 和 Row 的屬性一樣扳抽,對(duì)應(yīng)使用就可以,這里就不贅述了。
Flex 和 Expanded
Flex({
@required this.direction, //彈性布局的方向, Row默認(rèn)為水平方向贸呢,Column默認(rèn)為垂直方向
List<Widget> children = const <Widget>[],
})
由源碼可以看出 Flex 是 Row 和 Column 的基類(lèi)(用法也相同镰烧,這里不詳細(xì)介紹)
Expanded
const Expanded({
//flex參數(shù)為彈性系數(shù),如果為0或null楞陷,則child是沒(méi)有彈性的怔鳖,即不會(huì)被擴(kuò)伸占用的空間。
//如果大于0固蛾,所有的Expanded按照其flex的比例來(lái)分割主軸的全部空閑空間结执。
int flex = 1,
@required Widget child,
})
Flex + Expanded 使用簡(jiǎn)單例子
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: <Widget>[
Text('aaaa'),
//屏幕寬度減去Text的寬度,剩下的紅色和藍(lán)色寬度按1:2比例分配
Expanded(
flex: 1,
child: Container(
color: Colors.red,
)),
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
))
],
);
}
【注】:
- Expanded 是一種彈性的填充方式艾凯,默認(rèn)會(huì)把剩下的位置全部填滿(除了Text('aaaa')的位置剩下的全部)
- Expanded 無(wú)法直接單獨(dú)使用献幔,必須包含在Flex 里邊(包含Row 和 Column)
- Spacer 是一個(gè)特殊的Expanded,只有一個(gè) flex 屬性趾诗,純粹占位子使用
Wrap
流式布局(折行布局)蜡感,部件主軸方滿了自動(dòng)折行
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,//主軸方向子widget的間距
this.runAlignment = WrapAlignment.start,//縱軸方向的對(duì)齊方式
this.runSpacing = 0.0,//縱軸方向的間距
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
使用方法和上邊差不多,不贅述了恃泪。
Flow :
我們一般很少會(huì)使用Flow郑兴,因?yàn)槠溥^(guò)于復(fù)雜,需要自己實(shí)現(xiàn)子widget的位置轉(zhuǎn)換贝乎,在很多場(chǎng)景下首先要考慮的是Wrap是否滿足需求情连。Flow主要用于一些需要自定義布局策略或性能要求較高(如動(dòng)畫(huà)中)的場(chǎng)景。
優(yōu)點(diǎn):
- 性能好糕非;Flow是一個(gè)對(duì)子組件尺寸以及位置調(diào)整非常高效的控件蒙具,F(xiàn)low用轉(zhuǎn)換矩陣在對(duì)子組件進(jìn)行位置調(diào)整的時(shí)候進(jìn)行了優(yōu)化:在Flow定位過(guò)后,如果子組件的尺寸或者位置發(fā)生了變化朽肥,在FlowDelegate中的paintChildren()方法中調(diào)用context.paintChild 進(jìn)行重繪禁筏,而context.paintChild在重繪時(shí)使用了轉(zhuǎn)換矩陣,并沒(méi)有實(shí)際調(diào)整組件位置衡招。
- 靈活篱昔;由于我們需要自己實(shí)現(xiàn)FlowDelegate的paintChildren()方法,所以我們需要自己計(jì)算每一個(gè)組件的位置始腾,因此州刽,可以自定義布局策略。
缺點(diǎn):
- 使用復(fù)雜浪箭。
- 不能自適應(yīng)子組件大小穗椅,必須通過(guò)指定父容器大小或?qū)崿F(xiàn)TestFlowDelegate的getSize返回固定大小。
示例:
Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
children: <Widget>[
new Container(width: 80.0, height:80.0, color: Colors.red,),
new Container(width: 80.0, height:80.0, color: Colors.green,),
new Container(width: 80.0, height:80.0, color: Colors.blue,),
new Container(width: 80.0, height:80.0, color: Colors.yellow,),
new Container(width: 80.0, height:80.0, color: Colors.brown,),
new Container(width: 80.0, height:80.0, color: Colors.purple,),
],
)
//實(shí)現(xiàn)TestFlowDelegate:
class TestFlowDelegate extends FlowDelegate {
EdgeInsets margin = EdgeInsets.zero;
TestFlowDelegate({this.margin});
@override
void paintChildren(FlowPaintingContext context) {
var x = margin.left;
var y = margin.top;
//計(jì)算每一個(gè)子widget的位置
for (int i = 0; i < context.childCount; i++) {
var w = context.getChildSize(i).width + x + margin.right;
if (w < context.size.width) {
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x = w + margin.left;
} else {
x = margin.left;
y += context.getChildSize(i).height + margin.top + margin.bottom;
//繪制子widget(有優(yōu)化)
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x += context.getChildSize(i).width + margin.left + margin.right;
}
}
}
@override
getSize(BoxConstraints constraints){
//指定Flow的大小
return Size(double.infinity,200.0);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate != this;
}
}
Stack
Stack({
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
- alignment:此參數(shù)決定如何去對(duì)齊沒(méi)有定位(沒(méi)有使用Positioned)或部分定位的子組件奶栖。所謂部分定位匹表,在這里特指沒(méi)有在某一個(gè)軸上定位:left门坷、right為橫軸,top袍镀、bottom為縱軸默蚌,只要包含某個(gè)軸上的一個(gè)定位屬性就算在該軸上有定位。
- textDirection:和Row苇羡、Wrap的textDirection功能一樣绸吸,都用于確定alignment對(duì)齊的參考系,即:textDirection的值為T(mén)extDirection.ltr设江,則alignment的start代表左锦茁,end代表右,即從左往右的順序叉存;textDirection的值為T(mén)extDirection.rtl蜻势,則alignment的start代表右,end代表左鹉胖,即從右往左的順序。
- fit:此參數(shù)用于確定沒(méi)有定位的子組件如何去適應(yīng)Stack的大小够傍。StackFit.loose表示使用子組件的大小甫菠,StackFit.expand表示擴(kuò)伸到Stack的大小。
- overflow:此屬性決定如何顯示超出Stack顯示空間的子組件冕屯;值為Overflow.clip時(shí)寂诱,超出部分會(huì)被剪裁(隱藏),值為Overflow.visible 時(shí)則不會(huì)安聘。
Positioned
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
例子:
Stack(
alignment:Alignment.center , //指定未定位或部分定位widget的對(duì)齊方式
children: <Widget>[
Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
color: Colors.red,
),
Positioned(
left: 18.0,
child: Text("I am Jack"),
),
Positioned(
top: 18.0,
child: Text("Your friend"),
)
],
),
Align
Align({
Key key,
this.alignment = Alignment.center, //它有兩個(gè)常用的子類(lèi):Alignment和 FractionalOffset
this.widthFactor,//縮放因子
this.heightFactor,
Widget child,
})
例子:
Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment.topRight,
child: FlutterLogo(//圖片寬高為120*120 ,因?yàn)樵O(shè)置了縮放因子,所以用60
size: 60,
),
),
Alignment:
// 以屏幕中心為中心點(diǎn)(0.0,0.0)镊掖,x诵竭,y的范圍都是(-1~1)
// 上邊的例子Alignment.topRight = Alignment(1.0, -1.0);
//Container 有alignment 屬性,可以直接使用這個(gè)來(lái)實(shí)現(xiàn)的布局
Alignment(this.x, this.y)
FractionalOffset:
//和Alignment 一樣念颈,只是原點(diǎn)為屏幕的坐上角(這個(gè)和蘋(píng)果一樣)
// x和y的范圍是(0~1.0)
FractionalOffset(this.x, this.y),