Android

Android-FlexboxLayout源码浅析

2020-07-03  本文已影响0人  zzq_nene

看流式布局的源码解析,首先需要从自定义布局的几个步骤进行考虑,即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);
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读