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都可以无限往下扩张,所以可以正确显示。
而在水平方向上,由于最大宽度已经限死,所以在水平方向的设置上和竖直方向不同。
具体不同在哪里,由于我对源码部分的了解不清楚,我就以结果来简单说明:
- 忘了outRect.right这个值,这个值接下来用不上。
- 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