源碼探索系列10---替代Listview的RecycleView

自從有了Recycleview,很多原本是我們的Listview業(yè)務(wù)都被替代了,關(guān)于兩者的簡單比較,可以看這篇文章。我們今天就去看看他背后故事,下次再寫Listview,這名征戰(zhàn)多年的老將。

一些不要搞懂的問題

  1. 為何谷歌推薦用這個(gè),背后的效率是高在哪里?
  2. LayoutManager是怎么去弄不同布局的

起航

API:23 ,這RecyclerView有一萬多行,看起來真的亞歷山大啊。

我們常用的方式就是下面這樣:

mRecycleView.setAdapter(mAdapter);

扔給他一個(gè)適配器,所以這個(gè)就當(dāng)作我們的起航的第一個(gè)突破口吧,看下他背后都做了些什么事。

public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
}

他先去調(diào)用setLayoutFrozen()去停止移動(dòng),再更新適配器,最后調(diào)用requestLayout()去更新界面。這里補(bǔ)充說下,這個(gè)RecyclerView是直接繼承ViewGroup的。

public void setLayoutFrozen(boolean frozen) {
    if (frozen != mLayoutFrozen) { 
      ...
      final long now = SystemClock.uptimeMillis();
      MotionEvent cancelEvent = MotionEvent.obtain(now, now,
              MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
      onTouchEvent(cancelEvent);
      mLayoutFrozen = frozen;
      mIgnoreMotionEventTillDown = true;
      stopScroll();  
    }
}

我們看到他背后做的是發(fā)送一個(gè)cancelEvent同時(shí)調(diào)用了stopScroll()去停止?jié)L動(dòng),背后是怎么停止?jié)L動(dòng)的呢?

public void stopScroll() {
    setScrollState(SCROLL_STATE_IDLE);
    stopScrollersInternal();
}

private void setScrollState(int state) {
    if (state == mScrollState) {
        return;
    } 
    ...
    mScrollState = state; 
    dispatchOnScrollStateChanged(state);
}

void dispatchOnScrollStateChanged(int state) {
    // Let the LayoutManager go first; this allows it to bring any properties into
    // a consistent state before the RecyclerView subclass responds.
    if (mLayout != null) {
        mLayout.onScrollStateChanged(state);
    }

    // Let the RecyclerView subclass handle this event next; any LayoutManager property
    // changes will be reflected by this time.
    onScrollStateChanged(state);

    // Listeners go last. All other internal state is consistent by this point.
    if (mScrollListener != null) {
        mScrollListener.onScrollStateChanged(this, state);
    }
    if (mScrollListeners != null) {
        for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
            mScrollListeners.get(i).onScrollStateChanged(this, state);
        }
    }
}

/**
 * Similar to {@link #stopScroll()} but does not set the state.
 */
private void stopScrollersInternal() {
    mViewFlinger.stop();
    if (mLayout != null) {
        mLayout.stopSmoothScroller();
    }
}

void stopSmoothScroller() {
        if (mSmoothScroller != null) {
            mSmoothScroller.stop();
        }
    }

上面代碼我們看到些有意思的東西,他先去調(diào)用我們的mLayout去設(shè)置狀態(tài)是IDLE閑置狀態(tài),再不通知監(jiān)聽的接口更新狀態(tài)。最后才是實(shí)際的調(diào)用mLayout的stopSmoothScroller()去停止,這個(gè)SmoothScroller是一個(gè)靜態(tài)的抽象內(nèi)部類,具體干活的是LinearSmoothScroller
這個(gè)類最終是這mLayout是LayoutManager類,它是RecycleView的一個(gè)靜態(tài)的抽象內(nèi)部類,主要負(fù)責(zé)的是Measuring和Positioning我們的Item views 。
干活的有三個(gè)StaggeredGridLayoutManagerLinearLayoutManagerGridLayoutManager

StaggeredGridLayoutManager mGridLayoutManager =
                    new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//兩列豎直方向的瀑布流
mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);

相信使用過RecyclerView的應(yīng)該對這么名字不陌生,經(jīng)典的案例就是拿來修改方向燈。這個(gè)類有個(gè)2K行的就不深挖了,點(diǎn)到即可,繼續(xù)回主線。

    /**
     * Stops running the SmoothScroller in each animation callback. Note that this does not
     * cancel any existing {@link Action} updated by
     * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
     * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
     */
final protected void stop() {
        if (!mRunning) {
            return;
        }
        onStop();
        mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
        mTargetView = null;
        mTargetPosition = RecyclerView.NO_POSITION;
        mPendingInitialRun = false;
        mRunning = false;
        // trigger a cleanup
        mLayoutManager.onSmoothScrollerStopped(this);
        // clear references to avoid any potential leak by a custom smooth scroller
        mLayoutManager = null;
        mRecyclerView = null;
    } 

我們到一個(gè)有意思的事情了,他在運(yùn)行了得情況下并沒有實(shí)際的去停止運(yùn)行,就像我們的AsyncTask一樣,是個(gè)假停止。如果沒運(yùn)行,才調(diào)用SmoothScroller.onStop()去實(shí)際的停止。

繼續(xù)回主線,我們看完 setLayoutFrozen(false)的過程
現(xiàn)在繼續(xù)下一步

setAdapterInternal(adapter, false, true);

private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
     ...
     
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
}

這個(gè)更改適配器 的界面,主要就更換了原來的適配器,然后注冊新的數(shù)據(jù)觀察者等操作
重要一句是調(diào)用Recycler的onAdapterChanged()方法。這個(gè)Recycler主要的工作是負(fù)責(zé)我們在RecyclerView上的各自小itemView的重用功能,所以我們更新了適配器需要告訴下人家。

void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
            boolean compatibleWithPrevious) {
        clear();
        getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
    }

這樣他就先去調(diào)用clear函數(shù)去清空原有的。再去調(diào)用RecycledViewPool的更新。
需要補(bǔ)充下,這個(gè)RecycledViewPool是RecyclerViews的靜態(tài)內(nèi)部類,他可以讓你做到在不同的RecyclerViews內(nèi)共享Views,這確實(shí)對我們的第一個(gè)問題有一定的解答作用,因?yàn)檫@是一個(gè)靜態(tài)內(nèi)部類啊,而且我們的View都是繼承自ViewHolder的,就像我們java的object給人的感覺一樣。這樣用一個(gè)內(nèi)部的ViewPool的做法,就像線程池,我們可以達(dá)到了更高的復(fù)用,提高滾動(dòng)的效率。

private SparseArray<ArrayList<ViewHolder>> mScrap;

這個(gè)是RecycledViewPool內(nèi)部使用稀疏數(shù)組來存儲(chǔ)我們的ViewHolder。嗯,稀疏,直覺好像覺得不對啊,后面看完再看下是怎么回事.

void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
            boolean compatibleWithPrevious) {
    if (oldAdapter != null) {
        detach();
    }
    if (!compatibleWithPrevious && mAttachCount == 0) {
         clear();
     }
    if (newAdapter != null) {
       attach(newAdapter);
    }
 } 

void detach() {
     mAttachCount--;
}
    
void attach(Adapter adapter) {
      mAttachCount++;//啊...這句讓我有點(diǎn)意外,傳的參數(shù)留著以后用?那就以后再加嘛.. 
}

public void clear() {
        mScrap.clear();
    }

這里記錄有多少個(gè)適配器,同時(shí)保存我們的ViewHolder,當(dāng)我們的適配器都移除了,那就清空緩存的ViewHolder。
我們看下他存的方式

public void putRecycledView(ViewHolder scrap) {
     final int viewType = scrap.getItemViewType();
     final ArrayList scrapHeap = getScrapHeapForType(viewType);
     if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
         return;
     }
     if (DEBUG && scrapHeap.contains(scrap)) {
         throw new IllegalArgumentException("this scrap item already exists");
     }
     scrap.resetInternal();
     scrapHeap.add(scrap);
 }

private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
    ArrayList<ViewHolder> scrap = mScrap.get(viewType);
      if (scrap == null) {
          scrap = new ArrayList<ViewHolder>();
          mScrap.put(viewType, scrap);
          if (mMaxScrap.indexOfKey(viewType) < 0) {
              mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
          }
      }
      return scrap;
 }

他的存儲(chǔ)是用viewType來做key從而存儲(chǔ)對應(yīng)的ViewHolder列表。
目前在我的開發(fā)項(xiàng)目中,這個(gè)ViewType存在感有點(diǎn)弱啊。
查看整個(gè)過程,發(fā)現(xiàn)這個(gè)itemViewType最后就是調(diào)用的是getItemViewType(int position),默認(rèn)為0;

final int type = mAdapter.getItemViewType(offsetPosition);

這個(gè)補(bǔ)充一點(diǎn),在前面的一篇比較RecyclerView和Listview的文章有提到,如果要給我們的RecyclerView添加頭和尾,不想Listview那樣可以 簡單的加,實(shí)際會(huì)負(fù)責(zé)一點(diǎn),其中就需要用到這個(gè)函數(shù)。具體的看 Listview和RecycleView的簡單比較 這篇文章里面的缺點(diǎn)第一條。

看完大致的設(shè)置適配器部分內(nèi)容,我們繼續(xù)回主線。
到了最后的一個(gè)函數(shù)

requestLayout();

因?yàn)槲覀兊腞ecyclerView是直接繼承ViewGroup 的,那這句就會(huì)導(dǎo)致重畫等步驟,我們繼續(xù)看下去吧。
說道這里感覺也可以再開個(gè)貼,介紹下View的繪制流程和事件的傳遞流程,下次有空再寫吧,雖然現(xiàn)在介紹這個(gè)已是爛大街的了,但自己來寫應(yīng)該有什么感覺呢?寫了才知道 _
繼續(xù):

我們看下實(shí)際的繪制界面的部分吧


今天時(shí)間有限,下次繼續(xù)寫。。。

后記

那個(gè)layoutManager可以做很多文章啊,上次就看到一個(gè)有意思的項(xiàng)目叫倫敦眼的

LondonEyeLayoutManager

他的效果就像摩天輪一樣繞著轉(zhuǎn)動(dòng)!


這里寫圖片描述
這里寫圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,468評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,431評論 2 378

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

  • 自從有了Recycleview,很多原本是我們的Listview業(yè)務(wù)都被替代了,關(guān)于兩者的簡單比較,可以看這篇文章...
    SanjayF閱讀 2,731評論 0 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,715評論 25 708
  • 文/麗子 史鐵生說:“發(fā)燒了,才知道不發(fā)燒的日子是多么清爽;咳嗽了,才體會(huì)不咳嗽的嗓子多么安詳。剛坐上輪椅時(shí),我老...
    麗子a閱讀 336評論 8 6
  • 絲簾頻撫眉前,不平焉。 抿袖怨春寒兩次三番。 雖北地,經(jīng)微雨,似淮南。 清霧催人愁每每無端。 ————————— ...
    陳婉_閱讀 476評論 0 3
  • 題記:Each man is the architect of his own fate.——每個(gè)人都是自己命運(yùn)的...
    LynnXYT閱讀 876評論 1 4