本文主要介绍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,欢迎你们关注。