美文网首页安卓安卓开发
不嵌套RecyclerView!!!实现有HeaderView的

不嵌套RecyclerView!!!实现有HeaderView的

作者: 天上飘的是浮云 | 来源:发表于2018-12-19 12:41 被阅读230次

    序:前边我因为项目需要撸了一下RecyclerView GridLayoutManager item设置万能分隔线,感觉还是实用的吧,阅读量也水涨船高,令人欣喜,也满足了内心的小九九~~~ 😝

    至于这篇文章呢,是在上一篇的基础上做加法,增加了HeadView的显示。所以至于Item间距啥的算法,原理之类的这里就不再讲解了。没看过上一篇文章的亲们,直接先去撸一把~

    下面先贴个图:


    图1.1

    看到这个图大家第一想到的做法是什么?

    让我们猜猜看:是不是一个RecyclerView(LinearLayoutManager)嵌套另一个RecyclerView(GridLayoutManager)?

    嗯!我们一般都是这么做的,也没什么不妥。

    但是这里呢,我们换个角度,换个思维,尝试用给一个RecyclerView给它解决了~,年轻就是要燥😝

    照旧~ 无图无真相👀


    图1.2
    图1.3
    证明确实只用了一个RecyclerView

    一、首先,我们要对我们需要显示的Item进行ViewType区分

        @Override
        public int getItemViewType(int position) {
            if(listData != null)
                return listData.get(position).getViewType();
            return super.getItemViewType(position);
        }
    

    大家都看得懂哈~ 略...

    二、我们要对GridLayoutManager,做定制以用一行显示HeaderView

    final GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3);
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int itemViewType = subAdapter.getItemViewType(position);
                    if(itemViewType == Constants.PENDING_UPLOAD_SUB_VIEW_TYPE_HEADER)
                        return gridLayoutManager.getSpanCount();
                    else
                        return 1;
                }
            });
    

    这里实际上就是获取Item的ViewType,如果是HeadView的Type就将GridLayoutManger的spanCount,变成一行。
    spanSize 是说用多少个Item空间来显示这个View(比如说可以用2个Item位置显示该View,也可以3个等最大不超过设置的SpanCount),我们这里是获取spanCount,也就是3个,相当于一行。

    三、定制适合多个ViewType的ItemDecoration

    其实,使用一个RecyclerView来做图1.1的效果,最重要的是要定制适合的ItemDecoration,前面文章详解了GridLayout Item之间平均间距的原理和实现方法。这里就不多说了。

    为了实现HeadView和子Item同在一个RecyclerView,并且能正确的设置他们之间的间距,看起来还是挺繁琐的,我们来试试看

    3.1 首先我们要在自定义的ItemDecoration中,区分HeadView和subItem
     int spanCount = getSpanCount(parent);
     int spanSize = getSpanSize(itemPosition,parent);
     if(spanSize == spanCount){//这是有HeaderView 的情况
        ...        
     }else {//类似HeaderView 下的子item
        ...
     }
    

    这里我们可以通过获取GridLayoutManager的SpanCount和item的SpanSize,如果这两者相等就说明是HeadView Item,因为我们之前在设置GridLayoutManager spansize的时候设置为gridLayoutManager.getSpanCount()

    3.2 通过上一步我们能够区分HeadView和subItem,但是仍然有个问题是:我们如何能知道每个Item的相对Position?

    什么意思呢?

    1、比如说我们Item的绝对position,是按照顺序排列的,0、1、2、3、4....等等
    (实际上就是上一篇《RecyclerView GridLayoutManager item设置万能分隔线》中使用到的position)

    2、而我所说的Item的相对position,有点难解释
    先贴个上个文章的公式
    第一个Item:L0=sW R0=eW-sW
    第二个Item:L1=dW-R0=dW-eW+sW R1=eW-L1=2eW-dW-sW
    第三个Item:L2=dW-R1=2(dW-eW)+sW R2=eW-L2=3eW-2dW-sW
    所以根据以上可以得出
    Ln = (position % spanCount) * (dW-eW) + sW
    Rn = eW-Ln

    这里可看出我们要获取Ln = (position % spanCount) * (dW-eW) + sW中的position,而这里的这个position就不是上篇文章那个绝对Position了。

    对于HeadView的left、top、right、bottom都是要设置的,而对每个HeadView下面所属的subItem设置left、top、right、bottom就比较难了,因为如果按照《RecyclerView GridLayoutManager item设置万能分隔线》中直接用绝对position进行计算的话,界面就乱了。

    下面上一个图:


    图1.4

    在上图中
    区域一/区域二:代表了一个区块HeadView+subItems,而我在subItem上都标了0、1、2、3...等。这些标记的数字就代表相对position,而他们本身绝对position这是他们本身实际的position:可能是1、2、3、5、6、7、8这样。

    而区域二中图标0的Item实际position是多少呢? 是5! 那我们如何得到其相对position为0?那我们就需要用绝对position 减去 包含其headView之上的Item数量。相当于5-5=0。 然后其后面Item的相对position 也是6-5=1、7-5=2、8-5=3(图上标4的给标错了😅)

    那这样看就只有相对position才能套用Ln = (position % spanCount) * (dW-eW) + sW这个公式了。

    划重点:subItem的相对position就是相对于包括他的HeadView Item以及上面 所有的Item。

    3.3 我们怎么获取subItem的相对position

    通过3.1,我们能区分HeadView和subItem了。这点很重要

    3.3.1 首先我们先定义两个HashMap用于存储position信息
    /**
         * 意思是存储每一个个HeadView 的之前所有Item包括自己的数量
         */
        LinkedHashMap<Integer,Integer> headPositionTotalCountMap = new LinkedHashMap<>();
        /**
         * 每一个子Item(非HeadView),存储自己对应的headView的Item数量,
         * 主要用于取余计算时,位置换算
         */
        LinkedHashMap<Integer,Integer> subItemPositionCountMap = new LinkedHashMap<>();
    

    1、headPositionTotalCountMap:用于存储在它之前的并包括自己的item数量
    2、subItemPositionCountMap: 对应于每个subItem存储用于计算自己相对position的Items数量(每个HeadView下的subItem,相对Items数量是相等的)

    3.3.2 存储用于计算相对position的items total count
    if(spanSize == spanCount){//这是有HeaderView 的情况
        ...
        //如果HeadView Item没有保存count信息,则将它之前包括自身的count,记录      
        //到以其绝对position为Key的Map中
        if(!headPositionTotalCountMap.containsKey(itemPosition)) {
           headPositionTotalCountMap.put(itemPosition,itemPosition+1);
        }
     }else {//类似HeaderView 下的子item
        //如果subItem没有保存count信息,则将它HeadView记录的Count取出,记录      
        //到以其绝对position为Key的Map中
        if(!subItemPositionCountMap.containsKey(itemPosition)) {
           //找到headPostionTotalCountMap中最近的entry,获取其value 
           int headViewTotalCount = headPositionTotalCountMap.size() == 0 ? 0 : getMapTail(headPositionTotalCountMap).getValue();
           subItemPositionCountMap.put(itemPosition, headViewTotalCount);
        }
        ...
        //通过item自身记录的相对items count,计算出相对position“(itemPosition-subItemPositionCountMap.get(itemPosition))”,套入公式
        left = (itemPosition-subItemPositionCountMap.get(itemPosition)) % spanCount * (dividerItemWidth - eachItemWidth) + spaceWidth;
        right = eachItemWidth - left;
        bottom = 0;
        top = mDividerWidth;
     }
    
     outRect.set(left, top, right, bottom);
    

    代码中注释解释了一波,这里大家就看看,理解一下~

    3.4 画出每个区域之间的虚线

    图1.5
        //绘制不同HeadView之间虚线分割线
        private void draw(Canvas canvas, RecyclerView parent) {
            int width = mContext.getResources().getDisplayMetrics().widthPixels > mContext.getResources().getDisplayMetrics().heightPixels
                    ? mContext.getResources().getDisplayMetrics().heightPixels : mContext.getResources().getDisplayMetrics().widthPixels;
            int spanCount = getSpanCount(parent);
    
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int itemPosition = ((RecyclerView.LayoutParams) child.getLayoutParams()).getViewLayoutPosition();
                int spanSize = getSpanSize(itemPosition,parent);
    
                if(spanCount == spanSize && itemPosition != 0){
                    mPath.reset();
                    mPath.moveTo(child.getLeft()-5,child.getTop()-mDividerWidth);
                    mPath.lineTo(width-mDividerWidth+5,child.getTop()-mDividerWidth);
                    canvas.drawPath(mPath,mPaint);
                }
            }
        }
    

    画虚线就比较简单了几大件:Path、Paint、Canvas配置好就行,然后获取每个headView的top、left、屏幕宽度就可以画出虚线,这里就不多说了。

    好了,到这里《不嵌套RecyclerView,实现有HeaderView的GridLayoutManager》就讲完了 ,它没有嵌套RecyclerView来实现文中UI,并且能很好的平分间隔,不会使item位置错乱。另外,可能文中一些东西没讲清楚,如若有任何问题,都可以留言,我看到后会第一时间回复!See you next article~

    我已将GridDividerItemDecoration,上传到GitHub:https://github.com/haozi5460/GridDividerMoreTypeItemDecoration

    申明:禁用于商业用途,如若转载,请附带原文链接。https://www.jianshu.com/p/ea4d9843dada 蟹蟹(#.#)

    相关文章

      网友评论

        本文标题:不嵌套RecyclerView!!!实现有HeaderView的

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