flex 是 w3c 在 2009 年提出的响应式布局,现在已经得到所有主流的浏览器支持,也是当下前端开发主流的布局方式。
flex 凭借其布局属性适配不同的屏幕,提高开发效率,减适配问题。在如此优秀的响应式能力下,隐藏了什么设计和多少的复杂度,什么样的情况下会触发多次排版。了解内部实现能更好的在合适的场景使用选择性使用 flex,搭建更高效响应的页面。
Flex 布局属性
基础概念
主轴(main axis):容器根据 flex-direction 属性确定的排版方向,即横轴或竖轴
交叉轴(cross axis):与主轴垂直的排版方向,即横轴或竖轴
容器属性
display: flex
指定元素以 flex 方式布局
flex-direction: row(默认)/ column / row-reverse / column-reverse
指定主轴的排版方向
- row(默认)横向排版,起点在左端
- cloumn 竖向排版,起点在顶端
- row-reverse 横向排版,起点在右端
- column-reversse 竖向排版,七点在底端
flex-wrap: wrap(默认) / nowrap / wrap-reverse
决定当可用排版空间不足时,是否允许换行,以及换行后的顺序,模式包括允许换行、不换行、换行后整体反向。
- wrap (默认)空间不足时,进行换行
- nowrap 不换行
- wrap-reverse 换行后,第一行在最下方,行排版方向向上
justify-content: flex-start(默认) / flex-end / center / space-between / space-around
指定项目在主轴上的对齐方式
- flex-start (默认)对齐主轴的起点,例如:row 情况下左对齐
- flex-end 对齐主轴的终点,例如:row 情况下右对齐
- center 主轴内居中
- space-betwwen 均匀排列每个项目,首个项目放置于起点,末尾项目放置于终点
- space-around 均匀排列每个项目,每个项目周围分配相同的空间
align-items: stretch / flex-start / flex-end / center / baseline
指定项目在交叉轴的对齐方式
- stretch(默认)每个项目进行拉伸,知道所有项目大小铺满父容器
- flex-start 对齐交叉轴的起点
- flex-end 对齐交叉轴的终点
- center 交叉轴内居中
- baseline 在一行中,所有项目以首个项目的文字排版为基线对齐,仅在 flex-direction: row / row-reverse 生效
align-content: stretch / flex-start / flex-end / center / space-between / space-around
指定容器中存在多行情况下,在交叉轴上,行间对齐方式
- stretch(默认)行在交叉轴上的大小进行拉伸,铺满容器
- flex-start 行向交叉轴起点对齐
- flex-end 行向交叉轴终点对齐
- center 行在交叉轴上居中
- space-between 均匀排列每一行,第一行放置于起点,最后一行放置于终点
- space-around 均匀排列每一行,每一行周围分配相同的空间
项目属性
order: (默认 0)
指定项目的排列顺序
flex-grow: (默认 0)
指定项目的放大比例,默认为0,即如果存在剩余空间,也不进行放大。
flex-shrink: number (默认 1)
指定项目的缩小比例,默认为1,即在空间不足(仅当不换行时候起效),所有项目等比缩小,当设置为0,该项目不进行缩小。
flex-basis: number / auto(默认 auto)
指定项目的主轴的初始大小,auto 的含义是参考 width 或 height 的大小,
align-self: auto / stretch / flex-start / flex-end / center / base-line
指定项目在容器交叉轴的对齐方式,auto 为参照容器的 align-items,其余值和 align-items 介绍一致。
Flex 源码理解
凭借 ReactNative 的潮流,Yoga 迅速崛起,发展成了 flex 排版中的佼佼者。flex 设计始于W3C,逐渐被各大浏览器支持,所以像是 Webkit 这样的排版的代码是最开始的 flex 排版设计源码。我通过阅读 Webikit 的 RenderFlexibleBox 源码、Facebook Yoga 源码 和 Google 的 flexbox-layout 源码 了解 flex 排版的实现细节。这三者的思想和流程都是一致的,Webkit 的实现是最为全的,但是它受原有的其他属性所影响,看起来比较难理解,其他两个就比较纯粹一些。由于我最先接触 Yoga,所以这里以 Yoga 的代码为解析的源码进行分析,2017 年 5 月份的版本,到最新的版本中间有修一些 bug 和整体代码的结构化,但是整体关键内容还是一样的(主要是我看的时候忘记更新了,写了一大半)。当然,此时 Yoga 的代码写在一块了,晦涩难懂,这是 Yoga 不好的地方。
单位介绍
auto: YGUnitAuto / YGUnitUndefined 未设定,由父容器属性和子项目决定
百分比: YGUnitPercent 大小为父容器的宽度乘以设置的百分比得出来的值
数值: YGUnitPoint 大小为具体设置的数值
测量信息的模式
YGMeasureAtMost: 当前项目大小不确切,但有最大值限制
YGMeasureExactly: 当前项目的大小是确切可知的
YGMeasureUndefined: 当前项目大小不确定
项目空间的盒子模型
这个图看到的就是整个项目占据的空间。在盒模型中有个属性 box-sizing 用来定
- position 在当前盒子中项目四边的定位,优优先级 right > left & top > left。不会占据任何大小。
- margin 项目的外边距,项目空间大小的组成。
- border 项目的边框,项目空间大小的组成。
- padding 项目内边距,项目空间大小的组成。
- width & height,当 box-sizing 设为 content-box,指内部蓝色的区域。当 box-siziong 设为 boder-box时,指含 border 以内的区域。这两种模型是盒模型的演进遗物,必须要了解,否则后面有些会出现模棱两可的地方。Yoga 中默认是 border-box。
Yoga 特殊属性
不支持 order 。新增 aspect-ratio 横纵比设置,只有 width 或者 height 确定,就能确定另外一个变量。
排版测量概念
在排版引擎中有两个概念,layout 和 measure。在 Yoga 里面由于函数式代码的关系,看起来只有一个 Layout,但其实它也是具备这两个概念的。
measure 指测量项目所需要的大小。
layout 指将项目确定的放置在具体的 (x, y) 点
排版流程概述
在看细节代码前,先了解下整体的排版思路,对于看细节上对于前后代码能进行联系。Yoga 整体的思路是得到了当前项目的具体大小,然后获取子项目的大小(如果需要孙子项目确定则测量孙子项目),排版子项目,就这么从树节点一路排版下去,测量和排版阶段混合(混合主要的原因是 flex 中的位置属性也有可能引起大小的变化)。以下的流程存在递归逻辑。
- 入口函数由外层容器给出接下来的 flex 节点的可用空间,根据 flex 的根节点的 style 设置情况,确定给到根节点盒子大小,然后对根节点进行排版操作。
- 对于给定用于排版的空间,在排版或者测量阶段开始之前,先判断缓存的排版信息或者测量信息是否可用,如果没有或者不可用,则开始排版或者测量阶段。
- 在排版或者测量阶段,如果满足略过子项目的测量和排版的条件(没有孩子或本身具有外界设置的测量方法),则跳出该阶段。在测量阶段如果能满足不需要测量子项目就能知道大小,也跳出该阶段。否则继续下一步。
- 确定主轴和交叉轴的方向以及给子项目的可用空间,确定或者测量子项目的主轴大小(测量需要跳转第 2 步)。
- 根据 flex 的规则,进行行布局的排版或者测量,确定每一行的子项目数量,在当前行布局非伸缩子项目后主轴剩余空间,进行伸缩子项目的主轴大小伸缩,确定伸缩子项目的大小,重新测量伸缩子项目的孙子(跳转第 2 步)。
- 在当前行测量或排版阶段,根据主轴的排版方式 justify-content 确定子项目的在主轴的位置并计算项目主轴大小。统计交叉轴的大小,根据交叉轴的排版方式 align-items 确定子项目在交叉轴的位置,如果需要拉伸,则重新测量子项目。累计所有行的交叉轴的大小和最大主轴大小。如果有下一行则回到第 5 步继续下一行的计算,否则继续。
- 如果在排版阶段,根据 align-content 进行每一行的位置的确定,如果有拉伸操作,则需要重新排版该子项目。
- 根据自身 style 和孩子的测量大小确定项目的主轴和交叉轴的大小,如果在排版阶段,则进行绝对布局子项目的排版,以及确定其他子项目在所给空间上的位置。回到第 2 步的后续步骤第 9 步。
- 缓存测量或者排版的结果信息。
- 当整棵树排版完成后,进行整棵树的排版测量信息的取整。
实现细节
Yoga 代码的实现细节分析是基于 commit: f68b50bb4bc215edc45a10fda70a51028286f77e 的代码。
整体的实现非常长,多达 3500+ 行代码,而且每个步骤都是精华,所以还需要跟着以下步骤和思维一步一步跟下去,否则很容易迷失。
- 入口函数,从 YGJNI.cpp 的 YGNodeCalculateLayout 方法可以得到 Yoga 的入口是 YGNodeCalculateLayout 方法。
- 进入到 YGNodeCalculateLayout 方法后,这个入口是整体的入口,不是递归的入口。这里确定当前容器所给的宽高和其宽高的测量模式(这里的宽高代表盒子模型的大小),开始进行当前项目及其子项目的排版,当整体排版流程结束后,对于整个节点的排版信息进行四舍五入,确保是整型。
// Yoga 中设置的值为一个结构体,表示单位数值,包含了数值 value,和数值的单位
typedef struct YGValue {
float value;
YGUnit unit;
} YGValue;
// 获取项目的宽高尺寸,一般使用设置的宽高。如果最大值和最小值设置并相等,这使用该值作为尺寸。
static inline void YGResolveDimensions(YGNodeRef node) {
for (YGDimension dim = YGDimensionWidth; dim <= YGDimensionHeight; dim++) {
if (node->style.maxDimensions[dim].unit != YGUnitUndefined &&
YGValueEqual(node->style.maxDimensions[dim], node->style.minDimensions[dim])) {
node->resolvedDimensions[dim] = &node->style.maxDimensions[dim];
} else {
node->resolvedDimensions[dim] = &node->style.dimensions[dim];
}
}
}
// 根据单位算出真实值
static inline float YGResolveValue(const YGValue *const value, const float parentSize) {
switch (value->unit) {
case YGUnitUndefined:
case YGUnitAuto:
return YGUndefined; // 未定义
case YGUnitPoint:
return value->value; // 本身设置的值
case YGUnitPercent:
return value->value * parentSize / 100.0f; // 根据父亲百分比设置
}
return YGUndefined;
}
// 判断项目的在 style 中设置的尺寸是否是确切的,确切代表,单位不应该是 YGAuto 或 YGUndefined;如果单位
// 是 YGPoint,数值不能是负数;如果单位是百分比,数值也不能是负数。
static inline bool YGNodeIsStyleDimDefined(const YGNodeRef node,
const YGFlexDirection axis,
const float parentSize) {
return !(node->resolvedDimensions[dim[axis]]->unit == YGUnitAuto ||
node->resolvedDimensions[dim[axis]]->unit == YGUnitUndefined ||
(node->resolvedDimensions[dim[axis]]->unit == YGUnitPoint &&
node->resolvedDimensions[dim[axis]]->value < 0.0f) ||
(node->resolvedDimensions[dim[axis]]->unit == YGUnitPercent &&
(node->resolvedDimensions[dim[axis]]->value < 0.0f || YGFloatIsUndefined(parentSize))));
}
// 排版入口
void YGNodeCalculateLayout(const YGNodeRef node,
const float parentWidth,
const float parentHeight,
const YGDirection parentDirection) {
// 每一次进入 Yoga 的排版,这个值都自增并且被设置给每个项目,主要用于确保 dirty 的项目在父容器给定
// 空间不变时,只会被递归遍历一次。另外因为有些情况会略过子项目的大小测量或排版,例如当父项目宽度最大值为
// 0,在测量的时候就会被略过。后面可以理解到该属性的作用。
gCurrentGenerationCount++;
// 获取项目的尺寸
YGResolveDimensions(node);
// 确定宽度和宽度的模式
float width = YGUndefined;
YGMeasureMode widthMeasureMode = YGMeasureModeUndefined;
if (YGNodeIsStyleDimDefined(node, YGFlexDirectionRow, parentWidth)) {
// 如果项目的尺寸是确切的,则根据单位获取确切的大小,dim[YGFlexDirectionRow]=YGDimensionWidth
// 这里加上 margin 是要确保 availableWidth 是盒子的宽度。
width = YGResolveValue(node->resolvedDimensions[dim[YGFlexDirectionRow]], parentWidth) + YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth);
// 此处的尺寸模式为确切
widthMeasureMode = YGMeasureModeExactly;
} else if (YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth) >= 0.0f) {
// 如果项目的尺寸不是确切的,但是具有最大值,则取最大值。
width = YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth);
// 尺寸模式为有最大值限制
widthMeasureMode = YGMeasureModeAtMost;
} else {
// 如果以上两个条件都没有,宽度则使用父亲给定的宽度,项目的大小交由后续自身属性或孩子来决定。
width = parentWidth;
// 如果父亲尺寸为确定值,则尺寸模式为确切,否则尺寸模式为未知
widthMeasureMode = YGFloatIsUndefined(width) ? YGMeasureModeUndefined : YGMeasureModeExactly;
}
// 确定高度和高度尺寸的模式,和上述的宽度同理,代码也是类似,可自行对比。
float height = YGUndefined;
YGMeasureMode heightMeasureMode = YGMeasureModeUndefined;
if (YGNodeIsStyleDimDefined(node, YGFlexDirectionColumn, parentHeight)) {
height = YGResolveValue(node->resolvedDimensions[dim[YGFlexDirectionColumn]], parentHeight) +
YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth);
heightMeasureMode = YGMeasureModeExactly;
} else if (YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight) >= 0.0f) {
height = YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight);
heightMeasureMode = YGMeasureModeAtMost;
} else {
height = parentHeight;
heightMeasureMode = YGFloatIsUndefined(height) ? YGMeasureModeUndefined : YGMeasureModeExactly;
}
// 进入下一个环节
if (YGLayoutNodeInternal(node,
width,
height,
parentDirection,
widthMeasureMode,
heightMeasureMode,
parentWidth,
parentHeight,
true,
"initial",
node->config)) {
// 当所有节点都递归排版完毕,设置自身的位置
YGNodeSetPosition(node, node->layout.direction, parentWidth, parentHeight, parentWidth);
// 递归将所有节点的排版信息包括大小和位置均进行四舍五入,这里有很大学问
YGRoundToPixelGrid(node, node->config->pointScaleFactor, 0.0f, 0.0f);
if (gPrintTree) {
YGNodePrint(node, YGPrintOptionsLayout | YGPrintOptionsChildren | YGPrintOptionsStyle);
}
}
}
// 递归将所有节点的排版信息包括大小和位置均进行四舍五入
static void YGRoundToPixelGrid(const YGNodeRef node,
const float pointScaleFactor,
const float absoluteLeft,
const float absoluteTop) {
if (pointScaleFactor == 0.0f) {
return;
}
const float nodeLeft = node->layout.position[YGEdgeLeft];
const float nodeTop = node->layout.position[YGEdgeTop];
const float nodeWidth = node->layout.dimensions[YGDimensionWidth];
const float nodeHeight = node->layout.dimensions[YGDimensionHeight];
const float absoluteNodeLeft = absoluteLeft + nodeLeft;
const float absoluteNodeTop = absoluteTop + nodeTop;
const float absoluteNodeRight = absoluteNodeLeft + nodeWidth;
const float absoluteNodeBottom = absoluteNodeTop + nodeHeight;
// 如果自身拥有测量的方法,则不进行四舍五入,而是强行向上取整
const bool textRounding = node->nodeType == YGNodeTypeText;
node->layout.position[YGEdgeLeft] =
YGRoundValueToPixelGrid(nodeLeft, pointScaleFactor, false, textRounding);
node->layout.position[YGEdgeTop] =
YGRoundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding);
// 根据排版值最终确定大小,而不是直接强行强转测量大小
// 这里有一个场景,例如 父亲宽 200px,横向排版,具有三个 flex:1 的孩子,均分后的孩子宽度为
// 如果强行转测量大小,则孩子宽度为67、67、66,这就会出现和 web 不一样的结果,而按照这里的做法
// 则是67、66、67.
node->layout.dimensions[YGDimensionWidth] =
YGRoundValueToPixelGrid(absoluteNodeRight, pointScaleFactor, textRounding, false) -
YGRoundValueToPixelGrid(absoluteNodeLeft, pointScaleFactor, false, textRounding);
node->layout.dimensions[YGDimensionHeight] =
YGRoundValueToPixelGrid(absoluteNodeBottom, pointScaleFactor, textRounding, false) -
YGRoundValueToPixelGrid(absoluteNodeTop, pointScaleFactor, false, textRounding);
const uint32_t childCount = YGNodeListCount(node->children);
for (uint32_t i = 0; i < childCount; i++) {
YGRoundToPixelGrid(YGNodeGetChild(node, i), pointScaleFactor, absoluteNodeLeft, absoluteNodeTop);
}
}
复制代码
- 接下来的这个方法主要是用于判断是否需要重新进行项目空间计算(排版或者测量)的操作。判断的依据是本身是否已经排过版(脏标记是否更新),同时缓存的排版信息中父容器给定的可用宽高和模式与当前的的父容器给定的环境对比对于当前项目来说不需要重新进行测量重排,则可以使用缓存的信息,而不需要重新进行项目空间的计算。
// 入口
bool YGLayoutNodeInternal(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGDirection parentDirection,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight,
const bool performLayout,
const char *reason,
const YGConfigRef config) {
// 获取当前项目的排版信息
YGLayout *layout = &node->layout;
// 深度自增,没什么用
gDepth++;
// 判断是否需要重新进行项目计算,条件是以下两个其中一个
// 1.项目是脏的(需要重排),同时在一个大排版周期中项目还未被排版过( generationCount 在这里起了判断是
// 否排版过的作用);2.或者父亲排版方向改变了
const bool needToVisitNode =
(node->isDirty && layout->generationCount != gCurrentGenerationCount) ||
layout->lastParentDirection != parentDirection;
// 如果需要重新进行项目,则刷新缓存的数据
if (needToVisitNode) {
// 用于设定在一个排版周期中缓存排版的数量,这个最大值是16,代表复杂的排版可能会被重排版次数高达16次!
layout->nextCachedMeasurementsIndex = 0;
layout->cachedLayout.widthMeasureMode = (YGMeasureMode) -1;
layout->cachedLayout.heightMeasureMode = (YGMeasureMode) -1;
layout->cachedLayout.computedWidth = -1;
layout->cachedLayout.computedHeight = -1;
}
YGCachedMeasurement *cachedResults = NULL;
// 如果外部有设置测量函数则进入 if 函数。测量是一个非常耗时的操作,比如文字测量,所以能不能从缓存中获取非常重要
if (node->measure) {
// 横竖向的外边距
const float marginAxisRow = YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth);
const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth);
// 首先,判断能不能直接用当前的缓存的排版
if (YGNodeCanUseCachedMeasurement(widthMeasureMode,
availableWidth,
heightMeasureMode,
availableHeight,
layout->cachedLayout.widthMeasureMode,
layout->cachedLayout.availableWidth,
layout->cachedLayout.heightMeasureMode,
layout->cachedLayout.availableHeight,
layout->cachedLayout.computedWidth,
layout->cachedLayout.computedHeight,
marginAxisRow,
marginAxisColumn,
config)) {
cachedResults = &layout->cachedLayout;
} else {
// 将之前的缓存结果都拿出来看看是不是能用,这个能极大节省时间。
for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
if (YGNodeCanUseCachedMeasurement(widthMeasureMode,
availableWidth,
heightMeasureMode,
availableHeight,
layout->cachedMeasurements[i].widthMeasureMode,
layout->cachedMeasurements[i].availableWidth,
layout->cachedMeasurements[i].heightMeasureMode,
layout->cachedMeasurements[i].availableHeight,
layout->cachedMeasurements[i].computedWidth,
layout->cachedMeasurements[i].computedHeight,
marginAxisRow,
marginAxisColumn,
config)) {
cachedResults = &layout->cachedMeasurements[i];
break;
}
}
}
} else if (performLayout) {
// 如果是需要进行排版,则判断缓存的排版是否可用,判断可用标准是父亲给定的可用宽高及其模式没有变化
if (YGFloatsEqual(layout->cachedLayout.availableWidth, availableWidth) &&
YGFloatsEqual(layout->cachedLayout.availableHeight, availableHeight) &&
layout->cachedLayout.widthMeasureMode == widthMeasureMode &&
layout->cachedLayout.heightMeasureMode == heightMeasureMode) {
cachedResults = &layout->cachedLayout;
}
} else {
// 如果不是排版而是测量,则获取缓存的测量大小,判断可用标准父亲给定的可用宽高及其模式没有变化
for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
if (YGFloatsEqual(layout->cachedMeasurements[i].availableWidth, availableWidth) &&
YGFloatsEqual(layout->cachedMeasurements[i].availableHeight, availableHeight) &&
layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode &&
layout->cachedMeasurements[i].heightMeasureMode == heightMeasureMode) {
cachedResults = &layout->cachedMeasurements[i];
break;
}
}
}
if (!needToVisitNode && cachedResults != NULL)
// 如果不需要重新进行项目,同时有缓存就直接将缓存设置给 measuredDimensions (具体宽高)
layout->measuredDimensions[YGDimensionWidth] = cachedResults->computedWidth;
layout->measuredDimensions[YGDimensionHeight] = cachedResults->computedHeight;
if (gPrintChanges && gPrintSkips) {
printf("%s%d.{[skipped] ", YGSpacer(gDepth), gDepth);
if (node->print) {
node->print(node);
}
printf("wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n",
YGMeasureModeName(widthMeasureMode, performLayout),
YGMeasureModeName(heightMeasureMode, performLayout),
availableWidth,
availableHeight,
cachedResults->computedWidth,
cachedResults->computedHeight,
reason);
}
} else {
if (gPrintChanges) {
printf("%s%d.{%s", YGSpacer(gDepth), gDepth, needToVisitNode ? "*" : "");
if (node->print) {
node->print(node);
}
printf("wm: %s, hm: %s, aw: %f ah: %f %s\n",
YGMeasureModeName(widthMeasureMode, performLayout),
YGMeasureModeName(heightMeasureMode, performLayout),
availableWidth,
availableHeight,
reason);
}
// 如果需要重新进行项目测量或者排版,则进入下一环节
YGNodelayoutImpl(node,
availableWidth,
availableHeight,
parentDirection,
widthMeasureMode,
heightMeasureMode,
parentWidth,
parentHeight,
performLayout,
config);
if (gPrintChanges) {
printf("%s%d.}%s", YGSpacer(gDepth), gDepth, needToVisitNode ? "*" : "");
if (node->print) {
node->print(node);
}
printf("wm: %s, hm: %s, d: (%f, %f) %s\n",
YGMeasureModeName(widthMeasureMode, performLayout),
YGMeasureModeName(heightMeasureMode, performLayout),
layout->measuredDimensions[YGDimensionWidth],
layout->measuredDimensions[YGDimensionHeight],
reason);
}
// 记录当前父容器方向
layout->lastParentDirection = parentDirection;
// 如果缓存为空,设置缓存
if (cachedResults == NULL) {
// 缓存超出了可设置大小,代表之前的都没啥用,重新记录缓存
if (layout->nextCachedMeasurementsIndex == YG_MAX_CACHED_RESULT_COUNT) {
if (gPrintChanges) {
printf("Out of cache entries!\n");
}
layout->nextCachedMeasurementsIndex = 0;
}
// 获取需要更新缓存的入口,如果是排版,则获取排版单一的缓存入口,如果是测量,则获取当前指向的入口
YGCachedMeasurement *newCacheEntry;
if (performLayout) {
newCacheEntry = &layout->cachedLayout;
} else {
newCacheEntry = &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex];
layout->nextCachedMeasurementsIndex++;
}
// 更新相关参数
newCacheEntry->availableWidth = availableWidth;
newCacheEntry->availableHeight = availableHeight;
newCacheEntry->widthMeasureMode = widthMeasureMode;
newCacheEntry->heightMeasureMode = heightMeasureMode;
newCacheEntry->computedWidth = layout->measuredDimensions[YGDimensionWidth];
newCacheEntry->computedHeight = layout->measuredDimensions[YGDimensionHeight];
}
}
if (performLayout) {
// 如果是排版则记录排版的大小,更新脏标志。
node->layout.dimensions[YGDimensionWidth] = node->layout.measuredDimensions[YGDimensionWidth];
node->layout.dimensions[YGDimensionHeight] = node->layout.measuredDimensions[YGDimensionHeight];
node->hasNewLayout = true;
node->isDirty = false;
}
gDepth--;
layout->generationCount = gCurrentGenerationCount;
// 返回 true 是排版了,false 是跳过了使用缓存。
return (needToVisitNode || cachedResults == NULL);
}
// 判断当前缓存的测量值是否可用
bool YGNodeCanUseCachedMeasurement(const YGMeasureMode widthMode,
const float width,
const YGMeasureMode heightMode,
const float height,
const YGMeasureMode lastWidthMode,
const float lastWidth,
const YGMeasureMode lastHeightMode,
const float lastHeight,
const float lastComputedWidth,
const float lastComputedHeight,
const float marginRow,
const float marginColumn,
const YGConfigRef config) {
if (lastComputedHeight < 0 || lastComputedWidth < 0) {
return false;
}
bool useRoundedComparison = config != NULL && config->pointScaleFactor != 0;
const float effectiveWidth = useRoundedComparison ? YGRoundValueToPixelGrid(width, config->pointScaleFactor, false, false) : width;
const float effectiveHeight = useRoundedComparison ? YGRoundValueToPixelGrid(height, config->pointScaleFactor, false, false) : height;
const float effectiveLastWidth = useRoundedComparison ? YGRoundValueToPixelGrid(lastWidth, config->pointScaleFactor, false, false) : lastWidth;
const float effectiveLastHeight = useRoundedComparison ? YGRoundValueToPixelGrid(lastHeight, config->pointScaleFactor, false, false) : lastHeight;
// 1. 判断宽高和其模式是否相等
const bool hasSameWidthSpec = lastWidthMode == widthMode && YGFloatsEqual(effectiveLastWidth, effectiveWidth);
const bool hasSameHeightSpec = lastHeightMode == heightMode && YGFloatsEqual(effectiveLastHeight, effectiveHeight);
const bool widthIsCompatible =
hasSameWidthSpec ||
// 2. 当前宽度模式为确切,同时缓存的计算宽度和给出的宽度是相同的。
YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(widthMode, width - marginRow, lastComputedWidth) ||
// 3. 当前宽度模式为最大值,缓存宽度模式为未知,同时所给可用宽度大小大于或等于缓存的计算宽度
YGMeasureModeOldSizeIsUnspecifiedAndStillFits(widthMode,
width - marginRow,
lastWidthMode,
lastComputedWidth) ||
// 4. 当前宽度模式和缓存宽度模式均为最大范围,缓存可用宽度值大于当前可用宽度值,同时缓存的计算宽度
// 小于或等于当前可用宽度
YGMeasureModeNewMeasureSizeIsStricterAndStillValid(
widthMode, width - marginRow, lastWidthMode, lastWidth, lastComputedWidth);
// 同宽度分析
const bool heightIsCompatible =
hasSameHeightSpec || YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(heightMode,
height - marginColumn,
lastComputedHeight) ||
YGMeasureModeOldSizeIsUnspecifiedAndStillFits(heightMode,
height - marginColumn,
lastHeightMode,
lastComputedHeight) ||
YGMeasureModeNewMeasureSizeIsStricterAndStillValid(
heightMode, height - marginColumn, lastHeightMode, lastHeight, lastComputedHeight);
// 返回宽度和高度是否仍然适用
return widthIsCompatible && heightIsCompatible;
}
// 这个方法主要用来进行数值的四舍五入,要根据 pointScaleFactor 进行数值的四舍五入。防止直接对数值进行四
// 舍五入导致之后的换算回来有问题。简而言之根据缩放比率进行四舍五入,得到缩放比率同等级别的精度。可以保证
// 在不同大小的分辨率情况下不会出现可能左右偏移一个像素
static float YGRoundValueToPixelGrid(const float value,
const float pointScaleFactor,
const bool forceCeil,
const bool forceFloor) {
float fractial = fmodf(value, pointScaleFactor);
if (YGFloatsEqual(fractial, 0)) {
return value - fractial;
}
if (forceCeil) {
return value - fractial + pointScaleFactor;
} else if (forceFloor) {
return value - fractial;
} else {
return value - fractial + (fractial >= pointScaleFactor / 2.0f ? pointScaleFactor : 0);
}
}
// 当前宽高模式为确切,同时缓存的计算宽高和给出的宽高是相同的,代表确切值可用,返回true
static inline bool YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(YGMeasureMode sizeMode,
float size,
float lastComputedSize) {
return sizeMode == YGMeasureModeExactly && YGFloatsEqual(size, lastComputedSize);
}
// 当前宽高模式为最大值,缓存宽高模式为未知,同时所给的宽高大小大于或等于缓存的宽高,代表未知模式下测出来的值,在具有最大范围模式下仍然适用,返回true
static inline bool YGMeasureModeOldSizeIsUnspecifiedAndStillFits(YGMeasureMode sizeMode,
float size,
YGMeasureMode lastSizeMode,
float lastComputedSize) {
return sizeMode == YGMeasureModeAtMost && lastSizeMode == YGMeasureModeUndefined &&
(size >= lastComputedSize || YGFloatsEqual(size, lastComputedSize));
}
// 当前宽度模式和缓存宽度模式均为最大范围,缓存可用宽度值大于当前可用宽度值,同时缓存的计算宽度小于或等于当前可用宽度,当前情况宽度测量的结果必定一样,仍然适用,则返回true
static inline bool YGMeasureModeNewMeasureSizeIsStricterAndStillValid(YGMeasureMode sizeMode,
float size,
YGMeasureMode lastSizeMode,
float lastSize,
float lastComputedSize) {
return lastSizeMode == YGMeasureModeAtMost && sizeMode == YGMeasureModeAtMost &&
lastSize > size && (lastComputedSize <= size || YGFloatsEqual(size, lastComputedSize));
}
复制代码
- 接下来的代码分析都在 YGNodelayoutImpl 方法中,这个方法中包含了整个 flex 排版的精髓,主要用于测量自身大小,同时排版子项目。整体代码非常长,这里将代码分段介绍,直到结束。首先的操作盒子模型中 margin / border / padding,如果本身可以确定大小,同时不是在排版流程时或者是具有自身测量的方法则直接返回跳出后续的代码,因为后续的子项目暂时不需要进行大小测量(排版的流程是需要先知道子项目的大小,然后就进行子项目位置确定,子项目的子项目的大小可以延后等到子项目进行位置确定阶段)。
static void YGNodelayoutImpl(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGDirection parentDirection,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight,
const bool performLayout,
const YGConfigRef config) {
YGAssertWithNode(node,
YGFloatIsUndefined(availableWidth) ? widthMeasureMode == YGMeasureModeUndefined
: true,
"availableWidth is indefinite so widthMeasureMode must be "
"YGMeasureModeUndefined");
YGAssertWithNode(node,
YGFloatIsUndefined(availableHeight) ? heightMeasureMode == YGMeasureModeUndefined
: true,
"availableHeight is indefinite so heightMeasureMode must be "
"YGMeasureModeUndefined");
// 确定当前项目的方向,如RTL / LTR,如果是继承父亲,则使用父亲的。
const YGDirection direction = YGNodeResolveDirection(node, parentDirection);
node->layout.direction = direction;
// 根据项目方向,确定横竖轴方向,如 row / row-reverse
const YGFlexDirection flexRowDirection = YGResolveFlexDirection(YGFlexDirectionRow, direction);
const YGFlexDirection flexColumnDirection =
YGResolveFlexDirection(YGFlexDirectionColumn, direction);
// 盒子模型中边界都用 edge 表示,这样在横竖轴方向可以起到泛指的作用,否则容易迷糊。比如 EdgeStart 表
// 示起始位置,它代表 row 情况下盒子左侧,row-reverse 情况下盒子右侧。
// 计算这些边距值时由于设置的多样性,例如 padding: 10px 10px 或者 padding: 10px。就导致了 Yoga
// 在处理时化成了 YGEdgeVertical 或者 YGEdgeAll 这样去判断这些值是否设置。
node->layout.margin[YGEdgeStart] = YGNodeLeadingMargin(node, flexRowDirection, parentWidth);
node->layout.margin[YGEdgeEnd] = YGNodeTrailingMargin(node, flexRowDirection, parentWidth);
node->layout.margin[YGEdgeTop] = YGNodeLeadingMargin(node, flexColumnDirection, parentWidth);
node->layout.margin[YGEdgeBottom] = YGNodeTrailingMargin(node, flexColumnDirection, parentWidth);
node->layout.border[YGEdgeStart] = YGNodeLeadingBorder(node, flexRowDirection);
node->layout.border[YGEdgeEnd] = YGNodeTrailingBorder(node, flexRowDirection);
node->layout.border[YGEdgeTop] = YGNodeLeadingBorder(node, flexColumnDirection);
node->layout.border[YGEdgeBottom] = YGNodeTrailingBorder(node, flexColumnDirection);
node->layout.padding[YGEdgeStart] = YGNodeLeadingPadding(node, flexRowDirection, parentWidth);
node->layout.padding[YGEdgeEnd] = YGNodeTrailingPadding(node, flexRowDirection, parentWidth);
node->layout.padding[YGEdgeTop] = YGNodeLeadingPadding(node, flexColumnDirection, parentWidth);
node->layout.padding[YGEdgeBottom] =
YGNodeTrailingPadding(node, flexColumnDirection, parentWidth);
// 当然项目设置了测量的方法,则跳转到第 5 步,然后跳出排版步骤。这里默认有测量方式的项目都不具备孩子,即无论对于测量还是排版,都不需要往后继续遍历。
if (node->measure) {
YGNodeWithMeasureFuncSetMeasuredDimensions(node,
availableWidth,
availableHeight,
widthMeasureMode,
heightMeasureMode,
parentWidth,
parentHeight);
return;
}
const uint32_t childCount = YGNodeListCount(node->children);
// 当项目的孩子数量为0时,跳转到第 6 步,然后跳出排版步骤。这里默认没有孩子的项目都不需要往后继续遍历,
// 因为不需要为后面的孩子进行排版,只需要在这一步获得自身大小即可。
if (childCount == 0) {
YGNodeEmptyContainerSetMeasuredDimensions(node,
availableWidth,
availableHeight,
widthMeasureMode,
heightMeasureMode,
parentWidth,
parentHeight);
return;
}
// 当不需要进行子项目排版,同时项目大小可以马上确定(请看第 7 步),则直接跳出排版步骤。
if (!performLayout && YGNodeFixedSizeSetMeasuredDimensions(node,
availableWidth,
availableHeight,
widthMeasureMode,
heightMeasureMode,
parentWidth,
parentHeight)) {
return;
}
复制代码
- 当项目设置了测量方法,当可用空间不是未定义时,去除盒子模型的边距,获得剩余宽高。当宽高都是确切的,则直接使用宽高值并进行阈值限制。否则通过测量方法进行测量,在进行阈值限制。具有测量方法的项目默认孩子位置由其自身管理。
static void YGNodeWithMeasureFuncSetMeasuredDimensions(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight) {
YGAssertWithNode(node, node->measure != NULL, "Expected node to have custom measure function");
// 计算主轴交叉轴上的边距和边框
const float paddingAndBorderAxisRow =
YGNodePaddingAndBorderForAxis(node, YGFlexDirectionRow, availableWidth);
const float paddingAndBorderAxisColumn =
YGNodePaddingAndBorderForAxis(node, YGFlexDirectionColumn, availableWidth);
const float marginAxisRow = YGNodeMarginForAxis(node, YGFlexDirectionRow, availableWidth);
const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn, availableWidth);
// 当可用空间不是未定义时,去除边距和边框的内部宽高
const float innerWidth = YGFloatIsUndefined(availableWidth)
? availableWidth
: fmaxf(0, availableWidth - marginAxisRow - paddingAndBorderAxisRow);
const float innerHeight = YGFloatIsUndefined(availableHeight)
? availableHeight
: fmaxf(0, availableHeight - marginAxisColumn - paddingAndBorderAxisColumn);
if (widthMeasureMode == YGMeasureModeExactly && heightMeasureMode == YGMeasureModeExactly) {
// 当宽高都是确切的,则不需要经过测量的步骤,直接使用确切的宽高(availableWidth - marginAxisRow
// 就是确切的宽高,第 2 步使有阐述),确保确切宽高是在限制的阈值内
node->layout.measuredDimensions[YGDimensionWidth] = YGNodeBoundAxis(
node, YGFlexDirectionRow, availableWidth - marginAxisRow, parentWidth, parentWidth);
node->layout.measuredDimensions[YGDimensionHeight] = YGNodeBoundAxis(
node, YGFlexDirectionColumn, availableHeight - marginAxisColumn, parentHeight, parentWidth);
} else {
// 如果宽高不确定,则需要调用测量的方法确定大小,测量传入的可用宽高是去除了边框和边距的。
const YGSize measuredSize =
node->measure(node, innerWidth, widthMeasureMode, innerHeight, heightMeasureMode);
// 将获得的测量值进行阈值限制,同时如果模式是确切的,则使用确切值 (availableWidth - marginAxisRow),否则使用测量值 measureSize。
node->layout.measuredDimensions[YGDimensionWidth] =
YGNodeBoundAxis(node,
YGFlexDirectionRow,
(widthMeasureMode == YGMeasureModeUndefined ||
widthMeasureMode == YGMeasureModeAtMost)
? measuredSize.width + paddingAndBorderAxisRow
: availableWidth - marginAxisRow,
availableWidth,
availableWidth);
node->layout.measuredDimensions[YGDimensionHeight] =
YGNodeBoundAxis(node,
YGFlexDirectionColumn,
(heightMeasureMode == YGMeasureModeUndefined ||
heightMeasureMode == YGMeasureModeAtMost)
? measuredSize.height + paddingAndBorderAxisColumn
: availableHeight - marginAxisColumn,
availableHeight,
availableWidth);
}
}
// 确保确切的宽高不会超过最大值,同时不小于最小值。另外在 boder-box 中当内边距和边框的值大于宽高值则使用
// 前者作为宽高。
static inline float YGNodeBoundAxis(const YGNodeRef node,
const YGFlexDirection axis,
const float value,
const float axisSize,
const float widthSize) {
return fmaxf(YGNodeBoundAxisWithinMinAndMax(node, axis, value, axisSize),
YGNodePaddingAndBorderForAxis(node, axis, widthSize));
}
// 获取前沿和后沿的内边距和边框的和值,widthSize 这里对于主轴和交叉轴都是一样,原因是边距和边框设置的
// 百分比是根据项目宽度计算真实值。(敲黑板),接下来的代码就是获取边距和边框然后确定其值。
static inline float YGNodePaddingAndBorderForAxis(const YGNodeRef node,
const YGFlexDirection axis,
const float widthSize) {
return YGNodeLeadingPaddingAndBorder(node, axis, widthSize) +
YGNodeTrailingPaddingAndBorder(node, axis, widthSize);
}
复制代码
- 当项目的孩子数量为0时,项目的宽高仅有本身决定,即有确切宽高或限制宽高或内边距和边框则作为自身大小,否则自身大小就为 0。
static void YGNodeEmptyContainerSetMeasuredDimensions(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight) {
const float paddingAndBorderAxisRow =
YGNodePaddingAndBorderForAxis(node, YGFlexDirectionRow, parentWidth);
const float paddingAndBorderAxisColumn =
YGNodePaddingAndBorderForAxis(node, YGFlexDirectionColumn, parentWidth);
const float marginAxisRow = YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth);
const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth);
// 计算盒模型 width 和 height 时,
// 1. 确切宽高。当项目具有确切宽高时,使用确切宽高,并进行阈值限制。
// 2. 如果没有确切宽高,则使用内边距和边框的和值,并进行阈值限制。
node->layout.measuredDimensions[YGDimensionWidth] =
YGNodeBoundAxis(node,
YGFlexDirectionRow,
(widthMeasureMode == YGMeasureModeUndefined ||
widthMeasureMode == YGMeasureModeAtMost)
? paddingAndBorderAxisRow
: availableWidth - marginAxisRow,
parentWidth,
parentWidth);
node->layout.measuredDimensions[YGDimensionHeight] =
YGNodeBoundAxis(node,
YGFlexDirectionColumn,
(heightMeasureMode == YGMeasureModeUndefined ||
heightMeasureMode == YGMeasureModeAtMost)
? paddingAndBorderAxisColumn
: availableHeight - marginAxisColumn,
parentHeight,
parentWidth);
}
复制代码
- 当项目不需要进行排版,需要确定项目大小是否可以马上确定,如果可以马上确定,则返回 true,否则返回false。马上确定宽高的标准为,1. 宽或高模式为最大值且值为0,可以确定。2. 宽和高模式同时为确切。后续孩子的大小及排版(影响当前项目的大小)由当前项目在排版阶段遍历即可知道。
static bool YGNodeFixedSizeSetMeasuredDimensions(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight) {
// 这一步其实比较奇怪,因为本身即使为宽或高 0 ,该项目其中一个大小可能还是收子项目大小影响,但这里把这一步省略了,放到了排版阶段,这样在排版阶段又会引起一次大小的变化。如果能改成如下,在排版阶段不会影响父亲的大小,这样我想会更明了。
// if (((widthMeasureMode == YGMeasureModeAtMost && availableWidth <= 0.0f) &&
// (heightMeasureMode == YGMeasureModeAtMost && availableHeight <= 0.0f)) ||
// (widthMeasureMode == YGMeasureModeExactly && heightMeasureMode == YGMeasureModeExactly))
if ((widthMeasureMode == YGMeasureModeAtMost && availableWidth <= 0.0f) ||
(heightMeasureMode == YGMeasureModeAtMost && availableHeight <= 0.0f) ||
(widthMeasureMode == YGMeasureModeExactly && heightMeasureMode == YGMeasureModeExactly)) {
const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth);
const float marginAxisRow = YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth);
// 当项目的可用宽高是未知,而且模式是最大值的情况下可用宽高为 0, 那么使用 0 作为测量值,并进行阈值判
// 断。否则就代表宽高为确切,使用确切宽高(availableWidth - marginAxisRow)。
node->layout.measuredDimensions[YGDimensionWidth] =
YGNodeBoundAxis(node,
YGFlexDirectionRow,
YGFloatIsUndefined(availableWidth) ||
(widthMeasureMode == YGMeasureModeAtMost && availableWidth < 0.0f)
? 0.0f
: availableWidth - marginAxisRow,
parentWidth,
parentWidth);
node->layout.measuredDimensions[YGDimensionHeight] =
YGNodeBoundAxis(node,
YGFlexDirectionColumn,
YGFloatIsUndefined(availableHeight) ||
(heightMeasureMode == YGMeasureModeAtMost && availableHeight < 0.0f)
? 0.0f
: availableHeight - marginAxisColumn,
parentHeight,
parentWidth);
// 返回 true 代表不用进行子项目遍历,可以知道自己的大小
return true;
}
// 返回 false 代表不用进行子项目遍历,不知道自己的大小
return false;
}
复制代码
- 接下来的步骤就是确定主轴和交叉轴的方向,计算项目本身对于孩子而言在主轴和交叉轴可用空间包括最大值和最小值。
// 确定主轴和交叉轴的方向
const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction);
const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction);
const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
const YGJustify justifyContent = node->style.justifyContent;
const bool isNodeFlexWrap = node->style.flexWrap != YGWrapNoWrap;
// 确定主轴和交叉轴父容器提供的空间大小
const float mainAxisParentSize = isMainAxisRow ? parentWidth : parentHeight;
const float crossAxisParentSize = isMainAxisRow ? parentHeight : parentWidth;
// 用于记录 absolute 的子项目,在排版最后再进行这些项目的排版。
YGNodeRef firstAbsoluteChild = NULL;
YGNodeRef currentAbsoluteChild = NULL;
// 确定主轴和交叉轴上的内外边距和边框
const float leadingPaddingAndBorderMain =
YGNodeLeadingPaddingAndBorder(node, mainAxis, parentWidth);
const float trailingPaddingAndBorderMain =
YGNodeTrailingPaddingAndBorder(node, mainAxis, parentWidth);
const float leadingPaddingAndBorderCross =
YGNodeLeadingPaddingAndBorder(node, crossAxis, parentWidth);
const float paddingAndBorderAxisMain = YGNodePaddingAndBorderForAxis(node, mainAxis, parentWidth);
const float paddingAndBorderAxisCross =
YGNodePaddingAndBorderForAxis(node, crossAxis, parentWidth);
// 确定主轴和交叉轴的测量模式
YGMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode;
YGMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode;
// 确定横竖方向上的内边距和边框和值
const float paddingAndBorderAxisRow =
isMainAxisRow ? paddingAndBorderAxisMain : paddingAndBorderAxisCross;
const float paddingAndBorderAxisColumn =
isMainAxisRow ? paddingAndBorderAxisCross : paddingAndBorderAxisMain;
// 确定横竖方向上的外边距
const float marginAxisRow = YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth);
const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth);
// 根据最大最小大小,去除横竖方向上的内外边距,(这里我删了 - marginAxisRow 这个,因为是一个 bug,后面
// 被修复了),得出项目内部的最大最小宽度和高度的限制,在后续给子项目排版时用到。
const float minInnerWidth =
YGResolveValue(&node->style.minDimensions[YGDimensionWidth], parentWidth) -
paddingAndBorderAxisRow;
const float maxInnerWidth =
YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth) -
paddingAndBorderAxisRow;
const float minInnerHeight =
YGResolveValue(&node->style.minDimensions[YGDimensionHeight], parentHeight) - paddingAndBorderAxisColumn;
const float maxInnerHeight =
YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight) - paddingAndBorderAxisColumn;
// 换算成主轴空间的最大最小限制
const float minInnerMainDim = isMainAxisRow ? minInnerWidth : minInnerHeight;
const float maxInnerMainDim = isMainAxisRow ? maxInnerWidth : maxInnerHeight;
// 确定该项目可用的内部宽度,计算方式整个盒子的大小去除边距和边框
float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow;
if (!YGFloatIsUndefined(availableInnerWidth)) {
// 如果不是未定义,那么进行阈值限制。获得在限定大小内的可用空间
availableInnerWidth = fmaxf(fminf(availableInnerWidth, maxInnerWidth), minInnerWidth);
}
// 同可用内部宽度计算方式
float availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn;
if (!YGFloatIsUndefined(availableInnerHeight)) {
availableInnerHeight = fmaxf(fminf(availableInnerHeight, maxInnerHeight), minInnerHeight);
}
// 换算成主轴和交叉轴可用空间大小
float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight;
const float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth;
// singleFlexChild 具体作用需要往后继续看,后续的代码会将这个 child 的 computedFlexBasis 设置为 0,即
// flex-basis 为 0,意思是只要满足 flex-grow 和 flex-shrink 都大于 0,那么就认为剩余空间为父亲的大小,
// child 直接填充整个剩余空间。
// 但是,在 web 上,当仅有单个 child 并且满足上述条件,如果大小超出去了,flex-shrink 范围在(0, 1),之间
// 它不会撑满父亲,而是大于父亲,如父亲 width: 100px,孩子 width: 150px; flex-shrink: 0.5,那么计算
// 得出来的结果孩子的大小为 125px。(和 web 表现不一致,算是Yoga的一个 bug)
YGNodeRef singleFlexChild = NULL;
if (measureModeMainDim == YGMeasureModeExactly) {
for (uint32_t i = 0; i < childCount; i++) {
const YGNodeRef child = YGNodeGetChild(node, i);
if (singleFlexChild) {
if (YGNodeIsFlex(child)) {
singleFlexChild = NULL;
break;
}
} else if (YGResolveFlexGrow(child) > 0.0f && YGNodeResolveFlexShrink(child) > 0.0f) {
singleFlexChild = child;
}
}
}
float totalOuterFlexBasis = 0;
复制代码
- 确定每个子项目的 flex-basis 大小,便于后续根据 flex-basis 做拉伸的计算
for (uint32_t i = 0; i < childCount; i++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
// 如果是 display:none 则代表其孩子全部不需要展示,则递归遍历孩子并设置 0 排版,并更新脏标志。
if (child->style.display == YGDisplayNone) {
YGZeroOutLayoutRecursivly(child);
child->hasNewLayout = true;
child->isDirty = false;
continue;
}
// 确定子项目的设置大小
YGResolveDimensions(child);
if (performLayout) {
// 如果是排版操作,则设置子项目的排版的位置 layout.position (不是盒模型的 position),
const YGDirection childDirection = YGNodeResolveDirection(child, direction);
YGNodeSetPosition(child,
childDirection,
availableInnerMainDim,
availableInnerCrossDim,
availableInnerWidth);
}
if (child->style.positionType == YGPositionTypeAbsolute) {
// absolute 的子项目不参与 flex 排版,用链表方式记录,便于之后拿出来进行另外的排版
if (firstAbsoluteChild == NULL) {
firstAbsoluteChild = child;
}
if (currentAbsoluteChild != NULL) {
currentAbsoluteChild->nextChild = child;
}
currentAbsoluteChild = child;
child->nextChild = NULL;
} else {
// 如果不是 absolute 项目
if (child == singleFlexChild) {
// 如果子项目是唯一的拥有 flex 伸缩属性的项目,则将 computedFlexBasis 设置为 0
child->layout.computedFlexBasisGeneration = gCurrentGenerationCount;
child->layout.computedFlexBasis = 0;
} else {
// 计算单个子项目的 flex-basis,跳到第 10 步
YGNodeComputeFlexBasisForChild(node,
child,
availableInnerWidth,
widthMeasureMode,
availableInnerHeight,
availableInnerWidth,
availableInnerHeight,
heightMeasureMode,
direction,
config);
}
}
// 计算总体需要的主轴空间 flex-basis
totalOuterFlexBasis +=
child->layout.computedFlexBasis + YGNodeMarginForAxis(child, mainAxis, availableInnerWidth);
}
// 总体的 flex-basis 是否超出可用空间
const bool flexBasisOverflows = measureModeMainDim == YGMeasureModeUndefined
? false
: totalOuterFlexBasis > availableInnerMainDim;
// 如果是项目有换行,同时子项目需要的主轴空间超过了可用空间,同时测量模式是最大值,则将主轴的测量模式设
// 为确切的。因为总的子项目需要的超过了最大可用空间的,就按照最大值的确切的模式去计算子项目空间。
if (isNodeFlexWrap && flexBasisOverflows && measureModeMainDim == YGMeasureModeAtMost) {
measureModeMainDim = YGMeasureModeExactly;
}
复制代码
- 测量单个子项目的 flex-basis,这里的代码有些冗余,传入的参数就可以看出来。在这个方法里主要是获取主轴大小 flex-basis,它依赖于 style 中设置的值,或者是主轴方向设定的宽度或者高度,如果均未定义,则需要通过测量来自来确定 flex-basis。
static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, // 当前项目
const YGNodeRef child, // 子项目
const float width, // 当前项目可用宽度
const YGMeasureMode widthMode,
const float height,// 当前项目可用高度
const float parentWidth, // 当前项目可用宽度
const float parentHeight, // 当前项目可用高度
const YGMeasureMode heightMode,
const YGDirection direction,
const YGConfigRef config) {
// 确定主轴方向
const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction);
// 确定主轴是否横向
const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
// 确定主轴空间,下面两者是相等的,都是当前项目的主轴可用空间,冗余代码。
const float mainAxisSize = isMainAxisRow ? width : height;
const float mainAxisParentSize = isMainAxisRow ? parentWidth : parentHeight;
float childWidth;
float childHeight;
YGMeasureMode childWidthMeasureMode;
YGMeasureMode childHeightMeasureMode;
// 确定子项目主轴初始化大小
const float resolvedFlexBasis =
YGResolveValue(YGNodeResolveFlexBasisPtr(child), mainAxisParentSize);
// 子项目横纵向大小是否设置
const bool isRowStyleDimDefined = YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, parentWidth);
const bool isColumnStyleDimDefined =
YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, parentHeight);
if (!YGFloatIsUndefined(resolvedFlexBasis) && !YGFloatIsUndefined(mainAxisSize)) {
// 如果主轴初始化大小确定的,同时项目给予子项目的主轴空间是确定的,设置子项目的 layout.computedFlexBasis 为确定的值,同时保证能兼容内边距和边框
if (YGFloatIsUndefined(child->layout.computedFlexBasis) ||
(YGConfigIsExperimentalFeatureEnabled(child->config, YGExperimentalFeatureWebFlexBasis) &&
child->layout.computedFlexBasisGeneration != gCurrentGenerationCount)) {
child->layout.computedFlexBasis =
fmaxf(resolvedFlexBasis, YGNodePaddingAndBorderForAxis(child, mainAxis, parentWidth));
}
} else if (isMainAxisRow && isRowStyleDimDefined) {
// 主轴是横向,同时子项目的横向大小确定,flex-basis 为 auto 时参照子项目的 width,保证兼容内边距和边框。
child->layout.computedFlexBasis =
fmaxf(YGResolveValue(child->resolvedDimensions[YGDimensionWidth], parentWidth),
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, parentWidth));
} else if (!isMainAxisRow && isColumnStyleDimDefined) {
// 主轴是竖向,同时子项目的竖向大小确定,flex-basis 为 auto 时参照子项目的 height,保证兼容内边距和边框。
child->layout.computedFlexBasis =
fmaxf(YGResolveValue(child->resolvedDimensions[YGDimensionHeight], parentHeight),
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, parentWidth));
} else {
// 设置子项目初始值,childWidth childHeight 指子项目怎个盒子模型大小
childWidth = YGUndefined;
childHeight = YGUndefined;
childWidthMeasureMode = YGMeasureModeUndefined;
childHeightMeasureMode = YGMeasureModeUndefined;
// 确定子项目横竖向的外边距
const float marginRow = YGNodeMarginForAxis(child, YGFlexDirectionRow, parentWidth);
const float marginColumn = YGNodeMarginForAxis(child, YGFlexDirectionColumn, parentWidth);
// 当子项目宽高是被设定的,则直接使用设定值,并且测量模式设置为确切的
if (isRowStyleDimDefined) {
childWidth =
YGResolveValue(child->resolvedDimensions[YGDimensionWidth], parentWidth) + marginRow;
childWidthMeasureMode = YGMeasureModeExactly;
}
if (isColumnStyleDimDefined) {
childHeight =
YGResolveValue(child->resolvedDimensions[YGDimensionHeight], parentHeight) + marginColumn;
childHeightMeasureMode = YGMeasureModeExactly;
}
// 当主轴是竖向,同时 overflow 模式是 scroll。或者 overflow 不是 scroll。同时子项目宽度未定义
// 和该项目可用宽度是定义的,则子项目使用该项目的可用空间,并设置测量模式为最大值。
// 这个没有在 W3C 的标准中,但是主流的浏览器都支持这个逻辑。这种情况可以以 scrollview 为例思考一
// 下,子项目交叉轴的最大距离不应该超过父项目可用的大小,另外如果本身 div 不支持 scroll,那么给子项
// 目的可用空间也应该是子项目的最大可用空间。
if ((!isMainAxisRow && node->style.overflow == YGOverflowScroll) ||
node->style.overflow != YGOverflowScroll) {
if (YGFloatIsUndefined(childWidth) && !YGFloatIsUndefined(width)) {
childWidth = width;
childWidthMeasureMode = YGMeasureModeAtMost;
}
}
// 当主轴是横向,同时 overflow 模式是 scroll。或者 overflow 不是 scroll。同时子项目宽度未定义
// 和该项目可用宽度是定义的,则子项目使用该项目的可用空间,并设置测量模式为最大值。
if ((isMainAxisRow && node->style.overflow == YGOverflowScroll) ||
node->style.overflow != YGOverflowScroll) {
if (YGFloatIsUndefined(childHeight) && !YGFloatIsUndefined(height)) {
childHeight = height;
childHeightMeasureMode = YGMeasureModeAtMost;
}
}
// 在项目的交叉轴上,项目的交叉轴空间是确切的,而子项目的方向大小没有设定,同时子项目的 align-self
// 和项目的 align-items 得出的结果是 stretch 拉伸,则子项目的交叉轴上的大小应该设置为项目的交叉轴
// 大小,并设置模式为确切的。
if (!isMainAxisRow && !YGFloatIsUndefined(width) && !isRowStyleDimDefined &&
widthMode == YGMeasureModeExactly && YGNodeAlignItem(node, child) == YGAlignStretch) {
childWidth = width;
childWidthMeasureMode = YGMeasureModeExactly;
}
if (isMainAxisRow && !YGFloatIsUndefined(height) && !isColumnStyleDimDefined &&
heightMode == YGMeasureModeExactly && YGNodeAlignItem(node, child) == YGAlignStretch) {
childHeight = height;
childHeightMeasureMode = YGMeasureModeExactly;
}
// 如果子项目横纵比有设置,则主轴上的 flex-basis 可根据确切的横纵值去设置。
// 但是这里为什么要返回呢?为什么不遍历孩子?aspectRatio 这个是 Yoga 自己的属性,浏览器没有,
if (!YGFloatIsUndefined(child->style.aspectRatio)) {
// 主轴方向是竖向,同时宽度是确切的,那么 flex-basis 为宽度除以横纵比,并做边距边框限制
if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) {
child->layout.computedFlexBasis =
fmaxf((childWidth - marginRow) / child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, parentWidth));
return;
} else if (isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) {
// 主轴方向是横向,同时高度是确切的,那么 flex-basis 为高度乘以横纵比,并做边距边框限制
child->layout.computedFlexBasis =
fmaxf((childHeight - marginColumn) * child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, parentWidth));
return;
}
}
// 将子项目宽高进行最大值限制
YGConstrainMaxSizeForMode(
child, YGFlexDirectionRow, parentWidth, parentWidth, &childWidthMeasureMode, &childWidth);
YGConstrainMaxSizeForMode(child,
YGFlexDirectionColumn,
parentHeight,
parentWidth,
&childHeightMeasureMode,
&childHeight);
// 返回到了第 3 步了,仅调用子项目测量的方法。
YGLayoutNodeInternal(child,
childWidth,
childHeight,
direction,
childWidthMeasureMode,
childHeightMeasureMode,
parentWidth,
parentHeight,
false,
"measure",
config);
// 子项目的主轴 flex-basis 在进行测量后得出并进行限制。
child->layout.computedFlexBasis =
fmaxf(child->layout.measuredDimensions[dim[mainAxis]],
YGNodePaddingAndBorderForAxis(child, mainAxis, parentWidth));
}
// 用于记录是否在计算子项目的 flex-basis 时候进行了子项目的递归测量
child->layout.computedFlexBasisGeneration = gCurrentGenerationCount;
}
// 获取项目的主轴初始化大小 flex-basis 值
static inline const YGValue *YGNodeResolveFlexBasisPtr(const YGNodeRef node) {
// 如果设置的 flex-basis 不为 auto 或 undefined 则使用设置值
if (node->style.flexBasis.unit != YGUnitAuto && node->style.flexBasis.unit != YGUnitUndefined) {
return &node->style.flexBasis;
}
// 如果 flex 被设置,同时大于 0, 则在使用 web 默认情况下返回 auto ,否则返回 zero,flex-basis 为0
if (!YGFloatIsUndefined(node->style.flex) && node->style.flex > 0.0f) {
return node->config->useWebDefaults ? &YGValueAuto : &YGValueZero;
}
return &YGValueAuto;
}
// 为设置的大小,限制最大值
static void YGConstrainMaxSizeForMode(const YGNodeRef node,
const enum YGFlexDirection axis,
const float parentAxisSize,
const float parentWidth,
YGMeasureMode *mode,
float *size) {
const float maxSize = YGResolveValue(&node->style.maxDimensions[dim[axis]], parentAxisSize) +
YGNodeMarginForAxis(node, axis, parentWidth);
switch (*mode) {
case YGMeasureModeExactly:
case YGMeasureModeAtMost:
// 如果最大值设置了,则以最大值为 size
*size = (YGFloatIsUndefined(maxSize) || *size < maxSize) ? *size : maxSize;
break;
case YGMeasureModeUndefined:
// 㘝外部设置进来的未定义,而最大值存在,则使用最大值模式,及使用其值。
if (!YGFloatIsUndefined(maxSize)) {
*mode = YGMeasureModeAtMost;
*size = maxSize;
}
break;
}
}
复制代码
- 计算完所有子项目在主轴上的基准大小,就要根据基准大小开始在项目中放置子项目,同时统计行数。当然这个方法步骤很长,还包括了伸缩的操作。这里先进行行数统计的方法分析,主要是看子项目的盒子模型的主轴大小的累加是否超过了可用的主轴空间,是则换行,并计算剩余空间,以便后续的伸缩操作。
// 每一行开始的子项目索引
uint32_t startOfLineIndex = 0;
// 每一行结束的子项目索引
uint32_t endOfLineIndex = 0;
// 行数
uint32_t lineCount = 0;
// 用于统计交叉轴上所有行所需要的大小
float totalLineCrossDim = 0;
// 记录所有行中主轴上最大的大小
float maxLineMainDim = 0;
// 遍历所有行。当一行的空间被子项目填满了,就通过该循环计算下一行。
for (; endOfLineIndex < childCount; lineCount++, startOfLineIndex = endOfLineIndex) {
// 当前行中的子项目数量
uint32_t itemsOnLine = 0;
// 被具有确切的 flex-basis 消耗的总空间,排除了具有 display:none absolute flex-grow flex-shrink 的子项目
float sizeConsumedOnCurrentLine = 0;
float sizeConsumedOnCurrentLineIncludingMinConstraint = 0;
// 记录 flex-grow 的总数(分母)
float totalFlexGrowFactors = 0;
// 记录 flex-shrink 的总数(分母)
float totalFlexShrinkScaledFactors = 0;
// 记录可伸缩的子项目,方便待会进行遍历。
YGNodeRef firstRelativeChild = NULL;
YGNodeRef currentRelativeChild = NULL;
// 将孩子放入当前行,如果当前行被占满,则跳出该循环。
for (uint32_t i = startOfLineIndex; i < childCount; i++, endOfLineIndex++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
if (child->style.display == YGDisplayNone) {
continue;
}
child->lineIndex = lineCount;
if (child->style.positionType != YGPositionTypeAbsolute) {
// 计算主轴的外边距
const float childMarginMainAxis = YGNodeMarginForAxis(child, mainAxis, availableInnerWidth);
// 限制子项目的 flex-basis 在子项目最大值和最小值间。
const float flexBasisWithMaxConstraints =
fminf(YGResolveValue(&child->style.maxDimensions[dim[mainAxis]], mainAxisParentSize),
fmaxf(YGResolveValue(&child->style.minDimensions[dim[mainAxis]],
mainAxisParentSize),
child->layout.computedFlexBasis));
const float flexBasisWithMinAndMaxConstraints =
fmaxf(YGResolveValue(&child->style.minDimensions[dim[mainAxis]], mainAxisParentSize),
flexBasisWithMaxConstraints);
// 如果项目是允许换行的,同时当前行已经有多于一个元素了,当该子项目放入时,其最限制大小与累计的限
// 制大小的和超出了主轴可用空间,那么则将这个子项目放到下一行中。
if (sizeConsumedOnCurrentLineIncludingMinConstraint + flexBasisWithMinAndMaxConstraints +
childMarginMainAxis >
availableInnerMainDim &&
isNodeFlexWrap && itemsOnLine > 0) {
break;
}
// 记录当前消耗的总共空间,和当前消耗的具有限制的总空间,为什么要记录两个值?加星*
sizeConsumedOnCurrentLineIncludingMinConstraint +=
flexBasisWithMinAndMaxConstraints + childMarginMainAxis;
sizeConsumedOnCurrentLine += flexBasisWithMaxConstraints + childMarginMainAxis;
itemsOnLine++;
// 如果是一个可伸缩的自项目,记录累计的 flex-shrink 和 flex-grow 并构建链表
if (YGNodeIsFlex(child)) {
totalFlexGrowFactors += YGResolveFlexGrow(child);
// 注意注意:flex-shrink 和 flex-grow 不一样,flex-shrink 是需要参照 flex-basis 进行整体缩放的比例控制。
totalFlexShrinkScaledFactors +=
-YGNodeResolveFlexShrink(child) * child->layout.computedFlexBasis;
}
// 这里其实可以写在上面的括号上,记录可伸缩子项目的链表
if (firstRelativeChild == NULL) {
firstRelativeChild = child;
}
if (currentRelativeChild != NULL) {
currentRelativeChild->nextChild = child;
}
currentRelativeChild = child;
child->nextChild = NULL;
}
}
// 如果不需要测量交叉轴,或者不是排版流程,则跳过测量和排版伸缩孩子的过程
const bool canSkipFlex = !performLayout && measureModeCrossDim == YGMeasureModeExactly;
// 为了方便去进行孩子的主轴排版位置计算,用 leadingMainDim 表示起始边沿距离第一个元素的距离
// betweenMainDim 表示每个元素之间的距离。
float leadingMainDim = 0;
float betweenMainDim = 0;
// 如果可用的主轴空间的测量模式不为确切,必须确保主轴可用空间要在最大值和最小值范围内
if (measureModeMainDim != YGMeasureModeExactly) {
if (!YGFloatIsUndefined(minInnerMainDim) && sizeConsumedOnCurrentLine < minInnerMainDim) {
// 当主轴的空间大小是已知的,则需要根据最大值和最小值来计算可用主轴空间
availableInnerMainDim = minInnerMainDim;
} else if (!YGFloatIsUndefined(maxInnerMainDim) &&
sizeConsumedOnCurrentLine > maxInnerMainDim) {
// 当主轴空间未知,则默认使用被消耗的空间作为可用主轴空间,即没有剩余的空间给可以伸缩的子项目。
availableInnerMainDim = maxInnerMainDim;
} else {
if (!node->config->useLegacyStretchBehaviour &&
(totalFlexGrowFactors == 0 || YGResolveFlexGrow(node) == 0)) {
// 当没有任何可伸缩的孩子,同时子项目所占用总大小也在限制内,则使用该大小做为主轴可用空间,
// 因为后续不需要多余的空间再做任何变化,。
availableInnerMainDim = sizeConsumedOnCurrentLine;
}
}
}
// 确定剩余的可用空间
float remainingFreeSpace = 0;
if (!YGFloatIsUndefined(availableInnerMainDim)) {
// 当主轴可用空间是确定时,剩余空间为主轴可用空间去掉被无法伸缩项目所占用的总空间
remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine;
} else if (sizeConsumedOnCurrentLine < 0) {
// 当主轴可用空间不确定时,代表该项目的主轴的空间大小由孩子决定。同时非伸缩项目所占用的总空间为负值
// 时,使用其负数作为剩余可用空间。当 flex-wrap 为 nowrap 时,这里很好理解,假设某个子项目的
// margin-left 为负数,且绝对值大于所有占用的空间,本身父项目由孩子决定大小,而孩子这时候使父亲整
// 体大小为 0,所以可用来缩放的空间就是 0 - 占用的空间。当 flex-wrap 为 wrap 时,在上述情况下
// 的 web 表现不是这样子,这个问题有待研究,加星*。
remainingFreeSpace = -sizeConsumedOnCurrentLine;
}
// 记录原来的剩余空间和被使用后剩下的空间
const float originalRemainingFreeSpace = remainingFreeSpace;
float deltaFreeSpace = 0;
复制代码
- 如果不能跳过计算伸缩子项目的操作,则进入伸缩子项目的测量和排版的方法中。这个步骤主要做两次遍历去填充剩余的空间,第一次遍历去除受大小限制的缩放的项目所占用的看见和缩放因子,第二次遍历确定所有缩放项目的大小。接着就是确定交叉轴大小和模式,进行递归的孩子的测量或者排版操作。
if (!canSkipFlex) {
float childFlexBasis;
float flexShrinkScaledFactor;
float flexGrowFactor;
float baseMainSize;
float boundMainSize;
// 第一次遍历,判断可伸缩的子项目是不是受最大值和最小值限制。如果是则去除这些子项目在伸缩因子上的影响
// 即,这些子项目不会伸缩超过最大值和最小值的限制。下面两个值用于记录需要去除的影响值。
float deltaFlexShrinkScaledFactors = 0;
float deltaFlexGrowFactors = 0;
currentRelativeChild = firstRelativeChild;
while (currentRelativeChild != NULL) {
// 计算在最大最小值限制范围内的主轴 flex-basis 空间
childFlexBasis =
fminf(YGResolveValue(¤tRelativeChild->style.maxDimensions[dim[mainAxis]],
mainAxisParentSize),
fmaxf(YGResolveValue(¤tRelativeChild->style.minDimensions[dim[mainAxis]],
mainAxisParentSize),
currentRelativeChild->layout.computedFlexBasis));
// 如果是空间不足情况下,需要缩小。
if (remainingFreeSpace < 0) {
// 计算缩小因子
flexShrinkScaledFactor = -YGNodeResolveFlexShrink(currentRelativeChild) * childFlexBasis;
if (flexShrinkScaledFactor != 0) {
// 计算缩小后的项目的主轴大小
baseMainSize =
childFlexBasis +
remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor;
// 计算在最大最小值限制后的缩小主轴大小
boundMainSize = YGNodeBoundAxis(currentRelativeChild,
mainAxis,
baseMainSize,
availableInnerMainDim,
availableInnerWidth);
// 如果是受限制影响,则去除该子项目在缩小因子上额影响,
if (baseMainSize != boundMainSize) {
// 累计记录这类子项目受限制后可缩小的空间和因子,后面从总的可用空间和因子中减去
deltaFreeSpace -= boundMainSize - childFlexBasis;
deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor;
}
}
} else if (remainingFreeSpace > 0) {
// 在空间充足的情况下进行子项目的伸展
// 计算当前子项目的放大因子
flexGrowFactor = YGResolveFlexGrow(currentRelativeChild);
if (flexGrowFactor != 0) {
// 计算放大后子项目的主轴大小
baseMainSize =
childFlexBasis + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor;
// 将放大后的大小进行限制
boundMainSize = YGNodeBoundAxis(currentRelativeChild,
mainAxis,
baseMainSize,
availableInnerMainDim,
availableInnerWidth);
// 如果受限制影响,则在记录这类项目可以放大的最大空间和其因子,后面从总的可用空间和因子中减去
if (baseMainSize != boundMainSize) {
deltaFreeSpace -= boundMainSize - childFlexBasis;
deltaFlexGrowFactors -= flexGrowFactor;
}
}
}
currentRelativeChild = currentRelativeChild->nextChild;
}
// 从总的缩小因子中减去记录的待去除的因子
totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors;
// 从总的放大因子中减去记录的待去除的因子
totalFlexGrowFactors += deltaFlexGrowFactors;
// 从总的剩余空间中减去记录的待去除的空间
remainingFreeSpace += deltaFreeSpace;
// 第二次遍历,确定所有可伸缩子项目的大小
deltaFreeSpace = 0;
currentRelativeChild = firstRelativeChild;
while (currentRelativeChild != NULL) {
// 计算子项目主轴需要的大小
childFlexBasis =
fminf(YGResolveValue(¤tRelativeChild->style.maxDimensions[dim[mainAxis]],
mainAxisParentSize),
fmaxf(YGResolveValue(¤tRelativeChild->style.minDimensions[dim[mainAxis]],
mainAxisParentSize),
currentRelativeChild->layout.computedFlexBasis));
// 用于记录子项目经过缩放后的大小
float updatedMainSize = childFlexBasis;
if (remainingFreeSpace < 0) {
// 当进行缩小时,获取缩小因子
flexShrinkScaledFactor = -YGNodeResolveFlexShrink(currentRelativeChild) * childFlexBasis;
if (flexShrinkScaledFactor != 0) {
float childSize;
// 根据缩小因子计算缩小后的子项目大小
if (totalFlexShrinkScaledFactors == 0) {
childSize = childFlexBasis + flexShrinkScaledFactor;
} else {
childSize =
childFlexBasis +
(remainingFreeSpace / totalFlexShrinkScaledFactors) * flexShrinkScaledFactor;
}
// 将缩小后的大小进行限制,获得最终大小
updatedMainSize = YGNodeBoundAxis(currentRelativeChild,
mainAxis,
childSize,
availableInnerMainDim,
availableInnerWidth);
}
} else if (remainingFreeSpace > 0) {
// 当子项目进行放大时, 获取放大因子
flexGrowFactor = YGResolveFlexGrow(currentRelativeChild);
if (flexGrowFactor != 0) {
// 根据放大因子计算放大后的大小并进行限制
updatedMainSize =
YGNodeBoundAxis(currentRelativeChild,
mainAxis,
childFlexBasis +
remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor,
availableInnerMainDim,
availableInnerWidth);
}
}
// 记录剩余的空间
deltaFreeSpace -= updatedMainSize - childFlexBasis;
// 计算主轴和交叉轴的外边距
const float marginMain =
YGNodeMarginForAxis(currentRelativeChild, mainAxis, availableInnerWidth);
const float marginCross =
YGNodeMarginForAxis(currentRelativeChild, crossAxis, availableInnerWidth);
float childCrossSize;
// 子项目的主轴大小为缩放后的大小加外边距(盒子大小)
float childMainSize = updatedMainSize + marginMain;
YGMeasureMode childCrossMeasureMode;
YGMeasureMode childMainMeasureMode = YGMeasureModeExactly;
if (!YGFloatIsUndefined(availableInnerCrossDim) &&
!YGNodeIsStyleDimDefined(currentRelativeChild, crossAxis, availableInnerCrossDim) &&
measureModeCrossDim == YGMeasureModeExactly &&
!(isNodeFlexWrap && flexBasisOverflows) &&
YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch) {
// 当父亲给的可用空间和模式为确切,子项目本身大小为设定,同时父亲是可以单行排版的,
// 子项目在交叉轴上的排版方向是拉伸的,则子项目交叉轴大小拉伸与父亲相同,测量模式为确切。
childCrossSize = availableInnerCrossDim;
childCrossMeasureMode = YGMeasureModeExactly;
} else if (!YGNodeIsStyleDimDefined(currentRelativeChild,
crossAxis,
availableInnerCrossDim)) {
// 当子项目的交叉轴大小为未设定,那么子项目交叉轴大小为父亲的可用大小,如果是确切则模式为最大
// 值,否则为未知。
childCrossSize = availableInnerCrossDim;
childCrossMeasureMode =
YGFloatIsUndefined(childCrossSize) ? YGMeasureModeUndefined : YGMeasureModeAtMost;
} else {
// 出现其他的情况,如果项目的交叉轴大小一致,则为确切,否则为未知。当交叉轴的单位为百分比时
// 这里有个特殊操作,情况必须在其为可伸缩情况下,测量模式变为未知,大小交给子项目的孩子决定。
childCrossSize = YGResolveValue(currentRelativeChild->resolvedDimensions[dim[crossAxis]],
availableInnerCrossDim) +
marginCross;
const bool isLoosePercentageMeasurement =
currentRelativeChild->resolvedDimensions[dim[crossAxis]]->unit == YGUnitPercent &&
measureModeCrossDim != YGMeasureModeExactly;
childCrossMeasureMode = YGFloatIsUndefined(childCrossSize) || isLoosePercentageMeasurement
? YGMeasureModeUndefined
: YGMeasureModeExactly;
}
// 如果横纵比定义了,则要根据横纵比进行调整,这里其实不太协调,因为调整完之后如果主轴大小变了
// 上面的规则都行不通了。
if (!YGFloatIsUndefined(currentRelativeChild->style.aspectRatio)) {
childCrossSize = fmaxf(
isMainAxisRow
? (childMainSize - marginMain) / currentRelativeChild->style.aspectRatio
: (childMainSize - marginMain) * currentRelativeChild->style.aspectRatio,
YGNodePaddingAndBorderForAxis(currentRelativeChild, crossAxis, availableInnerWidth));
childCrossMeasureMode = YGMeasureModeExactly;
if (YGNodeIsFlex(currentRelativeChild)) {
childCrossSize = fminf(childCrossSize - marginCross, availableInnerCrossDim);
childMainSize =
marginMain + (isMainAxisRow
? childCrossSize * currentRelativeChild->style.aspectRatio
: childCrossSize / currentRelativeChild->style.aspectRatio);
}
childCrossSize += marginCross;
}
// 进行子项目可用主轴交叉轴大小限制
YGConstrainMaxSizeForMode(currentRelativeChild,
mainAxis,
availableInnerMainDim,
availableInnerWidth,
&childMainMeasureMode,
&childMainSize);
YGConstrainMaxSizeForMode(currentRelativeChild,
crossAxis,
availableInnerCrossDim,
availableInnerWidth,
&childCrossMeasureMode,
&childCrossSize);
// 确定在交叉轴上排版模式是否拉伸模式
const bool requiresStretchLayout =
!YGNodeIsStyleDimDefined(currentRelativeChild, crossAxis, availableInnerCrossDim) &&
YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch;
const float childWidth = isMainAxisRow ? childMainSize : childCrossSize;
const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize;
const YGMeasureMode childWidthMeasureMode =
isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;
const YGMeasureMode childHeightMeasureMode =
!isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;
// 递归调用子项目的孩子的排版,这里决定递归时是以测量还是排版模式由 performLayout 和
// requireStretchLayout 决定,前一个 flag 正常,后一个 flag 的作用主要是如果是不需要拉伸的
// 那么就直接排版,否则如果是要拉伸就只是测量。因为在后面排版 align-content 还可能根据
// 交叉轴是否 stretch 导致一次因为拉伸出现的大小改变,而在递归时需要
// 重新触发排版,所以当交叉轴是 stretch 时,这里的递归使用测量可以减少一次无用的排版递归操作
YGLayoutNodeInternal(currentRelativeChild,
childWidth,
childHeight,
direction,
childWidthMeasureMode,
childHeightMeasureMode,
availableInnerWidth,
availableInnerHeight,
performLayout && !requiresStretchLayout,
"flex",
config);
currentRelativeChild = currentRelativeChild->nextChild;
}
}
复制代码
- 在当前行子项目在主轴的大小均确认完毕后,需要再次确定剩余空间,以便根据项目主轴的排版方式确定子项目排版的位置和确定项目主轴和交叉轴大小。
remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace;
// 如果项目的主轴测量模式是最大值,同时有剩余空间没有使用(没有可缩放孩子),则根据项目主轴最小空间计算
// 剩余空间,如果最小空间未定义,同时项目大小未知,意味可以不需要剩余空间。
if (measureModeMainDim == YGMeasureModeAtMost && remainingFreeSpace > 0) {
if (node->style.minDimensions[dim[mainAxis]].unit != YGUnitUndefined &&
YGResolveValue(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) >= 0) {
remainingFreeSpace =
fmaxf(0,
YGResolveValue(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) -
(availableInnerMainDim - remainingFreeSpace));
} else {
remainingFreeSpace = 0;
}
}
// 计算当前行中子项目在主轴上的外边距 margin 是否有 auto 的设置。当有 auto 设置时,剩余的空间需要均匀分配给
// 这些子项目的主轴的前沿和后沿的 margin,但是分配的这个 margin 由于是额外的,在这里是直接由父亲计算好,没有
// 在递归时候把这个当做子项目盒模型一部分传递,但是它仍然是子项目盒模型的 margin。
int numberOfAutoMarginsOnCurrentLine = 0;
for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
if (child->style.positionType == YGPositionTypeRelative) {
if (YGMarginLeadingValue(child, mainAxis)->unit == YGUnitAuto) {
numberOfAutoMarginsOnCurrentLine++;
}
if (YGMarginTrailingValue(child, mainAxis)->unit == YGUnitAuto) {
numberOfAutoMarginsOnCurrentLine++;
}
}
}
// 当子项目中没有 margin auto 的设置,则可以依照 flex 的 justify-content 来设置主轴上的排版位置
// leadingMainDim 为首个子项目距离主轴前沿的距离,betweenMaindDim 子项目之间的间距
if (numberOfAutoMarginsOnCurrentLine == 0) {
switch (justifyContent) {
case YGJustifyCenter:
// 居中设置,主轴前沿的距离为剩余空间一半
leadingMainDim = remainingFreeSpace / 2;
break;
case YGJustifyFlexEnd:
// 尾对齐,主轴前沿的距离为所有剩余空间
leadingMainDim = remainingFreeSpace;
break;
case YGJustifySpaceBetween:
// 两边对齐,子项目间隔相等
if (itemsOnLine > 1) {
betweenMainDim = fmaxf(remainingFreeSpace, 0) / (itemsOnLine - 1);
} else {
betweenMainDim = 0;
}
break;
case YGJustifySpaceAround:
// 子项目两边的分配的空间相等
betweenMainDim = remainingFreeSpace / itemsOnLine;
leadingMainDim = betweenMainDim / 2;
break;
case YGJustifyFlexStart:
break;
}
}
// 主轴和交叉轴的大小,后面的代码也会利用这个变量进行子项目位置排列的计算
float mainDim = leadingPaddingAndBorderMain + leadingMainDim;
float crossDim = 0;
for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
if (child->style.display == YGDisplayNone) {
continue;
}
if (child->style.positionType == YGPositionTypeAbsolute &&
YGNodeIsLeadingPosDefined(child, mainAxis)) {
if (performLayout) {
// 当子项目是 absolute 绝对布局时,top 和 left 已经定义,则进行相应位置的摆放
child->layout.position[pos[mainAxis]] =
YGNodeLeadingPosition(child, mainAxis, availableInnerMainDim) +
YGNodeLeadingBorder(node, mainAxis) +
YGNodeLeadingMargin(child, mainAxis, availableInnerWidth);
}
} else {
// 绝对布局的子项目不参与 flex 布局
if (child->style.positionType == YGPositionTypeRelative) {
if (YGMarginLeadingValue(child, mainAxis)->unit == YGUnitAuto) {
// 当然子项目的前沿 margin 是 auto 时,主轴距离增加
mainDim += remainingFreeSpace / numberOfAutoMarginsOnCurrentLine;
}
// 如果是排版步骤,则设定孩子盒模型主轴起点
if (performLayout) {
child->layout.position[pos[mainAxis]] += mainDim;
}
// 当然子项目的后沿 margin 是 auto 时,主轴距离增加
if (YGMarginTrailingValue(child, mainAxis)->unit == YGUnitAuto) {
mainDim += remainingFreeSpace / numberOfAutoMarginsOnCurrentLine;
}
if (canSkipFlex) {
// 如果是跳过了 flex 的步骤,那么YGNodeDimWithMargin是不能用的,因为里面使用到的
// measureDim 是还未计算过的,这里使用 computedFlexBasis。
// 累加子项目的大小,最后可以得出项目的大小
mainDim += betweenMainDim + YGNodeMarginForAxis(child, mainAxis, availableInnerWidth) +
child->layout.computedFlexBasis;
// 因为跳过了 flex 代表交叉轴是确切的(原因看前面代码)
crossDim = availableInnerCrossDim;
} else {
// 累加子项目的大小,最后可以得出项目的大小
mainDim += betweenMainDim + YGNodeDimWithMargin(child, mainAxis, availableInnerWidth);
// 项目的交叉轴大小,由最大的子项目交叉轴大小决定
crossDim = fmaxf(crossDim, YGNodeDimWithMargin(child, crossAxis, availableInnerWidth));
}
} else if (performLayout) {
// 放置绝对布局项目
child->layout.position[pos[mainAxis]] +=
YGNodeLeadingBorder(node, mainAxis) + leadingMainDim;
}
}
}
// 累加尾部边距和边框,得到项目最终主轴大小,这个值已经加了内边距和 border
mainDim += trailingPaddingAndBorderMain;
float containerCrossAxis = availableInnerCrossDim;
if (measureModeCrossDim == YGMeasureModeUndefined ||
measureModeCrossDim == YGMeasureModeAtMost) {
// 当测量模式不是确切的,那么
// 如果交叉轴大小不是确切的或是最大值,则由最大的孩子的交叉轴值决定项目的交叉轴大小,并确保在限制内
containerCrossAxis = YGNodeBoundAxis(node,
crossAxis,
crossDim + paddingAndBorderAxisCross,
crossAxisParentSize,
parentWidth) -
paddingAndBorderAxisCross;
}
// 如果项目是单行排版,同时交叉轴测量模式为绝对值,则交叉轴大小为可用的交叉轴空间
if (!isNodeFlexWrap && measureModeCrossDim == YGMeasureModeExactly) {
crossDim = availableInnerCrossDim;
}
// 根据最大最小值进行限制,这个值没有加上内边距和 border
crossDim = YGNodeBoundAxis(node,
crossAxis,
crossDim + paddingAndBorderAxisCross,
crossAxisParentSize,
parentWidth) - paddingAndBorderAxisCross;
复制代码
- 接下来计算子项目在当前行交叉轴上的排版,这一步骤只在排版下进行,测量阶段不进行。如果需要进行 stretch 则需要使子项目进行排版和测量的操作,以便满足新的空间。这一步结束后,flex 计算行排版信息的循环到此终止。
// 这一步骤只在该项目排版下进行,测量不进行
if (performLayout) {
for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
if (child->style.display == YGDisplayNone) {
continue;
}
if (child->style.positionType == YGPositionTypeAbsolute) {
// 如果子项目是绝对定位,则根据四个定位 top / left / right / bottom 设置在交叉轴上的位置
if (YGNodeIsLeadingPosDefined(child, crossAxis)) {
child->layout.position[pos[crossAxis]] =
YGNodeLeadingPosition(child, crossAxis, availableInnerCrossDim) +
YGNodeLeadingBorder(node, crossAxis) +
YGNodeLeadingMargin(child, crossAxis, availableInnerWidth);
} else {
child->layout.position[pos[crossAxis]] =
YGNodeLeadingBorder(node, crossAxis) +
YGNodeLeadingMargin(child, crossAxis, availableInnerWidth);
}
} else {
float leadingCrossDim = leadingPaddingAndBorderCross;
// 子项目的交叉轴排版可以由父项目决定,自身设置的优先级更高
const YGAlign alignItem = YGNodeAlignItem(node, child);
// 当子项目在交叉轴的排版是拉伸,同时 marigin 均不是 auto(auto 的话就不需要拉伸,而是自由使
// 用 margin 撑满),那就需要重新计算孩子在交叉轴上的排版
if (alignItem == YGAlignStretch &&
YGMarginLeadingValue(child, crossAxis)->unit != YGUnitAuto &&
YGMarginTrailingValue(child, crossAxis)->unit != YGUnitAuto) {
// 如果子项目具有确切被设定的交叉轴大小,那么不需要进行拉伸,否则需要重新排版
if (!YGNodeIsStyleDimDefined(child, crossAxis, availableInnerCrossDim)) {
// 子项目主轴大小使用测量过的大小
float childMainSize = child->layout.measuredDimensions[dim[mainAxis]];
// 子项目交叉轴如果定义了横轴比则使用横纵比结果,否则使用当前父亲的行交叉轴大小
float childCrossSize =
!YGFloatIsUndefined(child->style.aspectRatio)
? ((YGNodeMarginForAxis(child, crossAxis, availableInnerWidth) +
(isMainAxisRow ? childMainSize / child->style.aspectRatio
: childMainSize * child->style.aspectRatio)))
: crossDim;
// 盒模型
childMainSize += YGNodeMarginForAxis(child, mainAxis, availableInnerWidth);
// 将交叉轴和主轴大小进行范围限制,这里主轴使用了确切的测量模式,这里有个疑惑就是,在
// 前面代码设置的主轴测量模式不一定是确切的。关于这个的解答应该是因为这次的测量是之前测量的
// 结果,所以孩子的测量结果不会和之前所测量的有出入。
YGMeasureMode childMainMeasureMode = YGMeasureModeExactly;
YGMeasureMode childCrossMeasureMode = YGMeasureModeExactly;
YGConstrainMaxSizeForMode(child,
mainAxis,
availableInnerMainDim,
availableInnerWidth,
&childMainMeasureMode,
&childMainSize);
YGConstrainMaxSizeForMode(child,
crossAxis,
availableInnerCrossDim,
availableInnerWidth,
&childCrossMeasureMode,
&childCrossSize);
const float childWidth = isMainAxisRow ? childMainSize : childCrossSize;
const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize;
const YGMeasureMode childWidthMeasureMode =
YGFloatIsUndefined(childWidth) ? YGMeasureModeUndefined : YGMeasureModeExactly;
const YGMeasureMode childHeightMeasureMode =
YGFloatIsUndefined(childHeight) ? YGMeasureModeUndefined : YGMeasureModeExactly;
// 递归测量排版子项目。
YGLayoutNodeInternal(child,
childWidth,
childHeight,
direction,
childWidthMeasureMode,
childHeightMeasureMode,
availableInnerWidth,
availableInnerHeight,
true,
"stretch",
config);
}
} else {
// 如果不需要拉伸,则根据剩余空间和排版模式,在交叉轴上放置子项目到对应的位置
// 剩余交叉轴空间
const float remainingCrossDim =
containerCrossAxis - YGNodeDimWithMargin(child, crossAxis, availableInnerWidth);
if (YGMarginLeadingValue(child, crossAxis)->unit == YGUnitAuto &&
YGMarginTrailingValue(child, crossAxis)->unit == YGUnitAuto) {
// 如果 margin 为 auto,则均匀的分配子项目交叉轴两侧空间。
leadingCrossDim += fmaxf(0.0f, remainingCrossDim / 2);
} else if (YGMarginTrailingValue(child, crossAxis)->unit == YGUnitAuto) {
// 如果尾部 margin 为 auto,则不用做任何操作,因为本身就已经把剩余空间放在尾部
} else if (YGMarginLeadingValue(child, crossAxis)->unit == YGUnitAuto) {
// 如果前沿 margin 为 auto,则将剩余空间都放在前沿
leadingCrossDim += fmaxf(0.0f, remainingCrossDim);
} else if (alignItem == YGAlignFlexStart) {
// 如果排版模式是对齐前沿,则不需要做任何操作
} else if (alignItem == YGAlignCenter) {
// 如果排版模式是居中,则将剩余空间均分
leadingCrossDim += remainingCrossDim / 2;
} else {
// 如果是对其尾部,则剩余空间都放在前沿
leadingCrossDim += remainingCrossDim;
}
}
// 设置子项目的排版位置
child->layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim;
}
}
}
// totalLineCrossDim 是多行情况下积累的交叉轴行高
totalLineCrossDim += crossDim;
// 计算项目整体最大行宽。
maxLineMainDim = fmaxf(maxLineMainDim, mainDim);
}
复制代码
- 计算 align-content 情况下多行的排版状况,行之间是没有间隔的,所以 align-content 是对每一行的子项目位置的一个整体重新确认。这里的 baseline 是一个值得关注的操作,支队主轴为横向起作用。另外在 stretch 情况下,如果子项目是需要拉伸的,则需要重新进行排版。
// align-content 针对的是行的排版方式,仅在排版情况下进行,同时满足行数大于一行,或者需要根据行的第一个元
// 素的 baseline 文字的基准对齐,并且该项目的交叉轴可用空间是确定值(用于确定剩余的交叉轴空间,否则为0)
// 注意:行都是撑满的,行间不会有间距。
if (performLayout && (lineCount > 1 || YGIsBaselineLayout(node)) &&
!YGFloatIsUndefined(availableInnerCrossDim)) {
// 交叉轴中排版完所有行之后剩余的空间
const float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim;
// 对于每一行而言 align-content 分配的多余空间
float crossDimLead = 0;
// 第一行的项目整体距离前沿的距离
float currentLead = leadingPaddingAndBorderCross;
switch (node->style.alignContent) {
case YGAlignFlexEnd:
// 整体对齐尾部,距离前沿的位置设定为所有剩余空间
currentLead += remainingAlignContentDim;
break;
case YGAlignCenter:
// 整体居中,距离前沿的位置设定位剩余空间的一半
currentLead += remainingAlignContentDim / 2;
break;
case YGAlignStretch:
if (availableInnerCrossDim > totalLineCrossDim) {
crossDimLead = remainingAlignContentDim / lineCount;
}
break;
case YGAlignSpaceAround:
// 每一行的前后两侧留下的空间相等
if (availableInnerCrossDim > totalLineCrossDim) {
currentLead += remainingAlignContentDim / (2 * lineCount);
if (lineCount > 1) {
crossDimLead = remainingAlignContentDim / lineCount;
}
} else {
currentLead += remainingAlignContentDim / 2;
}
break;
case YGAlignSpaceBetween:
// 前后两行对齐两边,行间间隔相等
if (availableInnerCrossDim > totalLineCrossDim && lineCount > 1) {
crossDimLead = remainingAlignContentDim / (lineCount - 1);
}
break;
case YGAlignAuto:
case YGAlignFlexStart:
case YGAlignBaseline:
break;
}
// 遍历所有行,确定当前行的大小,同时为行内的子项目进行放置,确定是否需要重新测量排版
uint32_t endIndex = 0;
for (uint32_t i = 0; i < lineCount; i++) {
const uint32_t startIndex = endIndex;
uint32_t ii;
float lineHeight = 0;
float maxAscentForCurrentLine = 0;
float maxDescentForCurrentLine = 0;
for (ii = startIndex; ii < childCount; ii++) {
const YGNodeRef child = YGNodeListGet(node->children, ii);
if (child->style.display == YGDisplayNone) {
continue;
}
if (child->style.positionType == YGPositionTypeRelative) {
// 根据 lineIndex 找到当前行的子项目
if (child->lineIndex != i) {
break;
}
if (YGNodeIsLayoutDimDefined(child, crossAxis)) {
// 寻找子项目中最大行高
lineHeight = fmaxf(lineHeight,
child->layout.measuredDimensions[dim[crossAxis]] +
YGNodeMarginForAxis(child, crossAxis, availableInnerWidth));
}
if (YGNodeAlignItem(node, child) == YGAlignBaseline) {
// 基准值排版的计算方式是,获取每个项目中以首个文本的 bottom 为基线(如果没有则以
// 当前项目的底部为基线)的距离顶部的距离 ascent,距离底部距离 descent,在项目中
// 获取最大的 maxAscent 和 maxDescent,这两个的和值就是整个行的高度。而每个项目
// 根据 maxAscent 和基线的差值,就可以计算出对齐基线时距离顶部的位置。
const float ascent =
YGBaseline(child) +
YGNodeLeadingMargin(child, YGFlexDirectionColumn, availableInnerWidth);
const float descent =
child->layout.measuredDimensions[YGDimensionHeight] +
YGNodeMarginForAxis(child, YGFlexDirectionColumn, availableInnerWidth) - ascent;
maxAscentForCurrentLine = fmaxf(maxAscentForCurrentLine, ascent);
maxDescentForCurrentLine = fmaxf(maxDescentForCurrentLine, descent);
lineHeight = fmaxf(lineHeight, maxAscentForCurrentLine + maxDescentForCurrentLine);
}
}
}
// 记录下一行的起始位置
endIndex = ii;
// 加上根据 align-content 分配的多余空间
lineHeight += crossDimLead;
// 这个 performLayout 的判断多余了
if (performLayout) {
// 对当前行的项目进行交叉轴上的放置
for (ii = startIndex; ii < endIndex; ii++) {
const YGNodeRef child = YGNodeListGet(node->children, ii);
// 忽略 displaynone 节点
if (child->style.display == YGDisplayNone) {
continue;
}
if (child->style.positionType == YGPositionTypeRelative) {
switch (YGNodeAlignItem(node, child)) {
case YGAlignFlexStart: {
// 当交叉轴排版是对齐前沿,则子项目的交叉轴顶部位置为前沿的距离与边距边框和值
child->layout.position[pos[crossAxis]] =
currentLead + YGNodeLeadingMargin(child, crossAxis, availableInnerWidth);
break;
}
case YGAlignFlexEnd: {
// 当交叉轴排版是对齐后沿,则子项目的交叉轴顶部位置为前沿距离与去除了自身
// 大小后的空间和值
child->layout.position[pos[crossAxis]] =
currentLead + lineHeight -
YGNodeTrailingMargin(child, crossAxis, availableInnerWidth) -
child->layout.measuredDimensions[dim[crossAxis]];
break;
}
case YGAlignCenter: {
// 当交叉轴居中,则顶部位置为去除自身大小后的空间的一半,同时加上前沿距离
float childHeight = child->layout.measuredDimensions[dim[crossAxis]];
child->layout.position[pos[crossAxis]] =
currentLead + (lineHeight - childHeight) / 2;
break;
}
case YGAlignStretch: {
// 如果是拉伸,则顶部位置就是行开始的位置
child->layout.position[pos[crossAxis]] =
currentLead + YGNodeLeadingMargin(child, crossAxis, availableInnerWidth);
// 重新测量和排版子项目的孩子,只是更新交叉轴的高度
if (!YGNodeIsStyleDimDefined(child, crossAxis, availableInnerCrossDim)) {
const float childWidth =
isMainAxisRow ? (child->layout.measuredDimensions[YGDimensionWidth] +
YGNodeMarginForAxis(child, mainAxis, availableInnerWidth))
: lineHeight;
const float childHeight =
!isMainAxisRow ? (child->layout.measuredDimensions[YGDimensionHeight] +
YGNodeMarginForAxis(child, crossAxis, availableInnerWidth))
: lineHeight;
if (!(YGFloatsEqual(childWidth,
child->layout.measuredDimensions[YGDimensionWidth]) &&
YGFloatsEqual(childHeight,
child->layout.measuredDimensions[YGDimensionHeight]))) {
YGLayoutNodeInternal(child,
childWidth,
childHeight,
direction,
YGMeasureModeExactly,
YGMeasureModeExactly,
availableInnerWidth,
availableInnerHeight,
true,
"multiline-stretch",
config);
}
}
break;
}
case YGAlignBaseline: {
// 如果是以基准排版,则顶部位置确定方式为利用行内最大的基准值减去当前基准值
// 加上前沿距离和边框边距,这里仅设置 position[YGEdgeTop] 的原因是
// baseline 仅对 flex-direction 横向起效,所以当排版模式为
// baselinse,只要设置 top 位置即可,后续的 reverse 反转操作也不会发
// 生在交叉轴上。
child->layout.position[YGEdgeTop] =
currentLead + maxAscentForCurrentLine - YGBaseline(child) +
YGNodeLeadingPosition(child, YGFlexDirectionColumn, availableInnerCrossDim);
break;
}
case YGAlignAuto:
case YGAlignSpaceBetween:
case YGAlignSpaceAround:
break;
}
}
}
}
currentLead += lineHeight;
}
}
// 计算基准 baseline 的方法
static float YGBaseline(const YGNodeRef node) {
if (node->baseline != NULL) {
// 如果该项目有设定基准的判定方法,则从该方法中获取
const float baseline = node->baseline(node,
node->layout.measuredDimensions[YGDimensionWidth],
node->layout.measuredDimensions[YGDimensionHeight]);
YGAssertWithNode(node,
!YGFloatIsUndefined(baseline),
"Expect custom baseline function to not return NaN");
return baseline;
}
// 如果项目本身没有计算 baseline 的方法,则询问孩子交叉轴排版方式 align 为 baseline 的孩子
// 如果孩子存在,则寻找其 baseline,否则直接使用当前项目的高度作为 baseline。
YGNodeRef baselineChild = NULL;
const uint32_t childCount = YGNodeGetChildCount(node);
for (uint32_t i = 0; i < childCount; i++) {
const YGNodeRef child = YGNodeGetChild(node, i);
if (child->lineIndex > 0) {
break;
}
if (child->style.positionType == YGPositionTypeAbsolute) {
continue;
}
if (YGNodeAlignItem(node, child) == YGAlignBaseline) {
baselineChild = child;
break;
}
if (baselineChild == NULL) {
baselineChild = child;
}
}
// 没有孩子排版方式为 baseline 则使用当前项目的高度作为 baseline。
if (baselineChild == NULL) {
return node->layout.measuredDimensions[YGDimensionHeight];
}
// 否则使用孩子的 baseline 及距离父亲的高度作为整体 baseline
const float baseline = YGBaseline(baselineChild);
return baseline + baselineChild->layout.position[YGEdgeTop];
}
复制代码
- 计算最终的大小,并对方向反转的行项目做出正确位置的放置,同时进行绝对布局的项目的排列。
// 测量大小直接通过可用空间减去外边距得到,这个值只有当主轴或者交叉轴的测量模式为确切的时候,才具有意义。否则会
// 被下面两个 if 分支所覆盖。
node->layout.measuredDimensions[YGDimensionWidth] = YGNodeBoundAxis(
node, YGFlexDirectionRow, availableWidth - marginAxisRow, parentWidth, parentWidth);
node->layout.measuredDimensions[YGDimensionHeight] = YGNodeBoundAxis(
node, YGFlexDirectionColumn, availableHeight - marginAxisColumn, parentHeight, parentWidth);
// 如果测量模式没有给定具体的主轴大小,或者只有最大值的限制且 overflow 不是 scroll,那么直接使用最大的行
// 宽作为节点的主轴测量大小,既依大小赖于孩子
if (measureModeMainDim == YGMeasureModeUndefined ||
(node->style.overflow != YGOverflowScroll && measureModeMainDim == YGMeasureModeAtMost)) {
// 进行大小的限制,确保不小于内边距和边框之和
node->layout.measuredDimensions[dim[mainAxis]] =
YGNodeBoundAxis(node, mainAxis, maxLineMainDim, mainAxisParentSize, parentWidth);
} else if (measureModeMainDim == YGMeasureModeAtMost &&
node->style.overflow == YGOverflowScroll) {
// 如果测量模式是最大值,同时 overflow 为 scroll,就代表当子项目总体主轴大小超过了所给可用空间
// 则该项目的大小应为可用空间的大小,这是确保可以滑动的前提(孩子总体大小超过父亲),这时 overflow 优
// 先级高于 min max。当子项目总体主轴大小小于所给最大空间,则以较小值作为基准,同时需要确保
node->layout.measuredDimensions[dim[mainAxis]] = fmaxf(
fminf(availableInnerMainDim + paddingAndBorderAxisMain,
YGNodeBoundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim, mainAxisParentSize)),
paddingAndBorderAxisMain);
}
// 与上述主轴的含义一致
if (measureModeCrossDim == YGMeasureModeUndefined ||
(node->style.overflow != YGOverflowScroll && measureModeCrossDim == YGMeasureModeAtMost)) {
node->layout.measuredDimensions[dim[crossAxis]] =
YGNodeBoundAxis(node,
crossAxis,
totalLineCrossDim + paddingAndBorderAxisCross,
crossAxisParentSize,
parentWidth);
} else if (measureModeCrossDim == YGMeasureModeAtMost &&
node->style.overflow == YGOverflowScroll) {
node->layout.measuredDimensions[dim[crossAxis]] =
fmaxf(fminf(availableInnerCrossDim + paddingAndBorderAxisCross,
YGNodeBoundAxisWithinMinAndMax(node,
crossAxis,
totalLineCrossDim + paddingAndBorderAxisCross,
crossAxisParentSize)),
paddingAndBorderAxisCross);
}
// 测量的结果仍然是以正常方向进行的,如果当 flex-wrap 是 wrap-reverse,那么需要将行的交叉轴排版方向反转
// 这里实现方式就是遍历所有孩子,项目整体大小减去子项目顶部距离和大小,达到反转效果。
if (performLayout && node->style.flexWrap == YGWrapWrapReverse) {
for (uint32_t i = 0; i < childCount; i++) {
const YGNodeRef child = YGNodeGetChild(node, i);
if (child->style.positionType == YGPositionTypeRelative) {
child->layout.position[pos[crossAxis]] = node->layout.measuredDimensions[dim[crossAxis]] -
child->layout.position[pos[crossAxis]] -
child->layout.measuredDimensions[dim[crossAxis]];
}
}
}
if (performLayout) {
// 在知道项目整体大小之后,就可以进行绝对布局的孩子的,布局方式详见第 17 步
for (currentAbsoluteChild = firstAbsoluteChild; currentAbsoluteChild != NULL;
currentAbsoluteChild = currentAbsoluteChild->nextChild) {
YGNodeAbsoluteLayoutChild(node,
currentAbsoluteChild,
availableInnerWidth,
isMainAxisRow ? measureModeMainDim : measureModeCrossDim,
availableInnerHeight,
direction,
config);
}
// 那 flex-direction 上的反转呢?如 row-reverse。在确定 crossAxis rowAxis,在计算 position
// 时候,当为 row 时,position[pos[mainAxis]] 设定的是 left 的位置,当为 row-reverse,设定的
// 是 rigint 的位置,也就是当 reverse 的时候并没有确切的设定 top 和 right 位置,而 top 和 right
// 是 Yoga 用于定位的,所以在此对 reverse 情况下的主轴和交叉轴需要重新设定 top 和 right的值
const bool needsMainTrailingPos =
mainAxis == YGFlexDirectionRowReverse || mainAxis == YGFlexDirectionColumnReverse;
// 交叉轴没有可能是 YGFlexDirectionColumnReverse。当 YGDirection 为 RTL 时候,才可能是 YGFlexDirectionRowReverse
const bool needsCrossTrailingPos =
crossAxis == YGFlexDirectionRowReverse || crossAxis == YGFlexDirectionColumnReverse;
// 重新设定 top 和 right的值
if (needsMainTrailingPos || needsCrossTrailingPos) {
for (uint32_t i = 0; i < childCount; i++) {
const YGNodeRef child = YGNodeListGet(node->children, i);
if (child->style.display == YGDisplayNone) {
continue;
}
if (needsMainTrailingPos) {
YGNodeSetChildTrailingPosition(node, child, mainAxis);
}
if (needsCrossTrailingPos) {
YGNodeSetChildTrailingPosition(node, child, crossAxis);
}
}
}
}
// 设置 node.layout.position 的 top 和 right 值
static void YGNodeSetChildTrailingPosition(const YGNodeRef node,
const YGNodeRef child,
const YGFlexDirection axis) {
const float size = child->layout.measuredDimensions[dim[axis]];
// 当需要设置的是 top position 时,计算方式为 parent size - child size - child bottom position。加上 size 的原因是因为在排版阶段,设定的 top 值实质被当做 position[YGEdgeBottom],因此在 reverse 时候需要减去 position[YGEdgeBottom] 和 child size,获取反转后 top position。对于 left position 的计算方式概念一样。
child->layout.position[trailing[axis]] =
node->layout.measuredDimensions[dim[axis]] - size - child->layout.position[pos[axis]];
}
复制代码
- 绝对布局项目的排版,对其子项目的测量和排版。绝对布局的项目主要依据 width / height 和 top / right / left / bottom 进行大小确定和定位,如果大小不能确定,则需要先进行孩子的测量再排版。
static void YGNodeAbsoluteLayoutChild(const YGNodeRef node,
const YGNodeRef child,
const float width,
const YGMeasureMode widthMode,
const float height,
const YGDirection direction,
const YGConfigRef config) {
// 确定主轴和交叉轴的方向
const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction);
const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction);
const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
// 预设测量模式和测量大小为未知
float childWidth = YGUndefined;
float childHeight = YGUndefined;
YGMeasureMode childWidthMeasureMode = YGMeasureModeUndefined;
YGMeasureMode childHeightMeasureMode = YGMeasureModeUndefined;
// 确定横向和竖向上的综外边距
const float marginRow = YGNodeMarginForAxis(child, YGFlexDirectionRow, width);
const float marginColumn = YGNodeMarginForAxis(child, YGFlexDirectionColumn, width);
if (YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, width)) {
// 如果 style 中设置固定大小的宽度,则使用该值,计算盒子大小
childWidth = YGResolveValue(child->resolvedDimensions[YGDimensionWidth], width) + marginRow;
} else {
// 如果没有设定宽度,但是前后的 position 设置了,则使用 position 来确定盒子大小。position 的意思是在
// 父项目的空间上,相对于父项目 top right left bottom 边的距离 offset。
if (YGNodeIsLeadingPosDefined(child, YGFlexDirectionRow) &&
YGNodeIsTrailingPosDefined(child, YGFlexDirectionRow)) {
childWidth = node->layout.measuredDimensions[YGDimensionWidth] -
(YGNodeLeadingBorder(node, YGFlexDirectionRow) +
YGNodeTrailingBorder(node, YGFlexDirectionRow)) -
(YGNodeLeadingPosition(child, YGFlexDirectionRow, width) +
YGNodeTrailingPosition(child, YGFlexDirectionRow, width));
childWidth = YGNodeBoundAxis(child, YGFlexDirectionRow, childWidth, width, width);
}
}
// 对于高度的确定和上述的宽度的确定的方式一致。
if (YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, height)) {
childHeight =
YGResolveValue(child->resolvedDimensions[YGDimensionHeight], height) + marginColumn;
} else {
if (YGNodeIsLeadingPosDefined(child, YGFlexDirectionColumn) &&
YGNodeIsTrailingPosDefined(child, YGFlexDirectionColumn)) {
childHeight = node->layout.measuredDimensions[YGDimensionHeight] -
(YGNodeLeadingBorder(node, YGFlexDirectionColumn) +
YGNodeTrailingBorder(node, YGFlexDirectionColumn)) -
(YGNodeLeadingPosition(child, YGFlexDirectionColumn, height) +
YGNodeTrailingPosition(child, YGFlexDirectionColumn, height));
childHeight = YGNodeBoundAxis(child, YGFlexDirectionColumn, childHeight, height, width);
}
}
// 当 aspectRatio 横纵比被设置时,如果宽度或者高度是确切的,则可以确定另外一边的确切大小
if (YGFloatIsUndefined(childWidth) ^ YGFloatIsUndefined(childHeight)) {
if (!YGFloatIsUndefined(child->style.aspectRatio)) {
if (YGFloatIsUndefined(childWidth)) {
childWidth =
marginRow + fmaxf((childHeight - marginColumn) * child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, width));
} else if (YGFloatIsUndefined(childHeight)) {
childHeight =
marginColumn + fmaxf((childWidth - marginRow) / child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, width));
}
}
}
// 如果宽度和高度有任一不确定值,则需要进行孩子大小的测量来确定改项目的大小
if (YGFloatIsUndefined(childWidth) || YGFloatIsUndefined(childHeight)) {
childWidthMeasureMode =
YGFloatIsUndefined(childWidth) ? YGMeasureModeUndefined : YGMeasureModeExactly;
childHeightMeasureMode =
YGFloatIsUndefined(childHeight) ? YGMeasureModeUndefined : YGMeasureModeExactly;
// 如果主轴是交叉轴,通过宽度未定义,而且测量模式不是未定义,则使用父亲的大小来限制该项目的盒子大小。
if (!isMainAxisRow && YGFloatIsUndefined(childWidth) && widthMode != YGMeasureModeUndefined &&
width > 0) {
childWidth = width;
childWidthMeasureMode = YGMeasureModeAtMost;
}
// 测量该项目的大小,会递归计算孩子所需大小
YGLayoutNodeInternal(child,
childWidth,
childHeight,
direction,
childWidthMeasureMode,
childHeightMeasureMode,
childWidth,
childHeight,
false,
"abs-measure",
config);
// 获取测量过后的大小
childWidth = child->layout.measuredDimensions[YGDimensionWidth] +
YGNodeMarginForAxis(child, YGFlexDirectionRow, width);
childHeight = child->layout.measuredDimensions[YGDimensionHeight] +
YGNodeMarginForAxis(child, YGFlexDirectionColumn, width);
}
// 对于该项目的子项目的排版操作,回到第 3 步骤
YGLayoutNodeInternal(child,
childWidth,
childHeight,
direction,
YGMeasureModeExactly,
YGMeasureModeExactly,
childWidth,
childHeight,
true,
"abs-layout",
config);
// 根据 position 的设置来确定在父空间中最终放置的位置
if (YGNodeIsTrailingPosDefined(child, mainAxis) && !YGNodeIsLeadingPosDefined(child, mainAxis)) {
// 如果主轴的后沿位置确定而前沿不确定,则位置就根据后沿确定,并反向计算出前沿的位置
child->layout.position[leading[mainAxis]] = node->layout.measuredDimensions[dim[mainAxis]] -
child->layout.measuredDimensions[dim[mainAxis]] -
YGNodeTrailingBorder(node, mainAxis) -
YGNodeTrailingMargin(child, mainAxis, width) -
YGNodeTrailingPosition(child, mainAxis, isMainAxisRow ? width : height);
} else if (!YGNodeIsLeadingPosDefined(child, mainAxis) &&
node->style.justifyContent == YGJustifyCenter) {
// 如果主轴前沿后沿没定义,同时主轴排列方式为居中,则计算出居中的前沿的位置
child->layout.position[leading[mainAxis]] = (node->layout.measuredDimensions[dim[mainAxis]] -
child->layout.measuredDimensions[dim[mainAxis]]) /
2.0f;
} else if (!YGNodeIsLeadingPosDefined(child, mainAxis) &&
node->style.justifyContent == YGJustifyFlexEnd) {
// 如果主轴前沿后沿没定义,同时主轴排列方式为对齐后沿,则计算出对齐后沿时前沿的位置
child->layout.position[leading[mainAxis]] = (node->layout.measuredDimensions[dim[mainAxis]] -
child->layout.measuredDimensions[dim[mainAxis]]);
} // 其他的主轴前沿位置均为 0
// 交叉轴的 position 计算方式和主轴的计算方式一致,不再赘述
if (YGNodeIsTrailingPosDefined(child, crossAxis) &&
!YGNodeIsLeadingPosDefined(child, crossAxis)) {
child->layout.position[leading[crossAxis]] = node->layout.measuredDimensions[dim[crossAxis]] -
child->layout.measuredDimensions[dim[crossAxis]] -
YGNodeTrailingBorder(node, crossAxis) -
YGNodeTrailingMargin(child, crossAxis, width) -
YGNodeTrailingPosition(child, crossAxis, isMainAxisRow ? height : width);
} else if (!YGNodeIsLeadingPosDefined(child, crossAxis) &&
YGNodeAlignItem(node, child) == YGAlignCenter) {
child->layout.position[leading[crossAxis]] =
(node->layout.measuredDimensions[dim[crossAxis]] -
child->layout.measuredDimensions[dim[crossAxis]]) /
2.0f;
} else if (!YGNodeIsLeadingPosDefined(child, crossAxis) &&
YGNodeAlignItem(node, child) == YGAlignFlexEnd) {
child->layout.position[leading[crossAxis]] = (node->layout.measuredDimensions[dim[crossAxis]] -
child->layout.measuredDimensions[dim[crossAxis]]);
}
}
复制代码
到此代码分析完毕
感谢你的耐心阅读,希望能保持耐心去做好喜欢的事情。如果有人和疑问欢迎留言讨论。