本文主要介紹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佈局,包括主軸、交叉軸等概念。函數
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。flex
其中MainAxisAlignment枚舉值:ui
其中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,歡迎你們關注。