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]]);
}
}
複製代碼
到此代碼分析完畢
感謝你的耐心閱讀,但願能保持耐心去作好喜歡的事情。若是有人和疑問歡迎留言討論。