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

序:前边我因为项目需要撸了一下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 蟹蟹(#.#)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容