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

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

一些不要搞懂的問題

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

起航

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

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

mRecycleView.setAdapter(mAdapter);

扔給他一個適配器获列,所以這個就當作我們的起航的第一個突破口吧谷市,看下他背后都做了些什么事。

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

他先去調(diào)用setLayoutFrozen()去停止移動击孩,再更新適配器迫悠,最后調(diào)用requestLayout()去更新界面。這里補充說下巩梢,這個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ā)送一個cancelEvent同時調(diào)用了stopScroll()去停止?jié)L動,背后是怎么停止?jié)L動的呢且改?

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)又跛。最后才是實際的調(diào)用mLayout的stopSmoothScroller()去停止碍拆,這個SmoothScroller是一個靜態(tài)的抽象內(nèi)部類,具體干活的是LinearSmoothScroller
這個類最終是這mLayout是LayoutManager類慨蓝,它是RecycleView的一個靜態(tài)的抽象內(nèi)部類感混,主要負責的是Measuring和Positioning我們的Item views 。
干活的有三個StaggeredGridLayoutManager礼烈,LinearLayoutManager弧满,GridLayoutManager

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

相信使用過RecyclerView的應(yīng)該對這么名字不陌生此熬,經(jīng)典的案例就是拿來修改方向燈庭呜。這個類有個2K行的就不深挖了滑进,點到即可,繼續(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;
    } 

我們到一個有意思的事情了扶关,他在運行了得情況下并沒有實際的去停止運行,就像我們的AsyncTask一樣数冬,是個假停止节槐。如果沒運行,才調(diào)用SmoothScroller.onStop()去實際的停止拐纱。

繼續(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();
}

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

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

這樣他就先去調(diào)用clear函數(shù)去清空原有的币绩。再去調(diào)用RecycledViewPool的更新。
需要補充下府阀,這個RecycledViewPool是RecyclerViews的靜態(tài)內(nèi)部類缆镣,他可以讓你做到在不同的RecyclerViews內(nèi)共享Views,這確實對我們的第一個問題有一定的解答作用试浙,因為這是一個靜態(tài)內(nèi)部類啊董瞻,而且我們的View都是繼承自ViewHolder的,就像我們java的object給人的感覺一樣田巴。這樣用一個內(nèi)部的ViewPool的做法钠糊,就像線程池,我們可以達到了更高的復(fù)用壹哺,提高滾動的效率抄伍。

private SparseArray<ArrayList<ViewHolder>> mScrap;

這個是RecycledViewPool內(nèi)部使用稀疏數(shù)組來存儲我們的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++;//啊...這句讓我有點意外箩朴,傳的參數(shù)留著以后用岗喉?那就以后再加嘛.. 
}

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

這里記錄有多少個適配器,同時保存我們的ViewHolder炸庞,當我們的適配器都移除了钱床,那就清空緩存的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;
 }

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

final int type = mAdapter.getItemViewType(offsetPosition);

這個補充一點,在前面的一篇比較RecyclerView和Listview的文章有提到懂衩,如果要給我們的RecyclerView添加頭和尾,不想Listview那樣可以 簡單的加金踪,實際會負責一點浊洞,其中就需要用到這個函數(shù)。具體的看 Listview和RecycleView的簡單比較 這篇文章里面的缺點第一條胡岔。

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

requestLayout();

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

我們看下實際的繪制界面的部分吧


今天時間有限,下次繼續(xù)寫匠楚。巍膘。。

后記

那個layoutManager可以做很多文章啊芋簿,上次就看到一個有意思的項目叫倫敦眼的

LondonEyeLayoutManager

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


這里寫圖片描述
這里寫圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市与斤,隨后出現(xiàn)的幾起案子肪康,更是在濱河造成了極大的恐慌,老刑警劉巖幽告,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梅鹦,死亡現(xiàn)場離奇詭異,居然都是意外死亡冗锁,警方通過查閱死者的電腦和手機齐唆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冻河,“玉大人箍邮,你說我怎么就攤上這事茉帅。” “怎么了锭弊?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵堪澎,是天一觀的道長。 經(jīng)常有香客問我味滞,道長樱蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任剑鞍,我火速辦了婚禮昨凡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚁署。我一直安慰自己便脊,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布光戈。 她就那樣靜靜地躺著哪痰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪久妆。 梳的紋絲不亂的頭發(fā)上晌杰,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音筷弦,去河邊找鬼乎莉。 笑死,一個胖子當著我的面吹牛奸笤,可吹牛的內(nèi)容都是我干的惋啃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼监右,長吁一口氣:“原來是場噩夢啊……” “哼边灭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起健盒,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绒瘦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扣癣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰帽,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓮具。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡讯私,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出力图,到底是詐尸還是另有隱情爽篷,我是刑警寧澤周蹭,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布爵嗅,位于F島的核電站娇澎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏睹晒。R本人自食惡果不足惜趟庄,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伪很。 院中可真熱鬧岔激,春花似錦、人聲如沸是掰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽键痛。三九已至,卻和暖如春匾七,著一層夾襖步出監(jiān)牢的瞬間絮短,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工昨忆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丁频,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓邑贴,卻偏偏與公主長得像席里,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拢驾,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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