GridItemDecoration实现网格布局Item等间距分割

1. 想要的效果

左右两列和上下两行ItemView贴边,与RecyclerView边界之间没有间隙,如图所示

在这里插入图片描述

2. ItemView平分实现

在设置ItemView之间的间隙之前,我们先想想如何使得ItemView平分行

我们的ItemView的布局如下

在这里插入图片描述

当最外层布局为固定宽度或者为wrap_content时,Recyclerview的效果如下:

wrap_content:
在这里插入图片描述

100dp:
在这里插入图片描述

itemView的最外层宽度为match_parent时,效果如下:
在这里插入图片描述

所以我们想要使得ItemView平分Recyclerview,就要求ItemView的宽度为match_parent

因此当我们在实际使用的时候,可以在真正的布局(紫色)部分外嵌套一层宽度为match_parent的布局,紫色布局居中。

3. ItemDecoration自定义思路

想要实现ItemView间的间隙效果,第一个想到的就是使用ItemDecorationgetItemOffsets()方法。想起原先写过的文章,为Recyclerview的列表添加间隙就是使用ItemDecoration

简单介绍下,蓝色部分就是我们的ItemView布局,想要和其他ItemView布局间形成间隙就是通过OutRect来设置的。它是在当前ItemView的四周扩展一部分作为间隙。

在这里插入图片描述

以三列为例,想要实现以下效果,有两种方案

在这里插入图片描述

这里我们采用方案二,方案一我在实际使用过程中遇到了一些问题,后面我们再讲讲.

3.1 创建类继承自RecyclerView.ItemDecoration()

class GridItemDecoration(leftAndRightSpace: Int = 0, topAndBottomSpace: Int = 0) :
RecyclerView.ItemDecoration() {
    //总共的列数
    var mSpanCount: Int = 0

    //左右间隔
    private val leftAndRightSpace = leftAndRightSpace
    //上下间隔
    private val topAndBottomSpace = topAndBottomSpace
    
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        super.getItemOffsets(outRect, view, parent, state)
    }
}

这里我们创建的类GridItemDecoration继承自RecyclerView.ItemDecoration()

leftAndRightSpacetopAndBottomSpace分别为ItemView之间的左右间隔和上下间隔

3.2 重写getItemOffsets()方法

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

    //总共的列数
    mSpanCount = (parent.layoutManager as GridLayoutManager).spanCount

    //当前View所属位置(从0计数),注意这里使用getChildAdapterPosition()而不是getChildLayoutPosition(),后面注意事项中单独说明
    val itemPosition = parent.getChildAdapterPosition(view)

    //当前所处行,从0计数
    val currentRow = itemPosition / mSpanCount
    //当前所处列,从0计数
    val currentColumn = itemPosition % mSpanCount

    //先默认所有的itemview的left和bottom为0
    //set(left,top,right,bottom)
    outRect.set(0, topAndBottomSpace, leftAndRightSpace, 0)
    //当前行处于第0行,top偏移均为0
    if (currentRow == 0) {
        outRect.top = 0
    }
    //当前列处于最后一列,right偏移均为0
    if (currentColumn == mSpanCount-1) {
        outRect.right = 0
    }
}

以上就完成了我们的ItemDecoration

4. 使用(包括移除操作)

4.1 创建适配器

这里就简单创建个适配器,如下所示:

class MyAdapter(private val data:MutableList<String>):RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    inner class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
        val tvName:TextView = itemView.findViewById(R.id.tvName)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view =LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false)
        return MyViewHolder(view)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.tvName.text = data[position]
    }
}

4.2 使用GridItemDeocation

class MainActivity : AppCompatActivity() {
    private val dataList = mutableListOf<String>()
    private val mAdapter: MyAdapter by lazy { MyAdapter(dataList) }
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
        //填充数据
        for (i in 0..20) {
            dataList.add("Item$i")
        }

		//设置适配器       
        val layoutManager = GridLayoutManager(this, 3)
        mRecyclerview.layoutManager = layoutManager
        mRecyclerview.adapter = mAdapter
        
        //使用自定义的ItemDeciration
        mRecyclerview.addItemDecoration(GridItemDecoration(20, 10))
        
    }
}

4.3 移除一项

这里我们默认测试一直删除下标position为4的那一项

class MainActivity : AppCompatActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
     btnRemove.setOnClickListener {
            position = 4
         
            if (position <= dataList.size - 1) {
                
                mAdapter.notifyItemRemoved(position)
                dataList.removeAt(position)      
                
                mAdapter.notifyItemRangeChanged(position, dataList.size - position)
            }
        }           
    }
}

注意,这里在调用notifyItemRemoved()后,还需要将数据项从list集合中去除,因为它并不会删除adapter数据集中真实的元素。

可以看到后面我们在移除后,一直是item14,数据项个数没有变化

在这里插入图片描述
最后一定要调用notifyItemRangeChanged()方法,因为notifyItemRemoved()只是移除了视图,并没有进行重新绑定,因此position还是之前的position,那样我们的GridItemDecoration内对位置的判断就会出现问题导致间隙错乱,所以需要对删除掉的位置及其后剩余项的所有itemview进行更新。

看如下所示,如果没有更新后面的部分,在移除后,后面ItemView之间的间隙就出现了问题。

在这里插入图片描述

5. 另外一种思路遇到的问题,待解决

其实一开始我使用的是思路一,每个ItemView都是四个方向有间隔,然后边界的再单独处理

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

        //总共的列数
        mSpanCount = (parent.layoutManager as GridLayoutManager).spanCount

        //当前View所属位置(从0计数)
        val itemPosition = parent.getChildAdapterPosition(view)

        //当前所处行,从0行计数
        val currentRow = itemPosition / mSpanCount
        //当前所处列,从0计数
        val currentColumn = itemPosition % mSpanCount
		//思路一
        outRect.set(
            leftAndRightSpace / 2,
            topAndBottomSpace / 2,
            leftAndRightSpace / 2,
            topAndBottomSpace / 2
        )
        //总共的个数
        val totalCount = (parent.layoutManager as GridLayoutManager).itemCount
        
        //第一行
        if (currentRow == 0) {
            outRect.top = 0
        }
        //如果总共的个数<=当前view所在行的最大个数,则代表它在最后一行
        if (totalCount <= (currentRow + 1) * mSpanCount) {            
            outRect.bottom = 0
        }
        //第0列
        if (currentColumn == 0) {
            outRect.left = 0
        }
        //最后一列
        if (currentColumn == mSpanCount - 1) {
            outRect.right = 0
        }
    }

但是在实际使用的时候发现问题,在移除到如图所示时,可以看到最后那两个ItemView的最外层布局变高了,按道理我们的ItemDecoration只是在ItemView的外侧来偏移间隙,而且我们的ItemView的布局是wrap_content,内部紫色部分是固定高度,为什么它会变高?当我将itemview布局设置为固定高度后还是会出现这样的状况,暂时还是想不通,所以就没有采用思路一。如果有大佬知道是什么问题的话,还请指明原因,感激不尽。

在这里插入图片描述

6. 注意事项

6.1 获取当前View的位置要使用getChildAdapterPosition

通过日志打印出在删除前的下标,可以看到getChildLayoutPosition()getChildAdapterPosition()获取到的每个ItemView的下标位置是相同的

getChildLayoutPosition为:0  getChildAdapterPosition为:0
getChildLayoutPosition为:1  getChildAdapterPosition为:1
getChildLayoutPosition为:2  getChildAdapterPosition为:2
getChildLayoutPosition为:3  getChildAdapterPosition为:3
getChildLayoutPosition为:4  getChildAdapterPosition为:4
getChildLayoutPosition为:5  getChildAdapterPosition为:5
getChildLayoutPosition为:6  getChildAdapterPosition为:6
getChildLayoutPosition为:7  getChildAdapterPosition为:7
getChildLayoutPosition为:8  getChildAdapterPosition为:8
getChildLayoutPosition为:9  getChildAdapterPosition为:9
getChildLayoutPosition为:10 getChildAdapterPosition为:10
getChildLayoutPosition为:11 getChildAdapterPosition为:11
getChildLayoutPosition为:12 getChildAdapterPosition为:12
getChildLayoutPosition为:13 getChildAdapterPosition为:13
getChildLayoutPosition为:14 getChildAdapterPosition为:14
getChildLayoutPosition为:15 getChildAdapterPosition为:15

删除掉下标为4的那一项后,打印日志,可以看到删除掉的那个ItemView通过getChildAdapterPosition()得到的下标为-1,而通过getChildLayoutPosition()得到的下标还是为4

getChildLayoutPosition为:4  getChildAdapterPosition为:-1
getChildLayoutPosition为:12 getChildAdapterPosition为:11
getChildLayoutPosition为:13 getChildAdapterPosition为:12
getChildLayoutPosition为:14 getChildAdapterPosition为:13
getChildLayoutPosition为:15 getChildAdapterPosition为:14
getChildLayoutPosition为:4  getChildAdapterPosition为:4
getChildLayoutPosition为:5  getChildAdapterPosition为:5
getChildLayoutPosition为:6  getChildAdapterPosition为:6
getChildLayoutPosition为:7  getChildAdapterPosition为:7
getChildLayoutPosition为:8  getChildAdapterPosition为:8
getChildLayoutPosition为:9  getChildAdapterPosition为:9
getChildLayoutPosition为:10 getChildAdapterPosition为:10

这个东西在刚开始使用的时候我还没发现异常,因为在手机上显示是没有任何问题的,可是当我在模拟器上使用时出现了如图所示的现象:在移除一项后,右下角那一项的偏移明显发生了问题。

在这里插入图片描述

这是因为:

getAdapterPosition()在数据刷新的时候提供一个-1的返回值,来告知视图其实正在重新绘制。

getLayoutPosition()不会告诉你数据正在刷新,始终会返回一个位置值。这个位置值有可能是之前的视图item位置,也有可能是刷新视图后的item位置,所以不采用这个方法。

6.2 移除数据注意要数据刷新

要进行如下固定的三步操作

mAdapter.notifyItemRemoved(position)
dataList.removeAt(position)                      
mAdapter.notifyItemRangeChanged(position, dataList.size - position)

7. 总结

总体上思路是挺简单的,还是那一套对Rect的运用,用其按照逻辑偏移出相应的距离即可。但是细节上例如ItemView的下标位置,移除数据该怎么操作还是要多注意。

以上就是全部内容,如果本文对你有帮助,请别忘记三连,如果有不恰当的地方也请提出来,下篇文章见。

8. 参考文章

android – 从RecyclerView.Adapter中删除项目后,RecyclerView.ItemDecoration不会更新

Android开发 RecyclerView.Adapter点击后的数组越界问题 与 getAdapterPosition() 与 getLayoutPosition() 的区别
关注公众号,回复 网格间距 获取文章源码
在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据提供的引用内容,可以使用RecyclerView.ItemDecoration类来设置RecyclerView网格布局的行间距。具体实现方法如下: ```java 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 } } } } ``` 在上述代码中,我们可以通过设置spacing参数来控制行间距的大小。如果includeEdge参数为true,则表示需要在每行的左右两端也设置间距,否则只在中间的格子之间设置间距。 使用方法如下: ```java int spanCount = 3; // 一行显示3个格子 int spacing = 20; // 行间距为20px boolean includeEdge = false; // 不在左右两端设置间距 recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge)); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重拾丢却的梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值