Flutter 布局(七)- Row、Column详解

  • spaceBetween:将主轴方向上的空白区域均分,使得children之间的空白区域相等,首尾child都靠近首尾,没有间隙;
  • spaceEvenly:将主轴方向上的空白区域均分,使得children之间的空白区域相等,包括首尾child;
  • start:将children放置在主轴的起点;

其中spaceAround、spaceBetween以及spaceEvenly的区别,就是对待首尾child的方式。其距离首尾的距离分别是空白区域的1/2、0、1。

MainAxisSize:在主轴方向占有空间的值,默认是max。

MainAxisSize的取值有两种:

  • max:根据传入的布局约束条件,最大化主轴方向的可用空间;
  • min:与max相反,是最小化主轴方向的可用空间;

CrossAxisAlignment:children在交叉轴方向的对齐方式,与MainAxisAlignment略有不同。

CrossAxisAlignment枚举值有如下几种:

  • baseline:在交叉轴方向,使得children的baseline对齐;
  • center:children在交叉轴上居中展示;
  • end:children在交叉轴上末尾展示;
  • start:children在交叉轴上起点处展示;
  • stretch:让children填满交叉轴方向;

TextDirection:阿拉伯语系的兼容设置,一般无需处理。

VerticalDirection:定义了children摆放顺序,默认是down。

VerticalDirection枚举值有两种:

  • down:从top到bottom进行布局;
  • up:从bottom到top进行布局。

top对应Row以及Column的话,就是左边和顶部,bottom的话,则是右边和底部。

TextBaseline:使用的TextBaseline的方式,有两种,前面已经介绍过。

1.5.2 源码

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 children = const [],
})

可以看出,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,遍历的作用有两点:

  • 对于存在flex值的child,计算出flex的和,找到最后一个包含flex值的child。找到这个child,是因为主轴对齐方式,可能会对它的位置做调整,需要找出来;
  • 对于不包含flex的child,根据交叉轴方向的设置,对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的child分配剩余的空间

对于每份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值,将其作为整体的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);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

学习交流

群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值