解决RecyclerView GridLayoutManager 设置分割线出现item宽度不等的问题

一、概述

最近研究RecyclerView给GridLayoutManager模式设置分割线的问题,在网上找了一些案例,却都有着item宽度大小不等的问题,以鸿洋大神的这篇http://blog.csdn.net/lmj623565791/article/details/45059587为例,divider的宽度变宽了之后,就明显存在问题,如下图所示:
解决RecyclerView GridLayoutManager 设置分割线出现item宽度不等的问题_第1张图片
最右边的宽度明显大于前面两个。

这是为什么呢?

二、分析

进入RecyclerView源码,源码中肯定有测量子控件宽高的方法,该方法为measureChild,代码如下:

        /**
         * Measure a child view using standard measurement policy, taking the padding
         * of the parent RecyclerView and any added item decorations into account.
         *
         * 

If the RecyclerView can be scrolled in either dimension the caller may * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.

* * @param child Child view to measure * @param widthUsed Width in pixels currently consumed by other views, if relevant * @param heightUsed Height in pixels currently consumed by other views, if relevant */
public void measureChild(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, canScrollVertically()); if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { child.measure(widthSpec, heightSpec); } }

这里就先看宽度,也就是widthSpec变量,该值通过getChildMeasureSpec方法得到,进入该方法,代码如下:

        /**
         * Calculate a MeasureSpec value for measuring a child view in one dimension.
         *
         * @param parentSize Size of the parent view where the child will be placed
         * @param parentMode The measurement spec mode of the parent
         * @param padding Total space currently consumed by other elements of parent
         * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
         *                       Generally obtained from the child view's LayoutParams
         * @param canScroll true if the parent RecyclerView can scroll in this dimension
         *
         * @return a MeasureSpec value for the child view
         */
        public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
                int childDimension, boolean canScroll) {
            int size = Math.max(0, parentSize - padding);
            int resultSize = 0;
            int resultMode = 0;
            if (canScroll) {
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    switch (parentMode) {
                        case MeasureSpec.AT_MOST:
                        case MeasureSpec.EXACTLY:
                            resultSize = size;
                            resultMode = parentMode;
                            break;
                        case MeasureSpec.UNSPECIFIED:
                            resultSize = 0;
                            resultMode = MeasureSpec.UNSPECIFIED;
                            break;
                    }
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
            } else {
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = parentMode;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
                        resultMode = MeasureSpec.AT_MOST;
                    } else {
                        resultMode = MeasureSpec.UNSPECIFIED;
                    }

                }
            }
            //noinspection WrongConstant
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

发现所需要的宽度也就是size变量,而size = Math.max(0, parentSize - padding),通过传进来的参数,可以得到size=getWidth()-(getPaddingLeft() + getPaddingRight() + widthUsed),说明子控件的宽度等于RecyclerView的宽度减去RecyclerView自身左右的padding值外,还要减去子控件的左右偏移量,因此观察hongyang大神的代码:

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition,
                               RecyclerView parent) {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }

调用outRect.set(int left, int top, int right, int bottom)方法时,left一直为0,right一直为divider的宽度,而每一项item的宽度都要减去(left+right)大小,我这里设divider宽度为20,因此第一项item的宽度要减,20,第二项item宽度要减20,第三项item的left为0,right也为0,宽度要减0。因此第三项就比前面两项都宽20了,item宽度就明显不相同了。

三、实现

因此要让每一项item宽度都相同,就要在outRect.set方法调用时让每一项left+right的大小相同,同时第一项的right加上第二项的left大小等于divider的宽度(后面就叫dividerWidth了)。先给张实现后的图:
解决RecyclerView GridLayoutManager 设置分割线出现item宽度不等的问题_第2张图片
图中每行itemView有3个,有2条divider,每一项itemView的偏移大小就是(2*dividerWidth)/3了,同理,给定每一行itemView的个数为spanCount,也就是RecyclerView的列数,则divider的条数为(spanCount-1),
因此定义每一项itemView的偏移宽度为:
eachWidth=(spanCount-1)* dividerWidth / spanCount;
因为第一项的left(之后用L0表示,以此类推,L1、L2…代表第二、三…项的left
)为0,所以第一项的right(之后用R0表示)为eachWidth-0,也就是说:
L0 = 0; R0=eachWidth;
第一项的right加上第二项的left等于dividerWidth,因此L1=dividerWidth-R0=dividerWidth-eachWidth,
R1=eachWidth-L1=eachWidth-(dividerWidth-eachWidth)=2eachWidth-dividerWidth
L1=dividerWidth-eachWidth; R1=2eachWidth-dividerWidth
因此L2=dividerWidth-R1=dividerWidth-(2eachWidth-dividerWidth)=2dividerWidth-2eachWidth=2L1,同理可得L3=3L1,L4=4L1……Ln=nL1,因此left可以用以下代码来表示:

left = itemPosition % spanCount * (dividerWidth - eachWidth); //itemPositison代表当前item位置,0,1,2

因此right可以表示为:

right = eachWidth - left;

因此可以这样重新getItemOffsets方法:

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();

        boolean isLastRow = isLastRow(parent, itemPosition, spanCount, childCount);

        int top = 0;
        int left;
        int right;
        int bottom;

        int eachWidth = (spanCount - 1) * mDividerWidth / spanCount;

        left = itemPosition % spanCount * (mDividerWidth - eachWidth);
        right = eachWidth - left;
        bottom = mDividerWidth;
        if (isLastRow){
            bottom = 0;
        }
        outRect.set(left, top, right, bottom);

    }

四、Github完整代码链接

GridDividerItemDecoration https://github.com/HZHAboom/RecyclerView.ItemDecoration/blob/master/GridDividerItemDecoration.java

LinearDividerItemDecoration
https://github.com/HZHAboom/RecyclerView.ItemDecoration/blob/master/LinearDividerItemDecoration.java

你可能感兴趣的:(解决RecyclerView GridLayoutManager 设置分割线出现item宽度不等的问题)