自定义View 之 RecyclerView.ItemDecoration

栏目: IOS · Android · 发布时间: 5年前

内容简介:年底了,赶项目,于是忙了一个月业务,忙了一个月没有营养的东西。为啥说没营养,因为就是很简简单单的展示,没有啥东西可写。我差点要搬出11月份的腾讯面试经历了,就在这时我给自己挖了个坑。 我本人的自定义View的能力是很差的,之前也没有写过,一直都用android自带或者github上写好的东西。所以这个坑挖的还是值。之前我们有一个报警消息展示界面,是这样的;有个功能是这样的,红点显示未读,点击一下就能消灭红点。

年底了,赶项目,于是忙了一个月业务,忙了一个月没有营养的东西。为啥说没营养,因为就是很简简单单的展示,没有啥东西可写。我差点要搬出11月份的腾讯面试经历了,就在这时我给自己挖了个坑。 我本人的自定义View的能力是很差的,之前也没有写过,一直都用android自带或者github上写好的东西。所以这个坑挖的还是值。

坑的来源

之前我们有一个报警消息展示界面,是这样的;

自定义View 之 RecyclerView.ItemDecoration

有个功能是这样的,红点显示未读,点击一下就能消灭红点。

问题就来了:后台表示不能提供是否已读的状态,我表示我这边本地存储报警消息状态并不合理。然后我就骚了一波,说接口不用改,我自己这边处理。其实我想的就是 仿微信朋友圈里面的文字分割线“以下是已读内容” ,这样就不用处理每一条消息了,哈哈哈哈哈哈哈。

两种方案与思路

一开始我想到了两种方案:

A :类似于添加head,footer,写个新的viewholder进去。

优点:网文较多;布局复杂的情况下比较好管理修改;

缺点:修改的东西比较多。

B:自定义RecyclerView.ItemDecoration

优点:修改东西较少;自定义的优点;

缺点:自定义的缺点;\

思路:无论是A方案还是B方案,我都需要知道这个分割线的position,在这里我是将上一次请求到的数据中最新一条的createTime存入SP中,我将通过这个值去对比每一次请求下来的数据集的createTime,当他相等时,这个item的position,就应该是分割线的position。(这里选择对比条件是一定要选择一个唯一,不重复的)。

在A方案中,adapter得到list后,可以找到分割线的position,然后在此position返回TextDivider的Viewholder。麻烦在于position之后的数据,TextDivider之后的每一个数据的position都必须+1。每一次都得重新去算。每次滑动都会算,这里处理起来可能不是很方便,而且会增加许多属性帮助确定真正的position。弃之

所以我选择了B方案。也是对自己个机会去学习自定义view。

“懒惰是第一生产力” —— 沃·兹基朔德

RecyclerView.ItemDecoration

public class TextDivider extends RecyclerView.ItemDecoration {
    public void onDraw(Canvas c, RecyclerView parent, State state){}
    public void onDrawOver(Canvas c, RecyclerView parent, State state){}
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state){}
}
复制代码

创建一个类去继承RecyclerView.ItemDecoration,有三个方法需要重写;

执行顺序是getItemOffsets(),onDraw(),onDrawOver();

看名字,ItemDecoration是一个装饰者,并且是给每一个item加一个装饰。我们常用场景是写个分割线,各种分割线,希望大家能通过我这篇文章,对ItemDecoration有更多新的骚操作。

我们先来说关于这三个方法的用法。

getItemOffsets

第一个参数Rect,看名字不是不太容易知道有啥用。其实它就是我们当前item的矩形。我们可以通过这个参数获取到他的top、bottom、left、right。也可以给这几个属性赋值。当我们不给这几个参数赋值时,默认为0;

自定义View 之 RecyclerView.ItemDecoration

当我们设置了rect的参数之后,就有了上图左边的效果,如果不赋值,默认就是右边这个样子。

onDraw与onDrawOver

这就是当灵魂画家的部分了,用canvas可以画你想画的东西。

parent帮助你获取当前item的属性。

state获取当前recycleView的状态。

这两个方法的区别在于先后顺序。

onDraw画的东西会被item布局挡住;

item布局里的东西会被onDrawOver挡住;

明白了吧?

自定义View 之 RecyclerView.ItemDecoration

左边的圆就是onDraw画的,右边的圆就是onDrawOver画的

tips!!!

上一个的item可能会被下一个item的onDraw东西给挡住,所以在画的时候一定要控制好你的范围。

代码!安排!

private int bottomDevider;//分割线宽度
    private int topDevider;//文字分割宽度
    private String textString;//分割线的文字

    Rect textBounds = new Rect();

    private Paint dividerPaint;
    private Paint textPaint;

    private Long lastReadMsgDate;//上次获取数据集的最新数据的createtime
复制代码

除了textBounds ,其他都很容易理解是干嘛的。

public TextDivider(Context context) {
        dividerPaint = new Paint();
        textPaint = new Paint();
        //设置分割线颜色
        dividerPaint.setColor(context.getResources().getColor(R.color.whitesmoke));
        textPaint.setColor(context.getResources().getColor(R.color.vpi__bright_foreground_disabled_holo_dark));
        textString = "--------------以-下-是-已-阅-读-内-容--------------";
        textPaint.setTextSize(32);
        textPaint.setTextAlign(Paint.Align.CENTER);
        //设置分割线宽度
        bottomDevider = context.getResources().getDimensionPixelSize(R.dimen.space_2);
        topDevider = 100;
        lastReadMsgDate = Long.parseLong(SPM.getStr(BaseApp.getContext(), LC.CONSTANT, LC.LAST_REMIND_MSG_DATA, "0"));
    }
复制代码

textPaint.setTextAlign(Paint.Align.CENTER); 这句代码是让所写的文字,居于原点水平居中。

private CreateTimeListener mListener;

    public void setCreateTimeListener(CreateTimeListener listener) {
        mListener = listener;
    }
    public interface CreateTimeListener {
        long getCreateTime(int position);
    }
复制代码

这是接口用来从外部获取当前item的createTime。

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = bottomDevider;
        if(lastReadMsgDate == mListener.getCreateTime(parent.getChildAdapterPosition(view))){
            outRect.top = topDevider;
        }
    }
复制代码

给每个item下方增加一段距离,用于画普通的分割线。

在需要画文字分割线的上方增加一段距离,用于画文字分割线

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        final int childCount = parent.getChildCount();
        final int left = parent.getLeft() + parent.getPaddingLeft();
        final int right = parent.getRight() - parent.getPaddingRight();
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            if(lastReadMsgDate == mListener.getCreateTime(position)){
                float top = view.getBottom();
                float bottom = view.getBottom() + bottomDevider;
                c.drawRect(left, top, right, bottom, dividerPaint);
                top = view.getTop() - bottomDevider;
                bottom = view.getTop();
                c.drawRect(left, top, right, bottom, dividerPaint);

                //文字居中线
                float x = (view.getRight() - view.getLeft())/2;
                //文字所占用的边框top,bottom位置
                top = view.getTop() - topDevider;
                bottom = view.getTop() - bottomDevider;
                //获取文字的Bounds
                textPaint.getTextBounds(textString, 0, textString.length(), textBounds);
                //计算文字的基线
                float y = ((bottom + top)/2) + (textBounds.height()/2);

                c.drawText(textString, x, y, textPaint);
            }else {
                float top = view.getBottom();
                float bottom = view.getBottom() + bottomDevider;
                c.drawRect(left, top, right, bottom, dividerPaint);
            }
        }
    }
复制代码

在画文字分割线的时候我觉得比较烦的就是算距离。

通常我们用canvas画东西的时候的原点,在左上角。

自定义View 之 RecyclerView.ItemDecoration

而文字分割线的原点在第一个字的左下角偏左一点点的距离。

自定义View 之 RecyclerView.ItemDecoration

文字垂直居中

关于点先生有多帅就不多讲了。这里说一说文字居中的问题。

本帅了解也不是很深, 就只找到了一种方法让它居中。

水平居中很简单,上面已经说到过了。

自定义View 之 RecyclerView.ItemDecoration

item的原点在左上角蓝色圆的位置,文字要想垂直居中,原点应该在紫色圆的位置。 找到紫圆的Y轴坐标就可以了。

((bottom + top)/ 2) + (文字所占的高度 / 2)

文字所占高度,就是最后的难点了。 各种get方法都找不到文字高度,最后在画文字时候传的一个参数Rect给找到方法了。

textPaint.getTextBounds(textString, 0, textString.length(), textBounds);
复制代码

跟上文说的一样,就是矩形,这里传进去的textBounds就是Rect,穿进去之后可以获取到当前文字的一些属性,问题迎刃而解。

自定义View 之 RecyclerView.ItemDecoration

在recycleView使用处调用也很简单。

textDivider = new TextDivider(getContext());
        textDivider.setCreateTimeListener(new TextDivider.CreateTimeListener() {
            @Override
            public long getCreateTime(int position) {
                if (cacherRmindMsgList.size()==0) return 0L;
                else return cacherRmindMsgList.get(position).getCreateTime();
            }
        });
        recyclerView.addItemDecoration(textDivider);

复制代码

嘻嘻!

自定义View 之 RecyclerView.ItemDecoration

后续

做完之后有个疑问。为啥获取文字属性的没有一个叫get***()的方法!

还要我亲自传一个参数进去接受这些东西。给个回调接口也好啊!

打脸也挺快,自己亲手写过的接口隔离原则都差点忘了。

Rect里面这么多属性,它又不知道我要什么东西,全都回调给我,也太傻逼了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

迎接互联网的明天

迎接互联网的明天

邹静 / 电子工业 / 2011-6 / 55.00元

《迎接互联网的明天-玩转3D Web(附盘)》,全书共5章,第1章主要阐述了国内外空前繁荣的3D互联网技术领域,以及这些领域透射出来的潜在商机;第2章主要用当下比较流行的Flash编程语言ActionScript 3,来向大家介绍面向对象编程语言的思想概念,以及一些3D渲染技术的入门知识;第3章注重建模知识的运用,主要运用WireFusion和3ds Max来制作3D网页;第4章主要介绍3D游戏编......一起来看看 《迎接互联网的明天》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码