1.背景
之前分析了下Weex Android渲染的逻辑,最后在DOM完成之后,会调用DomActionContext的batch方法,里面会调用LayoutEngine.layoutNode方法进行布局,细看了下,这个方法的实现虽然很长,但理清了过程还是很有意思的。
LayoutEngine里面主要有2个方法:layoutNode\layoutNodeImpl, 主要的实现是在layoutNodeImpl里面,下面我们也主要看这个方法。
在分析之前需要知道CSS Flex的布局方式与语法,因为本来LayoutEngine就是处理CSS相关属性的节点的布局的。
推荐阅读阮老师的教程
2.基础流程
layoutNodeImpl方法内部主要由7个循环(A-G)实现,下面简单说下7个循环 (A-G) 的作用,便于理解代码:
- Loop A : 确定非flexible Children的布局,同时统计各个类型的Children的个数。(三种类型:flexible、non flexible、absolutely)
- Loop B : 确定flexible Children的布局,同时分配剩余的空间(根据Flex或者justifyContent) 。
- Loop C : 确定主轴方向上Children的位置以及计算容器的尺寸。
- Loop D : 确定侧轴方向上Children的位置。
- Loop E : 多行情况下,确定侧轴方向Children的位置(事实上可以替代Loop D)。
- Loop F : 需要的话,完善Children位置的信息。
- Loop G :计算绝对布局元素的尺寸。
实际上还有个大循环,包括了A-D,用于多行处理的,不属于单独功能性的,所以不计入上述。循环的结构如下:
while(){
A
B
C
D
}
E
F
G
OK,上述流程大概了解之后,我们可以去分析源码了,Yeah,Show me the Code !
3.源码分析
3.1 坐标引用
不过别着急看源码,看源码之前先了解点知识,有助于理解代码。首先Weex是支持LTR / RTL两种布局方式的,所以LayoutEngine里对这方面也进行了处理,那么怎么处理呢?;其次Flex 布局有2个方向(横向、纵向),因此主轴可能是横向的、也可能是纵向的,代码也要考虑这种情况,这是个问题。
那么这两个问题怎么方便的解决呢?
首先说问题一、为了支持LTR \ RTL,所以代码中设置了4个布局方向:COLUMN (0)/ COLUMN_REVERSE (1)/ ROW(2) / ROW_REVERSE (3),用来表示这几种情况,虽然COLUMN_REVERSE可能用不着。
其次看问题二、实际上代码中不用考虑是横向纵向,主要分清楚是主轴和侧轴就行了,所以代码里获取元素都是根据主轴和侧轴。
值得一提的是:代码里面对坐标做了个映射。举个例子:
要获取主轴方向元素的dimension信息的代码是这样的:
node.cssstyle.dimensions[dim[mainAxis]]
其中mainAxis是上述4个布局方向之一(0-3),dim数组是预先定义好的,值是:
private static final int[] dim = {
DIMENSION_HEIGHT,DIMENSION_HEIGHT,
DIMENSION_WIDTH,
DIMENSION_WIDTH,
};
public static final int DIMENSION_HEIGHT = 1;
可以看到,当主轴是column方向的时候,主轴的dimension是高度。
3.2 源码
OK,下面我们就开始看源码,先从layoutNode方法开始:
/*package*/
static void layoutNode(
CSSLayoutContext layoutContext,
CSSNode node, //WXDomObject
float parentMaxWidth, //UNDEFINED
CSSDirection parentDirection) { // NULL
if (needsRelayout(node, parentMaxWidth)) { //需要重新布局
node.lastLayout.requestedWidth = node.csslayout.dimensions[DIMENSION_WIDTH];
node.lastLayout.requestedHeight = node.csslayout.dimensions[DIMENSION_HEIGHT];
node.lastLayout.parentMaxWidth = parentMaxWidth;
layoutNodeImpl(layoutContext, node, parentMaxWidth, parentDirection);
node.updateLastLayout(node.csslayout);
} else {
node.csslayout.copy(node.lastLayout); //复制上一个布局结果
node.updateLastLayout(node.lastLayout);//nothing changed
}
node.markHasNewLayout();
}
可以看到如果需要重新计算的话,那么会调用layoutNodeImpl重新计算,否则啥也不干。简单看一下needsRelayout的实现:
static boolean needsRelayout(CSSNode node, float parentMaxWidth) {
return node.isDirty() ||
!FloatUtil.floatsEqual( //上个布局结果的要求高度/宽度/parent最大宽度 不等于 当前layout结果的高度/宽度/parent最大宽度
node.lastLayout.requestedHeight,
node.csslayout.dimensions[DIMENSION_HEIGHT]) ||
!FloatUtil.floatsEqual(
node.lastLayout.requestedWidth,
node.csslayout.dimensions[DIMENSION_WIDTH]) ||
!FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth);
}
当node 是dirty状态 或者上次布局需要的宽高发生变化、parent最大width发生改变的时候会触发重新布局的逻辑。
接下来,让我们看下layoutNodeImpl方法,一个700行左右的方法。
private static void layoutNodeImpl(CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, CSSDirection parentDirection) {
for (int i = 0, childCount = node.getChildCount(); i < childCount; i++) { //重置所有child元素的csslayout结果
node.getChildAt(i).csslayout.resetResult();
}
if (!node.isShow()) {//节点不可见,直接返回
return;
}
/** START_GENERATED **/
CSSDirection direction = resolveDirection(node, parentDirection); //布局方向,LTR or RTL
int mainAxis = resolveAxis(getFlexDirection(node), direction); //设置主轴方向 (取决于布局方向)
int crossAxis = getCrossFlexDirection(mainAxis, direction); //设置竖轴方向
int resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); //根据布局方向,处理过的横向(如果是RTL的话 ROW -> ROW_REVERSE/ROW_REVERSE_)
// Handle width and height cssstyle attributes
setDimensionFromStyle(node, mainAxis); //宽或高的保存
setDimensionFromStyle(node, crossAxis); //宽或高的保存
// Set the resolved resolution in the node's csslayout
node.csslayout.direction = direction; //布局方向
// The position is set by the parent, but we need to complete it with a
// delta composed of the margin and left/top/right/bottom //位置
node.csslayout.position[leading[mainAxis]] += node.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
getRelativePosition(node, mainAxis);
node.csslayout.position[trailing[mainAxis]] += node.cssstyle.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) +
getRelativePosition(node, mainAxis);
node.csslayout.position[leading[crossAxis]] += node.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
getRelativePosition(node, crossAxis);
node.csslayout.position[trailing[crossAxis]] += node.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) +
getRelativePosition(node, crossAxis);
// Inline immutable values from the target node to avoid excessive method
// invocations during the csslayout calculation.
int childCount = node.getChildCount();//子View的数目
float paddingAndBorderAxisResolvedRow = ((node.cssstyle.padding.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) +
node.cssstyle.border.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis])) +
(node.cssstyle.padding.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]) +
node.cssstyle.border.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]))); //横向的border + padding
if (isMeasureDefined(node)) { //自定义计算方法不为空。
boolean isResolvedRowDimDefined = !Float.isNaN(node.csslayout.dimensions[dim[resolvedRowAxis]]); //横向的
float width = CSSConstants.UNDEFINED;
if ((!Float.isNaN(node.cssstyle.dimensions[dim[resolvedRowAxis]]) && node.cssstyle.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
width = node.cssstyle.dimensions[DIMENSION_WIDTH];
} else if (isResolvedRowDimDefined) {
width = node.csslayout.dimensions[dim[resolvedRowAxis]];
} else {
width = parentMaxWidth -
(node.cssstyle.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.cssstyle.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]));
}
width -= paddingAndBorderAxisResolvedRow;
// We only need to give a dimension for the text if we haven't got any
// for it computed yet. It can either be from the cssstyle attribute or because
// the element is flexible.
boolean isRowUndefined = !(!Float.isNaN(node.cssstyle.dimensions[dim[resolvedRowAxis]]) && node.cssstyle.dimensions[dim[resolvedRowAxis]] >= 0.0) && !isResolvedRowDimDefined;
boolean isColumnUndefined = !(!Float.isNaN(node.cssstyle.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.cssstyle.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) &&
Float.isNaN(node.csslayout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]);
// Let's not measure the text if we already know both dimensions
if (isRowUndefined || isColumnUndefined) { //宽 或 高不确定
MeasureOutput measureDim = node.measure( //使用自定义的measure方法
layoutContext.measureOutput,
width
);
if (isRowUndefined) { //宽不确定的话,那么宽度= 计算的宽度+padding border
node.csslayout.dimensions[DIMENSION_WIDTH] = measureDim.width +
paddingAndBorderAxisResolvedRow;
}
if (isColumnUndefined) {// node.csslayout.dimensions[DIMENSION_HEIGHT] = measureDim.height +
((node.cssstyle.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.cssstyle.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.cssstyle.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.cssstyle.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])));
}
}
if (childCount == 0) { //没有child的话,计算完毕,直接返回。
return;
}
}
boolean isNodeFlexWrap = (node.cssstyle.flexWrap == CSSWrap.WRAP); //是否是FlexWrap
CSSJustify justifyContent = node.cssstyle.justifyContent;
float leadingPaddingAndBorderMain = (node.cssstyle.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]));
float leadingPaddingAndBorderCross = (node.cssstyle.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.cssstyle.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]));
float paddingAndBorderAxisMain = ((node.cssstyle.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.cssstyle.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.cssstyle.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
float paddingAndBorderAxisCross = ((node.cssstyle.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.cssstyle.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.cssstyle.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.cssstyle.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])));
boolean isMainDimDefined = !Float.isNaN(node.csslayout.dimensions[dim[mainAxis]]); //主轴方向的尺寸是确定的
boolean isCrossDimDefined = !Float.isNaN(node.csslayout.dimensions[dim[crossAxis]]); //侧轴方向的尺寸是确定的
boolean isMainRowDirection = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE); //主轴是横向的
int i;
int ii;
CSSNode child;
int axis;
CSSNode firstAbsoluteChild = null; //保存的absolute child
CSSNode currentAbsoluteChild = null;
float definedMainDim = CSSConstants.UNDEFINED;
if (isMainDimDefined) {
definedMainDim = node.csslayout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain; //主轴方向的内容的dimension
}
// We want to execute the next two loops one per line with flex-wrap
int startLine = 0;
int endLine = 0;
// int nextOffset = 0;
int alreadyComputedNextLayout = 0;
// We aggregate the total dimensions of the container in those two variables
float linesCrossDim = 0; //如果有多行的话,这是每一行侧轴方向的尺寸
float linesMainDim = 0;//如果有多行的话,这是每一行侧轴方向的尺寸 int linesCount = 0; //行数
while (endLine < childCount) { //结束的行数 少于 子元素个数的时候。
// <Loop A> Layout non flexible children and count children by type : 布局非Flex的children & 分类计数。
// mainContentDim is accumulation of the dimensions and margin of all the
// non flexible children. This will be used in order to either set the
// dimensions of the node if none already exist, or to compute the
// remaining space left for the flexible children.
float mainContentDim = 0; //所有非Flexible Children 的尺寸+margin 之和。
// There are three kind of children, non flexible, flexible and absolute.
// We need to know how many there are in order to distribute the space.
int flexibleChildrenCount = 0;
float totalFlexible = 0; //总体的权重 weight
int nonFlexibleChildrenCount = 0;
// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
boolean isSimpleStackMain =
(isMainDimDefined && justifyContent == CSSJustify.FLEX_START) ||
(!isMainDimDefined && justifyContent != CSSJustify.CENTER); //主轴是否是简单的堆叠
int firstComplexMain = (isSimpleStackMain ? childCount : startLine); //第一个导致复杂布局的子元素
// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
boolean isSimpleStackCross = true; //侧轴方向
int firstComplexCross = childCount;
CSSNode firstFlexChild = null;
CSSNode currentFlexChild = null;
float mainDim = leadingPaddingAndBorderMain; //主轴头部的padding + border
float crossDim = 0;
float maxWidth;
for (i = startLine; i < childCount; ++i) { // Loop A : 满足一定条件、确定性的child的宽高、位置会被确定。、统计各个类型child的数目
child = node.getChildAt(i);
if (!child.isShow()) { //不可见,直接下一个。
endLine = i + 1;
continue;
}
child.lineIndex = linesCount; //行数,记录在child上,下面有用。
child.nextAbsoluteChild = null; //组成链式储存结构
child.nextFlexChild = null;
CSSAlign alignItem = getAlignItem(node, child); //Child的侧轴对齐方式
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive csslayout pass
if (alignItem == CSSAlign.STRETCH && //Child的侧轴方向的宽度为空或者小于0,alignitem属性是stretch,位置是相对定位,parent的侧轴尺寸确定 -> 侧轴方向的尺寸可确定
child.cssstyle.positionType == CSSPositionType.RELATIVE &&
isCrossDimDefined &&
!(!Float.isNaN(child.cssstyle.dimensions[dim[crossAxis]]) && child.cssstyle.dimensions[dim[crossAxis]] >= 0.0)) {
child.csslayout.dimensions[dim[crossAxis]] = Math.max(
boundAxis(child, crossAxis, node.csslayout.dimensions[dim[crossAxis]] -
paddingAndBorderAxisCross - (child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
// You never want to go smaller than padding + border
((child.cssstyle.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.cssstyle.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.cssstyle.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
);
} else if (child.cssstyle.positionType == CSSPositionType.ABSOLUTE) { //绝对布局,保存下来,这个布局比较easy。
// Store a private linked list of absolutely positioned children
// so that we can efficiently traverse them later.
if (firstAbsoluteChild == null) {
firstAbsoluteChild = child;
}
if (currentAbsoluteChild != null) {
currentAbsoluteChild.nextAbsoluteChild = child;
}
currentAbsoluteChild = child; //保存起来绝对布局的元素,以便有效的重用。
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!Float.isNaN(node.csslayout.dimensions[dim[axis]]) && // 主轴不为空、child节点主轴的尺寸不确定、child节点的position属性不为空。 -> Child节点的尺寸
!(!Float.isNaN(child.cssstyle.dimensions[dim[axis]]) && child.cssstyle.dimensions[dim[axis]] >= 0.0) &&
!Float.isNaN(child.cssstyle.position[leading[axis]]) &&
!Float.isNaN(child.cssstyle.position[trailing[axis]])) {
child.csslayout.dimensions[dim[axis]] = Math.max( //parent对应轴的尺寸- padding-maigin-border-position
boundAxis(child, axis, node.csslayout.dimensions[dim[axis]] -
((node.cssstyle.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + node.cssstyle.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (node.cssstyle.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + node.cssstyle.border.getWithFallback(trailingSpacing[axis], trailing[axis]))) -
(child.cssstyle.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(Float.isNaN(child.cssstyle.position[leading[axis]]) ? 0 : child.cssstyle.position[leading[axis]]) -
(Float.isNaN(child.cssstyle.position[trailing[axis]]) ? 0 : child.cssstyle.position[trailing[axis]])),
// You never want to go smaller than padding+border
((child.cssstyle.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + child.cssstyle.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (child.cssstyle.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + child.cssstyle.border.getWithFallback(trailingSpacing[axis], trailing[axis])))
);
}
}
}
float nextContentDim = 0; //下一个内容的尺寸
// It only makes sense to consider a child flexible if we have a computed
// dimension for the node.
if (isMainDimDefined && (child.cssstyle.positionType == CSSPositionType.RELATIVE && child.cssstyle.flex > 0)) { // Flexible Children
flexibleChildrenCount++;
totalFlexible += child.cssstyle.flex;
// Store a private linked list of flexible children so that we can
// efficiently traverse them later.
if (firstFlexChild == null) {
firstFlexChild = child;
}
if (currentFlexChild != null) {
currentFlexChild.nextFlexChild = child;
}
currentFlexChild = child; //保存FlexChild链表
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information, which represents
// the smallest possible size for the child, to compute the remaining
// available space.
nextContentDim = ((child.cssstyle.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.cssstyle.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.cssstyle.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))) +
(child.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
} else {
maxWidth = CSSConstants.UNDEFINED;
if (!isMainRowDirection) { //不是横向的时候,算出来最大宽度
if ((!Float.isNaN(node.cssstyle.dimensions[dim[resolvedRowAxis]]) && node.cssstyle.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.csslayout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else {
maxWidth = parentMaxWidth -
(node.cssstyle.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.cssstyle.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
paddingAndBorderAxisResolvedRow;
}
}
// This is the main recursive call. We csslayout non flexible children.
if (alreadyComputedNextLayout == 0) {
layoutNode(layoutContext, child, maxWidth, direction); // 对于非Flexible的Children、非换行情况下,递归调用。
}
// Absolute positioned elements do not take part of the csslayout, so we
// don't use them to compute mainContentDim
if (child.cssstyle.positionType == CSSPositionType.RELATIVE) { /绝对布局不参与计算,不需要累计绝对布局的尺寸到mainContentDim上。
nonFlexibleChildrenCount++;
// At this point we know the final size and margin of the element.
nextContentDim = (child.csslayout.dimensions[dim[mainAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
}
}
// The element we are about to add would make us go to the next line
if (isNodeFlexWrap && //节点的布局是FlexWrap
isMainDimDefined && //主轴尺寸确定
mainContentDim + nextContentDim > definedMainDim && //主要当前内容尺寸+下一个内容的尺寸 大于 主轴的最大内容
// If there's only one element, then it's bigger than the content
// and needs its own line
i != startLine) { //同时Children不只一个。 那么换行。
nonFlexibleChildrenCount--;
alreadyComputedNextLayout = 1;
break; //跳出Loop A
}
// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child.cssstyle.positionType != CSSPositionType.RELATIVE || (child.cssstyle.positionType == CSSPositionType.RELATIVE && child.cssstyle.flex > 0))) {
isSimpleStackMain = false;
firstComplexMain = i;
}
// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child.cssstyle.positionType != CSSPositionType.RELATIVE ||
(alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) ||
Float.isNaN(child.csslayout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}
if (isSimpleStackMain) { //主轴上简单堆叠
child.csslayout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
child.csslayout.position[trailing[mainAxis]] = node.csslayout.dimensions[dim[mainAxis]] - child.csslayout.dimensions[dim[mainAxis]] - child.csslayout.position[pos[mainAxis]];
}
mainDim += (child.csslayout.dimensions[dim[mainAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
crossDim = Math.max(crossDim, boundAxis(child, crossAxis, (child.csslayout.dimensions[dim[crossAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))));
}
if (isSimpleStackCross) { //侧轴上简单堆叠
child.csslayout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
child.csslayout.position[trailing[crossAxis]] = node.csslayout.dimensions[dim[crossAxis]] - child.csslayout.dimensions[dim[crossAxis]] - child.csslayout.position[pos[crossAxis]];
}
}
alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim; //当前行的主内容尺寸
endLine = i + 1; //下一行
}
// <Loop B> Layout flexible children and allocate empty space // 确定flexible children的尺寸 以及 分配剩余空间。
// In order to position the elements in the main axis, we have two
// controls. The space between the beginning and the first element
// and the space between each two elements.
float leadingMainDim = 0; //首部空余空间
float betweenMainDim = 0; //元素之前空余空间。
// The remaining available space that needs to be allocated
float remainingMainDim = 0;
if (isMainDimDefined) { //最大宽度确定的情况下。
remainingMainDim = definedMainDim - mainContentDim;
} else {
remainingMainDim = Math.max(mainContentDim, 0) - mainContentDim;
}
// If there are flexible children in the mix, they are going to fill the
// remaining space
if (flexibleChildrenCount != 0) { //有Children
float flexibleMainDim = remainingMainDim / totalFlexible;
float baseMainDim; //计算出来的空间
float boundMainDim; //根据max/min height/width 限制下来的空间。
// If the flex share of remaining space doesn't meet min/max bounds,
// remove this child from flex calculations.
currentFlexChild = firstFlexChild;
while (currentFlexChild != null) { //遍历flexchildren链表
if (currentFlexChild.isShow()) { //当前Flex Child可见
baseMainDim = flexibleMainDim * currentFlexChild.cssstyle.flex +
((currentFlexChild.cssstyle.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.cssstyle.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.cssstyle.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim);
if (baseMainDim != boundMainDim) { //两者不等于的话,意味着:child真正占据的空间和Flex比例对不上了,需要重新计算flexibleMainDim。
remainingMainDim -= boundMainDim;
totalFlexible -= currentFlexChild.cssstyle.flex;
}
}
currentFlexChild = currentFlexChild.nextFlexChild;
}
flexibleMainDim = remainingMainDim / totalFlexible; //如果触达了最大值,那么新计算出来的flexibleMainDim比以前的大;反之更小。
// The non flexible children can overflow the container, in this case
// we should just assume that there is no space available.
if (flexibleMainDim < 0) {
flexibleMainDim = 0;
}
currentFlexChild = firstFlexChild;
while (currentFlexChild != null) { //再次遍历
if (currentFlexChild.isShow()) {
// At this point we know the final size of the element in the main
// dimension //根据Flex计算出主轴方向的dimension
currentFlexChild.csslayout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis,
flexibleMainDim * currentFlexChild.cssstyle.flex +
((currentFlexChild.cssstyle.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.cssstyle.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.cssstyle.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))
);
maxWidth = CSSConstants.UNDEFINED;
if ((!Float.isNaN(node.cssstyle.dimensions[dim[resolvedRowAxis]]) && node.cssstyle.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.csslayout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else if (!isMainRowDirection) {
maxWidth = parentMaxWidth -
(node.cssstyle.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.cssstyle.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
paddingAndBorderAxisResolvedRow;
}
//递归调用
// And we recursively call the csslayout algorithm for this child
layoutNode(layoutContext, currentFlexChild, maxWidth, direction);
}
child = currentFlexChild;
currentFlexChild = currentFlexChild.nextFlexChild;
child.nextFlexChild = null;
}
// We use justifyContent to figure out how to allocate the remaining
// space available
} else if (justifyContent != CSSJustify.FLEX_START) {
if (justifyContent == CSSJustify.CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSSJustify.FLEX_END) {
leadingMainDim = remainingMainDim;
} else if (justifyContent == CSSJustify.SPACE_BETWEEN) {
remainingMainDim = Math.max(remainingMainDim, 0);
if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) {
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount - 1);
} else {
betweenMainDim = 0;
}
} else if (justifyContent == CSSJustify.SPACE_AROUND) {
// Space on the edges is half of the space between elements
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount);
leadingMainDim = betweenMainDim / 2;
}
}
// <Loop C> Position elements in the main axis and compute dimensions //计算主轴方向Children的位置
// At this point, all the children have their dimensions set. We need to
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
mainDim += leadingMainDim; //主轴方向 已占用的 dimension
for (i = firstComplexMain; i < endLine; ++i) { //第一个没法确定位置的元素开始
child = node.getChildAt(i);
if (!child.isShow()) {
continue;
}
if (child.cssstyle.positionType == CSSPositionType.ABSOLUTE &&
!Float.isNaN(child.cssstyle.position[leading[mainAxis]])) { // 绝对布局+margin不为空。
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said
// (and margin/border).
child.csslayout.position[pos[mainAxis]] = (Float.isNaN(child.cssstyle.position[leading[mainAxis]]) ? 0 : child.cssstyle.position[leading[mainAxis]]) +
node.cssstyle.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
child.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]);
} else {
// If the child is position absolute (without top/left) or relative,
// we put it at the current accumulated offset.
child.csslayout.position[pos[mainAxis]] += mainDim; //否则加上当前的pos值
// Define the trailing position accordingly.
if (isMainDimDefined) { //算出尾部的位置,相当于margin bottom
child.csslayout.position[trailing[mainAxis]] = node.csslayout.dimensions[dim[mainAxis]] - child.csslayout.dimensions[dim[mainAxis]] - child.csslayout.position[pos[mainAxis]];
}
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (child.cssstyle.positionType == CSSPositionType.RELATIVE) { //相对布局的时候,计算mainDim和crossDim
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + (child.csslayout.dimensions[dim[mainAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = Math.max(crossDim, boundAxis(child, crossAxis, (child.csslayout.dimensions[dim[crossAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))));
}
}
}
float containerCrossAxis = node.csslayout.dimensions[dim[crossAxis]];
if (!isCrossDimDefined) { //容器侧轴的尺寸不确定
containerCrossAxis = Math.max(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
}
// <Loop D> Position elements in the cross axis //计算侧轴方向元素的位置。
for (i = firstComplexCross; i < endLine; ++i) { //第一个无法确定位置的侧轴元素坐标
child = node.getChildAt(i);
if (!child.isShow()) {
continue;
}
if (child.cssstyle.positionType == CSSPositionType.ABSOLUTE &&
!Float.isNaN(child.cssstyle.position[leading[crossAxis]])) { //绝对布局的时候,直接加上去。
// In case the child is absolutely positionned and has a
// top/left/bottom/right being set, we override all the previously
// computed positions to set it correctly.
child.csslayout.position[pos[crossAxis]] = (Float.isNaN(child.cssstyle.position[leading[crossAxis]]) ? 0 : child.cssstyle.position[leading[crossAxis]]) +
node.cssstyle.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
} else {
float leadingCrossDim = leadingPaddingAndBorderCross; //侧轴头部内容(侧轴node节点的padding + border)
// For a relative children, we're either using alignItems (parent) or
// alignSelf (child) in order to determine the position in the cross axis
if (child.cssstyle.positionType == CSSPositionType.RELATIVE) { //相对布局
/*eslint-disable */
// This variable is intentionally re-defined as the code is transpiled to a block scope language
CSSAlign alignItem = getAlignItem(node, child);
/*eslint-enable */
if (alignItem == CSSAlign.STRETCH) { //撑满
// You can only stretch if the dimension has not already been set
// previously.
if (Float.isNaN(child.csslayout.dimensions[dim[crossAxis]])) {
child.csslayout.dimensions[dim[crossAxis]] = Math.max(
boundAxis(child, crossAxis, containerCrossAxis -
paddingAndBorderAxisCross - (child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
// You never want to go smaller than padding
((child.cssstyle.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.cssstyle.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.cssstyle.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
);
}
} else if (alignItem != CSSAlign.FLEX_START) {
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
paddingAndBorderAxisCross - (child.csslayout.dimensions[dim[crossAxis]] + child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]));
if (alignItem == CSSAlign.CENTER) {
leadingCrossDim += remainingCrossDim / 2;
} else { // CSSAlign.FLEX_END
leadingCrossDim += remainingCrossDim;
}
}
}
// And we apply the position
child.csslayout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
// Define the trailing position accordingly.
if (isCrossDimDefined) { //侧轴固定的话,还可以自己算出尾部的坐标。
child.csslayout.position[trailing[crossAxis]] = node.csslayout.dimensions[dim[crossAxis]] - child.csslayout.dimensions[dim[crossAxis]] - child.csslayout.position[pos[crossAxis]];
}
}
}
linesCrossDim += crossDim; //侧轴行尺寸(累计)
linesMainDim = Math.max(linesMainDim, mainDim); //主轴行的尺寸。(取最大值)
linesCount += 1; // 行数
startLine = endLine; //开始的child index ,开始换行。
}
//前四轮循环下来,所有的children的位置和宽高都确定了。
// <Loop E> : 超过一行的情况下,需要计算侧轴方向的位置和大小(根据alignItem和alignSelf属性)
//
// Note(prenaux): More than one line, we need to csslayout the crossAxis
// according to alignContent.
//
// Note that we could probably remove <Loop D> and handle the one line case
// here too, but for the moment this is safer since it won't interfere with
// previously working code.(实际上可以移除loop D,但是loop d之前跑的OK,就不干预了……)
//
// See specs:
// http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#csslayout-algorithm
// section 9.4
//
if (linesCount > 1 && isCrossDimDefined) { //超过一行,并且侧轴内容确定的
float nodeCrossAxisInnerSize = node.csslayout.dimensions[dim[crossAxis]] - paddingAndBorderAxisCross; //侧轴内容的size
float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; //剩余空间的尺寸
float crossDimLead = 0;
float currentLead = leadingPaddingAndBorderCross; //头部的距离()
CSSAlign alignContent = node.cssstyle.alignContent;
if (alignContent == CSSAlign.FLEX_END) { //尾部对齐的话,剩余空间全部加到首部距离里面
currentLead += remainingAlignContentDim;
} else if (alignContent == CSSAlign.CENTER) { //居中的话,加一半
currentLead += remainingAlignContentDim / 2;
} else if (alignContent == CSSAlign.STRETCH) { //撑满的话,
if (nodeCrossAxisInnerSize > linesCrossDim) {//如果内容尺寸大于所有行的尺寸,即剩余空间尺寸大于0
crossDimLead = (remainingAlignContentDim / linesCount); //按比例计算。
}
}
int endIndex = 0;
for (i = 0; i < linesCount; ++i) { //遍历所有的行
int startIndex = endIndex;
// compute the line's height and find the endIndex
float lineHeight = 0;
for (ii = startIndex; ii < childCount; ++ii) { //遍历每一行中的元素,计算出本行的行高
child = node.getChildAt(ii);
if (!child.isShow() || child.cssstyle.positionType != CSSPositionType.RELATIVE) {
continue;
}
if (child.lineIndex != i) { //下一行,跳出。
break;
}
if (!Float.isNaN(child.csslayout.dimensions[dim[crossAxis]])) {
lineHeight = Math.max(
lineHeight,
child.csslayout.dimensions[dim[crossAxis]] + (child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))
);
}
}
endIndex = ii;
lineHeight += crossDimLead; //修正行高
for (ii = startIndex; ii < endIndex; ++ii) { //根据计算出来的新的行高,重新确定位置。
child = node.getChildAt(ii);
if (!child.isShow() || child.cssstyle.positionType != CSSPositionType.RELATIVE) {
continue;
}
CSSAlign alignContentAlignItem = getAlignItem(node, child);
if (alignContentAlignItem == CSSAlign.FLEX_START) {
child.csslayout.position[pos[crossAxis]] = currentLead + child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
} else if (alignContentAlignItem == CSSAlign.FLEX_END) {
child.csslayout.position[pos[crossAxis]] = currentLead + lineHeight - child.cssstyle.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) - child.csslayout.dimensions[dim[crossAxis]];
} else if (alignContentAlignItem == CSSAlign.CENTER) {
float childHeight = child.csslayout.dimensions[dim[crossAxis]];
child.csslayout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2;
} else if (alignContentAlignItem == CSSAlign.STRETCH) {
child.csslayout.position[pos[crossAxis]] = currentLead + child.cssstyle.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
// TODO(prenaux): Correctly set the height of items with undefined
// (auto) crossAxis dimension.
}
}
currentLead += lineHeight;
}
}
boolean needsMainTrailingPos = false;
boolean needsCrossTrailingPos = false;
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (!isMainDimDefined) { //主轴不确定的话,尝试用主轴方向行的内容加padding、border计算。
node.csslayout.dimensions[dim[mainAxis]] = Math.max(
// We're missing the last padding at this point to get the final
// dimension
boundAxis(node, mainAxis, linesMainDim + (node.cssstyle.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.cssstyle.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))),
// We can never assign a width smaller than the padding and borders
paddingAndBorderAxisMain
);
if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { //如果是反向布局的话,需要知道尾部位置。
needsMainTrailingPos = true;
}
}
if (!isCrossDimDefined) { //侧轴不固定
node.csslayout.dimensions[dim[crossAxis]] = Math.max(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsCrossTrailingPos = true;
}
}
// <Loop F> Set trailing position if necessary
if (needsMainTrailingPos || needsCrossTrailingPos) { //如果是反向布局的话,这两个需要确定。
for (i = 0; i < childCount; ++i) {
child = node.getChildAt(i);
if (!child.isShow()) {
continue;
}
if (needsMainTrailingPos) { //根据node dimension和 child dimension 以及 child position计算
child.csslayout.position[trailing[mainAxis]] = node.csslayout.dimensions[dim[mainAxis]] - child.csslayout.dimensions[dim[mainAxis]] - child.csslayout.position[pos[mainAxis]];
}
if (needsCrossTrailingPos) {
child.csslayout.position[trailing[crossAxis]] = node.csslayout.dimensions[dim[crossAxis]] - child.csslayout.dimensions[dim[crossAxis]] - child.csslayout.position[pos[crossAxis]];
}
}
}
// <Loop G> Calculate dimensions for absolutely positioned elements //最后计算绝对布局的尺寸。
currentAbsoluteChild = firstAbsoluteChild;
while (currentAbsoluteChild != null) {
if (currentAbsoluteChild.isShow()) {
// Pre-fill dimensions when using absolute position and both offsets for
// the axis are defined (either both left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!Float.isNaN(node.csslayout.dimensions[dim[axis]]) &&
!(!Float.isNaN(currentAbsoluteChild.cssstyle.dimensions[dim[axis]]) && currentAbsoluteChild.cssstyle.dimensions[dim[axis]] >= 0.0) &&
!Float.isNaN(currentAbsoluteChild.cssstyle.position[leading[axis]]) &&
!Float.isNaN(currentAbsoluteChild.cssstyle.position[trailing[axis]])) {
currentAbsoluteChild.csslayout.dimensions[dim[axis]] = Math.max(
boundAxis(currentAbsoluteChild, axis, node.csslayout.dimensions[dim[axis]] -
(node.cssstyle.border.getWithFallback(leadingSpacing[axis], leading[axis]) + node.cssstyle.border.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(currentAbsoluteChild.cssstyle.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.cssstyle.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(Float.isNaN(currentAbsoluteChild.cssstyle.position[leading[axis]]) ? 0 : currentAbsoluteChild.cssstyle.position[leading[axis]]) -
(Float.isNaN(currentAbsoluteChild.cssstyle.position[trailing[axis]]) ? 0 : currentAbsoluteChild.cssstyle.position[trailing[axis]])
),
// You never want to go smaller than padding
((currentAbsoluteChild.cssstyle.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.cssstyle.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (currentAbsoluteChild.cssstyle.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + currentAbsoluteChild.cssstyle.border.getWithFallback(trailingSpacing[axis], trailing[axis])))
);
}
if (!Float.isNaN(currentAbsoluteChild.cssstyle.position[trailing[axis]]) &&
!!Float.isNaN(currentAbsoluteChild.cssstyle.position[leading[axis]])) {
currentAbsoluteChild.csslayout.position[leading[axis]] =
node.csslayout.dimensions[dim[axis]] -
currentAbsoluteChild.csslayout.dimensions[dim[axis]] -
(Float.isNaN(currentAbsoluteChild.cssstyle.position[trailing[axis]]) ? 0 : currentAbsoluteChild.cssstyle.position[trailing[axis]]);
}
}
}
child = currentAbsoluteChild;
currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild;
child.nextAbsoluteChild = null;
}
}
方法太长,基本流程就是我上面列举的,重要的看注释,会有对应流程的分析。
主要的注意的点是
A-D循环,每次计算的是一行。如果发生了换行,那么重新要run一次。
4 总结
布局代码看起来挺长、复杂,其实自信分析下来,除了几个比较关键要理解的点外,总体过程也不难,
另外中间的英文注释很重要。