自定义ItemDecoration分割线的高度、颜色、偏移,看完这个你就懂了

想到分割线,原先一直是在item的布局中直接加入,在adapter中进行判断,若是最后一个子项则将分割线隐藏,感觉太小儿科了,今天来好好研究这个ItemDecoration的使用。本来周末要写这篇文章的,但奈何只有单休,一偷懒就没写,今天给补上,毕业工作半年后的第一篇笔记,一定详细,不对的地方望指教,轻点打我...

文章参考自RecyclerView 之 ItemDecoration 讲解及高级特性实践,写的很详细,仔细看后就会用了,我只是在此基础上增添了可以更改颜色、宽度、左右偏移的功能。废话不多说,咱们开始做吧。

简单的添加分割线:

一、建立工程,创建Adapter,加载布局文件

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.recyclerview)
    RecyclerView recyclerview;          
    private List<String> dataList;      //数据项
    private MyAdapter myAdapter;        //适配器

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        initData();

        myAdapter = new MyAdapter(R.layout.item_recyclerview,dataList);
        recyclerview.setAdapter(myAdapter);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerview.setLayoutManager(layoutManager);
    }
    private void initData(){
        dataList = new ArrayList<>();
        for(int i = 0;i<20;i++){
            dataList.add("子项"+i);
        }
    }
}
public class MyAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
    public MyAdapter(int layoutResId, List<String> data) {
        super(layoutResId, data);
    }
    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setText(R.id.tv_content, item);
    }
}
1

二、建立分割器

提前说明,以下内容均是在每个ItemView的顶部加入分割线,第一个不加

通过recyclerview.addItemDecoration(new SimpleItemDecoration());将以下分割器加入到RecyclerView中即可

public class SimpleItemDecoration extends RecyclerView.ItemDecoration {
    /**
     * @param outRect   全为0的rect,用来指定偏移区域
     * @param view      指RecyclerView中的Item
     * @param parent    指RecyclerView本身
     * @param state     状态
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        if (parent.getChildAdapterPosition(view) != 0) {
            //直接设置为1px
            outRect.top = 1;
        }
    }
}

加入后的效果:


2

自定义颜色偏移宽度的分割器

以上方法,通过使每个ItemView向上撑出1px距离,而RecyclerView背景为灰色,这样就显示出1px的灰色线,实现分割线功能,看到这你可能会想,这也太粗糙了,如果我想要改变分割线的宽度、颜色该怎么办,总不能每写一个RecyclerView都再写一套分割器,更改RecyclerView背景颜色吧,而且一般分割线并不占满全部宽度,有左右偏移,那该怎么实现呢?

别急,我们先了解下getItemOffsets()方法中的outRect这个参数。

3

其中的蓝色部分为我们的RecyclerView的子项ItemView,外部黄色部分为outRect,只是黄色,并不包含ItemView压的那部分,left,right,top,bottom四个参数其实就是距离itemView的四个方向的偏移量,是指偏移 ItemView 各个方向的数值,在上面的例子中,我们设置了outRect.top=1,所以每个ItemView之间有1px的空隙,所以呈现出1px灰色的分割线,分割线颜色决定于RecyclerView的背景色。

一、设置高度:

既然知道了这四个参数代表相对itemview的偏移,那么分割线的高度就好办了。

4

如图,想要红色那样高度的分割线,只需要outRect.top等于该高度就可以了。我们将该高度定义为mDividerHeight

二、设置颜色、左右偏移:

高度有了,如果我们只想绘制红色那部分矩形而不是ItemView上方的全部该怎么办?我们知道每一个View中的onDraw()方法是用来绘制组件的UI效果,所以想要颜色的话,需要我们重写ItemDecoration中的onDraw()方法。

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state);

可以看到onDraw()方法中有参数Canvas,通过它来绘制红色矩形,所以我们需要知道该矩形的四条边的位置。

float dividerTop = view.getTop() - mDividerHeight;                          //矩形顶部
float dividerBottom = view.getTop();                                       //矩形底部
float dividerLeft = parent.getPaddingLeft() + margin;                         //矩形左侧         
float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;   //矩形右侧

矩形顶部=itemview的顶部加上分割线的高度,咦?我怎么写的减号?看下图你应该就会明白

5

安卓中坐标是这样的,向下向右为正,所以红色矩形顶部位置就应该是itemView的top位置-矩形高度

偏移的话左侧加上偏移量,右侧减去偏移量即可。

c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);

这样我们要绘制的矩形就出来了,等等,我们只是画了个矩形,还没颜色呢,再来看看drawRect()中的参数,我们还缺一个mPaint画笔,通过它来设置矩形分割线颜色。

public MyDecoration() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);          //抗锯齿
        mPaint.setColor(Color.GRAY);        //默认灰色
}

通过以上步骤,带有颜色和偏移量,且具有一定高度的分割线就画好了,其实还没完,需要注意:getItemOffsets 是针对每一个 ItemView,而 onDraw 方法却是针对 RecyclerView 本身,所以在 onDraw 方法中需要遍历屏幕上可见的 ItemView,分别获取它们的位置信息,然后分别的绘制对应的分割线。

代码如下:

  @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int childCount = parent.getChildCount();    //可见ItemView个数
//因为getItemOffsets是针对每一个ItemView,而onDraw方法是针对RecyclerView本身,所以需要循环遍历来设置
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int index = parent.getChildAdapterPosition(view);
            //第一个ItemView不需要绘制
            if (index == 0) {
                continue;//跳过本次循环体中尚未执行的语句,立即进行下一次的循环条件判断
            }
          float dividerTop = view.getTop() - mDividerHeight;                    //矩形顶部    
          float dividerLeft = parent.getPaddingLeft() + margin;                 //矩形左侧       
          float dividerBottom = view.getTop();                                  //矩形底部
          float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;//矩形右侧 
          c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
        }
    }

在实际运用中,我们的分割线颜色高度等样式可能不一样,这里我们通过建造者模式来设置这些属性

//设置左右偏移(默认是设置的一样的,若需要自己更改)
    public MyDecoration setMargin(float margin) {
        this.margin = margin;
        return this;
    }
    //设置颜色
    public MyDecoration setColor(int color) {
        mPaint.setColor(color);
        return this;
    }
    //设置分割线高度
    public MyDecoration setDividerHeight(float height) {
        this.mDividerHeight = height;
        return this;
    }

这样我们就完成了分割线的自定义

完整代码如下:

public class MyDecoration extends RecyclerView.ItemDecoration {

    private float mDividerHeight = 1; //线的高度
    private Paint mPaint;           //画笔将自己做出来的分割线矩形画出颜色
    private float margin = 0;       //左右偏移量

    public MyDecoration() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);          //抗锯齿
        mPaint.setColor(Color.GRAY);        //默认颜色
    }

    //通过建造者模式来设置三个属性
    //设置左右偏移(默认是设置的一样的,若需要自己更改)
    public MyDecoration setMargin(float margin) {
        this.margin = margin;
        return this;
    }

    //设置颜色
    public MyDecoration setColor(int color) {
        mPaint.setColor(color);
        return this;
    }

    //设置分割线高度
    public MyDecoration setDividerHeight(float height) {
        this.mDividerHeight = height;
        return this;
    }

    //在这里就已经把宽度的偏移给做好了
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //第一个ItemView不需要在上面绘制分割线
        if (parent.getChildAdapterPosition(view) != 0) {
            
            outRect.top = (int) mDividerHeight;//指相对itemView顶部的偏移量
        }
    }
    //这里主要是绘制颜色的
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int childCount = parent.getChildCount();
//因为getItemOffsets是针对每一个ItemView,而onDraw方法是针对RecyclerView本身,所以需要循环遍历来设置
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int index = parent.getChildAdapterPosition(view);
            //第一个ItemView不需要绘制
            if (index == 0) {
                continue;//跳过本次循环体中尚未执行的语句,立即进行下一次的循环条件判断
            }
            float dividerTop = view.getTop() - mDividerHeight;                                  
            float dividerLeft = parent.getPaddingLeft() + margin;                               
            float dividerBottom = view.getTop();
            float dividerRight = parent.getWidth() - parent.getPaddingRight() - margin;         
            c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
        }
    }
}

使用:

MyDecoration myDecoration = new MyDecoration();

myDecoration.setColor(ContextCompat.getColor(getContext(),R.color.line_gray)).setMargin(ConvertUtils.dp2px(getContext(), 15)).setDividerHeight(ConvertUtils.dp2px(getContext(),1));

recyclerView.addItemDecoration(myDecoration);

实际使用中我们是dp单位,所以这里我使用了ConvertUtils工具类,将dp转为px

代码如下:

public static int dp2px(Context context, final float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

看下效果:

6

到这里我们想要的功能就全部完成了。(终于码完了开心!)

如果我们的RecyclerView是横向的滑动,原理类似,剩下的就交给你们了(懒得写了嘿嘿)

GitHub地址

总结一下

一共两步:

1、通过getItemOffsets()在itemView顶部撑出来一片区域

2、通过onDraw()方法来在该区域内绘制想要颜色及偏移量的分割线

其实ItemDecoration还有很多很牛逼的地方,例如实现时光轴效果,排行榜的角标,可以看看我参考的那篇文章的实现,写得很详细的,是真大佬!如果想看我写的话,就等我哪天做出来了再来更新吧(你们催我的话就那我就只好赶紧抓紧实现下咯)

第一篇文章就这么多,有错误的地方还望指出,毕竟工作半年,经验少的很......如果对你有帮助记得点赞哦。写完这篇文章感觉收获多多,其实我还定了很多文章要写,看来要通过写文章来改变自己懒的毛病了嘿嘿

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349