美文网首页
自定义View 之 RecyclerView.ItemDecor

自定义View 之 RecyclerView.ItemDecor

作者: 点先生在这 | 来源:发表于2019-01-26 16:01 被阅读0次

    是这么一回事

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

    坑的来源

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

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

    两种方案与思路

    一开始我想到了两种方案:
    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;

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

    onDraw与onDrawOver

    这就是当灵魂画家的部分了,用canvas可以画你想画的东西。
    parent帮助你获取当前item的属性。
    state获取当前recycleView的状态。
    这两个方法的区别在于先后顺序。

    onDraw画的东西会被item布局挡住;
    item布局里的东西会被onDrawOver挡住;
    明白了吧?

    左边的圆就是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画东西的时候的原点,在左上角。

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

    文字垂直居中

    关于点先生有多帅就不多讲了。这里说一说文字居中的问题。
    本帅了解也不是很深, 就只找到了一种方法让它居中。
    水平居中很简单,上面已经说到过了。

    item的原点在左上角蓝色圆的位置,文字要想垂直居中,原点应该在紫色圆的位置。
    找到紫圆的Y轴坐标就可以了。
    ((bottom + top)/ 2) + (文字所占的高度 / 2)

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

      textPaint.getTextBounds(textString, 0, textString.length(), textBounds);
    

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

    在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);
    
    

    嘻嘻!


    后续

    做完之后有个疑问。为啥获取文字属性的没有一个叫get***()的方法!
    还要我亲自传一个参数进去接受这些东西。给个回调接口也好啊!

    打脸也挺快,自己亲手写过的接口隔离原则都差点忘了。
    Rect里面这么多属性,它又不知道我要什么东西,全都回调给我,也太傻逼了。

    相关文章

      网友评论

          本文标题:自定义View 之 RecyclerView.ItemDecor

          本文链接:https://www.haomeiwen.com/subject/dpoujqtx.html