flex 是 w3c 在 2009 年提出的响应式布局,如今已经获得全部主流的浏览器支持,也是当下前端开发主流的布局方式。前端
flex 凭借其布局属性适配不一样的屏幕,提升开发效率,减适配问题。在如此优秀的响应式能力下,隐藏了什么设计和多少的复杂度,什么样的状况下会触发屡次排版。了解内部实现能更好的在合适的场景使用选择性使用 flex,搭建更高效响应的页面。node
主轴(main axis):容器根据 flex-direction 属性肯定的排版方向,即横轴或竖轴c++
交叉轴(cross axis):与主轴垂直的排版方向,即横轴或竖轴git
display: flexgithub
指定元素以 flex 方式布局web
flex-direction: row(默认)/ column / row-reverse / column-reverse浏览器
指定主轴的排版方向缓存
flex-wrap: wrap(默认) / nowrap / wrap-reverse函数
决定当可用排版空间不足时,是否容许换行,以及换行后的顺序,模式包括容许换行、不换行、换行后总体反向。布局
justify-content: flex-start(默认) / flex-end / center / space-between / space-around
指定项目在主轴上的对齐方式
align-items: stretch / flex-start / flex-end / center / baseline
指定项目在交叉轴的对齐方式
align-content: 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 介绍一致。
凭借 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 用来定
不支持 order 。新增 aspect-ratio 横纵比设置,只有 width 或者 height 肯定,就能肯定另一个变量。
在排版引擎中有两个概念,layout 和 measure。在 Yoga 里面因为函数式代码的关系,看起来只有一个 Layout,但其实它也是具有这两个概念的。
measure 指测量项目所须要的大小。
layout 指将项目肯定的放置在具体的 (x, y) 点
在看细节代码前,先了解下总体的排版思路,对于看细节上对于先后代码能进行联系。Yoga 总体的思路是获得了当前项目的具体大小,而后获取子项目的大小(若是须要孙子项目肯定则测量孙子项目),排版子项目,就这么从树节点一路排版下去,测量和排版阶段混合(混合主要的缘由是 flex 中的位置属性也有可能引发大小的变化)。如下的流程存在递归逻辑。
Yoga 代码的实现细节分析是基于 commit: f68b50bb4bc215edc45a10fda70a51028286f77e 的代码。
总体的实现很是长,多达 3500+ 行代码,并且每一个步骤都是精华,因此还须要跟着如下步骤和思惟一步一步跟下去,不然很容易迷失。
// 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 的孩子,均分后的孩子宽度为
// 若是强行转测量大小,则孩子宽度为6七、6七、66,这就会出现和 web 不同的结果,而按照这里的作法
// 则是6七、6六、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));
}
复制代码
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);
}
复制代码
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);
}
复制代码
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;
复制代码
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;
}
复制代码
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;
复制代码
// 这一步骤只在该项目排版下进行,测量不进行
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 针对的是行的排版方式,仅在排版状况下进行,同时知足行数大于一行,或者须要根据行的第一个元
// 素的 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]];
}
复制代码
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]]);
}
}
复制代码
到此代码分析完毕
感谢你的耐心阅读,但愿能保持耐心去作好喜欢的事情。若是有人和疑问欢迎留言讨论。