一.背景

远古时代,GridView 和 ListView 可以直接使用其自带的 api 设置 item 之间的分割线,通过修改分割线的粗细和颜色等可以轻松实现分割线和间距类的效果,还有的直接通过在 item 的布局里设置 margin 或 padding 来实现,后来有了 RecyclerVIew,但是却没提供设置分割线的 api,不过提供了一个功能丰富的 ItemDecoration 类,这个类首先能实现的最简单的功能就是分割线了,这里先来记录一下关键方法 getItemOffsets 相关用法,里面的 outRect 容易理解错。

二.方案

tv 端应用里有很多这种间距平分的页面,如果不做任何处理,一般你会把 item 宽高设置固定值,但是因为没控制间距,总会出现间距不平分,item 会往左靠,最右边可能会有一部分空白,各 item 间左右会有间距,但是并不是你代码设置的,想修改也没地方改,这样肯定达不到设计图上的效果。

推荐方式(设计图尺寸是1920*1080):
item 宽度设置 match_parent,根据UI图上的 item 间距尺寸和距离屏幕边缘的尺寸来控制页面的尺寸,例如上图中(中间那部分黄色背景的RecyclerView)每个 item 之间的间距是48px,item 宽高分别为 250px,331px,距离父元素 RecyclerView 左右 90px,上 24px,下 36px,拿到这种设计图,可以这样来搞:
1.不用关心 item 的宽,只需要关心高和间距,以及距离父元素的距离;
2.通过 ItemDecoration 来设置 item 之间的间距;
3.不用 ItemDecoration 设置 item 距离父元素 RecyclerView 的距离,而是通过父元素自己设置 padding 来控制;
这样可以精细的控制 UI 效果,例如设置完间距后,间距的总宽度是 48*5+90*2 = 420,剩下的会因 item 宽度设置 match_parent 平分成 6 份 (1920-420)/6 = 250px,正好和设计图中完全对上。

三.实现

3.1先按方案中第一步的思路来设置 item 布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#c2b8b8"android:focusable="true"><ImageViewandroid:id="@+id/image"android:layout_width="match_parent"android:layout_height="@dimen/px_331"android:src="@mipmap/ic_img"android:clipChildren="false"android:scaleType="fitXY"/><TextViewandroid:id="@+id/textview"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/image"android:layout_marginEnd="@dimen/px_12"android:layout_marginStart="@dimen/px_12"android:layout_marginTop="@dimen/px_2"android:textColor="#333333"android:textSize="@dimen/px_31"/>
</RelativeLayout>

3.2自定义 ItemDecoration 实现间距:
后面全拿垂直的风格布局来说,这里有个容易理解错的地方,getItemOffsets 方法里的 outRect 可以理解成是包在 item 布局外面的一个参与 item 测量的框,不能理解成是每个 item 之间的只用来占位的一个 rect,如果理解错误会导致这样一种思路,如果是第一列,就设置 outRect.left=0,其它全部只设置 outRect.left=mSpace,outRect.right=0,或者全设置右边等于mSpace,左边等于0,最后一列右边等于0,程序运行起来会出现第一列的 item 的宽度都比其它的 item 宽的情况:

 @Overridepublic void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent,@NonNull RecyclerView.State state) {...int position = parent.getChildAdapterPosition(view);int column = position % spanCount;if (column == 0) { // 如果是第一列,不设置左边间距outRect.left = 0;} else { // 其它列全设置左边的间距outRect.left = mSpace;}}


上图效果就能很好的说明了 outRect 是包在 item 外的一层偏移,第一列 item 尺寸问题可在源码中找到原因:

    Rect getItemDecorInsetsForChild(View child) {...final Rect insets = lp.mDecorInsets;insets.set(0, 0, 0, 0);final int decorCount = mItemDecorations.size();for (int i = 0; i < decorCount; i++) {mTempRect.set(0, 0, 0, 0);mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);insets.left += mTempRect.left;insets.top += mTempRect.top;insets.right += mTempRect.right;insets.bottom += mTempRect.bottom;}lp.mInsetsDirty = false;return insets;}
...public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);widthUsed += insets.left + insets.right;heightUsed += insets.top + insets.bottom;final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),getPaddingLeft() + getPaddingRight()+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,canScrollHorizontally());final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),getPaddingTop() + getPaddingBottom()+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,canScrollVertically());if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {child.measure(widthSpec, heightSpec);}}

上面代码 29 行,获取自定义 ItemDecoration 中设置的 outRect,然后传到33行的 getChildMeasureSpec() 方法中:

public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,int childDimension, boolean canScroll) {int size = Math.max(0, parentSize - padding);int resultSize = 0;int resultMode = 0;...if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {resultSize = size;resultMode = parentMode;} else if (childDimension == LayoutParams.WRAP_CONTENT) {resultSize = size;if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {resultMode = MeasureSpec.AT_MOST;} else {resultMode = MeasureSpec.UNSPECIFIED;}}...//noinspection WrongConstantreturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

从第三行看到 size 赋的是 parentSize 减去传进来的 padding 来作为 item 的宽度不设固定值情况下的最大范围,由于第一列的 outRect 未设置值,所以这一列的 item 可分配的最大值比其它列多出了 outRect.left 的值,就造成了图中的错误效果。

正确的方式:
实际的结构如下图:

图中 insets 就是 getItemOffsets 方法中的 outRect;
上面从源码中也看到了这个 outRect 和 item 尺寸是有关系的,那么就可以按这个原则来搞,让所有的 outRect 的尺寸保持一致,例如所有 item的 outRect.left + outRect.right相等,outRect.top + outRect.bottom 相等;

按这个思路再想让所有的 item 间距平分,就需要分析计算一下了,下面都只拿横向平分来说;

计算这些值需要分两种情况,第一种是第一列和最后一列离两边的无边距,第二种是有边距,这里不考虑第二种情况,个人觉得这个边距由RecyclerView 设置 padding 或 margin 来控制更合适,使用 outRect 是可以控制,但是就感觉像是设置一个垂直LinearLayout 里的每一个子布局的 margin_left 和 margin_right 一样,为何不统一设置到 LinearLayout 的 padding_left 和 padding_right 上呢。

下面来看无边距的情况,按设计图尺寸,space = 48px,那么各 item 的 outRect.left + ourRect.right = 48 * 5 / 6 = 40px:

position=0:left 0,right 40
position=1:left 8,right 32
position=2:left 16,right 24

position=5:left 40,right 0

每个 item 的 outRect 除了第一列的左边,其它的左边等于 40px 减去前一个 item 的 outRect 的右边,右边等于 40px 减去自己的左边,就可以写成下面这样:

int position = parent.getChildAdapterPosition(view);
int column = position % spanCount;int totalSpace = mSpace * (spanCount - 1);// 48*5=240
int itemSpace = totalSpace / spanCount;// 240/6=40
int the = mSpace - itemSpace;// 等差数列的值 48 - 40 = 8outRect.left = column*the;// 0, 8, 16, 24
outRect.right = itemSpace-column*the;

然后把上面的计算一顿合并同类项,最终变成如下代码:

package com.lcp.tvtest;import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;/*** Created by Aislli on 2019/10/24 0024.*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {private int mSpace = 20;public GridItemDecoration(int space) {this.mSpace = space;}@Overridepublic void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {int spanCount;if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) {StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) parent.getLayoutManager();spanCount = manager.getSpanCount();} else if (parent.getLayoutManager() instanceof GridLayoutManager) {GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();spanCount = manager.getSpanCount();} else {spanCount = 1;}int position = parent.getChildAdapterPosition(view);int column = position % spanCount;outRect.left = column * mSpace / spanCount;outRect.right = mSpace - (column + 1) * mSpace / spanCount;if (position >= spanCount) {outRect.top = mSpace;}}
}

再设置 parent 的 padding 达到设置图中四边间距的效果

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"tools:context=".ListActivity"><android.support.v7.widget.RecyclerViewandroid:id="@+id/recycler"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#66e6e675"android:clipToPadding="false"android:paddingBottom="@dimen/px_36"android:paddingEnd="@dimen/px_90"android:paddingStart="@dimen/px_90"android:paddingTop="@dimen/px_24"><!--android:padding="@dimen/px_48"--></android.support.v7.widget.RecyclerView>
</RelativeLayout>

Activity中的测试代码:

public class ListActivity extends AppCompatActivity {private RecyclerView recyclerView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_list);recyclerView = findViewById(R.id.recycler);ArrayList<String> strings = new ArrayList<>();for (int i = 0; i < 12; i++) {strings.add("i:" + i);}GridItemDecoration gridItemDecoration = new GridItemDecoration((int) getResources().getDimension(R.dimen.px_48));GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 6);ListAdapter listAdapter = new ListAdapter(strings);recyclerView.setLayoutManager(gridLayoutManager);recyclerView.addItemDecoration(gridItemDecoration);recyclerView.setAdapter(listAdapter);}
}

这里就实现文中开始的第二张效果图样式了,去掉 RecyclerView 的 padding 就是第一张图的样式,其它方向的间距设置,原理和上面一致。

ItemDecoration实现等分间距相关推荐

  1. android n等分 layout,RecyclerView GridLayoutManager 等分间距

    RecyclerView 表格实现 RecyclerView 配合GridLayoutManager 可以实现类似表格的样式,为了实现均分,adapter 的布局宽度改为匹配父元素,即 android ...

  2. android item间距,RecyclerView 设置item之间的间距

    RecyclerView没有可以直接设置间距的属性,但看了源码之后可以发现RecyclerView有个内部类ItemDecoration,可以用ItemDecoration来装饰一个item,所以继承 ...

  3. 跟驰理论 matlab,[自然科学]第4章 跟驰理论.ppt

    [自然科学]第4章 跟驰理论 Ch4 跟驰理论 * 式中:uT-观测总时段的末速度 u0-观测总时段的初速度 Δu-速度的等分间距,Δui= niΔu 此式适用于坐标纸对速度和加速度观测值进行绘图计算 ...

  4. sketch插件 android,30个值得拥有的sketch插件(4)完结篇

    21 选择相似图层-Select Similar Layers 如果你常在sketch里面绘制适量插画,那这个插件绝对能够弥补你内心的空洞(哈哈).这个插件可以自己抓去具有相匹配填充颜色.描边颜色.描 ...

  5. 跟驰理论 matlab,第5章-跟驰理论ppt课件

    <第5章-跟驰理论ppt课件>由会员分享,可在线阅读,更多相关<第5章-跟驰理论ppt课件(71页珍藏版)>请在人人文库网上搜索. 1.Ch5 跟驰理论,1,第五章车辆跟驰理论 ...

  6. 跟驰理论 matlab,[经济学]第5章 跟驰理论.ppt

    Ch5 跟驰理论,1,第五章 车辆跟驰理论,Ch5 跟驰理论,2,本章主要内容,§1 线性跟驰模型的建立 §2 稳定性分析 §3 稳态流分析 §4 加速度干扰,Ch5 跟驰理论,3,教学目的:掌握线性 ...

  7. CSS3 弹性盒子和常用标签

    CSS3弹性盒子 学习目标 掌握CSS3弹性盒子的使用方法 掌握CSS3弹性盒子的水平分布方法 重点 掌握CSS3弹性盒子的垂直分布的方法 重点 掌握CSS3弹性盒子排序的用法 CSS3 弹性盒子属性 ...

  8. 10条SKETCH的秘诀,为你提高工作的效率

    "周一好!".优狐哥再分享10个SKETCH的小秘诀.此篇主要写一些常用到的快捷键和隐形功能.有些功能和PS是相通的,希望各位UIer在两个工具间互相打通学习 一   元件设置-排 ...

  9. 计算机绘图的最小单位,[工学]第2章 计算机绘图基础简洁版.ppt

    [工学]第2章 计算机绘图基础简洁版 * * 2.三角形面积累加法 考虑矢量的方向,取顺时针的顺序,即可得到由顶点坐标计算多边形的面积公式: 当i=n时, . * * 引申到闭合曲线求面积 由多边形面 ...

最新文章

  1. BZOJ3682 : Phorni
  2. 2020 云原生技术 7 大领域趋势全预测
  3. 文件内容批量修改工具
  4. 【译】x86程序员手册06 - 2.4指令格式
  5. 海量数据处理(一) :位图与布隆过滤器的概念以及实现
  6. 命令行参数怎么输入_太好用了!谷歌开源的命令行接口工具fire
  7. ICWAI和ICWA的完整形式是什么?
  8. 用千元左右的手机你会感到没面子吗?网友回答令人赞叹
  9. elk如何同步到es 方案靠谱吗_架构设计:微服务架构如何划分?这6个标准原则让你一目了然...
  10. 如何利用即时通讯工具进行营销
  11. COM组件注册DLL不成功
  12. qq登录界面句柄_天天玩QQ!知道登录界面那两个人是谁吗?网友:不是情侣?...
  13. 常用的表情和含义 各种笑脸;-)
  14. html5获取我的位置并在百度地图上显示
  15. 数据分析和数据挖掘的概念和理念
  16. 【如何学习CAN总线测试】——OSEK网络管理测试
  17. [小白教程]动态调试工具Ollydbg的简单使用
  18. 啄木鸟Python社区
  19. 基于Cat混沌与高斯变异的改进灰狼优化算法-附代码
  20. excel快速入门-学习笔记

热门文章

  1. 网络小说《赘婿》中涉及的地名及其地图
  2. 实训——智慧养老之画布1
  3. 新年新迹象,商场购物中心“牛”年如何装扮设计
  4. 科技感大屏边框框架 datav
  5. 诛仙手游服务器维护中,诛仙手游2021年7月22日更新维护公告
  6. 计算机无显示错误代码,电脑出现错误代码0xc0000001怎么办 电脑出现错误代码0xc0000001如何解决...
  7. ARP攻击原理及解决方法,很实用
  8. 大学生创业交流会计算机二级,我校举行第七届中国国际“互联网+”大学生创新创业大赛交流研讨会...
  9. java 代码实现163邮箱发送邮件到QQ邮箱
  10. 字符串的charCode