自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果
[转] 原文
自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果
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() - ) { outRect.bottom = topBottom; } outRect.top = topBottom; outRect.left = leftRight; outRect.right = leftRight; } else { //最后一项需要right if (parent.getChildAdapterPosition(view) == layoutManager.getItemCount() - ) { outRect.right = leftRight; } outRect.top = topBottom; outRect.left = leftRight; outRect.bottom = topBottom; } }
就这样,divider效果就实现了(当然是没有任何的颜色的)。调用方式只需要。
int leftRight = dip2px(); int topBottom = dip2px(); rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom));
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 == && childPosition > totalCount - layoutManager.getSpanCount() - ) { //后面几项需要bottom outRect.bottom = topBottom; } else if (surplusCount != && childPosition > totalCount - surplusCount - ) { outRect.bottom = topBottom; } if ((childPosition + ) % layoutManager.getSpanCount() == ) {//被整除的需要右边 outRect.right = leftRight; } outRect.top = topBottom; outRect.left = leftRight; } else { if (surplusCount == && childPosition > totalCount - layoutManager.getSpanCount() - ) { //后面几项需要右边 outRect.right = leftRight; } else if (surplusCount != && childPosition > totalCount - surplusCount - ) { outRect.right = leftRight; } if ((childPosition + ) % layoutManager.getSpanCount() == ) {//被整除的需要下边 outRect.bottom = topBottom; } outRect.top = topBottom; outRect.left = leftRight; } }
这样,GridLayoutManager的效果就实现了,调用方法跟LinearLayoutManager下是一样的。效果如下
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 != ) { 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() == ) { return; } int left; int right; int top; int bottom; final int childCount = parent.getChildCount(); if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) { for (int i = ; i < childCount - ; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); //将有颜色的分割线处于中间位置 float center = (layoutManager.getTopDecorationHeight(child) - topBottom) / ; //计算下边的 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 = ; i < childCount - ; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); //将有颜色的分割线处于中间位置 float center = (layoutManager.getLeftDecorationWidth(child) - leftRight) / ; //计算右边的 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(); int topBottom = dip2px(); rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom,getResources().getColor(R.color.colorPrimary)));
当然你也可以使用多个ItemDecoration
int leftRight = dip2px(); int topBottom = dip2px(); rv_content.addItemDecoration(new SpacesItemDecoration(leftRight, topBottom)); rv_content.addItemDecoration(new SpacesItemDecoration(dip2px(), dip2px(), getResources().getColor(R.color.colorPrimary)));
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() == ) { 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 = ; 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) / ; final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / ; //是否为最后一排 boolean isLast = surplusCount == ? position > totalCount - layoutManager.getSpanCount() - : position > totalCount - surplusCount - ; //画下边的,最后一排不需要画 if ((position + ) % layoutManager.getSpanCount() == && !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 + ) % layoutManager.getSpanCount() != ; boolean second = totalCount < layoutManager.getSpanCount() && position + != totalCount; if (first || second) { //计算右边的 left = (int) (child.getRight() + params.rightMargin + centerLeft); right = left + leftRight; top = child.getTop() + params.topMargin; //第一排的不需要上面那一丢丢 if (position > layoutManager.getSpanCount() - ) { top -= centerTop; } bottom = child.getBottom() - params.bottomMargin; //最后一排的不需要最底下那一丢丢 if (!isLast) { bottom += centerTop; } mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } } else { for (int i = ; 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) / ; final float centerTop = (layoutManager.getTopDecorationHeight(child) - topBottom) / ; //是否为最后一排 boolean isLast = surplusCount == ? position > totalCount - layoutManager.getSpanCount() - : position > totalCount - surplusCount - ; //画右边的,最后一排不需要画 if ((position + ) % layoutManager.getSpanCount() == && !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 + ) % layoutManager.getSpanCount() != ; boolean second = totalCount < layoutManager.getSpanCount() && position + != totalCount; //画下边的,能被整除的不需要下边 if (first || second) { left = child.getLeft() + params.leftMargin; if (position > layoutManager.getSpanCount() - ) { 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 + ) % layoutManager.getSpanCount() == && !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 + ) % layoutManager.getSpanCount() != ; boolean second = totalCount < layoutManager.getSpanCount() && position + != totalCount; if (first || second) { //计算右边的 left = (int) (child.getRight() + params.rightMargin + centerLeft); right = left + leftRight; top = child.getTop() + params.topMargin; //第一排的不需要上面那一丢丢 if (position > layoutManager.getSpanCount() - ) { top -= centerTop; } bottom = child.getBottom() - params.bottomMargin; //最后一排的不需要最底下那一丢丢 if (!isLast) { bottom += centerTop; } mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); }
HORIZONTAL下的情况可以进行类似的分析,代码的调用方式跟LinearLayoutManager下是一样的。
4 最后
至此,RecyclerView的divider效果已经基本实现了。当然,你可以在这基础上进行修改,满足自己的一些需求。欢迎大家一起相互交流。代码已经上传至github https://github.com/hzl123456/SpacesItemDecoration
(ps:在实际的使用过程中,当对RecyclerView的item进行增加和删除的操作是,会使ItemDecoration的分割区域计算错误。原因是在添加和删除操作的时候,只会计算更新的部分区域的OutRect,导致出现问题,这个时候我们只需要在添加和删除操作之后调用RecyclerView的invalidateItemDecorations()方法就可以解决问题了)
自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果的更多相关文章
- ViewPagerWithRecyclerDemo【RecyclerView+ViewPager实现类似TabLayout+ViewPager效果】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 使用RecyclerView+ViewPager实现类似TabLayout+ViewPager效果. 效果图 使用步骤 一.项目组织 ...
- RecyclerView.ItemDecoration
decoration 英文意思: 英[ˌdekəˈreɪʃn] 美[ˌdɛkəˈreʃən] n. 装饰品; 装饰,装潢; 装饰图案,装饰风格; 奖章; [例句]The decoration and ...
- RecyclerView.ItemDecoration 间隔线
内容已更新到:https://www.cnblogs.com/baiqiantao/p/19762fb101659e8f4c1cea53e7acb446.html 目录一个通用分割线ItemDecor ...
- IT蓝豹--RecyclerView加载不同view实现效果
本项目由开发者:黄洞洞精心为初学者编辑RecyclerView的使用方法. RecyclerView加载不同view实现效果,支持加载多个view,并且支持用volley获取数据, 项目主要介绍: 初 ...
- 钉钉自定义机器人 发送文本 换行 \n无效果
今天用php做钉钉自定义机器人 发送文本 换行 \n无效果,原来是我一直用单引号作为定义字符串,换成双引号就ok了.
- vue2 自定义全局组件(Loading加载效果)
vue2 自定义全局组件(Loading加载效果) github地址: https://github.com/ccyinghua/custom-global-component 一.构建项目 vue ...
- RecyclerView如何消除底部的分割线
最近遇到一个问题,用RecyclerView显示数据,纵向列表显示,添加默认分割线. 问题是:底部也会显示分割线,这很影响美观. 怎么解决这个问题呢?我想了很多办法,毫无头绪... 最后, ...
- RecyclerView 与 ItemTouchHelper 实现拖拽效果
截图 需求 App 开发新的需求,要求 RecyclerView 实现的九宫格样式可以拖拽,松手以后变更位置,类似于手机桌面拖动 app 变更位置. 分析 经过搜索,发现 support 中带有一个类 ...
- Android用RecyclerView实现的二维Excel效果组件
excelPanel 二维RecyclerView.不仅可以加载历史数据,而且可以加载未来的数据. 包括在您的项目中 excelPanel 二维RecyclerView.不仅可以加载历史数据,而且 ...
随机推荐
- DNS解析过程和域名收敛、域名发散、SPDY应用
前段时间项目要做域名收敛,糊里糊涂的完成了,好多原理不清晰,现在整理搜集下知识点. 域名收敛的目的是什么?简单来说就是域名解析慢.那为什么解析慢?且听下文慢慢道来. 什么是DNS? DNS( Doma ...
- [Linux]系统调用理解(2)
本文介绍了Linux下的进程概念,并着重讲解了与Linux进程管理相关的4个重要系统调用getpid,fork,exit和_exit,辅助一些例程说明了它们的特点和使用方法. 关于进程的一些必要知识 ...
- System Error Codes
很明显,以下的文字来自微软MSDN 链接http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx M ...
- js指定分隔符连接数组元素join()
指定分隔符连接数组元素join() join()方法用于把数组中的所有元素放入一个字符串.元素是通过指定的分隔符进行分隔的. 语法: arrayObject.join(分隔符) 参数说明: 注意:返回 ...
- .NET LINQ 联接运算
联接运算 将两个数据源“联接”就是将一个数据源中的对象与另一个数据源中共享某个通用特性的对象关联起来. 当查询所面向的数据源相互之间具有无法直接领会的关系时,联接就成为一项重要的运 ...
- 简单粗暴将sqlserver表以及数据迁移到oracle
1.利用sqlserver工具查询出表中所有数据,全选,右键 -连同标题一起复制. 2.将数据保存到excel文件,数据 时间类型(yyyy--MM--dd HH:mm:ss)最好处理一下,需要将id ...
- sql 删除表中某字段的重复数据
重复字段:BarCode SELECT * FROM dbo.AssetBarCode WHERE BarCode IN (SELECT BarCode FROM dbo.AssetBarCode G ...
- poj 1651 Multiplication Puzzle
题目链接:http://poj.org/problem?id=1651 思路:除了头尾两个数不能取之外,要求把所有的数取完,每取一个数都要花费这个数与相邻两个数乘积的代价,需要这个代价是最小的 用dp ...
- 给Android系统安装busybox
转自:http://blog.csdn.net/lxgwm2008/article/details/38925051 busybox号称Linux平台的瑞士军刀,它集成了100多个最常用的Linux命 ...
- 关于activity的生命周期的随笔
在activity的生命周期中,我总是容易混淆,onstart和on resume ,on pause和onstop 原来这个都是一对的. onstart 对应 onstop ,意义在于使页面显示出来 ...