Android Recyclerview GridLayoutManager列间距

时间:2015-02-15 22:31:18

标签: android gridview android-recyclerview recycler-adapter gridlayoutmanager

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

33 个答案:

答案 0 :(得分:351)

以下代码运行良好,每列的宽度相同:

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

答案 1 :(得分:303)

RecyclerViews支持ItemDecoration的概念:特殊偏移和绘制每个元素。如this answer中所示,您可以使用

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

答案 2 :(得分:79)

以下是逐步简单的解决方案,如果您希望项目周围的间距相等且项目大小相等。

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

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

答案 3 :(得分:27)

试试这个。它会照顾到周围相等的间距。适用于List,Grid和StaggeredGrid。

被修改

更新的代码应该处理大多数具有跨度,方向等的角落情况。 请注意,如果将setSpanSizeLookup()与GridLayoutManager一起使用,则出于性能原因,建议设置setSpanIndexCacheEnabled()。

注意,似乎使用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);
  }
}

希望它有所帮助。

答案 4 :(得分:18)

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

答案 5 :(得分:9)

以下解决方案不需要“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));

答案 6 :(得分:5)

复制的@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;
        }
    }
}

答案 7 :(得分:5)

如果您希望 FIXED 所有设备中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

答案 8 :(得分:5)

只有一个简单的解决方案,您可以在任何需要的地方记住并实施。没有错误,没有疯狂的计算。将保证金放入卡/项目布局并将与填充相同的大小放入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"/>

答案 9 :(得分:4)

上面的答案阐明了设置边距处理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();
    }
}

答案 10 :(得分:3)

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

答案 11 :(得分:3)

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

要获得更大的利润,请扩大项目标高。 CardElevation是可选的(使用默认值)。

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

答案 12 :(得分:1)

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

答案 13 :(得分:1)

对于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
                    ...
            }
        });

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

答案 14 :(得分:1)

所选答案几乎是完美的,但是根据空间的不同,项目的宽度可能不相等。 (对我而言,这很关键)。因此,我最终得到了这段代码,该代码会稍微增加空间,因此项目的宽度都相同。

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

}

答案 15 :(得分:1)

对于 StaggeredGridLayoutManager 用户,请注意,此处的许多答案(包括投票最多的答案)将使用以下代码来计算项目列:

int column = position % spanCount

假定第1 / 3rd / 5th / ..项目始终位于左侧,而第2/4/4 / 6th / ..项目始终位于右侧。这个假设是否总是正确的? 否。

让我们假设您的第一个项目高100dp,第二个项目仅50dp,猜想您的第三个项目在哪里,左还是右?

答案 16 :(得分:1)

int arrays[100];
fseek(fp, 30, SEEK_SET);
fread(arrays, sizeof(int), 20, fp);
printf("%i\n", arrays);
int size = (sizeof(arrays)/ sizeof(int));
printf("There are %i numbers.", size); 

//Output:
//6421876
//There are 100 numbers

与edwardaa的答案略有不同,区别在于如何确定列,因为在诸如具有不同高度的项目的情况下,无法仅通过%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 } } } } 确定列

答案 17 :(得分:1)

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

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

  1. 将CardView项目换行到另一个布局

  2. 为外部布局填充X / 2(4dp)

  3. 使外部布局背景透明

  4. ...

    <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)
    2. ...

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

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

答案 18 :(得分:1)

以下是我对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));

答案 19 :(得分:0)

这终于为我工作了

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

由于我只有两列(val spanCount = 2),因此我只能使用isLeft。如果有> 2列,那么我也需要一个isMiddle,并且两边的值都是px/2

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

答案 20 :(得分:0)

如果您已经滚动到足够远的地方来找到这个答案,我为等间距写了一个 library,它支持垂直/水平、LTR/RTL、LinearLayout/GridLayout 管理器和边缘包含。它基本上是一个文件,因此您可以将该文件复制粘贴到您的代码中。

enter image description here

我尝试支持 StaggeredGridLayout 但此布局返回的跨度索引不可靠。我很高兴听到任何建议。

答案 21 :(得分:0)

确保在 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));

答案 22 :(得分:0)

此问题的答案似乎比应该的要复杂。这是我的看法。

比方说,您希望网格项之间的间距为1dp。请执行以下操作:

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

答案 23 :(得分:0)

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

其他都一样。

答案 24 :(得分:0)

如果您将 Header GridLayoutManager 一起使用,请使用以 kotlin 编写的这段代码来分隔网格之间的间距:

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
mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

答案 25 :(得分:0)

如果你有一个切换开关,可以在列表到网格之间切换,在设置任何新的项目装饰之前不要忘记调用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)
        }

答案 26 :(得分: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
  }

}

答案 27 :(得分:0)

这也适用于getApplicationContext()标题。

RecyclerView

答案 28 :(得分:0)

这个Link适用于我可以尝试的所有情况。

答案 29 :(得分:0)

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

答案 30 :(得分:0)

感谢edwardaa的回答https://stackoverflow.com/a/30701422/2227031

需要注意的另一点是:

如果总间距和总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);

答案 31 :(得分:0)

我最终为我的 RecyclerView with 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));
}

答案 32 :(得分:-1)

对于像我这样想要最佳答案但在 kotlin 中的人,这里是:

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

,如果要从dimens.xml中获取数字,然后将其转换为原始像素,则可以使用getDimensionPixelOffset轻松地做到这一点,就像这样:

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