装饰RecyclerView(使用GridLayoutManager)以显示项目之间的分隔符

时间:2015-03-19 14:07:58

标签: android android-recyclerview

装饰RecyclerView以获得这样的外观和效果的最佳和最简单的方法是什么?感觉?

enter image description here

这里的主要挑战是只在项目之间设置分隔线,而不是在屏幕的项目和左/右边界之间设置分隔线。

有什么想法吗?

10 个答案:

答案 0 :(得分:100)

我不知道您为什么需要它,但使用RecyclerView装饰器可以很容易地实现这个UI。

mRecylerView.addItemDecoration(new ItemDecorationAlbumColumns(
    getResources().getDimensionPixelSize(R.dimen.photos_list_spacing), 
    getResources().getInteger(R.integer.photo_list_preview_columns)));

和decorator(需要一些refatoring)

import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;

public class ItemDecorationAlbumColumns extends RecyclerView.ItemDecoration {

    private int mSizeGridSpacingPx;
    private int mGridSize;

    private boolean mNeedLeftSpacing = false;

    public ItemDecorationAlbumColumns(int gridSpacingPx, int gridSize) {
        mSizeGridSpacingPx = gridSpacingPx;
        mGridSize = gridSize;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
        int padding = parent.getWidth() / mGridSize - frameWidth;
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
        if (itemPosition < mGridSize) {
            outRect.top = 0;
        } else {
            outRect.top = mSizeGridSpacingPx;
        }
        if (itemPosition % mGridSize == 0) {
            outRect.left = 0;
            outRect.right = padding;
            mNeedLeftSpacing = true;
        } else if ((itemPosition + 1) % mGridSize == 0) {
            mNeedLeftSpacing = false;
            outRect.right = 0;
            outRect.left = padding;
        } else if (mNeedLeftSpacing) {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx - padding;
            if ((itemPosition + 2) % mGridSize == 0) {
                outRect.right = mSizeGridSpacingPx - padding;
            } else {
                outRect.right = mSizeGridSpacingPx / 2;
            }
        } else if ((itemPosition + 2) % mGridSize == 0) {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx / 2;
            outRect.right = mSizeGridSpacingPx - padding;
        } else {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx / 2;
            outRect.right = mSizeGridSpacingPx / 2;
        }
        outRect.bottom = 0;
    }
}

enter image description here enter image description here

答案 1 :(得分:6)

这是一个更简单,更用户友好的实现:

app/html/comtrollers/auth

我在设置public class MediaSpaceDecoration extends RecyclerView.ItemDecoration { private final int spacing; private final List<Integer> allowedViewTypes = Arrays.asList( R.layout.item_image, R.layout.item_blur); public MediaSpaceDecoration(int spacing) { this.spacing = spacing; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { final int position = parent.getChildAdapterPosition(view); if (!isMedia(parent, position)) { return; } final int totalSpanCount = getTotalSpanCount(parent); int spanSize = getItemSpanSize(parent, position); if (totalSpanCount == spanSize) { return; } outRect.top = isInTheFirstRow(position, totalSpanCount) ? 0 : spacing; outRect.left = isFirstInRow(position, totalSpanCount) ? 0 : spacing / 2; outRect.right = isLastInRow(position, totalSpanCount) ? 0 : spacing / 2; outRect.bottom = 0; // don't need } private boolean isInTheFirstRow(int position, int spanCount) { return position < spanCount; } private boolean isFirstInRow(int position, int spanCount) { return position % spanCount == 0; } private boolean isLastInRow(int position, int spanCount) { return isFirstInRow(position + 1, spanCount); } private int getTotalSpanCount(RecyclerView parent) { final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanCount() : 1; } private int getItemSpanSize(RecyclerView parent, int position) { final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanSizeLookup().getSpanSize(position) : 1; } private boolean isMedia(RecyclerView parent, int viewPosition) { final RecyclerView.Adapter adapter = parent.getAdapter(); final int viewType = adapter.getItemViewType(viewPosition); return allowedViewTypes.contains(viewType); } } 之前也会检查,因为每个outRect都有spanSize个{我},我需要为viewType添加额外的中间空格。您可以轻松删除该验证,代码甚至更简单。对我来说这看起来像这样: GridLayout with a different spanSize for each viewType

答案 2 :(得分:5)

您可能得到了答案,但我仍然发布可以帮助他人的解决方案。通过传递方向,可以将其用于垂直,水平列表或网格视图。

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    public static final int GRID = 2;

    private Drawable mDivider;

    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST && orientation != GRID) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else if(mOrientation == HORIZONTAL_LIST){
            drawHorizontal(c, parent);
        } else {
            drawVertical(c, parent);
            drawHorizontal(c, parent);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        if (parent.getChildCount() == 0) return;

        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final View child = parent.getChildAt(0);
        if (child.getHeight() == 0) return;

        final RecyclerView.LayoutParams params =
                (RecyclerView.LayoutParams) child.getLayoutParams();
        int top = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
        int bottom = top + mDivider.getIntrinsicHeight();

        final int parentBottom = parent.getHeight() - parent.getPaddingBottom();
        while (bottom < parentBottom) {
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);

            top += mDivider.getIntrinsicHeight() + params.topMargin + child.getHeight() + params.bottomMargin + mDivider.getIntrinsicHeight();
            bottom = top + mDivider.getIntrinsicHeight();
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params =
                    (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = child.getRight() + params.rightMargin + mDivider.getIntrinsicHeight();
            final int right = left + mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else if(mOrientation == HORIZONTAL_LIST) {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
        }
    }
}

答案 3 :(得分:1)

  @BindingAdapter({"bind:adapter"})
    public static void bind(RecyclerView view, RecyclerView.Adapter<BaseViewHolder> adapter) {
        view.setLayoutManager(new GridLayoutManager(view.getContext(), 3));
        view.addItemDecoration(new SpacesItemDecorationGrid(view.getContext(), 4, 3));
        view.setItemAnimator(new DefaultItemAnimator());
        view.setAdapter(adapter);
    }


public class SpacesItemDecorationGrid extends RecyclerView.ItemDecoration {

    private int mSizeGridSpacingPx;
    private int mGridSize;
    private boolean mNeedLeftSpacing = false;

    /**
     * @param gridSpacingPx
     * @param gridSize
     */
    SpacesItemDecorationGrid(Context context, int gridSpacingPx, int gridSize) {
        mSizeGridSpacingPx = (int) Util.convertDpToPixel(gridSpacingPx, context);
        mGridSize = gridSize;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
        int padding = parent.getWidth() / mGridSize - frameWidth;
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
        int itemCount = parent.getAdapter().getItemCount() - mGridSize;


   /*     if (itemPosition < mGridSize) {
            outRect.top = mSizeGridSpacingPx;
        } else {
                   outRect.top = mSizeGridSpacingPx;
        }*/
        outRect.top = mSizeGridSpacingPx;
        if (itemPosition % mGridSize == 0) {
            outRect.left = mSizeGridSpacingPx;
            outRect.right = padding;
            mNeedLeftSpacing = true;
        } else if ((itemPosition + 1) % mGridSize == 0) {
            mNeedLeftSpacing = false;
            outRect.right = mSizeGridSpacingPx;
            outRect.left = padding;
        } else if (mNeedLeftSpacing) {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx - padding;
            if ((itemPosition + 2) % mGridSize == 0) {
                outRect.right = mSizeGridSpacingPx - padding;
            } else {
                outRect.right = mSizeGridSpacingPx / 2;
            }
        } else if ((itemPosition + 2) % mGridSize == 0) {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx / 2;
            outRect.right = mSizeGridSpacingPx - padding;
        } else {
            mNeedLeftSpacing = false;
            outRect.left = mSizeGridSpacingPx / 2;
            outRect.right = mSizeGridSpacingPx / 2;
        }
        if (itemPosition > itemCount) {
            outRect.bottom = mSizeGridSpacingPx;
        } else {
            outRect.bottom = 0;
        }

    }

}

答案 4 :(得分:1)

如果您有标题,请使用它。

要隐藏标头集skipHeaderDivider=false的分隔符,否则请设置true

class GridDividerItemDecoration : ItemDecoration() {

    var skipHeaderDivider = true

    private var divider: Drawable? = null

    private val bounds = Rect()

    private var spacing = 0

    fun setDrawable(drawable: Drawable) {
        divider = drawable
        divider?.intrinsicHeight?.let { spacing = it }
    }

    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        canvas.save()
        val childCount = parent.childCount
        for (i in 0 until childCount) {
            val child = parent.getChildAt(i)
            parent.layoutManager?.getDecoratedBoundsWithMargins(child, bounds)

            val right: Int = bounds.right + child.translationX.roundToInt()
            val left: Int = bounds.left - child.translationX.roundToInt()
            val bottom: Int = bounds.bottom + child.translationY.roundToInt()
            val top: Int = bounds.top - child.translationY.roundToInt()

            divider?.setBounds(left, top, right, bottom)
            divider?.draw(canvas)
        }
        canvas.restore()
    }

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val gridLayoutManager = parent.layoutManager as? GridLayoutManager ?: return
        val position = gridLayoutManager.getPosition(view)
        if (position < 0) return
        val spanCount = gridLayoutManager.spanCount
        val positionalSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position)

        if (skipHeaderDivider && positionalSpanSize == spanCount) return

        val itemCount = gridLayoutManager.itemCount

        val onBottom = position >= itemCount - spanCount
        var nextHeader = false

        run loop@{
            for (i in 1..spanCount) {
                val nextSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position + i)
                if (nextSpanSize == spanCount) {
                    nextHeader = true
                    return@loop
                }
            }
        }

        outRect.top = spacing
        outRect.left = 0
        outRect.right = spacing
        outRect.bottom = if (nextHeader || onBottom) spacing else 0
    }
}

答案 5 :(得分:0)

一个对我有用的简单解决方案。希望它有用。

Func<Product, bool> ItemProduct(IEnumerable<Iitem> items) => (pro) => pro.ItemID == items.Id;

答案 6 :(得分:0)

这是我在Kotlin的实现。我从其他答案中修改了代码,以便对全部内容也显示分隔线。

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

class GridDividerItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val position = parent.getChildAdapterPosition(view)

        val totalSpanCount = getTotalSpanCount(parent)
        val spanSize = getItemSpanSize(parent, position)

        outRect.top = if (isInTheFirstRow(position, totalSpanCount)) 0 else spacing
        outRect.left = if (isFirstInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
        outRect.right = if (isLastInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
        outRect.bottom = 0
    }

    private fun isInTheFirstRow(position: Int, totalSpanCount: Int): Boolean =
        position < totalSpanCount

    private fun isFirstInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
        if (totalSpanCount != spanSize) {
            position % totalSpanCount == 0
        } else true

    private fun isLastInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
        isFirstInRow(position + 1, totalSpanCount, spanSize)

    private fun getTotalSpanCount(parent: RecyclerView): Int =
        (parent.layoutManager as? GridLayoutManager)?.spanCount ?: 1

    private fun getItemSpanSize(parent: RecyclerView, position: Int): Int =
        (parent.layoutManager as? GridLayoutManager)?.spanSizeLookup?.getSpanSize(position) ?: 1
}

结果:

grid_divider_icon_decoration

答案 7 :(得分:0)

下面是我的自定义类,该类允许Kotlin中的网格单元之间具有相等的间距:

class GridItemOffsetDecoration(private val spanCount: Int, private var mItemOffset: Int) : ItemDecoration() {

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
                            state: RecyclerView.State) {
    val position = parent.getChildAdapterPosition(view)

    if (position < spanCount) {
        if (position % 2 == 0) { // left grid
            outRect.set(0, mItemOffset, mItemOffset / 2, mItemOffset / 2)
        } else { // right grid
            outRect.set(mItemOffset / 2, mItemOffset, 0, mItemOffset / 2)
        }

    } else if (position % 2 == 0) { // left grid
        outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset / 2)

    } else if (position % 2 == 1) { // right grid
        outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset / 2)

    } else {
        if (position % 2 == 0) { // left grid
            outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset)
        } else { // right grid
            outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset)
        }
    }
}

}

并要将其添加为RecyclerView中的商品装饰器,请添加以下行:

/*spanCount is the number of grids, for instance, (2 = 2*2 grid, 3 = 3*3)*/
binding.rvActiveChallenges.addItemDecoration(GridItemOffsetDecoration(2, resources.getDimensionPixelSize(R.dimen._10dp)))

答案 8 :(得分:0)

这是我的版本。基于Nicolay's answer,但经过改进,可以处理包含三个或更多图像的网格,并使用dp单位间隔。 (他的版本无法提供大小相等的图片/间距超过2张的图片。)

NB:计算每个图像上的间距的逻辑比仅仅将间距除以2(每个图像的一半)要复杂得多,这是大多数答案无法解释的。

/**
 * Class to add INTERNAL SPACING to a grid of items. Only works for a grid with 3 columns or more.
 */
class PhotoSpaceDecoration extends RecyclerView.ItemDecoration {
    private final int spacingWidthPx;

    /**
     * Initialise with the with of the spacer in dp
     *
     * @param spacingWidthDp this will be divided between elements and applied as a space on each side
     *                       NB: for proper alignment this must be divisible by 2 and by the number of columns
     */
    public PhotoSpaceDecoration(Context context, int spacingWidthDp) {
        // Convert DP to pixels
        this.spacingWidthPx = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingWidthDp,
                                                             context.getResources().getDisplayMetrics());
    }

    /**
     * @param index           a 0 indexed value of the current item
     * @param numberOfColumns
     * @return a 0 indexed Point with the x & y location of the item in the grid
     */
    private Point getItemXY(int index, int numberOfColumns) {
        int x = index % numberOfColumns;
        int y = index / numberOfColumns; // NB: integer division
        return new Point(x, y);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        final int position = parent.getChildAdapterPosition(view);
        final int columns = getTotalSpanCount(parent);
        final int rows = (int) Math.ceil(parent.getChildCount() / (double) columns); // NB: NOT integer division
        int spanSize = getItemSpanSize(parent, position);
        if (columns == spanSize) {
            return;
        }

        Point point = getItemXY(position, columns);
        int firstMargin = spacingWidthPx * (columns - 1) / columns;
        int secondMargin = spacingWidthPx - firstMargin;
        int middleMargin = spacingWidthPx / 2;

        if (point.x == 0) { // first column
            outRect.left = 0;
            outRect.right = firstMargin;
        } else if (point.x == 1) { // second column
            outRect.left = secondMargin;
            outRect.right = rows > 3 ? middleMargin : secondMargin;
        } else if (point.x - columns == -2) { // penultimate column
            outRect.left = rows > 3 ? middleMargin : secondMargin;
            outRect.right = secondMargin;
        } else if (point.x - columns == -1) { // last column
            outRect.left = firstMargin;
            outRect.right = 0;
        } else { // middle columns
            outRect.left = middleMargin;
            outRect.right = middleMargin;
        }

        if (point.y == 0) { // first row
            outRect.top = 0;
            outRect.bottom = firstMargin;
        } else if (point.y == 1) { // second row
            outRect.top = secondMargin;
            outRect.bottom = rows > 3 ? middleMargin : secondMargin;
        } else if (point.y - rows == -2) { // penultimate row
            outRect.top = rows > 3 ? middleMargin : secondMargin;
            outRect.bottom = secondMargin;
        } else if (point.y - rows == -1) { // last row
            outRect.top = firstMargin;
            outRect.bottom = 0;
        } else { // middle rows
            outRect.top = middleMargin;
            outRect.bottom = middleMargin;
        }
    }

    private int getTotalSpanCount(RecyclerView parent) {
        final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanCount() : 1;
    }

    private int getItemSpanSize(RecyclerView parent, int position) {
        final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanSizeLookup()
                                                                                               .getSpanSize(
                                                                                                       position) : 1;
    }
}

这将从Activity.onCreate()应用于回收者视图,如下所示

photosRecyclerView.addItemDecoration(new PhotoSpaceDecoration(this, 6));

示例: enter image description here

答案 9 :(得分:0)

感谢Yeldar.N solution在出现 headers 时也做了类似的事情。在2列和3列的垂直网格上进行测试,并具有不同位置的标题(甚至连续几个标题)。该解决方案要求您在滚动过程中不要更改适配器(否则标题的位置可能会更改)。

要将分隔符隐藏在标题上方,请设置skipHeaderDivider=true,否则请设置false(默认)。

import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.roundToInt

class GridDividerItemDecoration : RecyclerView.ItemDecoration() {

    var skipHeaderDivider = true

    private var divider: Drawable? = null
    private val bounds = Rect()
    private var horizontalSpacing = 0
    private var verticalSpacing = 0
    private val headerPositionsList = mutableListOf(-1) // Headers positions.
    private var headerPositionsIndex = 0
    private var prevHeaderPosition = headerPositionsList[0] // Header position before current position.
    private var nextHeaderPosition = headerPositionsList[0] // Header position after current position.

    fun setDrawable(drawable: Drawable) {
        divider = drawable
        divider?.intrinsicWidth?.let { horizontalSpacing = it }
        divider?.intrinsicHeight?.let { verticalSpacing = it }
    }

    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        canvas.save()
        val childCount = parent.childCount
        for (i in 0 until childCount) {
            val child = parent.getChildAt(i)
            parent.layoutManager?.getDecoratedBoundsWithMargins(child, bounds)

            val right: Int = bounds.right + child.translationX.roundToInt()
            val left: Int = bounds.left - child.translationX.roundToInt()
            val bottom: Int = bounds.bottom + child.translationY.roundToInt()
            val top: Int = bounds.top - child.translationY.roundToInt()

            divider?.setBounds(left, top, right, bottom)
            divider?.draw(canvas)
        }
        canvas.restore()
    }

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val gridLayoutManager = parent.layoutManager as? GridLayoutManager ?: return
        val position = gridLayoutManager.getPosition(view)
        if (position < 0) return
        val spanCount = gridLayoutManager.spanCount
        val positionalSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position)

        val isHeader = positionalSpanSize == spanCount
        if (isHeader) {
            // Add header's position to the list.
            if (!headerPositionsList.contains(position)) {
                headerPositionsList += position
                headerPositionsIndex++

                prevHeaderPosition = position
                nextHeaderPosition = prevHeaderPosition
            }
            // Skip header's aligning.
            if (skipHeaderDivider) return
        }

        // Seek header's positions so that 
        // prevHeaderPosition <= position <= nextHeaderPosition when possible.
        if (position < prevHeaderPosition) {
            // User scrolls up.
            while (headerPositionsIndex > 0) {
                nextHeaderPosition = prevHeaderPosition
                prevHeaderPosition = headerPositionsList[--headerPositionsIndex]
                if (prevHeaderPosition <= position) break
            }
        } else if (position > nextHeaderPosition) {
            // User scrolls down.
            while (headerPositionsIndex < headerPositionsList.size - 1) {
                prevHeaderPosition = nextHeaderPosition
                nextHeaderPosition = headerPositionsList[++headerPositionsIndex]
                if (position <= nextHeaderPosition) break
            }
        }

        // Is current item the most right in a row?
        val isRightInRow = isHeader || (position - prevHeaderPosition) % spanCount == 0

        outRect.top = verticalSpacing
        outRect.left = 0
        outRect.right = if (isRightInRow) 0 else horizontalSpacing
        outRect.bottom = 0
    }
}

enter image description here

用法:

val divider = GridDividerItemDecoration()
context?.let {
    divider.setDrawable(ContextCompat.getDrawable(it, R.drawable.list_divider)!!)
}
divider.skipHeaderDivider = true
recyclerView.addItemDecoration(divider)

list_divider.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:width="7dp"
        android:height="10dp" />
</shape>

您甚至可以设置分隔线的颜色:

enter image description here

有一个问题:当您向下滚动列表时,从当前位置打开另一个片段并返回,onCreateView/onCreate将再次被调用。因此,RecyclerView将刷新,headerPositionsList将使用错误的位置重新初始化,并且分隔符将变为错误。