RecyclerView使用RelativeLayout充当ItemView时加竖线不显示问题

原因很简单,在设置 RelativeLayout 的子View高度时,若高度不设置成一个固定值,在测量的过程中,则会设置成0。

为什么呢?

我们可以从以下代码进行分析:

首先来看一下RecyclerView的getChildMeasureSpec()方法源码:

public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
        boolean canScroll) {
    int size = Math.max(0, parentSize - padding);
    int resultSize = 0;
    int resultMode = 0;
    if (canScroll) {
        // MATCH_PARENT = -1
        // WRAP_CONTENT = -2
        // 大于等于0表示为该Item的Layout设置了固定的大小。
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else {
            // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
            // instead using UNSPECIFIED.
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
    } else {
       // ... 省略
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

通过上述代码可知 RecyclerView 在测量 itemView 的时候,返回给 RelativeLayout 一个由 size = 0,measureSpecMode = UNSPECIFIED 生成的 measureSpec。

我们再来看 RelativeLayout 中的代码,先来看 onMeasure(int,int) 方法的源码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // ...省略
    int myHeight = -1;
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    // ...省略
    if (heightMode != MeasureSpec.UNSPECIFIED) {
        myHeight = heightSize;
    }
    // ... 省略
    for (int i = 0; i < count; i++) {
        final View child = views[i];
        if (child.getVisibility() != GONE) {
            // ...省略
            // 此方法根据子View配置的垂直方向上的各种RelativeLayout rules进行计算
            // 如果有rules,那么就会给RelativeLayout.LayoutParams的私有成员mTop/mBottom赋值。
            // 垂直方向上的rules包括ALIGN_PARENT_BOTTOM、ABOVE、BELOW等。
            // 一般都不会设置规则,所以这里没有给mTop/mBottom赋值
            applyVerticalSizeRules(params, myHeight, child.getBaseline());
            measureChild(child, params, myWidth, myHeight);
            // ...省略
        }
        // ...省略
    }
    // ...省略
}

结合上面 RecyclerView 的 getChildMeasureSpec() 方法可知 myHeight = -1,且 params.Top 及 params.Bottom 都没有赋值,并将这些参数传入 measureChild() 方法中,那么我们追踪到 measureChiid() 方法中来看看:

private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
    // ...
    int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
            params.mBottom, params.height,
            params.topMargin, params.bottomMargin,
            mPaddingTop, mPaddingBottom,
            myHeight);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

该方法将这些参数传入了 RelativeLayout 的 getChildMeasureSpec() 方法中,那么继续追踪到 getChildMeasureSpec() 方法的源码:

private int (int childStart, int childEnd,
        int childSize, int startMargin, int endMargin, int startPadding,
        int endPadding, int mySize) {
    int childSpecMode = 0;
    int childSpecSize = 0;
    // Negative values in a mySize value in RelativeLayout
    // measurement is code for, "we got an unspecified mode in the
    // RelativeLayout's measure spec."
    // 由上述分析可知mySize = -1,childSize = MATCH_PARENT = -1
    final boolean isUnspecified = mySize < 0;
    // mAllowBrokenMeasureSpecs是为了修复低版本bug引入的变量,只有在API <= 17时才为true
    if (isUnspecified && !mAllowBrokenMeasureSpecs) {
        // 此处似乎隐含修复bug的可能。只要保证子view的LayoutParams的mTop/mBottom被设置就行了。
        if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
            // Constraints fixed both edges, so child has an exact size.
            childSpecSize = Math.max(0, childEnd - childStart);
            childSpecMode = MeasureSpec.EXACTLY;
        } else if (childSize >= 0) {
            // 当子View设置了指定大小时
            childSpecSize = childSize;
            childSpecMode = MeasureSpec.EXACTLY;
        } else {
            // Allow the child to be whatever size it wants.
            childSpecSize = 0;
            childSpecMode = MeasureSpec.UNSPECIFIED;
        }
        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
    }
    // ...
}

最后可知,当 RecyclerView 的子 View 未设置指定高度时,高度便为0,并指定了子 View 的 mode 为 MeasureSpec.UNSPECIFIED。

上述代码也说过,有一个修复该bug的可能,那么我们重新分析一下,看是否真的可以,我们来查看 applyVerticalSizeRules() 方法;

private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) {
    final int[] rules = childParams.getRules();
    // Baseline alignment overrides any explicitly specified top or bottom.
    // 如果设置了baseLine则以baseLine为基础设置mTop/mBottom。
    int baselineOffset = getRelatedViewBaselineOffset(rules);
    if (baselineOffset != -1) {
        if (myBaseline != -1) {
            baselineOffset -= myBaseline;
        }
        childParams.mTop = baselineOffset;
        // 设置baseLine不能改变mBottm的值,不符合条件。
        childParams.mBottom = VALUE_NOT_SET;
        return;
    }
    
    // 由布局文件可知该子View是顶部和头部是不存在其他View的,因此设置ABOVE/BELOW等属性均不可能。
    RelativeLayout.LayoutParams anchorParams;
    childParams.mTop = VALUE_NOT_SET;
    childParams.mBottom = VALUE_NOT_SET;
    anchorParams = getRelatedViewParams(rules, ABOVE);
    if (anchorParams != null) {
        childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
                childParams.bottomMargin);
    } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
        if (myHeight >= 0) {
            childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
        }
    }
    anchorParams = getRelatedViewParams(rules, BELOW);
    if (anchorParams != null) {
        childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
                childParams.topMargin);
    } else if (childParams.alignWithParent && rules[BELOW] != 0) {
        childParams.mTop = mPaddingTop + childParams.topMargin;
    }
    anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
    if (anchorParams != null) {
        childParams.mTop = anchorParams.mTop + childParams.topMargin;
    } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
        childParams.mTop = mPaddingTop + childParams.topMargin;
    }
    anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
    if (anchorParams != null) {
        childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
    } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
        if (myHeight >= 0) {
            childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
        }
    }
    
    // 设置ALIGN_PARENT_TOP可以使mTop得到值。
    if (0 != rules[ALIGN_PARENT_TOP]) {
        childParams.mTop = mPaddingTop + childParams.topMargin;
    }
    // 因为myHeight = -1,所以设置ALIGN_PARENT_BOTTOM不能使mBottom得到值。
    if (0 != rules[ALIGN_PARENT_BOTTOM]) {
        if (myHeight >= 0) {
            childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
        }
    }
}
...

经过上述分析,在现有布局条件下,无法通过为子 View 增加一些 rules 来修复这个 bug。只能通过布局调整来修复。

总结:

若要求RecyclerVIew的子View的高度为match_parent,应避免使用RelativeLayout 。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值