Android - 在网格布局中调整RecyclerView项目之间的间距

18
在我的Android应用程序中,我使用RecyclerView通过使用GridLayoutManager来显示网格中的项目。 在GridView中,为了指定元素之间的间距,我会设置horizontalSpacingverticalSpacing属性。
所以,我如何在RecyclerView上实现相同的效果?

请查看此链接,可能会对您有所帮助:https://dev59.com/A10Z5IYBdhLWcg3wnRgA#31243174 - N J
但是我不想放置一个分隔符。那么我应该放置一个看不见的视图作为分隔符吗? - Ernestina Juan
请看这里:https://dev59.com/KGAf5IYBdhLWcg3wdCl1#27037230 - Tin Megali
5个回答

17

我还没有用GridLayout测试过,但对于LinearLayout,我只是为每个列表项的根布局设置了一个margin。如果你想要所有项之间有相同的空白(比如说8dp),你需要这样做:

  1. 将RecyclerView的layout_padding设置为4dp。
  2. 将每个列表项的layout_margin设置为4dp。

这样每个项周围就会有一个恒定的8dp的空白。


下面的代码展示了一个横向的RecyclerView,它以8dp的正确间距显示图像:

<!-- fragment_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        tools:listitem="@layout/list_item" />

</LinearLayout>

<!-- list_item.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:layout_margin="4dp">

    <ImageView
        android:layout_width="@dimen/thumbnail_size"
        android:layout_height="@dimen/thumbnail_size"
        android:contentDescription="@string/image_content_desc" />

</LinearLayout>

编辑:我意识到项目视图需要有一个父 ViewGroup,所以我更新了代码片段。


7
你需要使用ItemDecorator来实现这个功能:
像这样:
public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {

    private final int mSpaceHeight;

    public EqualSpaceItemDecoration(int mSpaceHeight) {
        this.mSpaceHeight = mSpaceHeight;
    }

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

1
你复制/粘贴答案并没有提到“horizontalSpacing和verticalSpacing” :) - user3402040
顶部+底部=垂直,左侧+右侧=水平间距,我认为这很容易理解,所以没有特别提及。此外,它的名称表明它是equalSpace,意味着无论您传递什么参数,垂直和水平间距都将相同。 - JAAD
我阅读了这篇文章“https://dev59.com/KGAf5IYBdhLWcg3wdCl1”,发现你的解决方案只能在2列或更多列之间添加空格,而不能在列之间垂直或水平地添加边距!请注意:存在一个强大的库,不像你的示例那样只有4行代码。该库是github.com/lucasr/twoway-view。 - user3402040
nice and simple, tnx - Eggcellentos

5
RecyclerView中,可以使用ItemDecoration添加项目之间的间距。 在此之前,让我们进行一些数学计算,以找出列表项周围的边距。
在这里,我们得到了等距区域。在每个区域中,我们必须找到它们的边距并将其返回到outRect中。如果您正确地进行了数学计算,为了保持等距和等大小,您会发现这些偏移值不是常数,它们根据列索引和行索引而改变。让我们推导出偏移值的方程式。

带有外边缘

在第一种情况下,我们考虑边缘周围的边距。

enter image description here

For 0 and 1,
d + w + a = d − a + w + c
c = 2a

For 0 and 2,
d + w + a = d − c + w + e
e = a + c
e = 3a

If you see the pattern here,
right = a, 2a, 3a, ... , (n + 1) × a
right = (nᵢ + 1) × a

na = d
a = d ÷ n
right = (nᵢ + 1) × d ÷ n

Pattern of left side,
left = d, d − a, d − 2a, d − 3a, ... , d − na
left = d − nᵢ × a
left = d − nᵢ × d ÷ n

nᵢ - Column index

没有外边缘

在这里,我们进行数学计算时不考虑边缘。

enter image description here

For 0 and 1,
w + a = d − a + w + c
c = 2a − d

For 0 and 2,
w + a = d − c + w + e
e = 3a − 2d

The pattern here is,
right = a, 2a − d, 3a − 2d, ... , (n + 1) × a - nd
right = (nᵢ + 1) × a - nᵢd

(n + 1) × a − nd = 0
a = nd ÷ (n + 1)

∴ right = d − d × (nᵢ + 1) ÷ q
q = n + 1
q - Column count
nᵢ - Column index

left = 0, d − a, 2(d − a), ... , n(d − a)
left = nᵢ(d − a)
left = nᵢ × d ÷ q

让我们为此编写代码...

class SpacingItemDecoration(
    private val spacing: Int,
    private val includeEdge: Boolean,
    private val headerRowCount: Int = 0
) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val columnCount = getColumnCount(parent)
        val position = parent.getChildAdapterPosition(view) - headerRowCount * columnCount
        if (position >= 0) {
            val rowCount = getRowCount(parent, columnCount)
            val orientation = getOrientation(parent)
            val columnIndex = position % columnCount
            val rowIndex = position / columnCount
            if (includeEdge) {
                when (orientation) {
                    RecyclerView.VERTICAL -> {
                        outRect.left = getStartOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.right = getEndOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.top = getStartOffsetWithEdge(spacing, rowIndex, rowCount)
                        outRect.bottom = getEndOffsetWithEdge(spacing, rowIndex, rowCount)
                    }
                    RecyclerView.HORIZONTAL -> {
                        outRect.top = getStartOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.bottom = getEndOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.left = getStartOffsetWithEdge(spacing, rowIndex, rowCount)
                        outRect.right = getEndOffsetWithEdge(spacing, rowIndex, rowCount)
                    }
                }
            } else {
                when (orientation) {
                    RecyclerView.VERTICAL -> {
                        outRect.left = getStartOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.right = getEndOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.top = getStartOffsetWithoutEdge(spacing, rowIndex, rowCount)
                        outRect.bottom = getEndOffsetWithoutEdge(spacing, rowIndex, rowCount)
                    }
                    RecyclerView.HORIZONTAL -> {
                        outRect.top = getStartOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.bottom = getEndOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.left = getStartOffsetWithoutEdge(spacing, rowIndex, rowCount)
                        outRect.right = getEndOffsetWithoutEdge(spacing, rowIndex, rowCount)
                    }
                }
            }
        } else {
            outRect.left = 0
            outRect.right = 0
            outRect.top = 0
            outRect.bottom = 0
        }
    }

    private fun getColumnCount(parent: RecyclerView) = when (val layoutManager = parent.layoutManager) {
        is GridLayoutManager -> layoutManager.spanCount
        is StaggeredGridLayoutManager -> layoutManager.spanCount
        else -> 1
    }

    private fun getRowCount(parent: RecyclerView, columnCount: Int) =
        parent.adapter?.itemCount?.div(columnCount)?.minus(headerRowCount)

    private fun getOrientation(parent: RecyclerView) = when (val layoutManager = parent.layoutManager) {
        is LinearLayoutManager -> layoutManager.orientation
        is GridLayoutManager -> layoutManager.orientation
        is StaggeredGridLayoutManager -> layoutManager.orientation
        else -> RecyclerView.VERTICAL
    }

    private fun getStartOffsetWithEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return spacing
        return spacing - spacing * columnIndex / columnCount
    }

    private fun getEndOffsetWithEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return 0
        return spacing * (columnIndex + 1) / columnCount
    }

    private fun getStartOffsetWithoutEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return 0
        return spacing * columnIndex / columnCount
    }

    private fun getEndOffsetWithoutEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return spacing
        return spacing - spacing * (columnIndex + 1) / columnCount
    }

}

2
public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {

private final int mSpaceHeight;

public EqualSpaceItemDecoration(int mSpaceHeight) {
    this.mSpaceHeight = mSpaceHeight;
}

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

上述答案会在图像相遇处产生不均匀的边距宽度。
下面的解决方案可以产生均匀的边距。
 public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        
        int position= parent.getChildAdapterPosition (view);
        int column = position% numberOfColumns;       
            outRect.left= margin-column*spacingPx/numberOfColumns;
            outRect.right= (column+1)* margin/numberOfColumns;
            outRect.top= margin;           

        }

    }

只是一点小提示,答案的位置不是静态的。 - G_V
请详细说明。 - Thejashwini Dev
2
如果有人给你的解决方案点赞,它可能会在层次结构中排名更高,因此“上面的答案”就没有意义了,因为它假设了一个特定的顺序。 - G_V

2
你可以在RecyclerView中使用addItemDecoration方法。
ItemDecoration类文件:-
class ItemDecoration(space: Int) : RecyclerView.ItemDecoration() {
      private val halfSpace: Int
      override fun getItemOffsets(outRect: Rect,
                            view: View,
                            parent: RecyclerView,
                            state: RecyclerView.State) {
             if (parent.paddingLeft != halfSpace) {
                parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace)
                parent.clipToPadding = false
             }
             outRect.top = halfSpace
             outRect.bottom = halfSpace
             outRect.left = halfSpace
             outRect.right = halfSpace
    }

    init {
       halfSpace = space / 2
    }
}

在RecyclerView中的实现:

rvFeeds.apply {
            layoutManager = mLM
            adapter = mAdapter
            itemAnimator = null
            addItemDecoration(ItemDecoration(resources.getDimension(R.dimen.margin_very_small).toInt())) 
}

如果这两段代码都是用 Kotlin 编写的,我会给这个答案点赞 +1 :) - Jose_GD
已更改为 Kotlin,请检查。- @Jose_GD - Thakar Sahil

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