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

自從有了Recycleview,很多原本是我們的Listview業務都被替代了,關於兩者的簡單比較,可以看這篇文章。我們今天就去看看他背後故事,下次再寫Listview,這名征戰多年的老將。

一些不要搞懂的問題

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

他先去調用setLayoutFrozen()去停止移動,再更新適配器,最後調用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();  
    }
}

我們看到他背後做的是發送一個cancelEvent同時調用了stopScroll()去停止滾動,背後是怎麼停止滾動的呢?

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

上面代碼我們看到些有意思的東西,他先去調用我們的mLayout去設置狀態是IDLE閒置狀態,再不通知監聽的接口更新狀態。最後纔是實際的調用mLayout的stopSmoothScroller()去停止,這個SmoothScroller是一個靜態的抽象內部類,具體幹活的是LinearSmoothScroller
這個類最終是這mLayout是LayoutManager類,它是RecycleView的一個靜態的抽象內部類,主要負責的是Measuring和Positioning我們的Item views 。
幹活的有三個StaggeredGridLayoutManagerLinearLayoutManagerGridLayoutManager

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

相信使用過RecyclerView的應該對這麼名字不陌生,經典的案例就是拿來修改方向燈。這個類有個2K行的就不深挖了,點到即可,繼續回主線。

    /**
     * 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一樣,是個假停止。如果沒運行,才調用SmoothScroller.onStop()去實際的停止。

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

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

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

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

這樣他就先去調用clear函數去清空原有的。再去調用RecycledViewPool的更新。
需要補充下,這個RecycledViewPool是RecyclerViews的靜態內部類,他可以讓你做到在不同的RecyclerViews內共享Views,這確實對我們的第一個問題有一定的解答作用,因爲這是一個靜態內部類啊,而且我們的View都是繼承自ViewHolder的,就像我們java的object給人的感覺一樣。這樣用一個內部的ViewPool的做法,就像線程池,我們可以達到了更高的複用,提高滾動的效率。

private SparseArray<ArrayList<ViewHolder>> mScrap;

這個是RecycledViewPool內部使用稀疏數組來存儲我們的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++;//啊...這句讓我有點意外,傳的參數留着以後用?那就以後再加嘛.. 
}

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從而存儲對應的ViewHolder列表。
目前在我的開發項目中,這個ViewType存在感有點弱啊。
查看整個過程,發現這個itemViewType最後就是調用的是getItemViewType(int position),默認爲0;

final int type = mAdapter.getItemViewType(offsetPosition);

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

看完大致的設置適配器部分內容,我們繼續回主線。
到了最後的一個函數

requestLayout();

因爲我們的RecyclerView是直接繼承ViewGroup 的,那這句就會導致重畫等步驟,我們繼續看下去吧。
說道這裏感覺也可以再開個貼,介紹下View的繪製流程和事件的傳遞流程,下次有空再寫吧,雖然現在介紹這個已是爛大街的了,但自己來寫應該有什麼感覺呢?寫了才知道 ^_^
繼續:

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


今天時間有限,下次繼續寫。。。

後記

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

LondonEyeLayoutManager

他的效果就像摩天輪一樣繞着轉動!
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章