自定义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里面这么多属性,它又不知道我要什么东西,全都回调给我,也太傻逼了。


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

查看所有标签

猜你喜欢:

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

Coming of Age in Second Life

Coming of Age in Second Life

Tom Boellstorff / Princeton University Press / 2008-04-21 / USD 29.95

The gap between the virtual and the physical, and its effect on the ideas of personhood and relationships, is the most interesting aspect of Boellstorff's analysis... Boellstorff's portrayal of a virt......一起来看看 《Coming of Age in Second Life》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具