上文 Flutter 多子 Widget 佈局之線性佈局 Row、Column,介紹了
Row、Column
這兩個組件,查看源碼發現他倆都是繼承了Flex widget
,這篇來學習學習彈性佈局Flex,Explanded
彈性佈局 Flex、Expanded
Flex 表示彈性,Expanded 表示擴張,在這裏將二者歸爲彈性佈局。
Flex
Flex
顧名思義是彈性佈局,一般我們會使用它的子類Row 和 Column
.
Flex({
Key key,
@required this.direction,// 方向
this.mainAxisAlignment = MainAxisAlignment.start,
this.mainAxisSize = MainAxisSize.max,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
List<Widget> children = const <Widget>[],
})
Expanded
顧名思義是具有 擴展功能的
widget
,查看源碼發現Expanded
繼承自Flexible
構造函數也很簡單:
const Expanded({
Key key,
int flex = 1,// 如果爲空或爲零, child 不擴張,如果爲非 0 則 child 在主軸上分配的空間爲 flex 指定的值
@required Widget child,
})
看個示例效果圖就一目瞭然了。
flex:1
紅色部分和flex:2
黃色部分將主軸方向的屏幕等分爲3
分,紅色佔1/3
,黃色佔2/3
。
示例代碼如下:
Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 50.0,
color: Colors.red,
alignment: Alignment.center,
child: Text('Expanded flex: 1',style: TextStyle(color: Colors.white),),
)
),
Expanded(
flex: 2,
child: Container(
height: 50.0,
color: Colors.yellow,
alignment: Alignment.center,
child: Text('Expanded flex: 2',style: TextStyle(color: Colors.green),),
)
),
],
),
],
)
流式佈局 Wrap、Flow
Wrap
: 流式佈局,默認是水平方向擺列child
,當頁面顯示不下時會讓子控件自動換行。
Flow
:流式佈局,能根據座標繪製child
,定製性強也相對複雜一些,能實現Wrap
控件相同的效果
由於Flow
內部做了是否重繪的處理,所以效率上高一些。
但是使用上相對Wrap
複雜一些,所以使用時能用Wrap
實現的儘量使用Wrap
吧。
Wrap
相關字段作用如下:
Wrap({
Key key,
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>[],//子控件集合
})
可以看到以上參數基本和Row,Column
相似,僅多了兩個字段:
this.spacing = 0.0,//主軸上子控件中間的間距
this.runSpacing = 0.0,// 交叉軸上子控件之間的間距
我們通過示例效果來理解這兩個字段:
示例僞代碼:
/// 子佈局集合
List<Widget> _wrapItems() => List.generate(8, (index){
return Container(
color: Colors.red,
width: 50,
height: 50,
alignment: Alignment.center,
child: Text('$index',style: TextStyle(color: Colors.white),),
);
}
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wrap'),
),
body: Wrap(
spacing: 10,// 水平間距
runSpacing: 5,// 垂直間距
children: _wrapItems()
)
);
}
Flow
Flow
詳解可以查看這兩篇文章:
查看源碼,發現Flow
的字段也是很簡單:
Flow({
Key key,
@required this.delegate,
List<Widget> children = const <Widget>[],
})
Flow
的核心是delegate
,而delegate
裏面可以根據信息來計算child
的擺放位置。
把上面的Wrap
實現的效果,用Flow
控件實現,
直接放源碼,註釋也在源碼中說明了。
class FlowTestPage extends StatefulWidget {
@override
_FlowTestPageState createState() {
return _FlowTestPageState();
}
}
class _FlowTestPageState extends State<FlowTestPage> {
List<Widget> _wrapItems() => List.generate(8, (index){
return Container(
color: Colors.red,
width: 50,
height: 50,
alignment: Alignment.center,
child: Text('$index',style: TextStyle(color: Colors.white),),
);
}
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flow'),
),
body: Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0)),
children: _wrapItems()
)
);
}
}
class TestFlowDelegate extends FlowDelegate {
EdgeInsets margin = EdgeInsets.zero;
TestFlowDelegate({this.margin});
@override
void paintChildren(FlowPaintingContext context) {
/// 左邊距
var x = margin.left;
var y = margin.top;
for (int i = 0; i < context.childCount; i++) {
/// child 的寬度(沒有加上margin left)
var w = context.getChildSize(i).width + x + margin.right;
/// child 的寬度 < 屏幕的寬度, 單行繪製
if (w < context.size.width) {
context.paintChild(i, transform: new Matrix4.translationValues(x, y, 0.0));
/// 當前繪製的 child 距離左邊距大小(加上margin left)
x = w + margin.left;
} else {
/// 恢復繪製的 child 左邊距
x = margin.left;
/// 行數 + 1
y += context.getChildSize(i).height + margin.top + margin.bottom;
context.paintChild(i, transform: new Matrix4.translationValues(x, y, 0.0));
/// 下一行開始繪製 child 的左邊距
x += context.getChildSize(i).width + x + margin.right;
}
}
}
/// 是否重繪 FlowDelegate
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate != this;
}
}
完~