在介紹Row
和Column
時,如果子元件超出螢幕範圍,則會報溢位錯誤,如:
Row(
children: <Widget>[
Text("xxx"*100)
],
);
複製程式碼
執行效果如下:
可以看到,右邊溢位部分報錯,這是因為Row
預設只有一行,如果超出螢幕不會折行,我們把超出螢幕顯示範圍會自動折行的佈局稱為流式佈局。Flutter中通過Wrap
和Flow
來支援流式佈局,將上例中的Row
換成Wrap
後溢位部分則會自動折行,下面我們分別介紹一下Wrap
和Flow
。
Wrap
以下是Wrap
的定義:
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
複製程式碼
我們可以看到Wrap
的很多屬性在Row
、Column
以及Flex
中都有,如direction
、crossAxisAlignment
、textDirection
、verticalDirection
、children
等,這些屬性的意義是相同的,不過需要注意的是,Wrap
中的crossAxisAlignment
屬性和Row
、Column
以及Flex
中的crossAxisAlignment
略有差異:
-
取值方式不同;
Wrap
中的crossAxisAlignment
取值為WrapCrossAlignment.start
,而Row
、Column
以及Flex
中的crossAxisAlignment
則取值為CrossAxisAlignment.center
,前者使用WrapCrossAlignment
來取值,而後者使用CrossAxisAlignment
來取值。 -
在主軸為垂直方向時,子元件對齊方式略有差異;如在
Wrap
中crossAxisAlignment
值為WrapCrossAlignment.center
,且direction
為Axis.vertical
,同時寬度為螢幕寬度,此時Wrap
中的所有子元件都將以最大元件的寬度居中對齊,而Column
中的crossAxisAlignment
值為CrossAxisAlignment.center
,且寬度為螢幕寬度,那麼它們當中的所有子元件都將在螢幕中間且以最大元件寬度居中對齊。Wrap
的示例程式碼:ConstrainedBox( constraints: BoxConstraints(minWidth: double.infinity), child: Container( color: Colors.blue, child: Wrap( direction: Axis.vertical, crossAxisAlignment: WrapCrossAlignment.center, children: <Widget>[ Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),), label: Text("Hamilton"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),), label: Text("LafayetteLafayette"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),), label: Text("Mulligan"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),), label: Text("Laurens"), ), ], ), ), ), 複製程式碼
執行效果:
Column
的示例程式碼:ConstrainedBox( constraints: BoxConstraints(minWidth: double.infinity), child: Container( color: Colors.blue, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),), label: Text("Hamilton"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),), label: Text("LafayetteLafayette"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),), label: Text("Mulligan"), ), Chip( avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),), label: Text("Laurens"), ), ], ), ), ), 複製程式碼
執行效果:
除此之外,其它相同屬性並無差異,在此就不再一一贅述,讀者可以查閱前面介紹的Row
和Colum
的文章。接下來我們看一下Wrap
特有的幾個屬性:
spacing
:主軸方向子元件的間距runSpacing
:縱軸方向子元件的間距alignment
:主軸方向子元件的對齊方式runAlignment
:縱軸方向子元件的對齊方式
示例
Container(
color: Colors.blue,
child: Wrap(
spacing: 20.0,
runSpacing: 10.0,
alignment: WrapAlignment.center,
children: <Widget>[
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
label: Text("Hamilton"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
label: Text("Lafayette"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
label: Text("Mulligan"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
label: Text("Laurens"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
label: Text("Zane"),
),
],
),
),
複製程式碼
執行效果如下:
我們可以看出,Wrap
的寬度和高度都是自適應的,取決於它所包含的子元件,如果想要設定Wrap
的寬度和高度,可以通過ConstrainedBox
和SizedBox
來強制更改寬度和高度限制,如:
ConstrainedBox(
constraints: BoxConstraints(minWidth: double.infinity, minHeight: 600.0),
child: Container(
color: Colors.blue,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: <Widget>[
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
label: Text("Hamilton"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
label: Text("LafayetteLafayette"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
label: Text("Mulligan"),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
label: Text("Laurens"),
),
],
),
),
),
複製程式碼
Flow
我們一般很少會使用Flow
,因為其過於複雜,需要自己實現子元件的位置轉換,所以在很多場景下首先要考慮的是Wrap
是否滿足需求。Flow
主要用於一些需要自定義佈局策略或效能要求較高(如動畫中)的場景。
Flow
的優點
- 效能好:
Flow
是一個對子元件尺寸以及位置調整非常高效的控制元件,Flow
用轉換矩陣在對子元件進行位置調整的時候進行了優化:在Flow
定位過後,如果子元件的尺寸或者位置發生了變化,在FlowDelegate
中的paintChildren()
方法中呼叫context.paintChild
進行重繪,而context.paintChild
在重繪時使用了轉換矩陣,並沒有實際調整元件位置。 - 靈活:由於我們需要自己實現
FlowDelegate
中的paintChildren()
方法,所以需要計算每一個元件的位置,因此可以自定義佈局策略。
Flow
的缺點
- 使用複雜(程式碼實現還是要有點功底的)。
- 不能自適應子元件大小,必須通過指定父容器大小或實現
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,),
],
)
複製程式碼
實現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;
//計算每一個子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(有優化)
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;
}
}
複製程式碼
執行效果:
可以看到我們主要的任務就是實現paintChildren()
方法,該方法的主要作用是確定每個子元件的位置。由於Flow
不能自適應子元件的大小,因此我們通過在getSize()
返回中返回一個固定大小來指定Flow
的大小。