ByRecyclerView:真·万能分割线 (线性/宫格/瀑布流)

8,719 阅读5分钟

前言

我基本上找遍了网上所有通过ItemDecoration设置分隔线的文章,但都不尽如意,它们大多只适用于部分情况,比如只能给线性布局设置、只能设置color不能设置drawable、不能去除HeaderView部分的分割线、配置麻烦等等等。

于是我费尽周折出了两个类:SpacesItemDecorationGridSpaceItemDecoration。它们基本解决了上述所有问题!

它们有什么功能

SpacesItemDecoration

给LinearLayoutManager设置

  • 1、可设置color或drawable
  • 2、可设置分割线左右或上下的间距
  • 3、可设置header或footer不显示分割线的个数,功能似ListView的setHeaderDividersEnabled(ture)
  • 4、支持横向或纵向

GridSpaceItemDecoration

给GridLayoutManager或StaggeredGridLayoutManager设置

  • 1、可配置只在四周是否显示分割线
  • 2、可设置header或footer不显示分割线的个数

绘制原理

网上很多解释通过ItemDecoration绘制分割线的原理的文章,我简单总结一下,在getItemOffsets()方法里设置item宽度的偏移量,在onDraw()方法里主要绘制分割线颜色。getItemOffsets 是针对每一个 ItemView,而 onDraw 方法却是针对 RecyclerView 本身,所以在 onDraw 方法中需要遍历屏幕上可见的 ItemView,分别获取它们的位置信息,然后分别的绘制对应的分割线。 -- 参考:juejin.im/post/684490…

示例图

SpacesItemDecorationGridSpaceItemDecoration
线性宫格

参数配置

SpacesItemDecoration

构造方法有四个:

SpacesItemDecoration(Context context)
SpacesItemDecoration(Context context, int orientation)
SpacesItemDecoration(Context context, int orientation, int headerNoShowSize)
/**
 * @param context          Current context, it will be used to access resources.
 * @param orientation      水平方向or垂直方向,默认SpacesItemDecoration.VERTICAL
 * @param headerNoShowSize 不显示分割线的item个数 这里应该包含刷新头
 * @param footerNoShowSize 尾部 不显示分割线的item个数 默认不显示最后一个item的分割线
 */
public SpacesItemDecoration(Context context, int orientation, int headerNoShowSize, int footerNoShowSize)

其他参数设置,其中setDrawablesetParam只能选择其一:

/**
 * Sets the {@link Drawable} for this divider.
 *
 * @param drawable Drawable that should be used as a divider.
 */
public SpacesItemDecoration setDrawable(Drawable drawable)

/**
 * 直接设置分割线颜色等,不设置drawable
 *
 * @param dividerColor         分割线颜色
 * @param dividerSpacing       分割线间距
 * @param leftTopPaddingDp     如果是横向 - 左边距
 *                             如果是纵向 - 上边距
 * @param rightBottomPaddingDp 如果是横向 - 右边距
 *                             如果是纵向 - 下边距
 */
public SpacesItemDecoration setParam(int dividerColor, int dividerSpacing, float leftTopPaddingDp, float rightBottomPaddingDp)

一个完整的设置如下:

// 设置分割线color
SpacesItemDecoration itemDecoration = new SpacesItemDecoration(recyclerView.getContext(), SpacesItemDecoration.VERTICAL, 0, 1)
    .setParam(R.color.colorLine, 1, 12, 12);
recyclerView.addItemDecoration(itemDecoration);
        
// 设置分割线drawable
SpacesItemDecoration itemDecoration = new SpacesItemDecoration(recyclerView.getContext(), SpacesItemDecoration.VERTICAL, 0, 1)
    .setDrawable(R.drawable.shape_line);
recyclerView.addItemDecoration(itemDecoration);

核心代码

这里主要解释这几个参数配置的核心代码,具体请直接见源代码:

for (int i = 0; i < childCount; i++) {
    final View child = parent.getChildAt(i);
    final int childRealPosition = parent.getChildAdapterPosition(child);

    // 过滤到头部不显示的分割线
    if (childRealPosition < mHeaderNoShowSize) {
        continue;
    }
    // 过滤到尾部不显示的分割线
    if (childRealPosition <= lastPosition - mFooterNoShowSize) {

        // 设置drawable
        if (mDivider != null) {
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }

        // 设置color
        if (mPaint != null) {
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            // 首尾间距
            int left1 = left + mLeftTopPadding;
            int right1 = right - mRightBottomPadding;
            int top1 = child.getBottom() + params.bottomMargin;
            int bottom1 = top1 + mDividerSpacing;
            canvas.drawRect(left1, top1, right1, bottom1, mPaint);
        }
    }
}

GridSpaceItemDecoration

构造方法有两个:

GridSpaceItemDecoration(int spacing)
/**
 * @param spacing     item 间距
 * @param includeEdge item 距屏幕周围是否也有间距
 */
public GridSpaceItemDecoration(int spacing, boolean includeEdge)

其他参数设置:

/**
 * 设置从哪个位置 结束设置间距
 *
 * @param startFromSize 一般为HeaderView的个数 + 刷新布局(不一定设置)
 * @param endFromSize   默认为1,一般为FooterView的个数 + 加载更多布局(不一定设置)
 */
public GridSpaceItemDecoration setNoShowSpace(int startFromSize, int endFromSize)

完整设置如下:

GridSpaceItemDecoration itemDecoration = new GridSpaceItemDecoration(5, true)
        .setNoShowSpace(1, 1);
recyclerView.addItemDecoration(itemDecoration);

核心代码

int lastPosition = state.getItemCount() - 1;
int position = parent.getChildAdapterPosition(view);
if (mStartFromSize <= position && position <= lastPosition - mEndFromSize) {

    // 行
    int spanGroupIndex = -1;
    // 列
    int column = 0;
    // 瀑布流是否占满一行
    boolean fullSpan = false;

    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    if (layoutManager instanceof GridLayoutManager) {
        GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
        GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
        int spanCount = gridLayoutManager.getSpanCount();
        // 当前position的spanSize
        int spanSize = spanSizeLookup.getSpanSize(position);
        // 一行几个
        mSpanCount = spanCount / spanSize;
        // =0 表示是最左边 0 2 4
        int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
        // 列
        column = spanIndex / spanSize;
        // 行 减去mStartFromSize,得到从0开始的行
        spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount) - mStartFromSize;

    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
        // 瀑布流获取列方式不一样
        StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
        // 列
        column = params.getSpanIndex();
        // 是否是全一行
        fullSpan = params.isFullSpan();
        mSpanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
    }
    // 减掉不设置间距的position,得到从0开始的position
    position = position - mStartFromSize;

    if (mIncludeEdge) {
        /*
         *示例:
         * spacing = 10 ;spanCount = 3
         * ---------10--------
         * 10   3+7   6+4    10
         * ---------10--------
         * 10   3+7   6+4    10
         * ---------10--------
         */
        if (fullSpan) {
            outRect.left = 0;
            outRect.right = 0;
        } else {
            outRect.left = mSpacing - column * mSpacing / mSpanCount;
            outRect.right = (column + 1) * mSpacing / mSpanCount;
        }

        if (spanGroupIndex > -1) {
            // grid 显示规则
            if (spanGroupIndex < 1 && position < mSpanCount) {
                // 第一行才有上间距
                outRect.top = mSpacing;
            }
        } else {
            if (fullPosition == -1 && position < mSpanCount && fullSpan) {
                // 找到头部第一个整行的position,后面的上间距都不显示
                fullPosition = position;
            }
            // Stagger显示规则 头部没有整行或者头部体验整行但是在之前的position显示上间距
            boolean isFirstLineStagger = (fullPosition == -1 || position < fullPosition) && (position < mSpanCount);
            if (isFirstLineStagger) {
                // 第一行才有上间距
                outRect.top = mSpacing;
            }
        }

        outRect.bottom = mSpacing;

    } else {
        /*
         *示例:
         * spacing = 10 ;spanCount = 3
         * --------0--------
         * 0   3+7   6+4    0
         * -------10--------
         * 0   3+7   6+4    0
         * --------0--------
         */
        if (fullSpan) {
            outRect.left = 0;
            outRect.right = 0;
        } else {
            outRect.left = column * mSpacing / mSpanCount;
            outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount;
        }

        if (spanGroupIndex > -1) {
            if (spanGroupIndex >= 1) {
                // 超过第0行都显示上间距
                outRect.top = mSpacing;
            }
        } else {
            if (fullPosition == -1 && position < mSpanCount && fullSpan) {
                // 找到头部第一个整行的position
                fullPosition = position;
            }
            // Stagger上间距显示规则
            boolean isStaggerShowTop = position >= mSpanCount || (fullSpan && position != 0) || (fullPosition != -1 && position != 0);

            if (isStaggerShowTop) {
                // 超过第0行都显示上间距
                outRect.top = mSpacing;
            }
        }
    }
}

完整代码

SpacesItemDecoration:

/**
 * 给 LinearLayoutManager 增加分割线,可设置去除首尾分割线个数
 *
 * @author jingbin
 * https://github.com/youlookwhat/ByRecyclerView
 */
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
    public static final int VERTICAL = LinearLayout.VERTICAL;
    private static final String TAG = "itemDivider";
    private Context mContext;
    private Drawable mDivider;
    private Rect mBounds = new Rect();
    /**
     * 在AppTheme里配置 android:listDivider
     */
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    /**
     * 头部 不显示分割线的item个数 这里应该包含刷新头,
     * 比如有一个headerView和有下拉刷新,则这里传 2
     */
    private int mHeaderNoShowSize = 0;
    /**
     * 尾部 不显示分割线的item个数 默认不显示最后一个item的分割线
     */
    private int mFooterNoShowSize = 1;
    /**
     * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
     */
    private int mOrientation;
    private Paint mPaint;
    /**
     * 如果是横向 - 宽度
     * 如果是纵向 - 高度
     */
    private int mDividerSpacing;
    /**
     * 如果是横向 - 左边距
     * 如果是纵向 - 上边距
     */
    private int mLeftTopPadding;
    /**
     * 如果是横向 - 右边距
     * 如果是纵向 - 下边距
     */
    private int mRightBottomPadding;
    private ByRecyclerView byRecyclerView;

    public SpacesItemDecoration(Context context) {
        this(context, VERTICAL, 0, 1);
    }

    public SpacesItemDecoration(Context context, int orientation) {
        this(context, orientation, 0, 1);
    }

    public SpacesItemDecoration(Context context, int orientation, int headerNoShowSize) {
        this(context, orientation, headerNoShowSize, 1);
    }

    /**
     * Creates a divider {@link RecyclerView.ItemDecoration}
     *
     * @param context          Current context, it will be used to access resources.
     * @param orientation      Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
     * @param headerNoShowSize headerViewSize + RefreshViewSize
     * @param footerNoShowSize footerViewSize
     */
    public SpacesItemDecoration(Context context, int orientation, int headerNoShowSize, int footerNoShowSize) {
        mContext = context;
        mHeaderNoShowSize = headerNoShowSize;
        mFooterNoShowSize = footerNoShowSize;
        setOrientation(orientation);
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    /**
     * Sets the orientation for this divider. This should be called if
     * {@link RecyclerView.LayoutManager} changes orientation.
     *
     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
     */
    public SpacesItemDecoration setOrientation(int orientation) {
        if (orientation != HORIZONTAL && orientation != VERTICAL) {
            throw new IllegalArgumentException("Invalid orientation. It should be either HORIZONTAL or VERTICAL");
        }
        mOrientation = orientation;
        return this;
    }

    /**
     * Sets the {@link Drawable} for this divider.
     *
     * @param drawable Drawable that should be used as a divider.
     */
    public SpacesItemDecoration setDrawable(Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("drawable cannot be null.");
        }
        mDivider = drawable;
        return this;
    }

    public SpacesItemDecoration setDrawable(@DrawableRes int id) {
        setDrawable(ContextCompat.getDrawable(mContext, id));
        return this;
    }

    @Override
    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null || (mDivider == null && mPaint == null)) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(canvas, parent, state);
        } else {
            drawHorizontal(canvas, parent, state);
        }
    }

    private void drawVertical(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        final int lastPosition = state.getItemCount() - 1;
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final int childRealPosition = parent.getChildAdapterPosition(child);

            // 过滤到头部不显示的分割线
            if (childRealPosition < mHeaderNoShowSize) {
                continue;
            }
            // 过滤到尾部不显示的分割线
            if (childRealPosition <= lastPosition - mFooterNoShowSize) {
                if (mDivider != null) {
                    parent.getDecoratedBoundsWithMargins(child, mBounds);
                    final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
                    final int top = bottom - mDivider.getIntrinsicHeight();
                    mDivider.setBounds(left, top, right, bottom);
                    mDivider.draw(canvas);
                }

                if (mPaint != null) {
                    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                    int left1 = left + mLeftTopPadding;
                    int right1 = right - mRightBottomPadding;
                    int top1 = child.getBottom() + params.bottomMargin;
                    int bottom1 = top1 + mDividerSpacing;
                    canvas.drawRect(left1, top1, right1, bottom1, mPaint);
                }
            }
        }
        canvas.restore();
    }

    private void drawHorizontal(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        canvas.save();
        final int top;
        final int bottom;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        } else {
            top = 0;
            bottom = parent.getHeight();
        }

        final int childCount = parent.getChildCount();
        final int lastPosition = state.getItemCount() - 1;
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final int childRealPosition = parent.getChildAdapterPosition(child);

            // 过滤到头部不显示的分割线
            if (childRealPosition < mHeaderNoShowSize) {
                continue;
            }
            // 过滤到尾部不显示的分割线
            if (childRealPosition <= lastPosition - mFooterNoShowSize) {
                if (mDivider != null) {
                    parent.getDecoratedBoundsWithMargins(child, mBounds);
                    final int right = mBounds.right + Math.round(child.getTranslationX());
                    final int left = right - mDivider.getIntrinsicWidth();
                    mDivider.setBounds(left, top, right, bottom);
                    mDivider.draw(canvas);
                }

                if (mPaint != null) {
                    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                    int left1 = child.getRight() + params.rightMargin;
                    int right1 = left1 + mDividerSpacing;
                    int top1 = top + mLeftTopPadding;
                    int bottom1 = bottom - mRightBottomPadding;
                    canvas.drawRect(left1, top1, right1, bottom1, mPaint);
                }
            }
        }
        canvas.restore();
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mDivider == null && mPaint == null) {
            outRect.set(0, 0, 0, 0);
            return;
        }
        //parent.getChildCount() 不能拿到item的总数
        int lastPosition = state.getItemCount() - 1;
        int position = parent.getChildAdapterPosition(view);

        boolean mScrollTopFix = false;
        if (byRecyclerView == null && parent instanceof ByRecyclerView) {
            byRecyclerView = (ByRecyclerView) parent;
        }
        if (byRecyclerView != null && byRecyclerView.isRefreshEnabled()) {
            mScrollTopFix = true;
        }

        // 滚动条置顶
        boolean isFixScrollTop = mScrollTopFix && position == 0;
        boolean isShowDivider = mHeaderNoShowSize <= position && position <= lastPosition - mFooterNoShowSize;

        if (mOrientation == VERTICAL) {
            if (isFixScrollTop) {
                outRect.set(0, 0, 0, 1);
            } else if (isShowDivider) {
                outRect.set(0, 0, 0, mDivider != null ? mDivider.getIntrinsicHeight() : mDividerSpacing);
            } else {
                outRect.set(0, 0, 0, 0);
            }
        } else {
            if (isFixScrollTop) {
                outRect.set(0, 0, 1, 0);
            } else if (isShowDivider) {
                outRect.set(0, 0, mDivider != null ? mDivider.getIntrinsicWidth() : mDividerSpacing, 0);
            } else {
                outRect.set(0, 0, 0, 0);
            }
        }
    }

    /**
     * 设置不显示分割线的item位置与个数
     *
     * @param headerNoShowSize 头部 不显示分割线的item个数
     * @param footerNoShowSize 尾部 不显示分割线的item个数,默认1,不显示最后一个,最后一个一般为加载更多view
     */
    public SpacesItemDecoration setNoShowDivider(int headerNoShowSize, int footerNoShowSize) {
        this.mHeaderNoShowSize = headerNoShowSize;
        this.mFooterNoShowSize = footerNoShowSize;
        return this;
    }

    /**
     * 设置不显示头部分割线的item个数
     *
     * @param headerNoShowSize 头部 不显示分割线的item个数
     */
    public SpacesItemDecoration setHeaderNoShowDivider(int headerNoShowSize) {
        this.mHeaderNoShowSize = headerNoShowSize;
        return this;
    }

    public SpacesItemDecoration setParam(int dividerColor, int dividerSpacing) {
        return setParam(dividerColor, dividerSpacing, 0, 0);
    }

    /**
     * 直接设置分割线颜色等,不设置drawable
     *
     * @param dividerColor         分割线颜色
     * @param dividerSpacing       分割线间距
     * @param leftTopPaddingDp     如果是横向 - 左边距
     *                             如果是纵向 - 上边距
     * @param rightBottomPaddingDp 如果是横向 - 右边距
     *                             如果是纵向 - 下边距
     */
    public SpacesItemDecoration setParam(int dividerColor, int dividerSpacing, float leftTopPaddingDp, float rightBottomPaddingDp) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(ContextCompat.getColor(mContext, dividerColor));
        mDividerSpacing = dividerSpacing;
        mLeftTopPadding = dip2px(leftTopPaddingDp);
        mRightBottomPadding = dip2px(rightBottomPaddingDp);
        mDivider = null;
        return this;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
    
}

GridSpaceItemDecoration:

/**
 * 给 GridLayoutManager or StaggeredGridLayoutManager 设置间距,可设置去除首尾间距个数
 *
 * @author jingbin
 * https://github.com/youlookwhat/ByRecyclerView
 */

public class GridSpaceItemDecoration extends RecyclerView.ItemDecoration {

    /**
     * 每行个数
     */
    private int mSpanCount;
    /**
     * 间距
     */
    private int mSpacing;
    /**
     * 距屏幕周围是否也有间距
     */
    private boolean mIncludeEdge;
    /**
     * 头部 不显示间距的item个数
     */
    private int mStartFromSize;
    /**
     * 尾部 不显示间距的item个数 默认不处理最后一个item的间距
     */
    private int mEndFromSize = 1;
    /**
     * 瀑布流 头部第一个整行的position
     */
    private int fullPosition = -1;

    public GridSpaceItemDecoration(int spacing) {
        this(spacing, true);
    }

    /**
     * @param spacing     item 间距
     * @param includeEdge item 距屏幕周围是否也有间距
     */
    public GridSpaceItemDecoration(int spacing, boolean includeEdge) {
        this.mSpacing = spacing;
        this.mIncludeEdge = includeEdge;
    }

    /**
     * 已不需要手动设置spanCount
     */
    @Deprecated
    public GridSpaceItemDecoration(int spanCount, int spacing) {
        this(spanCount, spacing, true);
    }

    /**
     * 已不需要手动设置spanCount
     */
    @Deprecated
    public GridSpaceItemDecoration(int spanCount, int spacing, boolean includeEdge) {
        this.mSpanCount = spanCount;
        this.mSpacing = spacing;
        this.mIncludeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        int lastPosition = state.getItemCount() - 1;
        int position = parent.getChildAdapterPosition(view);
        if (mStartFromSize <= position && position <= lastPosition - mEndFromSize) {

            // 行
            int spanGroupIndex = -1;
            // 列
            int column = 0;
            // 瀑布流是否占满一行
            boolean fullSpan = false;

            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
                int spanCount = gridLayoutManager.getSpanCount();
                // 当前position的spanSize
                int spanSize = spanSizeLookup.getSpanSize(position);
                // 一行几个
                mSpanCount = spanCount / spanSize;
                // =0 表示是最左边 0 2 4
                int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
                // 列
                column = spanIndex / spanSize;
                // 行 减去mStartFromSize,得到从0开始的行
                spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount) - mStartFromSize;

            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                // 瀑布流获取列方式不一样
                StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
                // 列
                column = params.getSpanIndex();
                // 是否是全一行
                fullSpan = params.isFullSpan();
                mSpanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
            }
            // 减掉不设置间距的position,得到从0开始的position
            position = position - mStartFromSize;

            if (mIncludeEdge) {
                /*
                 *示例:
                 * spacing = 10 ;spanCount = 3
                 * ---------10--------
                 * 10   3+7   6+4    10
                 * ---------10--------
                 * 10   3+7   6+4    10
                 * ---------10--------
                 */
                if (fullSpan) {
                    outRect.left = 0;
                    outRect.right = 0;
                } else {
                    outRect.left = mSpacing - column * mSpacing / mSpanCount;
                    outRect.right = (column + 1) * mSpacing / mSpanCount;
                }

                if (spanGroupIndex > -1) {
                    // grid 显示规则
                    if (spanGroupIndex < 1 && position < mSpanCount) {
                        // 第一行才有上间距
                        outRect.top = mSpacing;
                    }
                } else {
                    if (fullPosition == -1 && position < mSpanCount && fullSpan) {
                        // 找到头部第一个整行的position,后面的上间距都不显示
                        fullPosition = position;
                    }
                    // Stagger显示规则 头部没有整行或者头部体验整行但是在之前的position显示上间距
                    boolean isFirstLineStagger = (fullPosition == -1 || position < fullPosition) && (position < mSpanCount);
                    if (isFirstLineStagger) {
                        // 第一行才有上间距
                        outRect.top = mSpacing;
                    }
                }

                outRect.bottom = mSpacing;

            } else {
                /*
                 *示例:
                 * spacing = 10 ;spanCount = 3
                 * --------0--------
                 * 0   3+7   6+4    0
                 * -------10--------
                 * 0   3+7   6+4    0
                 * --------0--------
                 */
                if (fullSpan) {
                    outRect.left = 0;
                    outRect.right = 0;
                } else {
                    outRect.left = column * mSpacing / mSpanCount;
                    outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount;
                }

                if (spanGroupIndex > -1) {
                    if (spanGroupIndex >= 1) {
                        // 超过第0行都显示上间距
                        outRect.top = mSpacing;
                    }
                } else {
                    if (fullPosition == -1 && position < mSpanCount && fullSpan) {
                        // 找到头部第一个整行的position
                        fullPosition = position;
                    }
                    // Stagger上间距显示规则
                    boolean isStaggerShowTop = position >= mSpanCount || (fullSpan && position != 0) || (fullPosition != -1 && position != 0);

                    if (isStaggerShowTop) {
                        // 超过第0行都显示上间距
                        outRect.top = mSpacing;
                    }
                }
            }
        }
    }

    /**
     * 设置从哪个位置 开始设置间距
     *
     * @param startFromSize 一般为HeaderView的个数 + 刷新布局(不一定设置)
     */
    public GridSpaceItemDecoration setStartFrom(int startFromSize) {
        this.mStartFromSize = startFromSize;
        return this;
    }

    /**
     * 设置从哪个位置 结束设置间距。默认为1,默认用户设置了上拉加载
     *
     * @param endFromSize 一般为FooterView的个数 + 加载更多布局(不一定设置)
     */
    public GridSpaceItemDecoration setEndFromSize(int endFromSize) {
        this.mEndFromSize = endFromSize;
        return this;
    }

    /**
     * 设置从哪个位置 结束设置间距
     *
     * @param startFromSize 一般为HeaderView的个数 + 刷新布局(不一定设置)
     * @param endFromSize   默认为1,一般为FooterView的个数 + 加载更多布局(不一定设置)
     */
    public GridSpaceItemDecoration setNoShowSpace(int startFromSize, int endFromSize) {
        this.mStartFromSize = startFromSize;
        this.mEndFromSize = endFromSize;
        return this;
    }
}

总结一下

这两个类SpacesItemDecorationGridSpaceItemDecoration 基本涵盖了所有列表的情况,如果有一些特殊的需求在上面稍微拓展一下就好,它们收录在本人开源的一个RecyclerView开源库里:youlookwhat/ByRecyclerView。如有其他问题,欢迎留言骚扰~