安卓Recyclerview的GridLayoutManager列间距问题

322

如何使用GridLayoutManager设置RecyclerView的列间距?在布局内设置margin/padding没有效果。


你尝试过继承GridLayoutManager并重写generateDefaultLayoutParams()及其相关方法吗? - CommonsWare
我之前没有想到可以设置网格视图的间距,可能是我没发现相关方法。我会尝试一下。 - Nick H
https://dev59.com/U1sW5IYBdhLWcg3wQVb2#35226600 - hch
尝试一下这个 https://gist.github.com/Arpit0492/cf14df02ddf53741df5dde864002e89c - Arpit J.
33个回答

2

这是我基于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
  }

}

2
这个问题的答案似乎比应该的要复杂。这是我的看法。
假设您想在网格项之间添加1dp间距。请按以下方式操作:
1. 对于每个项,添加0.5dp的填充。 2. 对于RecycleView,添加-0.5dp的填充。 3. 就这样! :)

它的问题! - Mahdi Safarmohammadloo
最佳...解决方案..简单易行 - Ucdemir

2

对于像我这样想要在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
                )
            )

1
如果您有一个在列表和网格之间切换的开关,请在设置任何新的项目装饰之前调用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)
}

1

对我来说,完美的解决方案是将布局管理器设置为 GridLayoutManager 的 RecyclerView 的宽度设置为 "wrap_content"。


1

请确保在您的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()));
}

最后按照下面这行所示的方式实现间距:
photosRecycler.addItemDecoration(new SpacingItemDecoration(2,  dpToPx(this, 4), true));

1
这也适用于带有标题的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;
        }
    }
    }
}

headerNum是什么? - Tim Kranen

1
对于使用StaggeredGridLayoutManager的用户,请注意,这里包括最受欢迎的答案在内的许多答案使用以下代码计算项目列:
int column = position % spanCount

假设第1/3/5/..项总是位于左侧,第2/4/6/..项总是位于右侧。这种假设总是正确的吗?不是。

假设你的第1项高度为100dp,而第2项只有50dp,那么猜猜你的第3项在哪里,左边还是右边?


0
为了使https://dev59.com/E14b5IYBdhLWcg3w7Fcv#29905000(上文)的解决方案生效,我不得不修改以下方法(以及所有后续调用)。
@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;
}

0

我最终为我的带有GridLayoutManager和HeaderView的RecyclerView这样做了。

在下面的代码中,我设置了每个项目之间的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));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接