本文主要介紹Flutter佈局中的Row、Column控件,詳細介紹了其佈局行爲以及使用場景,並對源碼進行了分析。html
A widget that displays its children in a horizontal array.git
在Flutter中很是常見的一個多子節點控件,將children排列成一行。估計是借鑑了Web中Flex佈局,因此不少屬性和表現,都跟其類似。可是注意一點,自身不帶滾動屬性,若是超出了一行,在debug下面則會顯示溢出的提示。github
Row的佈局有六個步驟,這種佈局表現來自Flex(Row和Column的父類):web
Row的佈局行爲表面上看有這麼多個步驟,其實也還算是簡單,能夠徹底參照web中的Flex佈局,包括主軸、交叉軸等概念。bash
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row
複製代碼
Row以及Column都是Flex的子類,它們的具體實現也都是由Flex完成,只是參數不一樣。函數
Row(
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
padding: EdgeInsets.all(5.0),
),
flex: 1,
),
Expanded(
child: Container(
color: Colors.yellow,
padding: EdgeInsets.all(5.0),
),
flex: 2,
),
Expanded(
child: Container(
color: Colors.blue,
padding: EdgeInsets.all(5.0),
),
flex: 1,
),
],
)
複製代碼
一個很簡單的例子,使用Expanded控件,將一行的寬度分紅四個等分,第1、三個child佔1/4的區域,第二個child佔1/2區域,由flex屬性控制。源碼分析
構造函數以下:佈局
Row({
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>[],
})
複製代碼
MainAxisAlignment:主軸方向上的對齊方式,會對child的位置起做用,默認是start。學習
其中MainAxisAlignment枚舉值:flex
其中spaceAround、spaceBetween以及spaceEvenly的區別,就是對待首尾child的方式。其距離首尾的距離分別是空白區域的1/二、0、1。
MainAxisSize:在主軸方向佔有空間的值,默認是max。
MainAxisSize的取值有兩種:
CrossAxisAlignment:children在交叉軸方向的對齊方式,與MainAxisAlignment略有不一樣。
CrossAxisAlignment枚舉值有以下幾種:
TextDirection:阿拉伯語系的兼容設置,通常無需處理。
VerticalDirection:定義了children擺放順序,默認是down。
VerticalDirection枚舉值有兩種:
top對應Row以及Column的話,就是左邊和頂部,bottom的話,則是右邊和底部。
TextBaseline:使用的TextBaseline的方式,有兩種,前面已經介紹過。
Row以及Column的源代碼就一個構造函數,具體的實現所有在它們的父類Flex中。
關於Flex的構造函數
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>[],
})
複製代碼
能夠看出,Flex的構造函數就比Row和Column的多了一個參數。Row跟Column的區別,正是這個direction參數的不一樣。當爲Axis.horizontal的時候,則是Row,當爲Axis.vertical的時候,則是Column。
咱們來看下Flex的佈局函數,因爲佈局函數比較多,所以分段來說解:
while (child != null) {
final FlexParentData childParentData = child.parentData;
totalChildren++;
final int flex = _getFlex(child);
if (flex > 0) {
totalFlex += childParentData.flex;
lastFlexChild = child;
} else {
BoxConstraints innerConstraints;
if (crossAxisAlignment == CrossAxisAlignment.stretch) {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(minHeight: constraints.maxHeight,
maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth);
break;
}
} else {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
break;
}
}
child.layout(innerConstraints, parentUsesSize: true);
allocatedSize += _getMainSize(child);
crossSize = math.max(crossSize, _getCrossSize(child));
}
child = childParentData.nextSibling;
}
複製代碼
上面這段代碼,我把中間的一些assert以及錯誤信息之類的代碼剔除了,不影響實際的理解。
在佈局的開始,首先會遍歷一遍child,遍歷的做用有兩點:
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
child = firstChild;
while (child != null) {
final int flex = _getFlex(child);
if (flex > 0) {
final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
double minChildExtent;
switch (_getFit(child)) {
case FlexFit.tight:
assert(maxChildExtent < double.infinity);
minChildExtent = maxChildExtent;
break;
case FlexFit.loose:
minChildExtent = 0.0;
break;
}
BoxConstraints innerConstraints;
if (crossAxisAlignment == CrossAxisAlignment.stretch) {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(minWidth: minChildExtent,
maxWidth: maxChildExtent,
minHeight: constraints.maxHeight,
maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: minChildExtent,
maxHeight: maxChildExtent);
break;
}
} else {
switch (_direction) {
case Axis.horizontal:
innerConstraints = new BoxConstraints(minWidth: minChildExtent,
maxWidth: maxChildExtent,
maxHeight: constraints.maxHeight);
break;
case Axis.vertical:
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
minHeight: minChildExtent,
maxHeight: maxChildExtent);
break;
}
}
child.layout(innerConstraints, parentUsesSize: true);
final double childSize = _getMainSize(child);
allocatedSize += childSize;
allocatedFlexSpace += maxChildExtent;
crossSize = math.max(crossSize, _getCrossSize(child));
}
if (crossAxisAlignment == CrossAxisAlignment.baseline) {
final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
if (distance != null)
maxBaselineDistance = math.max(maxBaselineDistance, distance);
}
final FlexParentData childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
複製代碼
上面的代碼段所作的事情也有兩點:
對於每份flex所對應的空間大小,它的計算方式以下:
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
複製代碼
其中,allocatedSize是不包含flex所佔用的空間。當每一份flex所佔用的空間計算出來後,則根據交叉軸的設置,對包含flex的child進行調整。
若是交叉軸的對齊方式爲baseline,則計算出最大的baseline值,將其做爲總體的baseline值。
switch (_mainAxisAlignment) {
case MainAxisAlignment.start:
leadingSpace = 0.0;
betweenSpace = 0.0;
break;
case MainAxisAlignment.end:
leadingSpace = remainingSpace;
betweenSpace = 0.0;
break;
case MainAxisAlignment.center:
leadingSpace = remainingSpace / 2.0;
betweenSpace = 0.0;
break;
case MainAxisAlignment.spaceBetween:
leadingSpace = 0.0;
betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
break;
case MainAxisAlignment.spaceAround:
betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
leadingSpace = betweenSpace / 2.0;
break;
case MainAxisAlignment.spaceEvenly:
betweenSpace = totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
leadingSpace = betweenSpace;
break;
}
複製代碼
而後,就是將child在主軸方向上按照設置的對齊方式,進行位置調整。上面代碼就是計算先後空白區域值的過程,能夠看出spaceBetween、spaceAround以及spaceEvenly的差異。
double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
child = firstChild;
while (child != null) {
final FlexParentData childParentData = child.parentData;
double childCrossPosition;
switch (_crossAxisAlignment) {
case CrossAxisAlignment.start:
case CrossAxisAlignment.end:
childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
== (_crossAxisAlignment == CrossAxisAlignment.start)
? 0.0
: crossSize - _getCrossSize(child);
break;
case CrossAxisAlignment.center:
childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
break;
case CrossAxisAlignment.stretch:
childCrossPosition = 0.0;
break;
case CrossAxisAlignment.baseline:
childCrossPosition = 0.0;
if (_direction == Axis.horizontal) {
assert(textBaseline != null);
final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
if (distance != null)
childCrossPosition = maxBaselineDistance - distance;
}
break;
}
if (flipMainAxis)
childMainPosition -= _getMainSize(child);
switch (_direction) {
case Axis.horizontal:
childParentData.offset = new Offset(childMainPosition, childCrossPosition);
break;
case Axis.vertical:
childParentData.offset = new Offset(childCrossPosition, childMainPosition);
break;
}
if (flipMainAxis) {
childMainPosition -= betweenSpace;
} else {
childMainPosition += _getMainSize(child) + betweenSpace;
}
child = childParentData.nextSibling;
}
複製代碼
最後,則是根據交叉軸的對齊方式設置,對child進行位置調整,到此,佈局結束。
咱們能夠順一下總體的流程:
Row和Column都是很是經常使用的佈局控件。通常狀況下,比方說須要將控件在一行或者一列顯示的時候,均可以使用。但並非說只能使用Row或者Column去佈局,也可使用Stack,看具體的場景選擇。
在講解Row的時候,實際上是按照Flex的一些佈局行爲來進行的,包括源碼分析,也都是在用Flex進行分析的。Row和Column都是Flex的子類,只是direction參數不一樣。Column各方面同Row,所以在這裏再也不另行講解。
在講解Flex的時候,也說過是參照了web的Flex佈局,若是有相關開發經驗的同窗,徹底能夠參照着去理解,這樣子更容易去理解它們的用法和原理。
筆者建了一個Flutter學習相關的項目,Github地址,裏面包含了筆者寫的關於Flutter學習相關的一些文章,會按期更新,也會上傳一些學習Demo,歡迎你們關注。