android--FlexboxLayout采坑记

android FlexboxLayout采坑记

  • google提供的弹性布局的方案:https://github.com/google/flexbox-layout

问题

子view被错误摆放位置

  • 我在RecyclerView中使用了FlexboxLayout这个控件
    android--FlexboxLayout采坑记_第1张图片
  • 上图是我使用FlexboxLayout后的预期摆放,第三行如果放到第二行末尾,会使总宽度超出父view所能容纳的最大宽度,因此这样摆放才是合理的,放不下就另起一行,这也就是Flexbox的作用
  • 但是我一运行后,却发现是这样的
  • android--FlexboxLayout采坑记_第2张图片
  • 第三行被强行放到第二行,同时由于宽度收到父view的限制,因此第二行每个view的宽度都减少了一部分,导致原本一行能放得下的文本放不下,只能撑开高度
  • 这显然是一个bug
  • 那么就来看看为何子view会被错误地挤在一起
  • 首先得理解下google工程师是怎么弹性布局的
  • google会遍历每一个子view,然后根据情况将每个子view放在适合的线上,如下图,红色的箭头就表示那条线
  • android--FlexboxLayout采坑记_第3张图片
  • 很显然会出现第二种情况,就是因为google错误地将第二和第三个子view都放置到了第二条线上,也就是这里的计算出现了失误
  • FlexboxLayout的计算主要都放在FlexboxHelper里
  • com.google.android.flexbox.FlexboxHelper#isWrapRequired这个方法就是来决定子view是否可以放在同一行,还是另起一行的,如果不能放在同一行,那么就新增FlexLine,如果可以在同一行,那么就往这一行的FlexLine的个数加1
  • 现在发现是第二个FilexLine的个数为2,说明flexboxlayout认为第二行能放下两个子view,但经过debug我发现两个子view的宽度加起来刚好等于父view能承受的最大宽度,由于两个子view之间还有间隔宽度,因此加上这个间隔宽度后两个子view是不可能放在一行的,因此已经超过了父view的最大值,应该另起一行才对
  • 后面跟踪发现是在计算间隔时返回了0,代码在com.google.android.flexbox.FlexboxHelper#isWrapRequired方法里
private boolean isWrapRequired(View view, int mode, int maxSize, int currentLength,
            int childLength, FlexItem flexItem, int index, int indexInFlexLine, int flexLinesSize) {
        if (mFlexContainer.getFlexWrap() == FlexWrap.NOWRAP) {
            return false;
        }
        if (flexItem.isWrapBefore()) {
            return true;
        }
        if (mode == View.MeasureSpec.UNSPECIFIED) {
            return false;
        }
        int maxLine = mFlexContainer.getMaxLine();
        // Judge the condition by adding 1 to the current flexLinesSize because the flex line
        // being computed isn't added to the flexLinesSize.
        if (maxLine != NOT_SET && maxLine <= flexLinesSize + 1) {
            return false;
        }
        int decorationLength =
                mFlexContainer.getDecorationLengthMainAxis(view, index, indexInFlexLine);
        if (decorationLength > 0) {
            childLength += decorationLength;
        }
        return maxSize < currentLength + childLength;
    }
  • 就是这一行
  int decorationLength =
                mFlexContainer.getDecorationLengthMainAxis(view, index, indexInFlexLine);
  • 计算出decorationLength 结果为0,此时参数里view为第三个子view,index为2,indexInFlexLine为0
  • 继续追踪,发现是这里com.google.android.flexbox.FlexboxLayout#allViewsAreGoneBefore返回了true
private boolean allViewsAreGoneBefore(int index, int indexInFlexLine) {
        for (int i = 1; i <= indexInFlexLine; i++) {
            View view = getReorderedChildAt(index - i);
            if (view != null && view.getVisibility() != View.GONE) {
                return false;
            }
        }
        return true;
    }
  • 当时我的条件是index为2,indexInFlexLine为0,因此返回了true,仔细想了下,此时的indexInFlexLine应该为1才对,怎么传了0?
  • 于是得看看indexInFlexLine的源头是谁设置的,也就是isWrapRequired方法的参数是从哪设置的
  • 找到代码如下
// The index of the view in the flex line.
        int indexInFlexLine = 0;

      ...
        int childCount = mFlexContainer.getFlexItemCount();
        for (int i = fromIndex; i < childCount; i++) {
           ...

            if (isWrapRequired(child, mainMode, mainSize, flexLine.mMainSize,
                    getViewMeasuredSizeMain(child, isMainHorizontal)
                            + getFlexItemMarginStartMain(flexItem, isMainHorizontal) +
                            getFlexItemMarginEndMain(flexItem, isMainHorizontal),
                    flexItem, i, indexInFlexLine, flexLines.size())) {
            ...

                flexLine = new FlexLine();
                flexLine.mItemCount = 1;
                flexLine.mMainSize = mainPaddingStart + mainPaddingEnd;
                flexLine.mFirstIndex = i;
                indexInFlexLine = 0;
                largestSizeInCross = Integer.MIN_VALUE;
            } else {
                flexLine.mItemCount++;
                indexInFlexLine++;
            }
  • 从这段逻辑我们可以看到,即使子view被放在第二个位置,此时isWrapRequired的参数indexInFlexLine还是0,按照我们理解应该为1,如果子view被放在第三个位置,此时indexInFlexLine应该为2,即这里的合理的值应该为flexLine.mItemCount才对
  • 因此只要修改isWrapRequired的第八个参数为flexLine.mItemCount即可,如下
if (isWrapRequired(child, mainMode, mainSize, flexLine.mMainSize,
                    getViewMeasuredSizeMain(child, isMainHorizontal)
                            + getFlexItemMarginStartMain(flexItem, isMainHorizontal) +
                            getFlexItemMarginEndMain(flexItem, isMainHorizontal),
                    flexItem, i, flexLine.mItemCount, flexLines.size())) {
                    }
  • 进过验证,确实得到了我的预期效果
  • 但是问题又来了,我难道要把Flexbox这个库下载下来修改源码,再替换我工程里的引用吗
  • 这样做就变成我要维护flexbox这个库了,后面如果有升级咋办,这显然是不行的
  • 那么就只能通过修改字节码的形式去修复这个bug
  • 具体该怎么做,待写…
  • 另外如何快速引用修改后的源码来验证,待写…(感觉自己在挖坑)

你可能感兴趣的:(android,android,FlexboxLayout)