如何使用GridLayoutManager与RecyclerView设置列间距? 在我的布局中设置空白/填充没有效果。


当前回答

这也适用于RecyclerView with header。

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;
    private boolean includeEdge;
    private int headerNum;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
        this.headerNum = headerNum;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view) - headerNum; // item position

        if (position >= 0) {
            int column = position % spanCount; // item column

            if (includeEdge) {
                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

                if (position < spanCount) { // top edge
                    outRect.top = spacing;
                }
                outRect.bottom = spacing; // item bottom
            } else {
                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
                if (position >= spanCount) {
                    outRect.top = spacing; // item top
                }
            }
        } else {
            outRect.left = 0;
            outRect.right = 0;
            outRect.top = 0;
            outRect.bottom = 0;
        }
    }
    }
}

其他回答

下面的代码将处理StaggeredGridLayoutManager、GridLayoutManager和LinearLayoutManager。

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int halfSpace;

    public SpacesItemDecoration(int space) {
        this.halfSpace = space / 2;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        if (parent.getPaddingLeft() != halfSpace) {
            parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
            parent.setClipToPadding(false);
        }

        outRect.top = halfSpace;
        outRect.bottom = halfSpace;
        outRect.left = halfSpace;
        outRect.right = halfSpace;
    }
}

然后使用它

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

如果你想要物品周围的间距和物品大小相等,下面是一步一步的简单解决方案。

ItemOffsetDecoration

public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {

    private int mItemOffset;

    public ItemOffsetDecoration(int itemOffset) {
        mItemOffset = itemOffset;
    }

    public ItemOffsetDecoration(@NonNull Context context, @DimenRes int itemOffsetId) {
        this(context.getResources().getDimensionPixelSize(itemOffsetId));
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(mItemOffset, mItemOffset, mItemOffset, mItemOffset);
    }
}

实现

在源代码中,将ItemOffsetDecoration添加到RecyclerView中。 项偏移值应该是实际值的一半大小,作为项之间的空间。

mRecyclerView.setLayoutManager(new GridLayoutManager(context, NUM_COLUMNS);
ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(context, R.dimen.item_offset);
mRecyclerView.addItemDecoration(itemDecoration);

同样,设置项目偏移值为itsRecyclerView的填充,并指定android:clipToPadding=false。

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview_grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:padding="@dimen/item_offset"/>

对于那些有问题的staggeredLayoutManager(如https://imgur.com/XVutH5u)

recyclerView的方法:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

有时返回-1作为索引,所以我们可能会遇到设置itemDecor的麻烦。我的解决方案是覆盖已弃用的ItemDecoration的方法:

public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

而不是新手:

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

是这样的:

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
                TheAdapter.VH vh = (TheAdapter.VH) recyclerView.findViewHolderForAdapterPosition(itemPosition);
                View itemView = vh.itemView;    //itemView is the base view of viewHolder
                //or instead of the 2 lines above maybe it's possible to use  View itemView = layoutManager.findViewByPosition(itemPosition)  ... NOT TESTED

                StaggeredGridLayoutManager.LayoutParams itemLayoutParams = (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams();

                int spanIndex = itemLayoutParams.getSpanIndex();

                if (spanIndex == 0)
                    ...
                else
                    ...
            }
        });

到目前为止似乎对我有用:)

对我来说,完美的解决方案是设置RecyclerView的宽度它的layoutmanager为GridLayoutManager为wrap_content

这是我在Kotlin中编写的更灵活的版本,您可以在dp中设置参数。

class ItemDividerGrid(private val numberOfColumns: Int, private val rowSpacingDP: Float = 0f, private val columnSpacingDP: Float = 0f, private val edgeSpacingVerticalDP: Float = 0f, private val edgeSpacingHorizontalDP: Float = 0f) : ItemDecoration() {

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val position = parent.getChildAdapterPosition(view)
        val numberOfRows = (parent.adapter?.itemCount?:-1)/numberOfColumns
        val column = position % numberOfColumns
        val row = position / numberOfColumns
        val context = view.context
        ///horizontal
        when(column){
            0 -> {
                outRect.left = convertDpToPixel(edgeSpacingVerticalDP,context)
                outRect.right = convertDpToPixel(columnSpacingDP/2, context)
            }
            numberOfColumns-1 -> {
                outRect.left = convertDpToPixel(columnSpacingDP/2, context)
                outRect.right = convertDpToPixel(edgeSpacingVerticalDP, context)
            }
            else -> {
                outRect.left = convertDpToPixel(columnSpacingDP/2, context)
                outRect.right = convertDpToPixel(columnSpacingDP/2, context)
            }
        }
        //vertical
        when(row){
            0  -> {
                outRect.top = convertDpToPixel(edgeSpacingHorizontalDP,context)
                outRect.bottom = convertDpToPixel(rowSpacingDP/2, context)
            }
            numberOfRows -> {
                outRect.top = convertDpToPixel(rowSpacingDP/2, context)
                outRect.bottom = convertDpToPixel(edgeSpacingHorizontalDP, context)
            }
            else -> {
                outRect.top = convertDpToPixel(rowSpacingDP/2, context)
                outRect.bottom = convertDpToPixel(rowSpacingDP/2, context)
            }
        }
    }
    fun convertDpToPixel(dp: Float, context: Context?): Int {
        return if (context != null) {
            val resources = context.resources
            val metrics = resources.displayMetrics
            (dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
        } else {
            val metrics = Resources.getSystem().displayMetrics
            (dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
        }
    }
}