看流式布局的源码解析,首先需要从自定义布局的几个步骤进行考虑,即onMeasure、onLayout、onDraw。因为流式布局是ViewGroup的,所以我们这里不考虑onDraw。
一、FlexboxLayout.onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 初始化缓存
if (mOrderCache == null) {
mOrderCache = new SparseIntArray(getChildCount());
}
// 判断缓存中的在最后一次测量之后是否发生了改变
if (mFlexboxHelper.isOrderChangedFromLastMeasurement(mOrderCache)) {
// 如果发生了改变,则重新针对View做索引排序
// 这里是将容器内存储对应每个View的Order集合做排序,
// 然后根据排序结果,将Order的index(代表View的索引)保存在reorderedIndices数组中
// 数组中保存的其实就是View的索引位置,并且会想缓存中拼接对应的Order信息
mReorderedIndices = mFlexboxHelper.createReorderedIndices(mOrderCache);
}
// TODO: Only calculate the children views which are affected from the last measure.
// 根据流式布局的排列方向进行展示
switch (mFlexDirection) {
// 横向排列,即按子View顺序在横向根据设置是从左到右还是从右到左
case FlexDirection.ROW: // Intentional fall through
case FlexDirection.ROW_REVERSE:
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
break;
// 纵向排列
case FlexDirection.COLUMN: // Intentional fall through
case FlexDirection.COLUMN_REVERSE:
measureVertical(widthMeasureSpec, heightMeasureSpec);
break;
default:
throw new IllegalStateException(
"Invalid value for the flex direction is set: " + mFlexDirection);
}
}
1.FlexboxHelper.createReorderedIndices
创建流式布局内的View的索引数组
int[] createReorderedIndices(SparseIntArray orderCache) {
int childCount = mFlexContainer.getFlexItemCount();
// 针对布局内的每一个View.LayoutParams创建一个Order对象,并保存在List集合
List<Order> orders = createOrders(childCount);
// 针对Order排序,并且缓存其index这个索引值,然后将Order信息缓存在OrderCache中
return sortOrdersIntoReorderedIndices(childCount, orders, orderCache);
}
(1)FlexboxHelper.createOrders
@NonNull
private List<Order> createOrders(int childCount) {
List<Order> orders = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
View child = mFlexContainer.getFlexItemAt(i);
FlexItem flexItem = (FlexItem) child.getLayoutParams();
Order order = new Order();
order.order = flexItem.getOrder();
order.index = i;
orders.add(order);
}
return orders;
}
(2)FlexboxHelper.sortOrdersIntoReorderedIndices
private int[] sortOrdersIntoReorderedIndices(int childCount, List<Order> orders,
SparseIntArray orderCache) {
Collections.sort(orders);
orderCache.clear();
int[] reorderedIndices = new int[childCount];
int i = 0;
for (Order order : orders) {
// 缓存Order的索引值
reorderedIndices[i] = order.index;
orderCache.append(order.index, order.order);
i++;
}
return reorderedIndices;
}
2.FlexboxLayout.measureHorizontal
这里就分析一个方向的关于子View的测量过程,着重分析水平方向的做法,因为水平方向和垂直方向大体相同
/**
* 具体测量布局的
* 这是测量横向排列的时候的布局情况
*/
private void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
// 因为onMeasure会多次调用测量,所以每次需要重置
mFlexLines.clear();
mFlexLinesResult.reset();
// 计算水平方向的每一行的信息
mFlexboxHelper
.calculateHorizontalFlexLines(mFlexLinesResult, widthMeasureSpec,
heightMeasureSpec);
mFlexLines = mFlexLinesResult.mFlexLines;
// 确定主轴方向的尺寸
mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec);
// TODO: Consider the case any individual child's mAlignSelf is set to ALIGN_SELF_BASELINE
if (mAlignItems == AlignItems.BASELINE) {
for (FlexLine flexLine : mFlexLines) {
// The largest height value that also take the baseline shift into account
int largestHeightInLine = Integer.MIN_VALUE;
for (int i = 0; i < flexLine.mItemCount; i++) {
// flexLine.mFirstIndex是当前行的第一个View的位置
// flexLine.mFirstIndex+i就表示当前行第i个View在
// 布局中的位置
int viewIndex = flexLine.mFirstIndex + i;
View child = getReorderedChildAt(viewIndex);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (mFlexWrap != FlexWrap.WRAP_REVERSE) {
int marginTop = flexLine.mMaxBaseline - child.getBaseline();
marginTop = Math.max(marginTop, lp.topMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getMeasuredHeight() + marginTop + lp.bottomMargin);
} else {
int marginBottom = flexLine.mMaxBaseline - child.getMeasuredHeight() +
child.getBaseline();
marginBottom = Math.max(marginBottom, lp.bottomMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getMeasuredHeight() + lp.topMargin + marginBottom);
}
}
flexLine.mCrossSize = largestHeightInLine;
}
}
// 计算辅轴的大小
mFlexboxHelper.determineCrossSize(widthMeasureSpec, heightMeasureSpec,
getPaddingTop() + getPaddingBottom());
// Now cross size for each flex line is determined.
// Expand the views if alignItems (or mAlignSelf in each child view) is set to stretch
// 拉伸视图,通过AlignItems的属性为STRETCH来判断是否拉伸
// 拉伸的方向是辅轴方向,如果横向是主轴,那么就拉伸纵向
// 根据辅轴方向的大小做拉伸限制。比如纵向是辅轴,就需要重新计算View的新的高度
mFlexboxHelper.stretchViews();
// 根据FlexboxLayout的widthMode和heightMode,确定其实际的widthSize和heightSize
setMeasuredDimensionForFlex(mFlexDirection, widthMeasureSpec, heightMeasureSpec,
mFlexLinesResult.mChildState);
}
(1)FlexboxHelper.calculateHorizontalFlexLines
测量水平每一行的子View的数量以及对每个子View做测量
void calculateHorizontalFlexLines(FlexLinesResult result, int widthMeasureSpec,
int heightMeasureSpec) {
calculateFlexLines(result, widthMeasureSpec, heightMeasureSpec, Integer.MAX_VALUE,
0, NO_POSITION, null);
}
(2)FlexboxHelper.calculateFlexLines
void calculateFlexLines(FlexLinesResult result, int mainMeasureSpec,
int crossMeasureSpec, int needsCalcAmount, int fromIndex, int toIndex,
@Nullable List<FlexLine> existingLines) {
// 判断主轴方向是否是水平方向
boolean isMainHorizontal = mFlexContainer.isMainAxisDirectionHorizontal();
// 获取主轴方向的mode和size
// 如果是横向排列,则是width的mode和size
int mainMode = View.MeasureSpec.getMode(mainMeasureSpec);
int mainSize = View.MeasureSpec.getSize(mainMeasureSpec);
int childState = 0;
// 初始化每一行的存储集合
List<FlexLine> flexLines;
if (existingLines == null) {
flexLines = new ArrayList<>();
} else {
flexLines = existingLines;
}
// 保存每一行数据集合到FlexLinesResult中,这是一个FlexboxHelper的静态内部类
result.mFlexLines = flexLines;
// 判断是否有记录的索引
boolean reachedToIndex = toIndex == NO_POSITION;
// 初始化FlexboxLayout的padding
int mainPaddingStart = getPaddingStartMain(isMainHorizontal);
int mainPaddingEnd = getPaddingEndMain(isMainHorizontal);
int crossPaddingStart = getPaddingStartCross(isMainHorizontal);
int crossPaddingEnd = getPaddingEndCross(isMainHorizontal);
// 初始化在辅轴方向的最大值
int largestSizeInCross = Integer.MIN_VALUE;
// The amount of cross size calculated in this method call.
int sumCrossSize = 0;
// The index of the view in the flex line.
int indexInFlexLine = 0;
// 初始化每一行的信息,包括当前行的第一个View的索引和使用的padding大小
FlexLine flexLine = new FlexLine();
flexLine.mFirstIndex = fromIndex;
flexLine.mMainSize = mainPaddingStart + mainPaddingEnd;
// 遍历FlexboxLayout内的每一个View
// 横向排列遍历时,fromIndex的初始值为0
int childCount = mFlexContainer.getFlexItemCount();
for (int i = fromIndex; i < childCount; i++) {
View child = mFlexContainer.getReorderedFlexItemAt(i);
if (child == null) {
// 如果child为null,判断是否是最后一行,是的话,添加到集合中保存
if (isLastFlexItem(i, childCount, flexLine)) {
addFlexLine(flexLines, flexLine, i, sumCrossSize);
}
continue;
} else if (child.getVisibility() == View.GONE) {
// 如果View的visibility是GONE,则记录,并且判断是否是最后一行
flexLine.mGoneItemCount++;
flexLine.mItemCount++;
if (isLastFlexItem(i, childCount, flexLine)) {
addFlexLine(flexLines, flexLine, i, sumCrossSize);
}
continue;
} else if (child instanceof CompoundButton) {
evaluateMinimumSizeForCompoundButton((CompoundButton) child);
}
// 获取child的LayoutParams
FlexItem flexItem = (FlexItem) child.getLayoutParams();
// 判断child的LayoutParams的AlignItems是否为STRETCH
if (flexItem.getAlignSelf() == AlignItems.STRETCH) {
// 保存AlignItems为STRETCH的View的索引
// AlignItems为STRETCH,当子View没高度时默认充满容器
flexLine.mIndicesAlignSelfStretch.add(i);
}
// 计算child的主轴大小(水平的就是宽度)
int childMainSize = getFlexItemSizeMain(flexItem, isMainHorizontal);
// 判断child的基本比例是否不是默认比例,并且FlexboxLayout的mainMode为EXACTLY确切值
if (flexItem.getFlexBasisPercent() != FLEX_BASIS_PERCENT_DEFAULT
&& mainMode == View.MeasureSpec.EXACTLY) {
// 重新计算child的主轴大小,按基本比例计算
childMainSize = Math.round(mainSize * flexItem.getFlexBasisPercent());
// Use the dimension from the layout if the mainMode is not
// MeasureSpec.EXACTLY even if any fraction value is set to
// layout_flexBasisPercent.
}
// 测量child的宽高
int childMainMeasureSpec;
int childCrossMeasureSpec;
if (isMainHorizontal) {
childMainMeasureSpec = mFlexContainer.getChildWidthMeasureSpec(mainMeasureSpec,
mainPaddingStart + mainPaddingEnd +
getFlexItemMarginStartMain(flexItem, true) +
getFlexItemMarginEndMain(flexItem, true),
childMainSize);
childCrossMeasureSpec = mFlexContainer.getChildHeightMeasureSpec(crossMeasureSpec,
crossPaddingStart + crossPaddingEnd +
getFlexItemMarginStartCross(flexItem, true) +
getFlexItemMarginEndCross(flexItem, true)
+ sumCrossSize,
getFlexItemSizeCross(flexItem, true));
child.measure(childMainMeasureSpec, childCrossMeasureSpec);
// 修改子View的MeasureSpec的缓存信息
updateMeasureCache(i, childMainMeasureSpec, childCrossMeasureSpec, child);
} else {
childCrossMeasureSpec = mFlexContainer.getChildWidthMeasureSpec(crossMeasureSpec,
crossPaddingStart + crossPaddingEnd +
getFlexItemMarginStartCross(flexItem, false) +
getFlexItemMarginEndCross(flexItem, false) + sumCrossSize,
getFlexItemSizeCross(flexItem, false));
childMainMeasureSpec = mFlexContainer.getChildHeightMeasureSpec(mainMeasureSpec,
mainPaddingStart + mainPaddingEnd +
getFlexItemMarginStartMain(flexItem, false) +
getFlexItemMarginEndMain(flexItem, false),
childMainSize);
child.measure(childCrossMeasureSpec, childMainMeasureSpec);
updateMeasureCache(i, childCrossMeasureSpec, childMainMeasureSpec, child);
}
// 修改子View在容器中的缓存,其实就是调用FlexboxLayout的updateViewCache方法
// 但是FlexboxLayout并没有实现该方法,是一个空方法
mFlexContainer.updateViewCache(i, child);
// Check the size constraint after the first measurement for the child
// To prevent the child's width/height violate the size constraints imposed by the
// {@link FlexItem#getMinWidth()}, {@link FlexItem#getMinHeight()},
// {@link FlexItem#getMaxWidth()} and {@link FlexItem#getMaxHeight()} attributes.
// E.g. When the child's layout_width is wrap_content the measured width may be
// less than the min width after the first measurement.
checkSizeConstraints(child, i);
childState = View.combineMeasuredStates(
childState, child.getMeasuredState());
// 判断是否是正常方向,并且当前子View加入之后是否需要换行
if (isWrapRequired(child, mainMode, mainSize, flexLine.mMainSize,
getViewMeasuredSizeMain(child, isMainHorizontal)
+ getFlexItemMarginStartMain(flexItem, isMainHorizontal) +
getFlexItemMarginEndMain(flexItem, isMainHorizontal),
flexItem, i, indexInFlexLine, flexLines.size())) {
// 满足if条件的是需要换行的
if (flexLine.getItemCountNotGone() > 0) {
addFlexLine(flexLines, flexLine, i > 0 ? i - 1 : 0, sumCrossSize);
sumCrossSize += flexLine.mCrossSize;
}
// 针对Item的高度为MATCH_PARENT的子View做特别的测量处理
if (isMainHorizontal) {
if (flexItem.getHeight() == ViewGroup.LayoutParams.MATCH_PARENT) {
// This case takes care of the corner case where the cross size of the
// child is affected by the just added flex line.
// E.g. when the child's layout_height is set to match_parent, the height
// of that child needs to be determined taking the total cross size used
// so far into account. In that case, the height of the child needs to be
// measured again note that we don't need to judge if the wrapping occurs
// because it doesn't change the size along the main axis.
childCrossMeasureSpec = mFlexContainer.getChildHeightMeasureSpec(
crossMeasureSpec,
mFlexContainer.getPaddingTop() + mFlexContainer.getPaddingBottom()
+ flexItem.getMarginTop()
+ flexItem.getMarginBottom() + sumCrossSize,
flexItem.getHeight());
child.measure(childMainMeasureSpec, childCrossMeasureSpec);
checkSizeConstraints(child, i);
}
} else {
if (flexItem.getWidth() == ViewGroup.LayoutParams.MATCH_PARENT) {
// This case takes care of the corner case where the cross size of the
// child is affected by the just added flex line.
// E.g. when the child's layout_width is set to match_parent, the width
// of that child needs to be determined taking the total cross size used
// so far into account. In that case, the width of the child needs to be
// measured again note that we don't need to judge if the wrapping occurs
// because it doesn't change the size along the main axis.
childCrossMeasureSpec = mFlexContainer.getChildWidthMeasureSpec(
crossMeasureSpec,
mFlexContainer.getPaddingLeft() + mFlexContainer.getPaddingRight()
+ flexItem.getMarginLeft()
+ flexItem.getMarginRight() + sumCrossSize,
flexItem.getWidth());
child.measure(childCrossMeasureSpec, childMainMeasureSpec);
checkSizeConstraints(child, i);
}
}
// 换行,对行变量重新初始化,并且将当前行的子View数量置为1
// 这是在当前子View测量的时候判断当前子View是否需要缓存处理
flexLine = new FlexLine();
flexLine.mItemCount = 1;
flexLine.mMainSize = mainPaddingStart + mainPaddingEnd;
flexLine.mFirstIndex = i;
indexInFlexLine = 0;
largestSizeInCross = Integer.MIN_VALUE;
} else {
// 如果不需要换行,当前行子View数量加1,并且在行内的索引也加1
flexLine.mItemCount++;
indexInFlexLine++;
}
flexLine.mAnyItemsHaveFlexGrow |= flexItem.getFlexGrow() != FLEX_GROW_DEFAULT;
flexLine.mAnyItemsHaveFlexShrink |= flexItem.getFlexShrink() != FLEX_SHRINK_NOT_SET;
if (mIndexToFlexLine != null) {
mIndexToFlexLine[i] = flexLines.size();
}
// 计算当前行的主轴大小
// 由上一次的结果+子View的主轴大小和主轴的开始和结束时的margin决定
flexLine.mMainSize += getViewMeasuredSizeMain(child, isMainHorizontal)
+ getFlexItemMarginStartMain(flexItem, isMainHorizontal) +
getFlexItemMarginEndMain(flexItem, isMainHorizontal);
flexLine.mTotalFlexGrow += flexItem.getFlexGrow();
flexLine.mTotalFlexShrink += flexItem.getFlexShrink();
// 当有一个新的子View加入容器中,在这里检查是否需要在开始或者中间有分割线
mFlexContainer.onNewFlexItemAdded(child, i, indexInFlexLine, flexLine);
largestSizeInCross = Math.max(largestSizeInCross,
getViewMeasuredSizeCross(child, isMainHorizontal) +
getFlexItemMarginStartCross(flexItem, isMainHorizontal) +
getFlexItemMarginEndCross(flexItem, isMainHorizontal) +
mFlexContainer.getDecorationLengthCrossAxis(child));
// Temporarily set the cross axis length as the largest child in the flexLine
// Expand along the cross axis depending on the mAlignContent property if needed
// later
flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestSizeInCross);
if (isMainHorizontal) {
if (mFlexContainer.getFlexWrap() != FlexWrap.WRAP_REVERSE) {
flexLine.mMaxBaseline = Math.max(flexLine.mMaxBaseline,
child.getBaseline() + flexItem.getMarginTop());
} else {
// if the flex wrap property is WRAP_REVERSE, calculate the
// baseline as the distance from the cross end and the baseline
// since the cross size calculation is based on the distance from the cross end
flexLine.mMaxBaseline = Math.max(flexLine.mMaxBaseline,
child.getMeasuredHeight() - child.getBaseline()
+ flexItem.getMarginBottom());
}
}
if (isLastFlexItem(i, childCount, flexLine)) {
addFlexLine(flexLines, flexLine, i, sumCrossSize);
sumCrossSize += flexLine.mCrossSize;
}
if (toIndex != NO_POSITION
&& flexLines.size() > 0
&& flexLines.get(flexLines.size() - 1).mLastIndex >= toIndex
&& i >= toIndex
&& !reachedToIndex) {
// Calculated to include a flex line which includes the flex item having the
// toIndex.
// Let the sumCrossSize start from the negative value of the last flex line's
// cross size because otherwise flex lines aren't calculated enough to fill the
// visible area.
sumCrossSize = -flexLine.getCrossSize();
reachedToIndex = true;
}
if (sumCrossSize > needsCalcAmount && reachedToIndex) {
// Stop the calculation if the sum of cross size calculated reached to the point
// beyond the needsCalcAmount value to avoid unneeded calculation in a
// RecyclerView.
// To be precise, the decoration length may be added to the sumCrossSize,
// but we omit adding the decoration length because even without the decorator
// length, it's guaranteed that calculation is done at least beyond the
// needsCalcAmount
break;
}
}
result.mChildState = childState;
}
(3)FlexboxHelper.determineMainSize
确定容器的实际的主轴尺寸,即MainSize。并且根据是否允许放大或者收缩以及计算得到的每一行的mainSize和FlexboxLayout的mainSize比较,对子View做一定的放大或者缩小
void determineMainSize(int widthMeasureSpec, int heightMeasureSpec) {
determineMainSize(widthMeasureSpec, heightMeasureSpec, 0);
}
void determineMainSize(int widthMeasureSpec, int heightMeasureSpec, int fromIndex) {
ensureChildrenFrozen(mFlexContainer.getFlexItemCount());
if (fromIndex >= mFlexContainer.getFlexItemCount()) {
return;
}
int mainSize;
int paddingAlongMainAxis;
// 获取FlexboxLayout的排列方向,根据排列方向确定mainSize
int flexDirection = mFlexContainer.getFlexDirection();
switch (mFlexContainer.getFlexDirection()) {
case FlexDirection.ROW: // Intentional fall through
case FlexDirection.ROW_REVERSE:
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
// 遍历每一行,获取每一行的主轴size,然后得到最大值
int largestMainSize = mFlexContainer.getLargestMainSize();
// 判断FlexboxLayout的widthMode,如果是EXACTLY,则直接使用FlexboxLayout的宽度
// 如果不是,则通过比较widthSize和largestMainSize取小值
if (widthMode == View.MeasureSpec.EXACTLY) {
mainSize = widthSize;
} else {
mainSize = largestMainSize > widthSize ? widthSize : largestMainSize;
}
// 主轴方向两侧的padding的总和
paddingAlongMainAxis = mFlexContainer.getPaddingLeft()
+ mFlexContainer.getPaddingRight();
break;
case FlexDirection.COLUMN: // Intentional fall through
case FlexDirection.COLUMN_REVERSE:
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == View.MeasureSpec.EXACTLY) {
mainSize = heightSize;
} else {
mainSize = mFlexContainer.getLargestMainSize();
}
paddingAlongMainAxis = mFlexContainer.getPaddingTop()
+ mFlexContainer.getPaddingBottom();
break;
default:
throw new IllegalArgumentException("Invalid flex direction: " + flexDirection);
}
int flexLineIndex = 0;
if (mIndexToFlexLine != null) {
flexLineIndex = mIndexToFlexLine[fromIndex];
}
List<FlexLine> flexLines = mFlexContainer.getFlexLinesInternal();
// 遍历每一行,并且根据每一行的宽度做判断
// 并且判断是否是允许展开或者收缩
for (int i = flexLineIndex, size = flexLines.size(); i < size; i++) {
FlexLine flexLine = flexLines.get(i);
// flexLine.mAnyItemsHaveFlexGrow是允许放大一定的比例
if (flexLine.mMainSize < mainSize && flexLine.mAnyItemsHaveFlexGrow) {
expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine,
mainSize, paddingAlongMainAxis, false);
}
// flexLine.mAnyItemsHaveFlexShrink是允许收缩一定的比例
else if (flexLine.mMainSize > mainSize && flexLine.mAnyItemsHaveFlexShrink) {
shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine,
mainSize, paddingAlongMainAxis, false);
}
}
}
网友评论