RecyclerView中用GridLayoutManager时如何正确的设置内部控件的间距Margin


最下面有完整代码(仅ItemDecoration类)和使用例,可以直接跳过去查看。

如果对你有帮助的话,可以考虑点个赞。

1. 模拟情况

我们用RecyclerView,并采用GridLayoutManager,然后columns设置为3,表示一行有三个item。
item我就用一个带边框的矩形,里面放一个String,width和height都写成固定值50dp。

在什么都不操作的情况下,最终的界面效果如图:
在这里插入图片描述
接下来我们需要让左右两边的item都贴边,然后中间的item按比例分割。上下的间距为最上面和最下面的item贴边,中间的item按照设定的值分割。

2. 踩坑

如果要设置内部间距,要调用RecyclerView的addItemDecoration方法,新建一个ItemDecoration并重写其中的getItemOffsets方法即可。

class InnerItemDecoration extends RecyclerView.ItemDecoration{

    private int spaceHorizontal; // 单位是px不是dp
    private int spaceVertical;

	// 构造方法,我在使用时往里面传了两个50,也就是水平和竖直的间隔都为50dp的意思
    public InnerItemDecoration(int spaceHorizontal, int spaceVertical, Context context){
        this.spaceHorizontal = PixelUtil.dipToPx(spaceHorizontal , context);
        this.spaceVertical = PixelUtil.dipToPx(spaceVertical , context);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    	// 这是错误代码,不能得到理想的运行结果
        int position = parent.getChildAdapterPosition(view);
        int totalCount = parent.getAdapter().getItemCount();

        outRect.top = spaceVertical/2;
        outRect.bottom = spaceVertical/2;
        outRect.left = spaceHorizontal /2;
        outRect.right = spaceHorizontal /2;

        // 最上面的一排(columns个item)
        if (position < columns)
            outRect.top = 0;
        // 最左边一排
        if (position % columns == 0)
            outRect.left = 0;
        // 最右边一排
        if (position % columns == columns - 1)
            outRect.left = 0;
        // 最下面一排
        int tem = (int) (Math.ceil((double) totalCount/columns) * columns);
        if (position >= tem-columns)
            outRect.bottom = 0;
    }
}

class PixelUtil {
    /**
     * 像素转换,dp转px
     * @param dp dp
     * @param context 上下文
     * @return px
     */
    public static int dipToPx(int dp, Context context){
        float scale = context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f);
    }
}

接下来,我传入的spaceHorizon和spaceVertical都为50,意思是水平和竖直都间隔50dp,结果如图:
在这里插入图片描述
竖直方向和设置的一样,但是水平方向就全部乱了。

3. 正确用法

那么为什么会出现这样情况呢,这是因为在竖直方向上,RecyclerView是可以无限延伸的,所以我们设置了多少dp,RecyclerView都可以无限往下扩张,所以可以正确显示。

而在水平方向上,由于最大宽度已经限死,所以在水平方向的设置上和竖直方向不同。

具体不同在哪里,由于我对源码部分的了解不清楚,我就以结果来简单说明:

  1. 忘了outRect.right这个值,这个值接下来用不上。
  2. outRect.left这个值代表着item从它原本的位置所向右移动的长度。

我这么说你可能不太理解,那么看看下面的代码和示例图,你一下就清楚了。

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    int position = parent.getChildAdapterPosition(view);
    int totalCount = parent.getAdapter().getItemCount();
    
    if (position == 2) // 将第三个item向右移动50dp
        outRect.left = spaceHorizontal;
}

在这里插入图片描述
还记得item的宽度吗,正好也是50dp。

从这个图就可以明白了,想让item在水平方向上呈现两边的item贴边,中间的item按一定比例的间隔分布,就要让它们往右边平移不同的长度。

平移多长呢?证明过程我这里先省略,直接说答案。
最终修改后的代码如下:

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    int position = parent.getChildAdapterPosition(view);
    int totalCount = parent.getAdapter().getItemCount();

	// 可以适配任意列数(只要放的下)
    double w = ((double)width - itemWidth * columns) / (columns * (columns-1));
    int p = position % columns;
    outRect.left = (int)(w*p);

}

其中width为RecyclerView的宽度,itemWidth为item的宽度,columns为使用的列数,三个值都要从外部传入。

最终的运行结果如下,省略了竖直的间距:
在这里插入图片描述

4. 平移长度的计算

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
:我在完成代码的时候并没有找到正确的公式,而是用数字一个个测出正确答案之后,才反推出的公式写进博客。所以这个公式不能保证是正确公式,可能会有错误。

5. 完整代码和使用例

5.1 完整代码

class InnerItemDecoration extends RecyclerView.ItemDecoration{

    private static final String TAG = "InnerItemDecoration";

	private int space; // 单位是px不是dp
    private int height;
    private int width;
    private int itemHeight;
    private int itemWidth;

    private int columns; // 列数


    public InnerItemDecoration(int space, int height, int width, int itemHeight, int itemWidth, int columns){
        this.space = space;
        this.columns = columns;
        this.width = width;
        this.height = height;
        this.itemHeight = itemHeight;
        this.itemWidth = itemWidth;
    }
    
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view);
        int totalCount = parent.getAdapter().getItemCount();

		outRect.top = space/2;
        outRect.bottom = space/2;

        double w = ((double)width - itemWidth * columns) / (columns * (columns-1));
        int p = position % columns;
        outRect.left = (int)(w*p);


        // 最上面的一排(columns个item)
        if (position < columns)
            outRect.top = 0;
        // 最下面一排
        int tem = (int) (Math.ceil((double) totalCount/columns) * columns);
        if (position >= tem-columns)
            outRect.bottom = 0;
    }
}

class PixelUtil {
    /**
     * 像素转换,dp转px
     * @param dp dp
     * @param context 上下文
     * @return px
     */
    public static int dipToPx(int dp, Context context){
        float scale = context.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f);
    }
}

4.2 使用例

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

		..........省略无数代码
		
        int space = 50;
        final int columns = 3;

        space = PixelUtil.dipToPx(space, this);
        final int finalSpace = space;
        recyclerView.post(new Runnable() { //用post才能正确的获取recyclerView的高度和宽度,否则只能获取到0
            @Override
            public void run() {
                int width = recyclerView.getWidth();
                int height = recyclerView.getMeasuredHeight();
                int itemWidth = 0;
                int itemHeight = 0;
                try {
                    itemWidth = recyclerView.getLayoutManager().getChildAt(0).getWidth();
                    itemHeight = recyclerView.getLayoutManager().getChildAt(0).getHeight();
                }catch (NullPointerException e){
                    e.printStackTrace();
                }

                recyclerView.addItemDecoration(new InnerItemDecoration(finalSpace, height, width,
                        itemHeight, itemWidth, columns));
            }
        });
    }
}

参考材料

RecyclerView 设置水平Item间距 - 简书
https://www.jianshu.com/p/a011145c5a1b
GridLayoutManager设置间距的相关问题 - 简书
https://www.jianshu.com/p/9777e1f4846b
在代码中正确获取View宽高的四种方法_Chrissen’s Blog-CSDN博客
https://blog.csdn.net/chrissen/article/details/81328671

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值