不嵌套R(shí)ecyclerView?。?實(shí)現(xiàn)有HeaderView的GridLayoutManager

序:前邊我因?yàn)轫?xiàng)目需要擼了一下RecyclerView GridLayoutManager item設(shè)置萬(wàn)能分隔線,感覺還是實(shí)用的吧,閱讀量也水漲船高,令人欣喜,也滿足了內(nèi)心的小九九~~~ ??

至于這篇文章呢,是在上一篇的基礎(chǔ)上做加法,增加了HeadView的顯示。所以至于Item間距啥的算法,原理之類的這里就不再講解了。沒看過(guò)上一篇文章的親們,直接先去擼一把~

下面先貼個(gè)圖:


圖1.1

看到這個(gè)圖大家第一想到的做法是什么?

讓我們猜猜看:是不是一個(gè)RecyclerView(LinearLayoutManager)嵌套另一個(gè)RecyclerView(GridLayoutManager)?

嗯!我們一般都是這么做的,也沒什么不妥。

但是這里呢,我們換個(gè)角度,換個(gè)思維,嘗試用給一個(gè)RecyclerView給它解決了~,年輕就是要燥??

照舊~ 無(wú)圖無(wú)真相??


圖1.2

圖1.3

證明確實(shí)只用了一個(gè)RecyclerView

一、首先,我們要對(duì)我們需要顯示的Item進(jìn)行ViewType區(qū)分

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

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

二、我們要對(duì)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;
            }
        });

這里實(shí)際上就是獲取Item的ViewType,如果是HeadView的Type就將GridLayoutManger的spanCount,變成一行。
spanSize 是說(shuō)用多少個(gè)Item空間來(lái)顯示這個(gè)View(比如說(shuō)可以用2個(gè)Item位置顯示該View,也可以3個(gè)等最大不超過(guò)設(shè)置的SpanCount),我們這里是獲取spanCount,也就是3個(gè),相當(dāng)于一行。

三、定制適合多個(gè)ViewType的ItemDecoration

其實(shí),使用一個(gè)RecyclerView來(lái)做圖1.1的效果,最重要的是要定制適合的ItemDecoration,前面文章詳解了GridLayout Item之間平均間距的原理和實(shí)現(xiàn)方法。這里就不多說(shuō)了。

為了實(shí)現(xiàn)HeadView和子Item同在一個(gè)RecyclerView,并且能正確的設(shè)置他們之間的間距,看起來(lái)還是挺繁瑣的,我們來(lái)試試看

3.1 首先我們要在自定義的ItemDecoration中,區(qū)分HeadView和subItem
 int spanCount = getSpanCount(parent);
 int spanSize = getSpanSize(itemPosition,parent);
 if(spanSize == spanCount){//這是有HeaderView 的情況
    ...        
 }else {//類似HeaderView 下的子item
    ...
 }

這里我們可以通過(guò)獲取GridLayoutManager的SpanCount和item的SpanSize,如果這兩者相等就說(shuō)明是HeadView Item,因?yàn)槲覀冎霸谠O(shè)置GridLayoutManager spansize的時(shí)候設(shè)置為gridLayoutManager.getSpanCount()。

3.2 通過(guò)上一步我們能夠區(qū)分HeadView和subItem,但是仍然有個(gè)問題是:我們?nèi)绾文苤烂總€(gè)Item的相對(duì)Position?

什么意思呢?

1、比如說(shuō)我們Item的絕對(duì)position,是按照順序排列的,0、1、2、3、4....等等
(實(shí)際上就是上一篇《RecyclerView GridLayoutManager item設(shè)置萬(wàn)能分隔線》中使用到的position)

2、而我所說(shuō)的Item的相對(duì)position,有點(diǎn)難解釋
先貼個(gè)上個(gè)文章的公式
第一個(gè)Item:L0=sW R0=eW-sW
第二個(gè)Item:L1=dW-R0=dW-eW+sW R1=eW-L1=2eW-dW-sW
第三個(gè)Item:L2=dW-R1=2(dW-eW)+sW R2=eW-L2=3eW-2dW-sW
所以根據(jù)以上可以得出
Ln = (position % spanCount) * (dW-eW) + sW
Rn = eW-Ln

這里可看出我們要獲取Ln = (position % spanCount) * (dW-eW) + sW中的position,而這里的這個(gè)position就不是上篇文章那個(gè)絕對(duì)Position了。

對(duì)于HeadView的left、top、right、bottom都是要設(shè)置的,而對(duì)每個(gè)HeadView下面所屬的subItem設(shè)置left、top、right、bottom就比較難了,因?yàn)槿绻凑铡禦ecyclerView GridLayoutManager item設(shè)置萬(wàn)能分隔線》中直接用絕對(duì)position進(jìn)行計(jì)算的話,界面就亂了。

下面上一個(gè)圖:


圖1.4

在上圖中
區(qū)域一/區(qū)域二:代表了一個(gè)區(qū)塊HeadView+subItems,而我在subItem上都標(biāo)了0、1、2、3...等。這些標(biāo)記的數(shù)字就代表相對(duì)position,而他們本身絕對(duì)position這是他們本身實(shí)際的position:可能是1、2、3、5、6、7、8這樣。

而區(qū)域二中圖標(biāo)0的Item實(shí)際position是多少呢? 是5! 那我們?nèi)绾蔚玫狡湎鄬?duì)position為0?那我們就需要用絕對(duì)position 減去 包含其headView之上的Item數(shù)量。相當(dāng)于5-5=0。 然后其后面Item的相對(duì)position 也是6-5=1、7-5=2、8-5=3(圖上標(biāo)4的給標(biāo)錯(cuò)了??)

那這樣看就只有相對(duì)position才能套用Ln = (position % spanCount) * (dW-eW) + sW這個(gè)公式了。

劃重點(diǎn):subItem的相對(duì)position就是相對(duì)于包括他的HeadView Item以及上面 所有的Item。

3.3 我們?cè)趺传@取subItem的相對(duì)position

通過(guò)3.1,我們能區(qū)分HeadView和subItem了。這點(diǎn)很重要

3.3.1 首先我們先定義兩個(gè)HashMap用于存儲(chǔ)position信息
/**
     * 意思是存儲(chǔ)每一個(gè)個(gè)HeadView 的之前所有Item包括自己的數(shù)量
     */
    LinkedHashMap<Integer,Integer> headPositionTotalCountMap = new LinkedHashMap<>();
    /**
     * 每一個(gè)子Item(非HeadView),存儲(chǔ)自己對(duì)應(yīng)的headView的Item數(shù)量,
     * 主要用于取余計(jì)算時(shí),位置換算
     */
    LinkedHashMap<Integer,Integer> subItemPositionCountMap = new LinkedHashMap<>();

1、headPositionTotalCountMap:用于存儲(chǔ)在它之前的并包括自己的item數(shù)量
2、subItemPositionCountMap: 對(duì)應(yīng)于每個(gè)subItem存儲(chǔ)用于計(jì)算自己相對(duì)position的Items數(shù)量(每個(gè)HeadView下的subItem,相對(duì)Items數(shù)量是相等的)

3.3.2 存儲(chǔ)用于計(jì)算相對(duì)position的items total count
if(spanSize == spanCount){//這是有HeaderView 的情況
    ...
    //如果HeadView Item沒有保存count信息,則將它之前包括自身的count,記錄      
    //到以其絕對(duì)position為Key的Map中
    if(!headPositionTotalCountMap.containsKey(itemPosition)) {
       headPositionTotalCountMap.put(itemPosition,itemPosition+1);
    }
 }else {//類似HeaderView 下的子item
    //如果subItem沒有保存count信息,則將它HeadView記錄的Count取出,記錄      
    //到以其絕對(duì)position為Key的Map中
    if(!subItemPositionCountMap.containsKey(itemPosition)) {
       //找到headPostionTotalCountMap中最近的entry,獲取其value 
       int headViewTotalCount = headPositionTotalCountMap.size() == 0 ? 0 : getMapTail(headPositionTotalCountMap).getValue();
       subItemPositionCountMap.put(itemPosition, headViewTotalCount);
    }
    ...
    //通過(guò)item自身記錄的相對(duì)items count,計(jì)算出相對(duì)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 畫出每個(gè)區(qū)域之間的虛線

圖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);
            }
        }
    }

畫虛線就比較簡(jiǎn)單了幾大件:Path、Paint、Canvas配置好就行,然后獲取每個(gè)headView的top、left、屏幕寬度就可以畫出虛線,這里就不多說(shuō)了。

好了,到這里《不嵌套R(shí)ecyclerView,實(shí)現(xiàn)有HeaderView的GridLayoutManager》就講完了 ,它沒有嵌套R(shí)ecyclerView來(lái)實(shí)現(xiàn)文中UI,并且能很好的平分間隔,不會(huì)使item位置錯(cuò)亂。另外,可能文中一些東西沒講清楚,如若有任何問題,都可以留言,我看到后會(huì)第一時(shí)間回復(fù)!See you next article~

我已將GridDividerItemDecoration,上傳到GitHub:https://github.com/haozi5460/GridDividerMoreTypeItemDecoration

申明:禁用于商業(yè)用途,如若轉(zhuǎn)載,請(qǐng)附帶原文鏈接。http://www.itdecent.cn/p/ea4d9843dada 蟹蟹(#.#)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容