移动开发Android移动开发

Android RecyclerView-使用Itemdecor

2018-02-01  本文已影响46人  _那个人

一 前言

该文详细的介绍了RecyclerView.ItemDecoration实现分组粘性头部的功能,让我们自己生产代码,告别代码搬运工的时代.另外文末附有完整Demo的连接.看下效果:

QQ20180201-0.gif

二 知识准备

RecyclerView.ItemDecoration对于我们最熟悉的功能就是给RecyclerView实现各种各样自定义的分割线了,实现分割线的功能其实和实现粘性头部的功能大同小异,那我们就来看看这神奇的RecyclerView.ItemDecoration.

该类是RecyclerView的内部静态抽象类:

 public abstract static class ItemDecoration {
       /**
        * 绘制*除Item内容*以外的布局,这个方法是再****Item的内容绘制之前****执行的,
        * 所以呢如果两个绘制区域重叠的话,Item的绘制区域会覆盖掉该方法绘制的区域.
        * 一般配合getItemOffsets来绘制分割线等.
        *
        * @param c      Canvas 画布
        * @param parent RecyclerView
        * @param state  RecyclerView的状态
        */
        public void onDraw(Canvas c, RecyclerView parent, State state) {
            onDraw(c, parent);
        }
        @Deprecated
        public void onDraw(Canvas c, RecyclerView parent) {
        }

        /**
         * 绘制*除Item内容*以外的东西,这个方法是在****Item的内容绘制之后****才执行的,
         * 所以该方法绘制的东西会将Item的内容覆盖住,既显示在Item之上.
         * 一般配合getItemOffsets来绘制分组的头部等.
         *
         * @param c      Canvas 画布
         * @param parent RecyclerView
         * @param state  RecyclerView的状态
         */
        public void onDrawOver(Canvas c, RecyclerView parent, State state) {
            onDrawOver(c, parent);
        }

        /**
         * @deprecated
         * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
         */
        @Deprecated
        public void onDrawOver(Canvas c, RecyclerView parent) {
        }


        /**
         * @deprecated
         * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
         */
        @Deprecated
        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
            outRect.set(0, 0, 0, 0);
        }

        /**
         * 设置Item的布局四周的间隔.
         *
        * @param outRect 确定间隔 Left  Top Right Bottom 数值的矩形.
        * @param view    RecyclerView的ChildView也就是每个Item的的布局.
        * @param parent  RecyclerView本身.
        * @param state   RecyclerView的各种状态.
         */
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

这里面呢有个问题一定要明白几个问题:

我们知道getItemOffsets()第一个参数是一个矩形的对象,这个对象的left、 top、right、bottpm四个属性值分别表示图中的outRect.left、outRect.top、outRect.right、outRect.bottom四个线段所表示的空间.也就是说当RecyclerView的Item再确定自己的大小的时候会将getItemOffsets()里面的Rect对象的Left、Top、Right、Bottom属性取出来,看看需要再Item布局的四周留出多大的空间.我们来看下源码:

Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }

        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
        //这里呢mTempRect就是我们再getItemOffsets()里面的第一个Rect的对象,我们再实现类的方法里面给mTempRect赋值.
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
    }
    
    
    这里呢就是RecyclerView再测量每个Child的大小的时候都把insets这个矩形的l t r  b 数值都加上了.insets就是方法getItemDecorInsetsForChild()返回的矩形对象.
     /**
         * Measure a child view using standard measurement policy, taking the padding
         * of the parent RecyclerView and any added item decorations into account.
         *
         * <p>If the RecyclerView can be scrolled in either dimension the caller may
         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
         *
         * @param child Child view to measure
         * @param widthUsed Width in pixels currently consumed by other views, if relevant
         * @param heightUsed Height in pixels currently consumed by other views, if relevant
         */
        public void measureChild(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;
            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
                    canScrollVertically());
            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }
        }

源码的讲解过于粗糙,希望大家见谅,目的就是为了让大家知道这个getItemOffsets()方法是怎么让RecyclerView再Item之外留出空间的.

[图片上传失败...(image-25a500-1517455778166)]

首先后台返回的数据一定要有组类区分,每个分组的标记不能一样,最好是我们方便处理的.该Demo采用的标记位是int类型的标记tag,每组的标记以此+1,每五个城市分为一组,每组的第一个城市当做头部局显示的内容.我们的分组头部的高度为40dp.

@Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (citiList == null || citiList.size() == 0) {
            return;
        }
        int adapterPosition = parent.getChildAdapterPosition(view);
        RecBean.CitiListBean beanByPosition = getBeanByPosition(adapterPosition);
        if(beanByPosition == null){
            return;
        }
        int preTage = -1;
        int tage = beanByPosition.getTage();
        //一定要记住这个 >= 0
        if(adapterPosition - 1 >= 0) {
            RecBean.CitiListBean nextBean = getBeanByPosition(adapterPosition - 1);
            if (nextBean == null) {
                return;
            }
            preTage = nextBean.getTage();
        }
        if(preTage != tage){
            outRect.top = headHeight;
        }else {
            //这个目的是留出分割线
            outRect.top = lineHeight;
        }

    }

这样下来我们给分组头部的空间就预留出来了.接下来绘制分组头部,因为分割线我直接显示的背景色所以就不用去绘制分割线了.

上代码:

 @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        if(citiList == null || citiList.size() == 0){
            return;
        }

        int parentLeft = parent.getPaddingLeft();
        int parentRight = parent.getWidth() - parent.getPaddingRight();

        int childCount = parent.getChildCount();
        int tag = -1;
        int preTag;
        for (int i = 0; i <childCount; i++) {
            View childView = parent.getChildAt(i);
            if(childView == null){
                continue;
            }
            int adapterPosition = parent.getChildAdapterPosition(childView);
            当前Item的Top
            int top = childView.getTop();
            int bottom = childView.getBottom();
            preTag = tag;
            tag = citiList.get(adapterPosition).getTage();
            //判断下一个是不是分组的头部
            if(preTag == tag){
                continue;
            }
            //这里面我把每个分组的头部显示的文字列表单独提出来了,为了测试方便用,
            String name = index.get((tag - 1 ) < 0 ? 0 : (tag -1));
            int height = Math.max(top,headHeight);
            //判断下一个Item是否是分组的头部
            if(adapterPosition + 1 < citiList.size()){
                int nextTag = citiList.get(adapterPosition + 1).getTage();
                if(tag != nextTag){
                   //这里就是实现渐变效果的地方
                   //因为如果遍历到
                    height = bottom;
                }
            }
            paint.setColor(Color.parseColor("#ffffff"));
            c.drawRect(parentLeft,height - headHeight,parentRight,height,paint);
            paint.setColor(Color.BLACK);
            paint.getTextBounds(name, 0, name.length(), rectOver);

            c.drawText(name, dip2px(10), height - (headHeight - rectOver.height()) / 2, paint);

        }


    }

到这里我们的功能已经结束了,我们要知道getItemOffsets()会提前执行,每个Item的回收和出现都会执行一次.onDraw或者onDrawOver再屏幕中的Item发生变化的时候都会执行,只要发生变化.我们的Head会不停的绘制.

结束

这是2018年的第一篇文章,之前太忙了也没好好的总结知识点.写的仓促希望大家多多指导文章出现的问题,谢谢大家的反馈,欢迎评论吐槽哦~</br>

欢迎大家关注</br>
我的掘金</br>
我的CSDN</br>
我的简书</br>

Demo下载

喜欢文字的同学也可以关注该公众号,与你一起同游文字的海洋~

WechatIMG21.jpeg
上一篇 下一篇

猜你喜欢

热点阅读