RecyclerView(3)-LayoutMagager源碼解析,LinearLayoutManager

上一節(jié)RecyclerView(2)- 自定義Decoration打造時(shí)光軸效果也已經(jīng)寫完了拴泌,希望有看到我文章的同學(xué)能有一些收獲熟嫩。layoutManager可以說(shuō)是一個(gè)重中之重欣鳖,代碼量非常多,且涉及到復(fù)用機(jī)制的調(diào)用等等熄驼。等源碼分析過(guò)后蛤售,同學(xué)們應(yīng)該可以通過(guò)自定義LayoutManager打造奇形怪狀的奇葩的UI需求了。

· RecyclerView(1)- Decoration源碼解析
· RecyclerView(2)- 自定義Decoration打造時(shí)光軸效果
· RecyclerView(3)- LayoutMagager源碼解析,LinearLayoutManager
· RecyclerView(4)- 核心喂江、Recycler復(fù)用機(jī)制_1
· RecyclerView(4)- 核心召锈、Recycler復(fù)用機(jī)制_2
· RecyclerView(5)- 自定義LayoutManager(布局、復(fù)用)
· RecyclerView(6)- 自定義ItemAnimator
· RecyclerView(7)- ItemTouchHelper
· RecyclerView(8)- MultiTypeAdapter文章开呐、MultiTypeAdapter Github地址
文章視頻地址:鏈接:http://pan.baidu.com/s/1hssvXC4 密碼:18v1

分析LinearLayoutManager烟勋,先羅列幾個(gè)想弄明白的問(wèn)題

· 1、如何擺位置筐付;
· 2卵惦、按需加載布局,位置擺放的規(guī)則
· 3瓦戚、滑動(dòng)時(shí)itemview的位置改變遵循的規(guī)則

LayoutManager如何擺位置沮尿;

先從 測(cè)量 onMeasure開(kāi)始

RecyclerView.class
    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout.mAutoMeasure) { // 自動(dòng)測(cè)量
             mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);// 調(diào)用一次recyclerview的測(cè)量
             //....
            dispatchLayoutStep2();
        }
        else{//自定義測(cè)量規(guī)則
        
        }
    }

    private void dispatchLayoutStep2() {
        //....
         mState.mItemCount = mAdapter.getItemCount();
        //.... 自定義擺放位置
        mLayout.onLayoutChildren(mRecycler, mState);
        //....
    }

可以看到在recyclerview的 onMeasure 調(diào)用了 mLayout的onLayoutChildren方法 并將Recycler與 包含了 適配器一些信息的包裝成一個(gè) State參數(shù) 傳入mLayout的onLayoutChildren

接下來(lái)看一下LinearLayoutManager

LinearLayoutManager.class
    //初始化
    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        setOrientation(orientation);
        setReverseLayout(reverseLayout);
        setAutoMeasureEnabled(true);
    }
    public void setAutoMeasureEnabled(boolean enabled) {
        mAutoMeasure = enabled;
    }
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state
        if (DEBUG) {
            Log.d(TAG, "is pre layout:" + state.isPreLayout());
        }
        if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
            if (state.getItemCount() == 0) {
            // 如果沒(méi)有項(xiàng)目 移除布局
                removeAndRecycleAllViews(recycler);
                return;
            }
        }
        
        // ...
        this.updateLayoutStateToFillStart(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForStart;
        fill(recycler, mLayoutState, state, false);
        //...
        this.updateLayoutStateToFillEnd(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForEnd;
        this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
        this.fill(recycler, this.mLayoutState, state, false);
    }
    // 填充view
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
            //.. 擺位置
             layoutChunk(recycler, state, layoutState, layoutChunkResult);
            //...  布局回收
              this.recycleByLayoutState(recycler, layoutState);
    }
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        //...
        addView(view);  // -> addView(View child, int index) ->  addViewInt(View child, int index, boolean disappearing)
        //計(jì)算位置
         this.measureChildWithMargins(view, 0, 0);
            result.mConsumed = this.mOrientationHelper.getDecoratedMeasurement(view);
            int left;
            int top;
            int right;
            int bottom;
            if(this.mOrientation == 1) {
                if(this.isLayoutRTL()) {
                    right = this.getWidth() - this.getPaddingRight();
                    left = right - this.mOrientationHelper.getDecoratedMeasurementInOther(view);
                } else {
                    left = this.getPaddingLeft();
                    right = left + this.mOrientationHelper.getDecoratedMeasurementInOther(view);
                }

                if(layoutState.mLayoutDirection == -1) {
                    bottom = layoutState.mOffset;
                    top = layoutState.mOffset - result.mConsumed;
                } else {
                    top = layoutState.mOffset;
                    bottom = layoutState.mOffset + result.mConsumed;
                }
            } else {
                top = this.getPaddingTop();
                bottom = top + this.mOrientationHelper.getDecoratedMeasurementInOther(view);
                if(layoutState.mLayoutDirection == -1) {
                    right = layoutState.mOffset;
                    left = layoutState.mOffset - result.mConsumed;
                } else {
                    left = layoutState.mOffset;
                    right = layoutState.mOffset + result.mConsumed;
                }
            }
        
        //布局
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        //...
    }
    private void addViewInt(View child, int index, boolean disappearing) {
          final ViewHolder holder = getChildViewHolderInt(child);
          mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);//依附在parent  這邊是 parent 是 recyclerView
          
          // 對(duì)holder 做一些變量改變
    }

RecyclerView.class   
    static class LayoutState {
        View next(RecyclerView.Recycler recycler) {
            //..
            //從 recyler 中取出view
            final View view = recycler.getViewForPosition(mCurrentPosition);
            //...
            return view;
        }
    }

一開(kāi)始的注釋說(shuō)了整個(gè)大概的流程:
1、先檢查children和其它變量畜疾,找到一個(gè)錨點(diǎn)坐標(biāo)與錨點(diǎn)(如果實(shí)在線性布局中赴邻,相當(dāng)于找到當(dāng)前界面內(nèi)第一個(gè)VIew,與第一個(gè)view的坐標(biāo)點(diǎn))
2啡捶、填充從底部開(kāi)始堆疊
3姥敛、從頂部填充到端部
4、計(jì)算是否還有滾動(dòng)瞎暑,添加各種變量彤敛,創(chuàng)建布局。

其實(shí)原理還是挺簡(jiǎn)單的了赌,循環(huán)判斷是否超出邊界墨榄,測(cè)量view,添加view 布局view勿她,判定值改變 在跳到上面循環(huán)袄秩。好多人看不懂是因?yàn)間oogle工程師將這些判斷數(shù)據(jù)抽取封裝了起來(lái),而其中字段非常多逢并,讓人眼花繚亂之剧。
一些總結(jié):
開(kāi)始調(diào)用 onLayoutChildren() 調(diào)用fill() 擺放位置
fill():判斷相應(yīng)規(guī)則 回收一部分view, 調(diào)用layoutChunk() 獲取 view、填充筒狠、擺放view;
layoutChunk(): 獲取view,計(jì)算 view的位置
layoutDecoratedWithMargins():拿到裝飾器設(shè)置的偏移量猪狈,擺放view箱沦;

2辩恼、按需加載布局,位置擺放的規(guī)則

上面也可以知道 在fill()方法內(nèi)我們先去判斷位置擺放的規(guī)則 在決定是否加載下一個(gè)/上一個(gè)View(也就是調(diào)用 layoutChunk),那么我們就來(lái)看一下判斷規(guī)則是怎么樣的吧

   while((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
           int start = layoutState.mAvailable;
        if(layoutState.mScrollingOffset != -2147483648) {
            if(layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }

            this.recycleByLayoutState(recycler, layoutState);
        }

        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LinearLayoutManager.LayoutChunkResult layoutChunkResult = this.mLayoutChunkResult;
            layoutChunkResult.resetInternal();
            this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if(layoutChunkResult.mFinished) {
                break;
            }

            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            if(!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if(layoutState.mScrollingOffset != -2147483648) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if(layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }

                this.recycleByLayoutState(recycler, layoutState);
            }

            if(stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }

可以看到while關(guān)鍵字內(nèi)谓形,有l(wèi)auoutState.mInfinite remainingspace layoutState.haseMore(state)變量灶伊,就是這三個(gè)變量控制著我們的擺放view的數(shù)量。
字面意思可以猜出來(lái):無(wú)窮寒跳、可用空間>0; 是否有更多

那我們來(lái)一個(gè)一個(gè)看一下聘萨,這幾個(gè)變量都經(jīng)歷了什么。

2.1童太、layoutState.mInfinite

字面意思 mInfinite是無(wú)窮的意思...

    this.mLayoutState.mInfinite = this.resolveIsInfinite();
    boolean resolveIsInfinite() {
    return this.mOrientationHelper.getMode() == 0 && this.mOrientationHelper.getEnd() == 0;
}
new OrientationHelper(layoutManager) {
         public int getEnd() {
                return this.mLayoutManager.getHeight();
            }
         public int getMode() {
                return this.mLayoutManager.getHeightMode();
          }    
        }

可以看到 mInfinite 的值與 recyclerview的高度 與規(guī)格有關(guān)米辐,判斷==0 ? 這是什么鬼书释! 看起來(lái)一點(diǎn)卵用都沒(méi)有 ?_?(是在下輸了)

2.1翘贮、remainingspace

  int remainingSpace = layoutState.mAvailable + layoutState.mExtra;

第一次 layoutState.mAvailable的取值

    private void updateAnchorInfoForLayout(Recycler recycler, State state, LinearLayoutManager.AnchorInfo anchorInfo) {
        if(!this.updateAnchorFromPendingData(state, anchorInfo)) {
        }
        
    private boolean updateAnchorFromPendingData(State state, LinearLayoutManager.AnchorInfo anchorInfo) {
    //.....
               if(anchorInfo.mLayoutFromEnd) {
            anchorInfo.mCoordinate = this.mOrientationHelper.getEndAfterPadding() - this.mPendingSavedState.mAnchorOffset;
        } else {
            anchorInfo.mCoordinate = this.mOrientationHelper.getStartAfterPadding() + this.mPendingSavedState.mAnchorOffset;
        }
        //.........
    }
    
    private void updateLayoutStateToFillStart(LinearLayoutManager.AnchorInfo anchorInfo) {
        this.updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
    }

    private void updateLayoutStateToFillStart(int itemPosition, int offset) {
        this.mLayoutState.mAvailable = offset - this.mOrientationHelper.getStartAfterPadding();
    }
    

可以看到 第一次 layoutState.mAvailable的值是通過(guò) 錨點(diǎn) AnchorInfo來(lái)計(jì)算的。
其中還判斷了mLayoutFromEnd ...

看到這里我是暈的爆惧,變量太多了狸页。。扯再。

第一次 layoutState.mExtra的值
extra意思是額外...

2.3芍耘、layoutstate.hasMore(state)

        boolean hasMore(State state) {
            return this.mCurrentPosition >= 0 && this.mCurrentPosition < state.getItemCount();
        }

就是判斷 position址遇;

2.4、擺完一個(gè)布局以后斋竞,干了些什么

布局是一個(gè)一個(gè)添加的倔约,那么在加載完一個(gè)children view后在加載了什么呢?

    int fill(Recycler recycler, LinearLayoutManager.LayoutState layoutState, State state, boolean stopOnFocusable) {
    
        while((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if(layoutChunkResult.mFinished) {
                break;
            }

    // 加上偏移量
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            if(!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                //  減去剩余空間
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if(layoutState.mScrollingOffset != -2147483648) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if(layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }

                this.recycleByLayoutState(recycler, layoutState);
            }

            if(stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
            //....
        }
    }
void layoutChunk(Recycler recycler, State state, LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result){
  //......
    //得到 消耗的高度/寬度
    result.mConsumed = this.mOrientationHelper.getDecoratedMeasurement(view);
    // 布局
    addView(view); 
    layout
    // 判斷是否被隱藏忽略
     if(params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
     }
     // 焦點(diǎn)
     result.mFocusable = view.isFocusable();
}

mOrientationHelper.calss
        public int getDecoratedMeasurement(View view) {
            LayoutParams params = (LayoutParams)view.getLayoutParams();
            return this.mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin;
        }

可以看到 layoutChunk()中賦值layoutChunkResult坝初,在之后偏移量添加layoutState.mOffset+=...跺株,剩余空間減去了view消耗的數(shù)量 remainingSpace-= layoutChunkResult.mConsumed。

還是回到了剛開(kāi)始的注釋: 先找到錨點(diǎn)脖卖,計(jì)算第一個(gè)坐標(biāo)乒省, 在循環(huán)添加view、計(jì)算偏移量確定擺放位置畦木。

2.5 布局的一些總結(jié)

來(lái)一點(diǎn)總結(jié)吧:

涉及到的主要類
1袖扛、 LinearLayoutManager.LayoutState:布局狀態(tài)類(有布局方向(向上向下)、結(jié)束布局十籍、偏移量...)
2蛆封、Recycler 布局支持類、可回收view勾栗、創(chuàng)建view...
3惨篱、AnchorInfo 錨點(diǎn)信息類

布局流程:
1、以開(kāi)始方向先更新錨點(diǎn)信息
2围俘、在通過(guò)錨點(diǎn)信息 賦值layoutState 得到可用空間砸讳,偏移量等信息, while(剩余空間) 擺放childrenView 改變偏移量;
3、以底部更新錨點(diǎn)信息
4界牡、重復(fù)第2步
5簿寂、結(jié)束

原理其實(shí)是很簡(jiǎn)單的,只不過(guò)其中變量太多了宿亡,代碼也是很多所以看得云里霧里的常遂,有時(shí)候還會(huì)跑偏找不到南北。
畫(huà)一個(gè)流程圖吧挽荠。

onChildren填充機(jī)制

其中填充規(guī)則 fill()方法流程如下

fill填充規(guī)則

3克胳、 滑動(dòng)時(shí)干了哪些事情

第一次布局看起來(lái)比較簡(jiǎn)單,我們就可以去猜測(cè) 滑動(dòng)時(shí)應(yīng)該加載布局也是一樣的圈匆。 去看看源碼吧漠另。

    public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
        return this.mOrientation == 1?0:this.scrollBy(dx, recycler, state);
    }

    public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
        return this.mOrientation == 0?0:this.scrollBy(dy, recycler, state);
    }

有兩個(gè), 分析scrollVerticallyBy臭脓。

    int scrollBy(int dy, Recycler recycler, State state) {
        if(this.getChildCount() != 0 && dy != 0) {
            this.mLayoutState.mRecycle = true;
            this.ensureLayoutState();
            int layoutDirection = dy > 0?1:-1;
            int absDy = Math.abs(dy);
            this.updateLayoutState(layoutDirection, absDy, true, state);
            // 滑動(dòng)且 返回消耗的距離
            int consumed = this.mLayoutState.mScrollingOffset + this.fill(recycler, this.mLayoutState, state, false);
            if(consumed < 0) {
                return 0;
            } else {
            // 計(jì)算滑動(dòng)的量 
                int scrolled = absDy > consumed?layoutDirection * consumed:dy;
                this.mOrientationHelper.offsetChildren(-scrolled);
                this.mLayoutState.mLastScrollDelta = scrolled;
                return scrolled;
            }
        } else {
            return 0;
        }
    }
    
      private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, State state) {
        this.mLayoutState.mInfinite = this.resolveIsInfinite();
        this.mLayoutState.mExtra = this.getExtraLayoutSpace(state);
        this.mLayoutState.mLayoutDirection = layoutDirection;
        int scrollingOffset;
        View child;
        if(layoutDirection == 1) {
            this.mLayoutState.mExtra += this.mOrientationHelper.getEndPadding();
            child = this.getChildClosestToEnd();
            this.mLayoutState.mItemDirection = this.mShouldReverseLayout?-1:1;
            this.mLayoutState.mCurrentPosition = this.getPosition(child) + this.mLayoutState.mItemDirection;
            this.mLayoutState.mOffset = this.mOrientationHelper.getDecoratedEnd(child);
            scrollingOffset = this.mOrientationHelper.getDecoratedEnd(child) - this.mOrientationHelper.getEndAfterPadding();
        } else {
            child = this.getChildClosestToStart();
            this.mLayoutState.mExtra += this.mOrientationHelper.getStartAfterPadding();
            this.mLayoutState.mItemDirection = this.mShouldReverseLayout?1:-1;
            this.mLayoutState.mCurrentPosition = this.getPosition(child) + this.mLayoutState.mItemDirection;
            this.mLayoutState.mOffset = this.mOrientationHelper.getDecoratedStart(child);
            scrollingOffset = -this.mOrientationHelper.getDecoratedStart(child) + this.mOrientationHelper.getStartAfterPadding();
        }
        // 可用距離  偏移距離 
        this.mLayoutState.mAvailable = requiredSpace;
        if(canUseExistingSpace) {
            this.mLayoutState.mAvailable -= scrollingOffset;
        }

        this.mLayoutState.mScrollingOffset = scrollingOffset;
    }

scrollVerticallyBy 的作用是 酗钞,改變layoutState,調(diào)用fill,布局view砚作,返回實(shí)際消耗的距離窘奏。

fill的流程我們?cè)谏厦嬉惨呀?jīng)做了說(shuō)明了。
補(bǔ)充一點(diǎn)的是葫录,當(dāng)我們滑動(dòng)的距離在一定范圍內(nèi)(沒(méi)有超出頁(yè)面上 第一個(gè)或最后一個(gè)child在屏幕上的范圍)是不會(huì)重新

4着裹、 錨點(diǎn)的位置是如何確定的

從以上流程中我們了解到了layoutManager的一些布局的規(guī)則,我們的布局都是通過(guò)一個(gè)基準(zhǔn)點(diǎn)來(lái)進(jìn)行上下布局米同,那么最重要的肯定就是這個(gè)基準(zhǔn)點(diǎn)即錨點(diǎn)的位置骇扇。
我們知道錨點(diǎn)的信息 保存在 LinearLayoutManager.AnchorInfo中,進(jìn)而layouState控制布局

1面粮、第一次錨點(diǎn)得位置

void onLayoutChildren(Recycler recycler, State state) {
}
    private void updateAnchorInfoForLayout(Recycler recycler, State state, LinearLayoutManager.AnchorInfo anchorInfo) {
        if(!this.updateAnchorFromPendingData(state, anchorInfo)) {
            if(!this.updateAnchorFromChildren(recycler, state, anchorInfo)) {
            //分配坐標(biāo)   
                anchorInfo.assignCoordinateFromPadding();
            //得到數(shù)據(jù)集位置
                anchorInfo.mPosition = this.mStackFromEnd?state.getItemCount() - 1:0;
            }
        }
    }
        class AnchorInfo {
            void assignCoordinateFromPadding() {
            this.mCoordinate = this.mLayoutFromEnd?LinearLayoutManager.this.mOrientationHelper.getEndAfterPadding():LinearLayoutManager.this.mOrientationHelper.getStartAfterPadding();
            }
        }

第一次特別簡(jiǎn)單少孝,就判斷布局方向獲取mCoordinate 開(kāi)始或結(jié)束的padding與itemView的數(shù)據(jù)集位置position。

2熬苍、 滑動(dòng)位置的確定

    int scrollBy(int dy, Recycler recycler, State state) {
            this.updateLayoutState(layoutDirection, absDy, true, state);
    }
    private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, State state) {
        this.mLayoutState.mInfinite = this.resolveIsInfinite();
        this.mLayoutState.mExtra = this.getExtraLayoutSpace(state);
        this.mLayoutState.mLayoutDirection = layoutDirection;
        int scrollingOffset;
        View child;
        if(layoutDirection == 1) {
            this.mLayoutState.mExtra += this.mOrientationHelper.getEndPadding();
            child = this.getChildClosestToEnd();
            this.mLayoutState.mItemDirection = this.mShouldReverseLayout?-1:1;
            
            this.mLayoutState.mCurrentPosition = this.getPosition(child) + this.mLayoutState.mItemDirection; 
            this.mLayoutState.mOffset = this.mOrientationHelper.getDecoratedEnd(child);
            scrollingOffset = this.mOrientationHelper.getDecoratedEnd(child) - this.mOrientationHelper.getEndAfterPadding();
        } else {
            child = this.getChildClosestToStart();
            this.mLayoutState.mExtra += this.mOrientationHelper.getStartAfterPadding();
            this.mLayoutState.mItemDirection = this.mShouldReverseLayout?1:-1;
            this.mLayoutState.mCurrentPosition = this.getPosition(child) + this.mLayoutState.mItemDirection;
            this.mLayoutState.mOffset = this.mOrientationHelper.getDecoratedStart(child);
            scrollingOffset = -this.mOrientationHelper.getDecoratedStart(child) + this.mOrientationHelper.getStartAfterPadding();
        }

        this.mLayoutState.mAvailable = requiredSpace;
        if(canUseExistingSpace) {
            this.mLayoutState.mAvailable -= scrollingOffset;
        }

        this.mLayoutState.mScrollingOffset = scrollingOffset;
    }

給張圖吧

文章視頻地址:鏈接:http://pan.baidu.com/s/1o7Ai48E 密碼:98pc

recyclerView滾動(dòng)時(shí)布局流程

· RecyclerView(1)- Decoration源碼解析
· RecyclerView(2)- 自定義Decoration打造時(shí)光軸效果
· RecyclerView(3)- LayoutMagager源碼解析,LinearLayoutManager
· RecyclerView(4)- 核心稍走、Recycler復(fù)用機(jī)制_1
· RecyclerView(4)- 核心、Recycler復(fù)用機(jī)制_2
· RecyclerView(5)- 自定義LayoutManager(布局柴底、復(fù)用)
· RecyclerView(6)- 自定義ItemAnimator
· RecyclerView(7)- ItemTouchHelper
· RecyclerView(8)- MultiTypeAdapter文章婿脸、MultiTypeAdapter Github地址
文章視頻地址:鏈接:http://pan.baidu.com/s/1hssvXC4 密碼:18v1


希望我的文章不會(huì)誤導(dǎo)在觀看的你,如果有異議的地方歡迎討論和指正柄驻。
如果能給觀看的你帶來(lái)收獲狐树,那就是最好不過(guò)了。

人生得意須盡歡, 桃花塢里桃花庵
點(diǎn)個(gè)關(guān)注唄鸿脓,對(duì)抑钟,不信你點(diǎn)試試?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末答憔,一起剝皮案震驚了整個(gè)濱河市味赃,隨后出現(xiàn)的幾起案子掀抹,更是在濱河造成了極大的恐慌虐拓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傲武,死亡現(xiàn)場(chǎng)離奇詭異蓉驹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)揪利,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門态兴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人疟位,你說(shuō)我怎么就攤上這事瞻润。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵绍撞,是天一觀的道長(zhǎng)正勒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)傻铣,這世上最難降的妖魔是什么章贞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮非洲,結(jié)果婚禮上鸭限,老公的妹妹穿的比我還像新娘。我一直安慰自己两踏,他們只是感情好败京,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著梦染,像睡著了一般喧枷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弓坞,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天隧甚,我揣著相機(jī)與錄音,去河邊找鬼渡冻。 笑死戚扳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的族吻。 我是一名探鬼主播帽借,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼超歌!你這毒婦竟也來(lái)了砍艾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巍举,失蹤者是張志新(化名)和其女友劉穎脆荷,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體懊悯,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜓谋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炭分。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桃焕。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捧毛,靈堂內(nèi)的尸體忽然破棺而出观堂,到底是詐尸還是另有隱情让网,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布师痕,位于F島的核電站寂祥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏七兜。R本人自食惡果不足惜丸凭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腕铸。 院中可真熱鬧惜犀,春花似錦、人聲如沸狠裹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涛菠。三九已至莉御,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俗冻,已是汗流浹背礁叔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迄薄,地道東北人琅关。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像讥蔽,于是被迫代替她去往敵國(guó)和親涣易。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • RecyclerView 源碼分析 本文原創(chuàng)冶伞,轉(zhuǎn)載請(qǐng)注明出處新症。歡迎關(guān)注我的 簡(jiǎn)書(shū) ,關(guān)注我的專題 Android ...
    MeloDev閱讀 10,082評(píng)論 6 49
  • RecyclerView 概要 RecyclerView是Android 5.0開(kāi)始提供一個(gè)可回收容器响禽,它比 Li...
    rexyren閱讀 5,605評(píng)論 10 27
  • RecyclerView包含以下幾個(gè)重要的組件:1.LayoutManager: 測(cè)量和布局子View2.Recy...
    烏龜愛(ài)吃肉閱讀 3,516評(píng)論 4 7
  • 假如說(shuō)“我愛(ài)你” 我害怕 天際會(huì)劃落星辰 黑夜吞噬白晝 假如說(shuō)“我愛(ài)你” 我害怕 河流會(huì)失去海洋 洪水浸沒(méi)家園...
    花香蝶往閱讀 269評(píng)論 0 0
  • 唧唧蟲(chóng)聲響徒爹,汪汪狗吠長(zhǎng)。 江南種紅豆金抡,相思在北方瀑焦。
    何典閱讀 271評(píng)論 2 2