成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

嗨!這是一篇值得深入學(xué)習(xí)的控件-RecyclerView(源碼解析篇)

myeveryheart / 2184人閱讀

摘要:其實(shí)通過(guò)父類(lèi)的這個(gè)方法之后會(huì)調(diào)用它的方法,這個(gè)名字熟悉自定義的童鞋都知道了。

為什么要寫(xiě)這篇源碼解析呢?

我一直在說(shuō)RecyclerView是一個(gè)值得深入學(xué)習(xí),甚至可以說(shuō)是一門(mén)具有藝術(shù)性的控件。那到底哪里值得我們花時(shí)間去深入學(xué)習(xí)呢。沒(méi)錯(cuò)了,就是源碼的設(shè)計(jì)。但是看源碼其實(shí)是一件不簡(jiǎn)單的事情,就拿RecyclerView的源碼來(lái)說(shuō),打開(kāi)源碼一看,往下拉啊拉啊,我擦,怎么還沒(méi)到頭,汗....居然有12k+行??吹竭@里恐怕會(huì)嚇一跳,就這么一個(gè)看似簡(jiǎn)單的控件就這么多行源碼,這讓我從何看起,一股畏懼感油然而生。

其實(shí)不需要害怕,我們不需要一開(kāi)始就想完全弄懂它每一步怎么實(shí)現(xiàn)的,這樣反而會(huì)造成只見(jiàn)森林不見(jiàn)樹(shù)木的感覺(jué)。我們就把源碼就當(dāng)成一片森林來(lái)說(shuō)吧。首先我們只需要先抓住一條路徑去看,也就是帶著一個(gè)問(wèn)題去看,這樣就能夠把這條路徑上的樹(shù)都看明白了。就不會(huì)有只見(jiàn)森林不見(jiàn)樹(shù),一臉茫然了。當(dāng)然我們大多數(shù)情況肯定是不滿足于此一條路徑,想完全看明白它是怎么實(shí)現(xiàn)的,那就繼續(xù)另開(kāi)路徑(再帶著另外一個(gè)問(wèn)題),繼續(xù)看這條路上的樹(shù)。當(dāng)你把每條路都走差不多了,再回頭來(lái)看,就會(huì)發(fā)現(xiàn)你既見(jiàn)到了森林又見(jiàn)到了一顆顆清晰樹(shù)木,猶如醍醐灌頂、豁然開(kāi)朗。

說(shuō)著很簡(jiǎn)單,但是不得不說(shuō)看源碼的過(guò)程還是有點(diǎn)小痛苦的。不過(guò),不用慌,看完之后你所獲得那種充實(shí)感和滿足感會(huì)遠(yuǎn)遠(yuǎn)大于過(guò)程中的痛苦感。畢竟這是一個(gè)充滿藝術(shù)感的控件嘛,值得我們?nèi)バ蕾p和學(xué)習(xí)。

那么開(kāi)始放正片了......
一、開(kāi)辟一條路徑

從使用RecyclerView的時(shí)候,它的一個(gè)功能就讓我感覺(jué)很這個(gè)控件不簡(jiǎn)單,不知道你和我想的是不是一樣。那是什么功能呢?我們只需改變一行代碼就可以直接設(shè)置它的ItemView為水平布局、垂直布局、表格布局以及瀑布流布局。這是ListView所不能做到的。用起來(lái)簡(jiǎn)單,其背后肯定有故事啊。那我們就以這條路為核心來(lái)看這片森林了。

二、開(kāi)始尋路

從哪里開(kāi)始看呢?
1.我們先從setAdapter()看起,這個(gè)方法我們比較熟悉,在Activity中這是我們直接接觸的方法。

/**
*Replaces the current adapter with the new one and triggers listeners.
*/
 public void setAdapter(Adapter adapter){

        .....

        //用一個(gè)新的設(shè)配器和觸發(fā)器來(lái)替代目前正在使的
        setAdapterInternal(adapter,false,true);
        //請(qǐng)求布局,直接調(diào)用View類(lèi)的請(qǐng)求布局方法
        requestLayout();
    }

setAdapter里面主要做了兩件事:

首先調(diào)用setAdapterInternal方法,目的是用一個(gè)新的設(shè)配器和觸發(fā)器來(lái)替代目前正在使用的。
我們深入進(jìn)去看看它做了什么?

對(duì)于熟悉了觀察者設(shè)計(jì)模式的,可以從下面的代碼看出來(lái),其實(shí)里面有個(gè)操作是:

注銷(xiāo)觀察者(之前的設(shè)配器)和注冊(cè)觀察者(新的設(shè)配器)操作。簡(jiǎn)單的理解一下就是設(shè)配器觀察者會(huì)監(jiān)測(cè)一些對(duì)象的狀態(tài),當(dāng)這些對(duì)象狀態(tài)改變,它可以通過(guò)這種設(shè)計(jì)模式低耦合的做出相應(yīng)的改變。最后調(diào)用markKnownViewsInvalid方法刷新一下視圖。

如果你想深入了解觀察者設(shè)計(jì)模式的可以看一下這篇文章

傳送門(mén):觀察者設(shè)計(jì)模式

{
 Adapter mAdapter;
......

 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
                                    boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);  //注銷(xiāo)觀察者
            mAdapter.onDetachedFromRecyclerView(this);          //Called by RecyclerView when it stops observing this Adapter.
        }
        ......
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);  //注冊(cè)觀察者
            adapter.onAttachedToRecyclerView(this);
        }
      ......
        //刷新視圖
        markKnownViewsInvalid();
    }

之后調(diào)用了 requestLayout方法請(qǐng)求重新布局。這個(gè)方法很關(guān)鍵,和我們的這次選的路是相通的。

 @Override
    public void requestLayout() {
        if (mEatRequestLayout == 0 && !mLayoutFrozen) {
            super.requestLayout();
        } else {
            mLayoutRequestEaten = true;
        }
    }

這么關(guān)鍵的方法代碼卻這么少?而且好像只做了一個(gè)操作?沒(méi)錯(cuò),表面上只調(diào)用了父類(lèi)View的requestLayout方法。其實(shí)通過(guò)父類(lèi)的這個(gè)方法之后會(huì)調(diào)用它的onLayout方法,這個(gè)名字熟悉自定義View的童鞋都知道了。但我們看父類(lèi)View的onLayout方法其實(shí)是個(gè)空方法。也就是說(shuō)最終需要由它的子類(lèi)來(lái)重寫(xiě),也即RecyclerVie調(diào)用自身的onLayout方法。

   @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

onLayout又調(diào)用了dispatchLayout方法,來(lái)分發(fā)layout

void dispatchLayout() {
       ......
        if (mState.mLayoutStep == State.STEP_START) {
          //分發(fā)第一步
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
              //分發(fā)第二步
            dispatchLayoutStep2();
        } 
        ......
         //分發(fā)第三步
        dispatchLayoutStep3();
        ......
    }

它把這個(gè)分發(fā)的過(guò)程分為了三步走
step1:做一下準(zhǔn)備工作:決定哪一個(gè)動(dòng)畫(huà)被執(zhí)行,保存一些目前view的相關(guān)信息

 private void dispatchLayoutStep1() {
       ......
        if (mState.mRunSimpleAnimations) {
            // Step 0: Find out where all non-removed items are, pre-layout
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                mViewInfoStore.addToPreLayout(holder, animationInfo);

            }
            ......
        }

step2:找到實(shí)際的view和最終的狀態(tài)后運(yùn)行l(wèi)ayout。

    private void dispatchLayoutStep2() {
        eatRequestLayout();
        onEnterLayoutOrScroll();
       ......

        mState.mInPreLayout = false;
         // Step 2: 運(yùn)行l(wèi)ayout
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        ....
        resumeRequestLayout(false);
    }

這里面有個(gè)方法很關(guān)鍵了,就是下面這個(gè)onLayoutChildren,這個(gè)為什么關(guān)鍵呢,先提一下這個(gè),待會(huì)要詳細(xì)說(shuō)的。

mLayout.onLayoutChildren(mRecycler, mState);

step3:做一些分發(fā)的收尾工作了,保存動(dòng)畫(huà)和一些其他的信息。和我們不同路,就不看它了。

看了這么多先喝一杯92年的肥宅快樂(lè)水壓壓驚吧~~,順便看張圖小結(jié)一下上面的過(guò)程

三、尋得果樹(shù)

之前說(shuō)過(guò)RecyclerView和ListView最大的不同就是在它們的布局實(shí)現(xiàn)上。在ListView中布局是通過(guò)自身的layoutChildren方法實(shí)現(xiàn)的,但對(duì)于RecyclerView來(lái)說(shuō)就不是了,那是誰(shuí)來(lái)實(shí)現(xiàn)了呢?

這就要從剛才結(jié)束的onLayoutChildren方法說(shuō)起了,它不是RecyclerView的類(lèi)直接方法,它是RecyclerView的內(nèi)部類(lèi)LayoutManager的方法,顧名思義,就是布局管理者了。我們的RecyclerView布局就通過(guò)這個(gè)布局管理者來(lái)做了,把這樣一個(gè)很重要的職責(zé)就交給它了。從而實(shí)現(xiàn)某種程度上的低耦合。

那我們繼續(xù)走,它是怎么執(zhí)行這一職責(zé)的。
但是點(diǎn)進(jìn)去看onLayoutChildren方法,發(fā)現(xiàn)只有一行代碼,而且還是打印的日志:必須重寫(xiě)這個(gè)方法。

 public void onLayoutChildren(Recycler recycler, State state) {
            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
        }

那么既然要重寫(xiě)必須要尋找一個(gè)子類(lèi),所以這里我就找了一個(gè)子類(lèi)LinearLayoutManager類(lèi),也是我們最常用的一種線性布局來(lái)看。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

       ......
        int startOffset;
        int endOffset;
        final int firstLayoutDirection;
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        ......
        if (mAnchorInfo.mLayoutFromEnd) {
            // 底部向頂部的填充
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;

            //填充
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            final int firstElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // 頂部向底部的填充
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
              //填充
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;

            ......
            }
        } else {
           ......
        }

        ......
    }

這個(gè)方法主要就是通過(guò)一個(gè)布局算法,實(shí)現(xiàn)itemView從頂部到底部或者底部到頂部的填充,并創(chuàng)建一個(gè)布局的狀態(tài)。接下來(lái)看一下fill方法是怎么進(jìn)行填充的。

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        ......
        //1.計(jì)算RecyclerView可用的布局寬或高
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        //2.迭代布局item View
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //3.布局item view
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            //4.計(jì)算布局偏移量
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;

            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                //5.計(jì)算剩余的可用空間
                remainingSpace -= layoutChunkResult.mConsumed;
            }
            ......
        }

        return start - layoutState.mAvailable;
    }

fill方法總的來(lái)說(shuō)用了5步實(shí)現(xiàn)了itemVIew的填充:

(1)計(jì)算RecyclerView可用的布局寬或高

(2)迭代布局item View

(3)布局itemview

(4)計(jì)算布局偏移量

(5)計(jì)算剩余的可用空間

fill方法又會(huì)循環(huán)的調(diào)用layoutChunk來(lái)進(jìn)行itemView的布局,下面先看看layoutChunk的實(shí)現(xiàn)

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //1.獲取itemview
        View view = layoutState.next(recycler);
        ......
        //2.獲取itemview的布局參數(shù)
        LayoutParams params = (LayoutParams) view.getLayoutParams();

        //3.測(cè)量Item View
        measureChildWithMargins(view, 0, 0);
        //4.計(jì)算該itemview消耗的寬和高
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);

        int left, top, right, bottom;
        //5.按照水平或豎直方向布局來(lái)計(jì)算itemview的上下左右坐標(biāo)
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            ......
        }
        6.計(jì)算itemview的邊界比如下劃線和margin,從而確定itemview準(zhǔn)確的位實(shí)現(xiàn)最終的布局
        layoutDecoratedWithMargins(view, left, top, right, bottom);

        }
        result.mFocusable = view.hasFocusable();
    }

在layoutChunk中首先從layoutState獲取此時(shí)的itemview,然后根據(jù)獲得的這個(gè)itemview獲取它的布局參數(shù)和尺寸信息,并且判斷布局方式(橫向或者縱向),以此計(jì)算出itemview的上下左右坐標(biāo)。最后調(diào)用layoutDecoratedWithMargins方法完成布局。

這樣一看就對(duì)整個(gè)過(guò)程有了個(gè)清晰的認(rèn)識(shí)了吧,有沒(méi)有感覺(jué)設(shè)計(jì)的很優(yōu)雅。

四、貫穿布局的一條線

到這里已經(jīng)算走完我們之前準(zhǔn)備走的一條路了。但從開(kāi)始到這里始終忽略了一個(gè)東西沒(méi)有說(shuō),那就在布局過(guò)程的大多方法中的參數(shù)都有一個(gè)Recycler對(duì)象。這個(gè)Recycler是什么呢?

在使用RecyclerView的過(guò)程中,我們都知道Adapter被緩存的單位不再是普通的itemview了,而是一個(gè)ViewHolder。這是和listview的一個(gè)很大的不同。

public final class Recycler {
        final ArrayList mAttachedScrap = new ArrayList<>();
        ArrayList mChangedScrap = null;
        final ArrayList mCachedViews = new ArrayList();

        private final List
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

     public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }

        View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }

         ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {

                ...
                }  

        ......

在Recycler類(lèi)的開(kāi)始就看到mAttachedScrap、mChangedScrap、mCachedViews、 mUnmodifiableAttachedScrap這幾個(gè)ViewHolder的列表對(duì)象,它們就是用來(lái)緩存ViewHolder的。

具體是怎么實(shí)現(xiàn)的這里就不做詳細(xì)的解釋了。因?yàn)檫@里一說(shuō)又會(huì)牽涉到其他的點(diǎn),子子孫孫無(wú)窮盡也,畢竟這是一個(gè)有藝術(shù)感的控件,不能指望一篇文章把它說(shuō)透哈。

到這里我們就結(jié)束了我們對(duì)RecyclerView的的源碼分析了。相信你看完會(huì)有所收獲。

作者:錦小白
https://www.jianshu.com/p/102...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76915.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<