Android——RecyclerView入门学习之ItemDecoration
2017-04-04 12:14
411 查看
学习资料:
使用ItemDecoration为RecyclerView打造带悬停头部的分组列表深入理解 RecyclerView 系列之一:ItemDecoration
Piasy大神的每篇博客质量都很高,强烈推荐。貌似博客网站安全证书有些问题,关注了他的微博,知道随意浏览也不会出现啥问题,我是直接无视浏览器警告进行浏览
网上有很多关于
RecyclerView学习博客,之前看了几篇,但基本侧重点都是
RecyclerView.Adapter。关于
RecyclerView的侧滑删除,之前有过简单学习ItemTouchHleper实现RecyclerView侧滑删除,但对
RecyclerView了解远远不够。除了
Adapter外,
RecyclerView还有很多其他强大的地方需要学习
天才木木同学收集整理的的Android开发之一些好用的RecyclerView轮子非常好
学习计划:
ItemDecoration
LayoutManager
RecyclerView.Adapter
DiffUtil
SimpleOnItemTouchListener
SmoothScroller
ItemAnimator
1. ItemDecoration 条目装饰
是一个抽象类,顾名思义,就是用来装饰RecyclerView的子
item的,通过名字就可以知道,功能并不仅仅是添加间距绘制分割线,是用来装饰
item的。源码中的描述:
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
基本的功能是可以用来给
RecyclerView的子
item设置四边边距,以及上下左右绘制分割线。当然功能不止这些
ItemDecoration一个有6个抽象方法,有3个还废弃了,也就剩下3个需要学习
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 设置四边边距
onDraw(Canvas c, RecyclerView parent, State state) 绘制装饰
onDrawOver(Canvas c, RecyclerView parent, State state) 绘制蒙层
1.1 使用RecyclerView展示50条字符串数据
直接使用RecyclerView展示50条纯字符串数据,代码:
public class MainActivity extends AppCompatActivity { private RecyclerView rv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { rv = (RecyclerView) findViewById(R.id.rv_main_activity); //设置布局管理器 LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.VERTICAL); rv.setLayoutManager(manager); //设置ItemDecoration //适配器 RecyclerViewAdapter adapter = new RecyclerViewAdapter(rv, R.layout.item_layout); rv.setAdapter(adapter); //添加数据 addData(adapter); } /** * 添加数据 */ private void addData(RecyclerViewAdapter adapter) { List<String> listData = new ArrayList<>(); for (int i = 0; i < 50; i++) { listData.add("英勇青铜5---->"+i); } adapter.setData(listData); } @Override protected void onDestroy() { super.onDestroy(); if (null != rv) { rv.setAdapter(null); } } }
代码中没有为
RecyclerView设置
ItemDecoration,
LayoutManager为
LineatLayoutManager
子item布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/tv_item_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent" android:textAllCaps="false" android:textColor="@android:color/white" android:textSize="20sp" /> </LinearLayout>
布局也特别简单,给
TextView设置了背景色,字体是白色
运行效果:
不设置ItemDecroation
item间就没有间距,也没有任何的分割线,
TextView背景色导致整个
RecyclerView看起来都设置了背景色
下面为每个
item底部添加间距
1.2 getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 设置四边偏移量
自定义一个RVItemDecoration继承
ItemDecroation,重写
getItemOffsets()
代码:
public class RVItemDecoration extends RecyclerView.ItemDecoration { private static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;//水平方向 private static final int VERTICAL = LinearLayoutManager.VERTICAL;//垂直方向 private int orientation;//方向 private final int decoration;//边距大小 px public RVItemDecoration(@LinearLayoutCompat.OrientationMode int orientation int orientation, int decoration) { this.orientation = orientation; this.decoration = decoration; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); final int lastPosition = state.getItemCount() - 1;//整个RecyclerView最后一个item的position final int current = parent.getChildLayoutPosition(view);//获取当前要进行布局的item的position Log.e("0000", "0000---->" + current); Log.e("0000", "0000state.getItemCount()---->" + state.getItemCount()); Log.e("0000", "0000getTargetScrollPosition---->" + state.getTargetScrollPosition()); Log.e("0000", "0000state---->" + state.toString()); if (current == -1) return;//holder出现异常时,可能为-1 if (layoutManager instanceof LinearLayoutManager && !(layoutManager instanceof GridLayoutManager)) {//LinearLayoutManager if (orientation == LinearLayoutManager.VERTICAL) {//垂直 outRect.set(0, 0, 0, decoration); if (current == lastPosition) {//判断是否为最后一个item outRect.set(0, 0, 0, 0); } else { outRect.set(0, 0, 0, decoration); } } else {//水平 if (current == lastPosition) {//判断是否为最后一个item outRect.set(0, 0, 0, 0); } else { outRect.set(0, 0,decoration, 0); } } } } }
在
Acivity中,初始化
RecyclerView的时候使用:
//设置ItemDecoration rv.addItemDecoration(new RVItemDecoration(LinearLayoutManager.VERTICAL,30));
运行后效果
添加底部间距
由于是入门学习,暂时也只是针对对
LinearLayoutManager做了一点简单处理,最后1个
item不再添加底部间距。实际开发的时候考虑的就要比这复杂的多。
LinearLayoutManager大部分时候考虑
item的
position就可以,但
GridLayoutManager和
StaggeredGridLayoutManager需要考虑
行和列,情况就比较复杂。
方法中有4个参数
Rect outRect:可以简单理解为
item四边边距奉封装在这个对象中,用来设置
Item的
padding
View view: childView,就是item,可以理解为item的根
View,并不是item中的控件
RecyclerView parent:就是
RecyclerView自身
RecyclerView.State state : RecyclerView的状态,但并不包含滑动状态
1.2.1 RecyclerView.State
这个类是RecyclerView的一个静态内部类,源码中的解释:
Contains useful information about the current RecyclerView state like target scroll position or view focus. State object can also keep arbitrary data, identified by resource ids.
个人理解:
这个
State封装着
RecyclerView当前的状态,例如滑动目标的
Position或者子控件的焦点。
State对象也可以对任意的数据通过资源
id进行保存或者识别
在
State中有3个用于标记当前所处步骤的常量值:
STEP_START:布局开始
STEP_LAYOUT:布局中
STEP_ANIMATIONS:处于动画中
RecyclerView的工作流程肯定也会是
measure,layout,draw。3个值在
RecyclerView的
onMeasure()有使用,感觉是用来标识
RecyclerView在测量过程中所处于的不同时机。目前并不清楚具体的影响,
RecyclerView工作流程需要以后再进行深入学习
方法 | 作用 |
---|---|
getItemCount() | 得到整个RecyclerView中,目前的 item的数量 |
isMeasuring() | 是否正在测量 |
isPreLayout() | 是否准备进行布局 |
get(int resourceId) | 根据资源id获取 item中的控件,建议使用 R.id.* |
put(int resourceId, Object data) | 添加一个指定id映射的资源对象,建议使用 R.id.*来避免冲突 |
remove(int resourceId) | 根据使用R.id.*指定 id来删除存入的控件对象 |
getTargetScrollPosition() | 返回已经可见的滑动目标在Adapter的索引值,滑动目标由 SmoothScroller来指定 |
hasTargetScrollPosition() | 判断是否已经滑动到目标 |
willRunPredictiveAnimations() | 判断是否进行预测模式的动画在布局过程中 |
willRunSimpleAnimations() | 判断是否进行简单模式的动画在布局过程中 |
getItemCount()并不是完全等于
getAdapter.getItemCount(),在源码的注释中,关于
postion的计算,建议使用
State.getItemCount()而非立即直接通过
Adapter
State有些方法和属性涉及到其他的类,有些涉及
RecyclerView的工作过程,目前我的学习程度也不是很了解,暂时并不打算继续深挖学习下去,总觉得理解有错误,知道的同学请指出
1.3 onDraw(Canvas c, RecyclerView parent, State state)绘制装饰
这个用于绘制divider,绘制在
item的下一层,也就是说
item会盖在
divider所在层的上面
使用重写了
onDrawer()方法和
onDrawOver()的
ItemDecoration后,对
RecyclerView在绘制
item时有些影响,主要是由于绘制顺序:
mItemDecoration.onDraw()-->item.onDraw()--->mItemDecoration.onDrawOver()
onDraw()方法可以为
divier设置绘制范围,并且绘制范围可以超出在
getItemOffsets中设置的范围,但由于是在
item下面一层进行绘制,会存在
overdraw
简单使用,完整代码
public class RVItemDecoration extends RecyclerView.ItemDecoration { private final int orientation;//方向 private final int decoration;//边距大小 px private final int lineSize ;//分割线厚度 private final ColorDrawable mDivider; public RVItemDecoration(@LinearLayoutCompat.OrientationMode int orientation, int decoration, @ColorInt int color, int lineSize) { mDivider = new ColorDrawable(color); this.orientation = orientation; this.decoration = decoration; this.lineSize = lineSize; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); final int lastPosition = state.getItemCount() -1;//整个RecyclerView最后一个item的position final int current = parent.getChildLayoutPosition(view);//获取当前要进行布局的item的position if (current == -1) return; if (layoutManager instanceof LinearLayoutManager && !(layoutManager instanceof GridLayoutManager)) {//LinearLayoutManager if (orientation == LinearLayoutManager.VERTICAL) {//垂直 if (current == lastPosition) {//判断是否为最后一个item outRect.set(0, 0, 0, 0); } else { outRect.set(0, 0, 0, decoration); } } else {//水平 if (current == lastPosition) {//判断是否为最后一个item outRect.set(0, 0, 0, 0); } else { outRect.set(0, 0, decoration, 0); } } } } /** * 绘制装饰 */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if (orientation == LinearLayoutManager.VERTICAL) {//垂直 drawHorizontalLines(c, parent); } else {//水平 drawVerticalLines(c, parent); } } /** * 绘制垂直布局 水平分割线 */ private void drawHorizontalLines(Canvas c, RecyclerView parent) { // final int itemCount = parent.getChildCount()-1;//出现问题的地方 下面有解释 final int itemCount = parent.getChildCount(); Log.e("item","---->"+itemCount); final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); for (int i = 0; i < itemCount; i++) { final View child = parent.getChildAt(i); if (child == null) return; final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top +lineSize; mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } /** * 绘制水平布局 竖直的分割线 */ private void drawVerticalLines(Canvas c, RecyclerView parent) { final int itemCount = parent.getChildCount(); final int top = parent.getPaddingTop(); for (int i = 0; i < itemCount; i++) { final View child = parent.getChildAt(i); if (child == null) return; final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); final int bottom = child.getHeight() - parent.getPaddingBottom(); final int left = child.getRight() + params.rightMargin; final int right = left +lineSize; if (mDivider == null) return; mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } }
运行后的效果:
绘制底部分割线
同样这里也只是考虑了最简单的
LinerLayoutManager一种情况。使用这个方法时,注意绘制范围,尽量避免
overdraw
当间距小于分割线的宽度时,分割线绘制的厚度会保持与间距一样
1.3 onDrawOver(Canvas c, RecyclerView parent, State state) 绘制蒙层
这个方法是在item的
onDraw()方法之后进行回调,也就绘制在了最上层
简单使用,绘制一个颜色红黄渐变的圆
@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); //画笔 final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); //圆心 x 坐标 final float x = parent.getWidth() / 2; ////圆心 y 坐标 final float y = 100; //半径 final float radius = 100; //渐变着色器 坐标随意设置的 final LinearGradient shader = new LinearGradient(x-50, 0, x+100, 200, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT); paint.setShader(shader); //绘制圆 c.drawCircle(x, y, radius, paint); }
绘制一个圆
只要手指在
RecyclerView上进行滑动,
onDrawOver()方法就会被回调。但
onDrawOver()每回调一次,会将上次的绘制清除,只有最后一次的绘制会被保留。也就是说绘制的蒙层在屏幕只会有一个
2. 遇到的问题
在绘制底部分割线的时候,遇到一个问题:遇到的问题
当快速滑动时,底部会闪动,造成体验不好,如果分割线比较窄,不是很明显,分割线宽的时候就很明显
已解决 ,原因分析在下面
2.1 补充,问题修复
问题原因:问题出在
drawHorizontalLines()方法中
final int itemCount = parent.getChildCount()-1这行代码,之所以减一考虑的是为了使最后一个
item下,不用再绘制分割线。
RecyclerView.getChildCount()方法的返回值并不是
recyclerView的
Adapter中所有的
item的数量,而是当前屏幕中出现在
RecyclerView中
item的数量,一个
item只要露出一点点,就算出现,就会被包含在内。
-1就会导致
RecycelrView统计已经出现的
item时的数量少一个,就会导致滑动过程中,屏幕中最后一个
item的底部分割线不进行绘制,造成闪屏
解决办法:
不减1,就OK,修改为:
final int itemCount = parent.getChildCount();
注意:
ViewGroup的
getChildCount()方法的返回值
itemCount便是
getChildAt(int index)这个方法
index的区间上限 ,
[0,itemCount)。例如:
position示例
当前屏幕显示的是
25--到-->42,
parent.getChildCount()的返回结果
itemCount便是
18。凡是在屏幕上第一个出现的
item的
index便是
0,哪怕只是漏出一点点。在
parent.getChildAt(int index)中,
index的取值范围便是
0<= index < 18
2016.10.17 13:48
3.0 补充 官方推出DividerItemDecoration
2016.10.20Android support libraries更新了
25.0.0,新增了
BottomNavigationView,并增加了一个官方版的
DividerItemDecoration,可以学习下代码,有一些不错的细节优化
以上信息从drakeet 博客得知,果然关注大神,能够多了解信息
3. 最后
作为一个青铜5的选手,也是热爱
LOL的,也有着一颗
王者心,可
RNG,EDG全输了,止步8强,郁闷
本人很菜,有错误请指出
一个完整的练习:TitleItemDecoration
慕课有一个不错的视屏不一样的RecyclerView优雅实现复杂列表布局
相关文章推荐
- Android-UI布局---RecyclerView学习(三)匹配LinearLayoutManager的ItemDecoration
- Android学习之RecyclerView(三)-ItemDecoration(二)之瀑布流
- Android学习之RecyclerView(三)-ItemDecoration
- Android——RecyclerView入门学习之LayoutManager
- Android——RecyclerView入门学习之RecyclerView.Adapter
- Android RecyclerView简单入门学习
- Android-UI布局---RecyclerView学习(四)匹配GridLayoutManager的ItemDecoration
- Android Material Design学习之RecyclerView代替 ListView
- Android学习之RecyclerView
- android 学习之RecyclerView
- Android 5.0学习之ListView升级版RecyclerView
- Android-UI布局---RecyclerView学习(二)利用它做的相册集效果
- Android-UI布局---RecyclerView学习(六)item添加删除展现的动画效果
- Android学习之RecyclerView
- Android 5.0学习之ListView升级版RecyclerView
- Android开发学习之路-RecyclerView使用初探
- Android-UI布局---RecyclerView学习(五)长按item删除事件
- Android RecyclerView简单入门介绍
- Android学习之RecyclerView
- android 5.0新特性学习--RecyclerView