[转] 原文

自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果

字数1598 阅读302 评论2 喜欢23

1.背景

  RecyclerView是谷歌V7包下新增的控件,用来替代ListView和GridView使用的一个控件。在使用的过程中,往往需要使用到divider的效果(item之间的分割线)。而RecyclerView并不像ListView一样自带有divider的属性。而是需要用到RecyclerView.ItemDecoration这样一个类,但是ItemDecoration是一个抽象类,而且android内部并没有给它做一些效果的实现。那么就需要我们自己去继承并实现其中的方法,本文讲述的就是在GridLayoutManager和LinearLayoutManager下如何去实现ItemDecoration。至于RecyclerView.ItemDecoration的具体分析,大家可以去看看这篇文章http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/ 这里不作过多的阐述。

2.实现基本的Item的divider

2.1 创建SpacesItemDecoration

  创建一个类SpacesItemDecoration继承与RecyclerView.ItemDecoration,实现其中的onDraw和getItemOffsets方法,在这里我们的设计是左右距离相等,上下距离相等。

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {     private int leftRight;     private int topBottom;      public SpacesItemDecoration(int leftRight, int topBottom) {         this.leftRight = leftRight;         this.topBottom = topBottom;     }      @Override     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {       super.onDraw(c, parent, state);     }      @Override     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {      } }

  在这里我们主要实现的方法是onDraw和getItemOffsets,getItemOffsets重要确定divider的范围,而onDraw是对divider的具体实现。

2.2 LinearLayoutManager下divider的实现

  首先在getItemOffsets方法中需要判断当前的RecyclerView所采用的哪种LayoutManager。这里要注意的是GridLayoutManager是继承LinearLayoutManager的,所以需要先判断是否为GridLayoutManager。

private SpacesItemDecorationEntrust getEntrust(RecyclerView.LayoutManager manager) {         SpacesItemDecorationEntrust entrust = null;         //要注意这边的GridLayoutManager是继承LinearLayoutManager,所以要先判断GridLayoutManager         if (manager instanceof GridLayoutManager) {             entrust = new GridEntrust(leftRight, topBottom, mColor);         } else {//其他的都当做Linear来进行计算             entrust = new LinearEntrust(leftRight, topBottom, mColor);         }         return entrust;     }

  然后我们来看具体的实现,首先判断是VERTICAL还是HORIZONTAL。对于VERTICAL,每一个item必需的是top,left和right,但是最后一个item还需要bottom。而对于HORIZONTAL,每一个item必需的是top,left和bottom,但是最后一个item还需要right。

 @Override     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {         LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();         //竖直方向的         if (layoutManager.getOrientation() == LinearLayoutManager.VERTICAL) {             //最后一项需要 bottom             if (parent.getChildAdapterPosition(view) == layoutManager.getItemCount() - 1) {                 outRect.bottom = topBottom;             }             outRect.top = topBottom;             outRect.left = leftRight;             outRect.right = leftRight;         } else {             //最后一项需要right             if (parent.getChildAdapterPosition(view) == layoutManager.getItemCount() - 1) {                 outRect.right = leftRight;             }             outRect.top = topBottom;             outRect.left = leftRight;             outRect.bottom = topBottom;         }     }

  就这样,divider效果就实现了(当然是没有任何的颜色的)。调用方式只需要。

  int leftRight = dip2px(7);   int topBottom = dip2px(7);   rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom));

VERTICAL

HORIZONTAL.png

2.3 GridLayoutManager下divider的实现

  对于GridLayoutManager下的实现,相比LinearLayoutManager要复杂一些。首先当然是判断VERTICAL还是HORIZONTAL。对于VERTICAL,我们每一项必须的是top和left,但是对于最后一排的还需要bottom,同时对于最右侧的还需要right。根据这些,就很好写出它的一个规则了。同时HORIZONTAL下的也是类似的分析。

 @Override     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {         GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();         //判断总的数量是否可以整除         int totalCount = layoutManager.getItemCount();         int surplusCount = totalCount % layoutManager.getSpanCount();         int childPosition = parent.getChildAdapterPosition(view);         if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) {//竖直方向的             if (surplusCount == 0 && childPosition > totalCount - layoutManager.getSpanCount() - 1) {                 //后面几项需要bottom                 outRect.bottom = topBottom;             } else if (surplusCount != 0 && childPosition > totalCount - surplusCount - 1) {                 outRect.bottom = topBottom;             }             if ((childPosition + 1) % layoutManager.getSpanCount() == 0) {//被整除的需要右边                 outRect.right = leftRight;             }             outRect.top = topBottom;             outRect.left = leftRight;         } else {             if (surplusCount == 0 && childPosition > totalCount - layoutManager.getSpanCount() - 1) {                 //后面几项需要右边                 outRect.right = leftRight;             } else if (surplusCount != 0 && childPosition > totalCount - surplusCount - 1) {                 outRect.right = leftRight;             }             if ((childPosition + 1) % layoutManager.getSpanCount() == 0) {//被整除的需要下边                 outRect.bottom = topBottom;             }             outRect.top = topBottom;             outRect.left = leftRight;         }     }

  这样,GridLayoutManager的效果就实现了,调用方法跟LinearLayoutManager下是一样的。效果如下

VERTICAL

HORIZONTAL

3.实现Item的带颜色分割线的效果

3.1 LinearManager下的实现

  上述基本实现了item分割的效果,但是它没有办法设置颜色,颜色,颜色(重要的问题说三遍)。要实现颜色,首先我们得传入一个颜色色值。

//color的传入方式是resouce.getcolor protected Drawable mDivider;  public SpacesItemDecorationEntrust(int leftRight, int topBottom, int mColor) {         this.leftRight = leftRight;         this.topBottom = topBottom;         if (mColor != 0) {             mDivider = new ColorDrawable(mColor);         }     }

  有了颜色,那么我们就需要去重写onDraw方法了,我们需要去确定绘制的区域。先贴上代码

 @Override     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {         LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();         //没有子view或者没有没有颜色直接return         if (mDivider == null || layoutManager.getChildCount() == 0) {             return;         }         int left;         int right;         int top;         int bottom;         final int childCount = parent.getChildCount();         if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) {             for (int i = 0; i < childCount - 1; i++) {                 final View child = parent.getChildAt(i);                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();                 //将有颜色的分割线处于中间位置                 float center = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;                 //计算下边的                 left = layoutManager.getLeftDecorationWidth(child);                 right = parent.getWidth() - layoutManager.getLeftDecorationWidth(child);                 top = (int) (child.getBottom() + params.bottomMargin + center);                 bottom = top + topBottom;                 mDivider.setBounds(left, top, right, bottom);                 mDivider.draw(c);             }         } else {             for (int i = 0; i < childCount - 1; i++) {                 final View child = parent.getChildAt(i);                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();                 //将有颜色的分割线处于中间位置                 float center = (layoutManager.getLeftDecorationWidth(child) - leftRight) / 2;                 //计算右边的                 left = (int) (child.getRight() + params.rightMargin + center);                 right = left + leftRight;                 top = layoutManager.getTopDecorationHeight(child);                 bottom = parent.getHeight() - layoutManager.getTopDecorationHeight(child);                 mDivider.setBounds(left, top, right, bottom);                 mDivider.draw(c);             }         }     }

  RecyclerView的机制是去绘制要显示在屏幕中的view,而没有显示出来的是不会去绘制。所以这边需要使用的是layoutManager.getChildCount()而不是layoutManager.getItemCount()。对于LinearManager下来说,需要绘制分割线的区域是两个item之间,这里分为VERTICAL和HORIZONTAL。我们拿VERTICAL来进行分析,首先获取item以及它的LayoutParams,在这里计算float center = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;因为一个RecyclerView可以添加多个ItemDecoration,而且方法的调用顺序是先实现所有ItemDecoration的getItemOffsets方法,然后再去实现onDraw方法。目前没有找到办法去解决每个ItemDecoration的具体区域。所以退而求其次的将分割线绘制在所有ItemDecoration的中间区域(基本能满足一般的需求,当然可以自己修改位置满足自己的需求)。
  然后我们要去确定绘制的区域,left就是所有ItemDecoration的宽度,right就是parent的宽度减去所有ItemDecoration的宽度。top是child的底部位置然后还要加上center(center的目的是绘制在中间区域),bottom就是top加上需要绘制的高度。同理在HORIZONTAL模式下可以类似的实现。使用一个ItemDecoration的效果

 int leftRight = dip2px(2);  int topBottom = dip2px(2);  rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom,getResources().getColor(R.color.colorPrimary)));

VERTICAL

HORIZONTAL

  当然你也可以使用多个ItemDecoration

 int leftRight = dip2px(10);  int topBottom = dip2px(10);  rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom));  rv_content.addItemDecoration(new SpacesItemDecoration(dip2px(2), dip2px(2), getResources().getColor(R.color.colorPrimary)));

VERTICAL

HORIZONTAL

3.2 GridManager下的实现

  GridManager下的实现的步骤类似与LinearManager,不同的是确定绘制分割线的区域。它的分割线的区域是相邻的item之间都需要有分割线。废话不多说,先上代码。

@Override     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {         GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();         if (mDivider == null || layoutManager.getChildCount() == 0) {             return;         }         //判断总的数量是否可以整除         int totalCount = layoutManager.getItemCount();         int surplusCount = totalCount % layoutManager.getSpanCount();          int left;         int right;         int top;         int bottom;          final int childCount = parent.getChildCount();         if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) {              for (int i = 0; i < childCount; i++) {                 final View child = parent.getChildAt(i);                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();                 //得到它在总数里面的位置                 final int position = parent.getChildAdapterPosition(child);                 //将带有颜色的分割线处于中间位置                 final float centerLeft = (layoutManager.getLeftDecorationWidth(child) - leftRight) / 2;                 final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;                 //是否为最后一排                 boolean isLast = surplusCount == 0 ?                         position > totalCount - layoutManager.getSpanCount() - 1 :                         position > totalCount - surplusCount - 1;                 //画下边的,最后一排不需要画                 if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {                     //计算下边的                     left = layoutManager.getLeftDecorationWidth(child);                     right = parent.getWidth() - layoutManager.getLeftDecorationWidth(child);                     top = (int) (child.getBottom() + params.bottomMargin + centerTop);                     bottom = top + topBottom;                     mDivider.setBounds(left, top, right, bottom);                     mDivider.draw(c);                 }                 //画右边的,能被整除的不需要右边,并且当数量不足的时候最后一项不需要右边                 boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;                 boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount;                 if (first || second) {                     //计算右边的                     left = (int) (child.getRight() + params.rightMargin + centerLeft);                     right = left + leftRight;                     top = child.getTop() + params.topMargin;                     //第一排的不需要上面那一丢丢                     if (position > layoutManager.getSpanCount() - 1) {                         top -= centerTop;                     }                     bottom = child.getBottom() - params.bottomMargin;                     //最后一排的不需要最底下那一丢丢                     if (!isLast) {                         bottom += centerTop;                     }                     mDivider.setBounds(left, top, right, bottom);                     mDivider.draw(c);                 }             }         } else {              for (int i = 0; i < childCount; i++) {                 final View child = parent.getChildAt(i);                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();                 //得到它在总数里面的位置                 final int position = parent.getChildAdapterPosition(child);                 //将带有颜色的分割线处于中间位置                 final float centerLeft = (layoutManager.getLeftDecorationWidth(child) - leftRight) / 2;                 final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / 2;                 //是否为最后一排                 boolean isLast = surplusCount == 0 ?                         position > totalCount - layoutManager.getSpanCount() - 1 :                         position > totalCount - surplusCount - 1;                 //画右边的,最后一排不需要画                 if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {                     //计算右边的                     left = (int) (child.getRight() + params.rightMargin + centerLeft);                     right = left + leftRight;                     top = layoutManager.getTopDecorationHeight(child);                     bottom = parent.getHeight() - layoutManager.getTopDecorationHeight(child);                     mDivider.setBounds(left, top, right, bottom);                     mDivider.draw(c);                 }                 boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;                 boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount;                 //画下边的,能被整除的不需要下边                 if (first || second) {                     left = child.getLeft() + params.leftMargin;                     if (position > layoutManager.getSpanCount() - 1) {                         left -= centerLeft;                     }                     right = child.getRight() - params.rightMargin;                     if (!isLast) {                         right += centerLeft;                     }                     top = (int) (child.getBottom() + params.bottomMargin + centerTop);                     bottom = top + topBottom;                     mDivider.setBounds(left, top, right, bottom);                     mDivider.draw(c);                 }             }         }     }

  我们就VERTICAL的情况下来进行分析,首先横向的分割线,只需要在最左侧的item绘制出来的时候进行分割线的绘制就行了。当然最后一排是不需要的。

 if ((position + 1) % layoutManager.getSpanCount() == 1 && !isLast) {                     //计算下边的     left = layoutManager.getLeftDecorationWidth(child);     right = parent.getWidth() - layoutManager.getLeftDecorationWidth(child);     top = (int) (child.getBottom() + params.bottomMargin + centerTop);     bottom = top + topBottom;     mDivider.setBounds(left, top, right, bottom);      mDivider.draw(c); }

  水平的分割线的计算方式类似与LinearLayoutManager下的计算方式。这里不过多阐述。而竖直方向的会有一些区别。由于GridLayoutManager下,item的数量不一定能够刚好整除每排的数量。所以这边的绘制区域是根据每个item来进行确定的。
  能被整除的或者当数量不足的时候最后一项不需要竖直的分割线。同时要注意补齐centerTop(分割线绘制在中间区域的位置)。

 //画右边的,能被整除的不需要右边,并且当数量不足的时候最后一项不需要右边  boolean first = totalCount > layoutManager.getSpanCount() && (position + 1) % layoutManager.getSpanCount() != 0;  boolean second = totalCount < layoutManager.getSpanCount() && position + 1 != totalCount; if (first || second) { //计算右边的    left = (int) (child.getRight() + params.rightMargin + centerLeft);    right = left + leftRight;    top = child.getTop() + params.topMargin;    //第一排的不需要上面那一丢丢     if (position > layoutManager.getSpanCount() - 1) {           top -= centerTop;        }     bottom = child.getBottom() - params.bottomMargin;      //最后一排的不需要最底下那一丢丢    if (!isLast) {        bottom += centerTop;        }    mDivider.setBounds(left, top, right, bottom);    mDivider.draw(c); }

  HORIZONTAL下的情况可以进行类似的分析,代码的调用方式跟LinearLayoutManager下是一样的。

VERTICAL

HORIZONTAL

4 最后

  至此,RecyclerView的divider效果已经基本实现了。当然,你可以在这基础上进行修改,满足自己的一些需求。欢迎大家一起相互交流。代码已经上传至github https://github.com/hzl123456/SpacesItemDecoration
(ps:在实际的使用过程中,当对RecyclerView的item进行增加和删除的操作是,会使ItemDecoration的分割区域计算错误。原因是在添加和删除操作的时候,只会计算更新的部分区域的OutRect,导致出现问题,这个时候我们只需要在添加和删除操作之后调用RecyclerView的invalidateItemDecorations()方法就可以解决问题了)

转载于:https://www.cnblogs.com/wxmdevelop/p/6201961.html

自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果相关推荐

  1. 安卓作业----慕课移动应用开发作业13之使用自定义RecyclerView.ItemDecoration实现列表悬浮顶部效果

    此博客通过RecyclerView.TextView等进行界面布局,使用自定义RecyclerView.Adapter.RecyclerViewAdapter.ViewHolder以及自定义Recyc ...

  2. Android RecyclerView ItemDecoration 分割线

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/111302734 本文出自[赵彦军的博客] 文章目录 如何设置分割线 Divider ...

  3. 【RecyclerView】 六、RecyclerView.ItemDecoration 条目装饰 ( 简介 | onDraw | onDrawOver | getItemOffsets )

    文章目录 一.RecyclerView.ItemDecoration 简介 1.onDraw() 方法 2.onDrawOver () 方法 3.getItemOffsets () 方法 二.Recy ...

  4. 【RecyclerView】 七、RecyclerView.ItemDecoration 条目装饰 ( getItemOffsets 边距设置 )

    文章目录 一.RecyclerView.ItemDecoration 方法说明 三.getItemOffsets 设置要点 四.完整代码示例 五.RecyclerView 相关资料 一.Recycle ...

  5. 自定义RecyclerView动画——实现remove飞出效果

    目录 前言 创建ItemAnimator 处理重叠 总结 源码 前言 我们经常会遇到在一个list中删除一条数据,这时候一般会有一个飞出的动画效果,如下图: 在RecyclerView中可以通过set ...

  6. android弧形左右滑动空间,自定义LayoutManager 实现弧形以及滑动放大效果RecyclerView...

    我们都知道RecyclerView可以通过将LayoutManager设置为StaggeredGridLayoutManager来实现瀑布流的效果.默认的还有LinearLayoutManager用于 ...

  7. 通过一个简单例子理解 RecyclerView.ItemDecoration

    一.前言 RecyclerView 是从5.0推出的 MD 风格的控件.RecyclerView 之前有 ListView.GridView,但是功能很有限,例如 ListView 只能实现垂直方向上 ...

  8. Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

    本文是Android导航分组列表系列上,因时间和篇幅原因分上下,最终上下合璧,完整版效果如下:   上部残卷效果如下:两个ItemDecoration,一个实现悬停头部分组列表功能,一个实现分割线(官 ...

  9. 【Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/52355199 本文 ...

最新文章

  1. spring boot +QQmail
  2. Visual Studio 2008 Express版本下载
  3. Linux IPC POSIX 消息队列
  4. Asp.net常用技巧
  5. ubuntu16.04的sudo设置为免密码(注意这里不是su免密码)
  6. 关于我的知识星球服务
  7. Google Chrome浏览器可能在您不知情的情况下破坏了您的测试
  8. 微信 for Mac 3.1.0 测试版发布(附安装包),新增「发朋友圈」功能
  9. GoldenGate 之 Bounded Recovery说明
  10. 拼多多组织架构大变动:黄峥不再担任公司CEO
  11. Python socket编程模拟最简单的HTTP响应
  12. Asp.Net WebAPI传递json对象、后台手动接收参数
  13. 单片机 WIFI模块发送AT指令收不到回复问题
  14. USACO Raucous Rockers——dp
  15. 机器翻译之人工智能方法
  16. golang使用gorm出现reflect.Value.Addr of unaddressable value [recovered]
  17. SAP 采购定价日期控制
  18. css 图片波浪效果
  19. setjmp and longjmp
  20. 飞机上可以带充电宝吗?充电宝放书包里面能不能带到飞机上?

热门文章

  1. POJ2750 Potted Flower (线段树+动态规划)
  2. js获取日期选择器值html,利用Query+bootstrap和js两种方式实现日期选择器
  3. 为确保网络中不同计算机,在计算机网络中,为确保网络中不同计算机之间能正确地传送和接收数据,它们必须遵循一组共同的规则和约定。这些规则、约定或标准通常被称为____。...
  4. linux点阵数字图案,LED8X8点阵显示数字0-9数字
  5. java session机制_如何学习Session的机制使用
  6. 预算分配Budget Allocation:Morphl-AI的营销科学解决方案(一)
  7. Python中的闭包总结
  8. Micropython教程之TPYBoard DIY金属探测仪实例演示(萝卜学科编程教育)
  9. Thread.currentThread().getContextClassLoader()和Class.getClassLoader()区别
  10. 弹出ifame页面(jquery.reveal.js)