Android Recyclerview GridLayoutManager列间距

你如何设置列间距与一个RecyclerView使用GridLayoutManager? 在我的布局中设置边距/填充没有效果

306124 次浏览

RecyclerViews支持ItemDecoration的概念:围绕每个元素的特殊偏移量和绘图。如在这个答案中所见,您可以使用

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;


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


@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;


// Add top margin only for the first item to avoid double space between items
if (parent.getChildLayoutPosition(view) == 0) {
outRect.top = space;
} else {
outRect.top = 0;
}
}
}

然后通过

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

试试这个。它会照顾到周围的等距。适用于列表,网格和StaggeredGrid。

编辑

更新后的代码应该处理大多数具有跨度、方向等的角落情况。 注意,如果在GridLayoutManager中使用setSpanSizeLookup(),出于性能考虑,建议设置setSpanIndexCacheEnabled()。< / p >

注意,似乎与StaggeredGrid,似乎有一个错误的索引的孩子变得古怪和难以跟踪,所以下面的代码可能不会很好地与StaggeredGridLayoutManager。

public class ListSpacingDecoration extends RecyclerView.ItemDecoration {


private static final int VERTICAL = OrientationHelper.VERTICAL;


private int orientation = -1;
private int spanCount = -1;
private int spacing;
private int halfSpacing;




public ListSpacingDecoration(Context context, @DimenRes int spacingDimen) {


spacing = context.getResources().getDimensionPixelSize(spacingDimen);
halfSpacing = spacing / 2;
}


public ListSpacingDecoration(int spacingPx) {


spacing = spacingPx;
halfSpacing = spacing / 2;
}


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


super.getItemOffsets(outRect, view, parent, state);


if (orientation == -1) {
orientation = getOrientation(parent);
}


if (spanCount == -1) {
spanCount = getTotalSpan(parent);
}


int childCount = parent.getLayoutManager().getItemCount();
int childIndex = parent.getChildAdapterPosition(view);


int itemSpanSize = getItemSpanSize(parent, childIndex);
int spanIndex = getItemSpanIndex(parent, childIndex);


/* INVALID SPAN */
if (spanCount < 1) return;


setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex);
}


protected void setSpacings(Rect outRect, RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {


outRect.top = halfSpacing;
outRect.bottom = halfSpacing;
outRect.left = halfSpacing;
outRect.right = halfSpacing;


if (isTopEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
outRect.top = spacing;
}


if (isLeftEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
outRect.left = spacing;
}


if (isRightEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
outRect.right = spacing;
}


if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
outRect.bottom = spacing;
}
}


@SuppressWarnings("all")
protected int getTotalSpan(RecyclerView parent) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getSpanCount();
} else if (mgr instanceof StaggeredGridLayoutManager) {
return ((StaggeredGridLayoutManager) mgr).getSpanCount();
} else if (mgr instanceof LinearLayoutManager) {
return 1;
}


return -1;
}


@SuppressWarnings("all")
protected int getItemSpanSize(RecyclerView parent, int childIndex) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
} else if (mgr instanceof StaggeredGridLayoutManager) {
return 1;
} else if (mgr instanceof LinearLayoutManager) {
return 1;
}


return -1;
}


@SuppressWarnings("all")
protected int getItemSpanIndex(RecyclerView parent, int childIndex) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
} else if (mgr instanceof StaggeredGridLayoutManager) {
return childIndex % spanCount;
} else if (mgr instanceof LinearLayoutManager) {
return 0;
}


return -1;
}


@SuppressWarnings("all")
protected int getOrientation(RecyclerView parent) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof LinearLayoutManager) {
return ((LinearLayoutManager) mgr).getOrientation();
} else if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getOrientation();
} else if (mgr instanceof StaggeredGridLayoutManager) {
return ((StaggeredGridLayoutManager) mgr).getOrientation();
}


return VERTICAL;
}


protected boolean isLeftEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {


if (orientation == VERTICAL) {


return spanIndex == 0;


} else {


return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);
}
}


protected boolean isRightEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {


if (orientation == VERTICAL) {


return (spanIndex + itemSpanSize) == spanCount;


} else {


return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);
}
}


protected boolean isTopEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {


if (orientation == VERTICAL) {


return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);


} else {


return spanIndex == 0;
}
}


protected boolean isBottomEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {


if (orientation == VERTICAL) {


return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);


} else {


return (spanIndex + itemSpanSize) == spanCount;
}
}


protected boolean isFirstItemEdgeValid(boolean isOneOfFirstItems, RecyclerView parent, int childIndex) {


int totalSpanArea = 0;
if (isOneOfFirstItems) {
for (int i = childIndex; i >= 0; i--) {
totalSpanArea = totalSpanArea + getItemSpanSize(parent, i);
}
}


return isOneOfFirstItems && totalSpanArea <= spanCount;
}


protected boolean isLastItemEdgeValid(boolean isOneOfLastItems, RecyclerView parent, int childCount, int childIndex, int spanIndex) {


int totalSpanRemaining = 0;
if (isOneOfLastItems) {
for (int i = childIndex; i < childCount; i++) {
totalSpanRemaining = totalSpanRemaining + getItemSpanSize(parent, i);
}
}


return isOneOfLastItems && (totalSpanRemaining <= spanCount - spanIndex);
}
}

希望能有所帮助。

下面的代码工作得很好,每个列都有相同的宽度:

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {


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


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


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
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
}
}
}
}

使用

1. 没有优势

enter image description here

int spanCount = 3; // 3 columns
int spacing = 50; // 50px
boolean includeEdge = false;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));

2. 与边缘

enter image description here

int spanCount = 3; // 3 columns
int spacing = 50; // 50px
boolean includeEdge = true;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));

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

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);

同样,将项目偏移值设置为其__abc0的填充,并指定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"/>

下面的代码将处理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));

上面的回答已经阐明了设置边缘处理GridLayoutManager和LinearLayoutManager的方法。

但是对于StaggeredGridLayoutManager, Pirdad Sakhizada的回答是:“它可能不太适合StaggeredGridLayoutManager”。应该是关于IndexOfSpan的问题。

您可以通过以下方式获取:

private static class MyItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int index = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
}
}

下面是我对SpacesItemDecoration的修改,它可以取numOfColums上、下、左、右的空间相等

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private int mNumCol;


public SpacesItemDecoration(int space, int numCol) {
this.space = space;
this.mNumCol=numCol;
}


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


//outRect.right = space;
outRect.bottom = space;
//outRect.left = space;


//Log.d("ttt", "item position" + parent.getChildLayoutPosition(view));
int position=parent.getChildLayoutPosition(view);


if(mNumCol<=2) {
if (position == 0) {
outRect.left = space;
outRect.right = space / 2;
} else {
if ((position % mNumCol) != 0) {
outRect.left = space / 2;
outRect.right = space;
} else {
outRect.left = space;
outRect.right = space / 2;
}
}
}else{
if (position == 0) {
outRect.left = space;
outRect.right = space / 2;
} else {
if ((position % mNumCol) == 0) {
outRect.left = space;
outRect.right = space/2;
} else if((position % mNumCol) == (mNumCol-1)){
outRect.left = space/2;
outRect.right = space;
}else{
outRect.left=space/2;
outRect.right=space/2;
}
}


}


if(position<mNumCol){
outRect.top=space;
}else{
outRect.top=0;
}
// Add top margin only for the first item to avoid double space between items
/*
if (parent.getChildLayoutPosition(view) == 0 ) {


} else {
outRect.top = 0;
}*/
}
}

并在逻辑上使用下面的代码。

recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol));

对于这个问题,有一个非常简单而灵活的解决方案,它只使用适用于每个LayoutManager的XML。

假设你想要一个相等的X间距(例如8dp)。

  1. 在另一个布局中包装CardView项

  2. 给外层布局填充X/2 (4dp)

  3. 使外层布局背景透明

...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/transparent"
android:padding="4dip">


<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.CardView>


</LinearLayout>
  1. 给你的RecyclerView填充X/2 (4dp)

...

<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp" />

就是这样。你有完美的X (8dp)间距。

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {


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


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


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
int column = params.getSpanIndex();


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
}
}
}
}

与edwardaa的回答略有不同,不同之处在于列是如何确定的,因为在诸如具有不同高度的项目的情况下,列不能简单地通过% spanCount确定

复制@edwardaa提供的代码,我使它完美地支持RTL:

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
private int headerNum;
private boolean isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;


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(isRtl) {
column = spanCount - 1 - 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;
}
}
}

我最终为我的RecyclerView与GridLayoutManager和HeaderView这样做。

在下面的代码中,我在每个项目之间设置了4dp空间(每个项目周围都是2dp,整个recyclerview周围是2dp填充)。

layout.xml

<android.support.v7.widget.RecyclerView
android:id="@+id/recycleview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="2dp" />

片段/活动

GridLayoutManager manager = new GridLayoutManager(getContext(), 3);
recyclerView.setLayoutManager(manager);
int spacingInPixels = Utils.dpToPx(2);
recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

SpaceItemDecoration.java

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {


private int mSpacing;


public SpacesItemDecoration(int spacing) {
mSpacing = spacing;
}


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
outRect.left = mSpacing;
outRect.top = mSpacing;
outRect.right = mSpacing;
outRect.bottom = mSpacing;
}
}

Utils.java

public static int dpToPx(final float dp) {
return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
下面是一个解决方案不需要“spanCount”(列数) 我使用它是因为我使用GridAutofitLayoutManager(根据所需的单元格大小计算列数)

(注意,这只适用于GridLayoutManager)

public class GridSpacesItemDecoration extends RecyclerView.ItemDecoration {
private final boolean includeEdge;
private int spacing;




public GridSpacesItemDecoration(int spacing, boolean includeEdge) {
this.spacing = spacing;
this.includeEdge = includeEdge;
}


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager)parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount();
int position = parent.getChildAdapterPosition(view); // item position
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
}
}


}


}
}

下面是任何人都感兴趣的GridAutofitLayoutManager:

public class GridAutofitLayoutManager extends GridLayoutManager {
private int mColumnWidth;
private boolean mColumnWidthChanged = true;


public GridAutofitLayoutManager(Context context, int columnWidth)
{
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}


public GridAutofitLayoutManager(Context context,int unit, int columnWidth)
{
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
int pixColumnWidth = (int) TypedValue.applyDimension(unit, columnWidth, context.getResources().getDisplayMetrics());
setColumnWidth(checkedColumnWidth(context, pixColumnWidth));
}


public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout)
{
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1, orientation, reverseLayout);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}


private int checkedColumnWidth(Context context, int columnWidth)
{
if (columnWidth <= 0)
{
/* Set default columnWidth value (48dp here). It is better to move this constant
to static constant on top, but we need context to convert it to dp, so can't really
do so. */
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
context.getResources().getDisplayMetrics());
}
return columnWidth;
}


public void setColumnWidth(int newColumnWidth)
{
if (newColumnWidth > 0 && newColumnWidth != mColumnWidth)
{
mColumnWidth = newColumnWidth;
mColumnWidthChanged = true;
}
}


@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
{
int width = getWidth();
int height = getHeight();
if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0)
{
int totalSpace;
if (getOrientation() == VERTICAL)
{
totalSpace = width - getPaddingRight() - getPaddingLeft();
}
else
{
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / mColumnWidth);
setSpanCount(spanCount);


mColumnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}

最后:

mDevicePhotosView.setLayoutManager(new GridAutofitLayoutManager(getContext(), getResources().getDimensionPixelSize(R.dimen.item_size)));
mDevicePhotosView.addItemDecoration(new GridSpacesItemDecoration(Util.dpToPx(getContext(), 2),true));

谢谢edwardaa的回答

另一点需要注意的是:

如果total spacing和total itemWidth不等于屏幕宽度,您还需要调整itemWidth,例如,在适配器onBindViewHolder方法

Utils.init(_mActivity);
int width = 0;
if (includeEdge) {
width = ScreenUtils.getScreenWidth() - spacing * (spanCount + 1);
} else {
width = ScreenUtils.getScreenWidth() - spacing * (spanCount - 1);
}
int itemWidth = width / spanCount;


ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) holder.imageViewAvatar.getLayoutParams();
// suppose the width and height are the same
layoutParams.width = itemWidth;
layoutParams.height = itemWidth;
holder.imageViewAvatar.setLayoutParams(layoutParams);

如果你想在所有设备中RecyclerView项的大小。你可以这样做

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {


private int mSpanCount;
private float mItemSize;


public GridSpacingItemDecoration(int spanCount, int itemSize) {
this.mSpanCount = spanCount;
mItemSize = itemSize;
}


@Override
public void getItemOffsets(final Rect outRect, final View view, RecyclerView parent,
RecyclerView.State state) {
final int position = parent.getChildLayoutPosition(view);
final int column = position % mSpanCount;
final int parentWidth = parent.getWidth();
int spacing = (int) (parentWidth - (mItemSize * mSpanCount)) / (mSpanCount + 1);
outRect.left = spacing - column * spacing / mSpanCount;
outRect.right = (column + 1) * spacing / mSpanCount;


if (position < mSpanCount) {
outRect.top = spacing;
}
outRect.bottom = spacing;
}
}

recyclerview_item.xml

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/recycler_view_item_width"
...
>
...
</LinearLayout>

dimens.xml

 <dimen name="recycler_view_item_width">60dp</dimen>

活动

int numberOfColumns = 3;
mRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns));
mRecyclerView.setAdapter(...);
mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3,
getResources().getDimensionPixelSize(R.dimen.recycler_view_item_width)));

enter image description here enter image description here < / p >

只有一个简单的解决方案,您可以记住并在任何需要的地方实施。没有bug,没有疯狂的计算。在卡片/物品布局中放置空白,并在RecyclerView中放置相同大小的填充:

item_layout.xml

<CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:margin="10dp">

activity_layout.xml

<RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"/>

< >强更新: enter image description here < / p >

为了使https://stackoverflow.com/a/29905000/1649371(上面)解决方案工作,我必须修改以下方法(以及所有后续调用)

@SuppressWarnings("all")
protected int getItemSpanSize(RecyclerView parent, View view, int childIndex) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
} else if (mgr instanceof StaggeredGridLayoutManager) {
return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).isFullSpan() ? spanCount : 1;
} else if (mgr instanceof LinearLayoutManager) {
return 1;
}


return -1;
}


@SuppressWarnings("all")
protected int getItemSpanIndex(RecyclerView parent, View view, int childIndex) {


RecyclerView.LayoutManager mgr = parent.getLayoutManager();
if (mgr instanceof GridLayoutManager) {
return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
} else if (mgr instanceof StaggeredGridLayoutManager) {
return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
} else if (mgr instanceof LinearLayoutManager) {
return 0;
}


return -1;
}

这个链接适用于我的所有情况,你可以尝试一下。

这也适用于带有header的RecyclerView

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;
}
}
}
}

我根据edwardaa的回答做了一个Kotlin版本

class RecyclerItemDecoration(private val spanCount: Int, private val spacing: Int) : RecyclerView.ItemDecoration() {


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


val spacing = Math.round(spacing * parent.context.resources.displayMetrics.density)
val position = parent.getChildAdapterPosition(view)
val column = position % spanCount


outRect.left = spacing - column * spacing / spanCount
outRect.right = (column + 1) * spacing / spanCount


outRect.top = if (position < spanCount) spacing else 0
outRect.bottom = spacing
}


}

如果你有一个在列表和网格之间切换的拨动开关,不要忘记在设置任何新的Item装饰之前调用recyclerView.removeItemDecoration()。如果不是这样,那么新的间隔计算将是不正确的。


就像这样。

        recyclerView.removeItemDecoration(gridItemDecorator)
recyclerView.removeItemDecoration(listItemDecorator)
if (showAsList){
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.addItemDecoration(listItemDecorator)
}
else{
recyclerView.layoutManager = GridLayoutManager(this, spanCount)
recyclerView.addItemDecoration(gridItemDecorator)
}

选择的答案几乎是完美的,但根据空间的不同,项目的宽度可能不相等。(对我来说,这很关键)。所以我最终用这个代码增加了一点空间,所以项目都是相同的宽度。

   class GridSpacingItemDecoration(private val columnCount: Int, @Px preferredSpace: Int, private val includeEdge: Boolean): RecyclerView.ItemDecoration() {


/**
* In this algorithm space should divide by 3 without remnant or width of items can have a difference
* and we want them to be exactly the same
*/
private val space = if (preferredSpace % 3 == 0) preferredSpace else (preferredSpace + (3 - preferredSpace % 3))


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


if (includeEdge) {


when {
position % columnCount == 0 -> {
outRect.left = space
outRect.right = space / 3
}
position % columnCount == columnCount - 1 -> {
outRect.right = space
outRect.left = space / 3
}
else -> {
outRect.left = space * 2 / 3
outRect.right = space * 2 / 3
}
}


if (position < columnCount) {
outRect.top = space
}


outRect.bottom = space


} else {


when {
position % columnCount == 0 -> outRect.right = space * 2 / 3
position % columnCount == columnCount - 1 -> outRect.left = space * 2 / 3
else -> {
outRect.left = space / 3
outRect.right = space / 3
}
}


if (position >= columnCount) {
outRect.top = space
}
}
}


}

对于那些有问题的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
...
}
});

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

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


override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: State
) {
val layoutManager = parent.layoutManager as? GridLayoutManager
if (layoutManager == null || layoutManager.orientation != VERTICAL) {
return super.getItemOffsets(outRect, view, parent, state)
}


val spanCount = layoutManager.spanCount
val position = parent.getChildAdapterPosition(view)
val column = position % spanCount
with(outRect) {
left = if (column == 0) 0 else spacing / 2
right = if (column == spanCount.dec()) 0 else spacing / 2
top = if (position < spanCount) 0 else spacing
}
}
}

如果你正在使用GridLayoutManager,使用下面写在芬兰湾的科特林中的代码来分隔网格:

inner class SpacesItemDecoration(itemSpace: Int) : RecyclerView.ItemDecoration() {
var space: Int = itemSpace


override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent!!.getChildAdapterPosition(view)
val viewType = parent.adapter.getItemViewType(position)
//check to not to set any margin to header item
if (viewType == GridViewAdapter.TYPE_HEADER) {
outRect!!.top = 0
outRect.left = 0
outRect.right = 0
outRect.bottom = 0
} else {
outRect!!.left = space
outRect.right = space
outRect.bottom = space


if (parent.getChildLayoutPosition(view) == 0) {
outRect.top = space
} else {
outRect.top = 0
}
}
}
}

并将ItemDecoration传递给recyclerview as

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

Yqritc的回答非常适合我。我用的是Kotlin,这里有一个等价物。

class ItemOffsetDecoration : RecyclerView.ItemDecoration  {


// amount to add to padding
private val _itemOffset: Int


constructor(itemOffset: Int) {
_itemOffset = itemOffset
}


constructor(@NonNull context: Context, @DimenRes itemOffsetId: Int){
_itemOffset = context.resources.getDimensionPixelSize(itemOffsetId)
}


/**
* Applies padding to all sides of the [Rect], which is the container for the view
*/
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.set(_itemOffset, _itemOffset, _itemOffset, _itemOffset)
}
}

其他一切都是一样的。

这个问题的答案似乎比他们应该做的要复杂。以下是我的看法。

假设您希望网格项之间有1dp的间距。做以下几点:

  1. 每一项添加0.5 dp的填充
  2. RecycleView中添加-0.5 dp的填充
  3. 就是这样!:)

对于StaggeredGridLayoutManager用户,要小心,这里有很多答案,包括投票最多的一个,用下面的代码计算item列:

int column = position % spanCount

假设第1 /3 /5 /..物品总是放在左边和第二/第四/第六/..物品总是放在右边。这个假设总是正确的吗?不。

假设你的第一件物品是100dp高,第二件只有50dp高,猜猜你的第三件物品位于哪里,左边还是右边?

当为儿童使用CardView时,项目之间的空格问题可以通过设置app:cardUseCompatPadding为true来解决。

为更大的边际扩大项目抬高。CardElevation是可选的(使用默认值)。

<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardUseCompatPadding="true"
app:cardElevation="2dp">

这就是最后对我有用的东西

        binding.rows.addItemDecoration(object: RecyclerView.ItemDecoration(){
val px = resources.getDimensionPixelSize(R.dimen.grid_spacing)
val spanCount = 2
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val index = parent.getChildLayoutPosition(view)
val isLeft = (index % spanCount == 0)
outRect.set(
if (isLeft) px else px/2,
0,
if (isLeft) px/2 else px,
px
)
}
})

因为对我来说只有2列(val spanCount = 2),所以我可以只使用isLeft。如果有>2列,那么我也需要一个isMiddle,两边的值都是px/2

我希望有一种方法可以直接从RecyclerView中获得app:spanCount,但我不相信有这种方法。

对于像我这样的人,谁想要最好的答案,但在芬兰湾的科特林,这里是:

class GridItemDecoration(
val spacing: Int,
private val spanCount: Int,
private val includeEdge: Boolean
) :
RecyclerView.ItemDecoration() {


/**
* Applies padding to all sides of the [Rect], which is the container for the view
*/
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view) // item position
val 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
}
}
}
}

+如果你想从dimensions .xml中获取数字,然后将其转换为原始像素,你可以使用getDimensionPixelOffset轻松完成,就像这样:

recyclerView.addItemDecoration(
GridItemDecoration(
resources.getDimensionPixelOffset(R.dimen.h1),
3,
true
)
)

如果你已经滚动到这个答案,我写了一个图书馆等距,它支持垂直/水平,LTR/RTL,线性布局/GridLayout管理器和边缘包含。它基本上是一个文件,所以你可以复制粘贴该文件到你的代码中。

enter image description here

我试图支持StaggeredGridLayout,但这个布局返回的span索引不可靠。我很乐意听到任何有关这方面的建议。

这是我在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()
}
}
}

确保你在gradle模块中实现了这个功能

    implementation 'com.github.grzegorzojdana:SpacingItemDecoration:1.1.0'

创建这个简单的函数

    public static int dpToPx(Context c, int dp) {
Resources r = c.getResources();
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}

最后实现这一行所示的间距

 photos_recyclerview.addItemDecoration(new SpacingItemDecoration(2,  dpToPx(this, 4), true));

对我来说,完美的解决方案是将RecyclerView的宽度设置为“__abc0”,它的layoutmanager为GridLayoutManager。