Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecyclerView的使用與深入分析

RecyclerView的使用與深入分析

編輯:關於Android編程

最近一直在搗鼓RecyclerView,今天閒來無事就把之前收集到的資料和使用過程中的體會整理一下,寫了如下這篇博客。博客的結構跟之前的博客結構類似,首先簡單交代背景,隨後給出一個簡單使用的實例,最終根據前面遇到的一些問題,走進底層看看源碼如何實現。不過我們的重點是分析RecyclerView的ViewHolder資源回收策略。

基礎介紹

動態布局 RecyclerView的官方資料介紹是:A flexible view for providing a limited window into a large data set,大體意思就是RecyclerView是一個用於顯示大量數據的彈性視圖控件。在RecyclerView出現之前,我們往往使用ListView顯示大量的數據,對於ListView,其官方介紹是:A view that shows items in a vertically scrolling list,即垂直顯示一組數據,注意這裡加入了垂直兩個字,這也正是RecyclerView和ListView的一個非常直觀的區別。使用RecylclerView能夠很容易的實現水平、垂直、瀑布流等顯示樣式,而ListView只能進行垂直顯示。究其原因在於,ListView把布局方式硬編碼進ListView類中,而RecyclerView則把布局方式和自身解耦,交給第三方實現,因此RecyclerView可以動態設置內容的顯示方式。在這裡也建議廣大童鞋,最好把自己寫的代碼解耦出來,這樣的好處真是簡直了。 視圖資源回收 和ListView的RecycleBin類似,RecyclerView通過RecyclerView.Recycler內部類(非抽象)管理已經廢棄或與RecyclerView分離的(scrapped or detached)item view,內部使用多級回收策略。LayoutManager向Recycler要ViewHolder,Recycler先從各級緩存中尋找合適的ViewHolder,最後如果在RecyclerPool中依然沒有得到ViewHolder那麼就會通過Adapter的onCreateViewHolder方法創建一個ViewHolder。該部分具體細節在本篇博客的最後會做詳細的介紹。 豐富內部類 RecyclerView類中定義了很多的抽象公用類,把抽象類的定義寫在RecyclerView中的好處在於,當客戶使用該抽象公共類時,可以很明顯的知道當前抽象類應該是為RecyclerView服務的。比如ListView使用需要設置一個Adapter,如果對ListView不熟悉的話,你肯定不知道具體應該使用按個Adapter,而Android的Adapter又比較多。RecyclerView就好多了,它對應的是RecyclerView.Adapter。把抽象類定義在RecyclerView中還有諸如訪問RecyclerView的私有方法和私有域等好處。RecyclerView中經常使用的幾個定義在RecyclerView類中的抽象類有: RecyclerView.Adapter
  • RecyclerView.Adapter的使用方式和ListView的ListAdapter類似,向RecyclerView提供顯示的數據。但是!!RecyclerView.Adapter做了一件了不起的優化,那就是RecyclerView.Adapter的public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 方法能夠保證當前RecyclerView是確實需要你創建一個新的ViewHolder對象。而ListView的ListAdapter對應的方法getView(int position, View convertView, ViewGroup parent)需要在方法內部判斷是重新創建一個View還是刷新一個View的數據,而不明所以的客戶可能每次都會返回一個新創建的View造成ListView的卡頓和資源的浪費,;創建和刷新作為兩個不同的功能本來就應該在兩個方法中實現!慶幸的是RecyclerView.Adapter解決了這個問題。
RecyclerView.LayoutManager
  • 對於這一點我們在本節一開始的動態布局中就已經說過了,在此就不再說了。這裡給出經常使用的三個已經實現好了的LayoutManager類,LinearLayoutManager、 StaggeredGridLayoutManager、GridLayoutManager,他們分別提供線性、瀑布流、網絡視圖的布局。
RecyclerView.ViewHolder
  • RecyclerView中強制客戶使用ViewHolder,之前在談及ListView的時候就經常說到使用ViewHolder來進行優化。使用ViewHolder其中一點好處是能夠避免重復調用方法findViewById(),對當前item的View中的子View進行管理。
  • ViewHolder 描述RecylerView中某個位置的itemView和元數據信息,屬於Adapter的一部分。其實現類通常用於保存 findViewById 的結果。
ViewHolder的mFlags屬性
  • FLAG_BOUND ——ViewHolder已經綁定到某個位置,mPosition、mItemId、mItemViewType都有效
  • FLAG_UPDATE ——ViewHolder綁定的View對應的數據過時需要重新綁定,mPosition、mItemId還是一致的
  • FLAG_INVALID ——ViewHolder綁定的View對應的數據無效,需要完全重新綁定不同的數據
  • FLAG_REMOVED ——ViewHolder對應的數據已經從數據集移除
  • FLAG_NOT_RECYCLABLE ——ViewHolder不能復用
  • FLAG_RETURNED_FROM_SCRAP ——這個狀態的ViewHolder會加到scrap list被復用。
  • FLAG_CHANGED ——ViewHolder內容發生變化,通常用於表明有ItemAnimator動畫
  • FLAG_IGNORE ——ViewHolder完全由LayoutManager管理,不能復用
  • FLAG_TMP_DETACHED ——ViewHolder從父RecyclerView臨時分離的標志,便於後續移除或添加回來
  • FLAG_ADAPTER_POSITION_UNKNOWN ——ViewHolder不知道對應的Adapter的位置,直到綁定到一個新位置
  • FLAG_ADAPTER_FULLUPDATE ——方法 addChangePayload(null) 調用時設置
RecyclerView.ItemAnimator
  • 用於在item項數據變化時的動畫效果;當調用Adapter的notifyItemChanged、notifyItemInserted、notifyItemMoved等方法,會觸發該對象顯示相應的動畫。
  • RecyclerView 的 ItemAnimator 使得item的動畫實現變得簡單而樣式豐富,我們可以自定義item項不同操作(如添加,刪除)的動畫效果;
ItemAnimator作觸發於以下三種事件:
  • 某條數據被插入到數據集合中 ,對應public final void notifyItemInserted(int position)方法
  • 從數據集合中移除某條數據 ,對應public final void notifyItemRemoved(int position) 方法
  • 更改數據集合中的某條數據,對應public final void notifyItemChanged(int position) 方法
  • 注意:notifyDataSetChanged(),會觸發列表的重繪,並不會出現任何動畫效果
  • 使用:Animator使用到的邏輯比較多,因此最方便的就是使用第三方庫:https://github.com/wasabeef/recyclerview-animators
RecyclerView.ItemDecoration
  • 用於繪制itemView之間的一些特殊UI,比如在itemView之前設置空白區域、畫線等。
  • RecyclerView 將itemView和裝飾UI分隔開來,裝飾UI即 ItemDecoration ,主要用於繪制item間的分割線、高亮或者margin等
  • 通過recyclerView.addItemDecoration(new DividerDecoration(this))對item添加裝飾;對RecyclerView設置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration並調用裡面的繪制方法,對Item進行裝飾。
  • public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋
  • public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之後調用,因此裝飾將浮於Item之上
  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸並設置偏移量  
 

簡單使用

一、引入依賴
    compile 'com.android.support:recyclerview-v7:23.2.1'
    compile 'com.android.support:appcompat-v7:23.3.0'
二、定義數據源對象
public class Item {
    private String title;
    private int idSource;
    public String getTitle().....
}
三、為Item創建一個布局文件 item.xml

 
    
         
         
    
四、創建一個繼承Recycler.Adapter的類和一個繼承Recycler.ViewHolder的類 一般情況自定義ViewHolder被客戶定義在自定義的Adapter類中
public class MAdapter extends RecyclerView.Adapter{
    private ArrayList items;
    private Context context;
    public MTest(ArrayList items, Context context) {
        this.items = items;
        this.context = context;
    }
    @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
        return new ItemViewHolder(view);
    }
    @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder){
            ItemViewHolder itemViewHolder= (ItemViewHolder)holder;
            itemViewHolder.imageView.setImageBitmap(...);
            itemViewHolder.textView.setText("test");
        }        
    }
    @Override public int getItemCount() {
        return items.size();
    }
    class ItemViewHolder extends RecyclerView.ViewHolder{
        private ImageView imageView;
        private TextView textView;
        public ItemViewHolder(View itemView) {
            super(itemView);
            initView(itemView);
        }
        private void initView(View view){
            imageView = (ImageView)view.findViewById(R.id.item_imageView);
            textView = (TextView)view.findViewById(R.id.item_textView);
        }
    }
}
五、在Activity的布局文件中引入RecyclerView布局 activity_main.xml

六、在Activity中為RecyclerView進行初始化設置
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recycleView = (RecycleView)findViewById(R.id.recycleView);
        recycleView.addItemDecoration(...);
        recycleView.setItemAnimator(...);
        recycleView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
        recycleView.setAdapter(....); //保證一定在setLayoutManager方法之後調用該方法!!尤其是你使用RecyclerView顯示Header視圖的時候。
}
補充: 對於如何在RecyclerView中添加Header、Footer視圖;給RecyclerView設置下拉上拉刷新視圖等高級使用限於篇幅,而且相關的資料網上也很容易找到,這裡不再詳細介紹。下面給出實現的大體思想。 Header和Footer視圖的添加兩者原理一致,在listView中我們有addHeaderView和addFooterView兩個方法向ListView中添加Header和Footer視圖。如果我們的RecyclerView僅僅局限在垂直布局中顯示,即不使用瀑布流、網格等復雜布局,通過重寫Adapter的getItemViewType(int position)方法我們可以分分鐘就實現添加Header和Footer。但是既然使用了RecyclerView就不可能僅僅局限在使用垂直布局,因此下面給出一種通用的解決方案。重寫Adapter的如下兩個方法,具體內容如下:
/**
     * 對GridLayoutManager的處理;該方法會在RecyclerView.setAdapter()方法中被調用,因此前面建議保證一定在setLayoutManager方法之後調用該方法
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {  
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if(layoutManager instanceof GridLayoutManager){
            final GridLayoutManager gridLayoutManager = (GridLayoutManager)layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if: positon是Header視圖或Footer視圖顯示的位置
return gridLayoutManager.getSpanCount();
                    return 1;
                }
            });
        }
    }
    /**
     * 對StaggeredGridLayoutManager的處理
     * @param holder
     */
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        if(layoutParams!=null&& layoutParams instanceof StaggeredGridLayoutManager.LayoutParams ){
            StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) layoutParams; 
            int position = holder.getLayoutPosition();
  if: positon是Header視圖或Footer視圖顯示的位置
lp.setFullSpan(true);
        }
    }
對於上下拉加載的實現方法,網上有各種各樣的實現方法;但是最核心的東西都沒有變,下拉刷新都是通過重寫RecyclerView的onTouchEvent(MotionEvent e)方法,方法內部判斷當前RecyclerView的position為0的View是否處於顯示狀態,即視圖是否是最頂層,如果是則利用用戶在y軸的滑動距離改變下拉刷新視圖的顯示高度,最終顯示高度超過設定阈值則進入刷新狀態,直到調用相關方法才停止刷新操作。上拉刷新相對於下拉刷新簡單很多,上拉刷新視圖沒有下拉刷新視圖的狀態處理,可以通過重寫RecyclerView的onScrollStateChanged(int state)方法判斷當前RecyclerView是否已經顯示最後一個數據,如果是就調用加載方法,方法加載完畢通過Adapter添加Item數據,並notifyItem...通知刷新RecyclerView視圖,並隱藏FooterView。 當然還有一種就是使用RecyclerView + SwipeRefreshLayout的模式實現下拉刷新,不過個人覺得它太不美觀因此也就不推薦。感興趣可以看看這個文章  

深入分析

首先將RecyclerView常用的幾個域,摘錄如下:

RecyclerView中的域

private Adapter mAdapter;//與RecyclerView綁定的Adapter
@VisibleForTesting LayoutManager mLayout; //與RecyclerView綁定的LayoutManager
private final ArrayList mItemDecorations = new ArrayList<>(); //存儲所有裝飾對象
ItemAnimator mItemAnimator = new DefaultItemAnimator(); //刪除、插入、添加等操作對應顯示的動畫

//OnScrollListener定義了onScrolled和onScrollStateChanged方法
private OnScrollListener mScrollListener;  //監聽器 對應setOnScrollListener方法,該方法已經標注為已過時
private List mScrollListeners;//監聽器 對應addOnScrollListener方法 

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
//調用Adapter的notifyDataXXX()方法,實際上是在調用 mObserver的對應方法,作用就是從Adapter取出更新的數據

final Recycler mRecycler = new Recycler();//管理RecyclerView暫時不使用的ViewHolder對象

private final ViewFlinger mViewFlinger = new ViewFlinger();//實現根據用戶滑動的動作慣性的繼續執行一段滑動,
往下就按照RecyclerView使用的流程為主線進行分析。即
  1. 首先對addItemDecoration、setItemAnimator、setLayoutManager和setAdapter幾個方法進行分析,了解其底層對RecyclerView對象的影響;
  2. 隨後對View的事件分發機制onTouchEvent方法進行分析,了解滑動過程中LayoutManager如何刷新視圖內容;
  3. 最後對ViewGroup的繪制流程中onMeasure、onLayout、draw和onDraw幾個方法進行分析。

Part1——常用方法

  addItemDecoration()@RecyclerView.class
public void addItemDecoration(ItemDecoration decor) {
        addItemDecoration(decor, -1);
}
addItemDecoration()@RecyclerView.class
public void addItemDecoration(ItemDecoration decor, int index) {
        if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"  + " layout"); } //如果當前Layout處於繪制狀態會報錯
        ...
        if (index < 0) { mItemDecorations.add(decor);  } //末尾添加
        else {mItemDecorations.add(index, decor);  }//指定位置添加
        .....
        requestLayout(); 
}
requestLayout()@RecyclerView.class
public void requestLayout() {
        if (mEatRequestLayout == 0 && !mLayoutFrozen) {  super.requestLayout(); } 
        else {  mLayoutRequestEaten = true; } 
}
該方法大體思路很簡單就是將參數ItemDecoration decor存入集合ArrayList mItemDecorations中。 setItemAnimator()@RecyclerView.class
public void setItemAnimator(ItemAnimator animator) {
        if (mItemAnimator != null) { //
            mItemAnimator.endAnimations();
            mItemAnimator.setListener(null);
        }
        mItemAnimator = animator;
        if (mItemAnimator != null) {
            mItemAnimator.setListener(mItemAnimatorListener); //note1
        }
}
跟addItemDecoration()方法類似,只不過這裡是更新ItemAnimator mItemAnimator域 setLayoutManager()@RecyclerView.class
public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) {  return; } 
        stopScroll();
        if (mLayout != null) { //第一次調用該語句為假
            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
        }
        mRecycler.clear(); //clear操作
        .....
        mLayout = layout; //更新mLayout域
        if (layout != null) {
            if (layout.mRecyclerView != null) { throw ....}
            mLayout.setRecyclerView(this); //LayoutManager和當前RecyclerView對象
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        requestLayout(); //重繪
}
進行的主要操作有:清空Recycler mRecycler中的數據、更新LayoutManager mLayout域、將當前RecyclerView和LayoutManager綁定; setAdapter()@RecyclerView.class
public void setAdapter(Adapter adapter) {
        setLayoutFrozen(false); 
        setAdapterInternal(adapter, false, true);
        requestLayout(); //重繪
}

setAdapterInternal()@RecyclerView.class
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { 
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);  
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {//對之前的View、動畫、裝飾等視圖進行回收
            //一般情況會進到這裡
            if (mItemAnimator != null) {  mItemAnimator.endAnimations();  } 
            if (mLayout != null) {
                mLayout.removeAndRecycleAllViews(mRecycler);
                mLayout.removeAndRecycleScrapInt(mRecycler);
            }
            mRecycler.clear();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter; //賦予新值
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver); 
            adapter.onAttachedToRecyclerView(this); //在方法內部針對GridLayoutManager布局進行的設置,使得可以顯示Header和Footer視圖
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        .....
}
該方法的主要動作是,移除LayoutManager中的視圖、清空Recycler mRecycler中的數據、更新Adapter mAdapter域、注冊數據觀察者、Adapter和RecyclerView綁定; 小結:addItemDecoration、setItemAnimator、setLayoutManager和setAdapter幾個方法相對簡單,大體都是更新RecyclerView中的相關域,方法的最後會申請requestLayout進行繪制。

Partt2——事件分發

分析完上面幾個常用的方法我們分析一下滑動過程中RecyclerView是如何動態刷新視圖的。 onTouchEvent()@RecyclerView.class
public boolean onTouchEvent(MotionEvent e) {
        ......
        if (dispatchOnItemTouch(e)) { //note1
            cancelTouch();
            return true;
        }
        if (mLayout == null) { return false; } 
       ......
        switch (action) {
            case MotionEvent.ACTION_DOWN: { //記錄當前按下的坐標值
                ......
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
               ......
            } break;
           
            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                if (index < 0) {  return false; } 
                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    dx -= mScrollConsumed[0];
                    dy -= mScrollConsumed[1];
                    vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                    mNestedOffsets[0] += mScrollOffset[0];
                    mNestedOffsets[1] += mScrollOffset[1];
                }
                if (mScrollState != SCROLL_STATE_DRAGGING) { //不是在拖動頁面
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        if (dx > 0) {   dx -= mTouchSlop; } else { dx += mTouchSlop;}
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        if (dy > 0) {   dy -= mTouchSlop; } else {  dy += mTouchSlop; }
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);//note2
                    }
                }
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];
                    if (scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0,  vtev)) //note3
                         {  getParent().requestDisallowInterceptTouchEvent(true); } 
                }
            } break;
           .....
        }
         .....
        vtev.recycle();
        return true;
}
1、分派當前事件給子View處理,如果沒有任何子View處理則進行後續操作 2、該方法內部如下
 
 
 
 
 
 
 
 
 
private void setScrollState(int state) {
        if (state == mScrollState) {   return; }
        mScrollState = state;
        if (state != SCROLL_STATE_SETTLING) { stopScrollersInternal();   } 
        dispatchOnScrollStateChanged(state); //note1
}
setScrollState處理的參數主要有SCROLL_STATE_IDLE表示當前並不處於滑動狀態、SCROLL_STATE_DRAGGING 表示當前RecyclerView處於滑動狀態(手指在屏幕上)
SCROLL_STATE_SETTLING 表示當前RecyclerView處於滑動狀態,(手已經離開屏幕)
1、該方法底層先後調用LayoutManager的onScrollStateChanged(state)方法、RecyclerView的onScrollStateChanged(state)方法,最後調用RecyclerView下所有的mScrollListener域的onScrollStateChanged(state) 方法。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3、該方法具體內容如下
scrollByInternal()@RecyclerView.class
boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;
        .....
        if (mAdapter != null) {
            ......
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); //note1
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); //note2
                unconsumedY = y - consumedY;
            }
            .........
        if (consumedX != 0 || consumedY != 0) {
            dispatchOnScrolled(consumedX, consumedY); //note3
        }
        if (!awakenScrollBars()) {
            invalidate();
        }
        return consumedX != 0 || consumedY != 0;
}
1-2、這裡調用LayoutManager的scrollXXBy方法,具體LayoutManager會根據參數進行具體的繪制,該部分我們暫時不細談,後面會講。 3、該方法底層先後調用RecyclerView的onScrolled(hresult, vresult);方法,然後調用RecyclerView下所有的mScrollListener域的onScrolled(this, hresult, vresult);方法
  小結:在事件分發中,首先將事件分派給子View去處理,如果子View沒有消耗當前事件,則事件才會交給RecyclerView執行。RecyclerView可能對事件再包裝交給自己的監聽器去處理也可能觸發刷新視圖的操作,具體通過調用LayoutManager的scrollXXBy方法。

Part3——繪制流程

對於RecyclerView的繪制流程,因為它繼承自ViewGroup,因此我們首先回顧一下ViewGroup的繪制流程:
  1. ViewGroup沒有重寫View的onMeasure方法,但提供了一個measureChildren方法用於調用children的measure方法,但是很多子類都不用該方法,而是自己重寫View的onMeasure方法。
  2. ViewGroup重寫了View的onLayout方法,但是聲明為抽象方法交給子類實現。
  3. ViewGroup沒有重寫View的draw和onDraw方法,但是View的draw方法會調用dispatchDraw(canvas)方法,ViewGroup實現了dispatchDraw(canvas)方法,對於dispatchDraw(canvas)方法ViewGroup子類一般都不會去重寫。
所以ViewGroup要求子類必須實現onLayout方法,對ViewGroup下面的View顯示位置方式進行控制。下面按照onMeasure、onLayout、draw和onDraw的順序來分析RecyclerView的繪制流程。 onMeasure()@RecyclerView.class
protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);//note0
            return;
        }
        if (mLayout.mAutoMeasure) {
            ......
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);//note1
            ......
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            .....
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);//note2
            ......
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            ......
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
           ....
        }   
}
0、方法實現如下
void defaultOnMeasure(int widthSpec, int heightSpec) {
        final int width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); 
        final int height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this)); 
        setMeasuredDimension(width, height); //這個是View的方法,我們就不介紹了
}
1、mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);一般情況下就等於defaultOnMeasure(widthSpec, heightSpec);; 2、mLayout.setMeasuredDimensionFromChildren顧名思義就是調用子View的onMeasure方法。 onMeasure暫時沒有我們太關心的東西,接著往下看。 onLayout()@RecyclerView.class
protected void onLayout(boolean changed, int l, int t, int r, int b) {
        .....
        dispatchLayout();
        ....
}
void dispatchLayout() {
        if (mAdapter == null) {  return;  } 
        if (mLayout == null) {  return; } 
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||  mLayout.getHeight() != getHeight()) { 
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
}
該方法主要是對LayoutManager的相關方法的調用,後面LayoutManager.class部分會再講。 draw()@RecyclerView.class
public void draw(Canvas c) { 
        super.draw(c);  //內部會調用執行完onDraw方法之後才返回
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState); //會在temDecoration的onDraw之後調用
        }
        ....
}
給itemView繪制完後在其基礎上繪制一些裝飾圖案 onDraw()@RecyclerView.class
public void onDraw(Canvas c) {
        super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState); //會在temDecoration的onDrawOver之前調用
        }
}
在itemView繪制之前繪制一些裝飾如背景、畫布形狀。 小結:本節分析的onMeasure和onLayout方法暫時沒有找到令我們興奮 的東西,但是在Draw和onDraw方法中我們找到了ItemDecoration的使用!在每次RecyclerView正式繪制之前都會先調用ItemDecorations集合中所有的ItemDecoration對象的onDraw方法,等ItemView繪制完畢,再調用ItemDecorations集合中所有的ItemDecoration對象的onDrawOver方法。     到此為止我們分析完了Part1、Part2、Part3三部分,中間遇到了如下的一些方法還沒有解釋清楚。
  • Adapter
    • onDetachedFromRecyclerView、onAttachedToRecyclerView、registerAdapterDataObserver、unregisterAdapterDataObserver
    • LayoutManager
      • setRecyclerView、onAdapterChanged、dispatchDetachedFromWindow、dispatchAttachedToWindow、removeAndRecycleAllViews、removeAndRecycleScrapInt、scrollHorizontallyBy、scrollVerticallyBy
      • Recycler
        • clear
下面我們就按照這裡的順序依次查看這些方法底層的實現。  

Adapter.calss

    Fields   private final AdapterDataObservable mObservable = new AdapterDataObservable();   onAttachedToRecyclerView()@Adapter.class public void onAttachedToRecyclerView(RecyclerView recyclerView) { } 當RecyclerView開始使用當前Adapter對象時會調用該方法 onDetachedFromRecyclerView()@Adapter.class public void onDetachedFromRecyclerView(RecyclerView recyclerView) { } 當RecyclerView停止使用當前Adapter對象時會調用該方法 registerAdapterDataObserver()@Adapter.class public void registerAdapterDataObserver(AdapterDataObserver observer) { mObservable.registerObserver(observer); } 注冊一個新的觀察者 unregisterAdapterDataObserver()@Adapter.class public void unregisterAdapterDataObserver(AdapterDataObserver observer) { mObservable.unregisterObserver(observer); } 注銷一個觀察者 notifyItemInserted()@Adapter.class public final void notifyItemInserted(int position) { mObservable.notifyItemRangeInserted(position, 1); } 通過mObservable去通知所有的觀察者處理該事件   小結:Adapter是RecyclerView的數據供應方,當Adapter和RecyclerView綁定的時候其實就是一個相互引用的過程。綁定過程中RecyclerView注冊成為Adapter的觀察者,一旦Adapter中的數據發生改變就通過調用Adapter的notifyXX方法告知RecyclerView去刷新視圖。綁定過程中Adapter可以通過onAttachedToRecyclerView()方法對RecyclerView進行相應的處理。   注意:當有數據更新的時候,需要通知對應的Adapter;而對於RecyclerView而言,不能夠僅僅通過調用notifyDataSetChanged()就完事兒了的,因為RecyclerView還有更多精細的動作,需要處理,而不像listView那麼簡單;因此推薦一旦有事件需要處理則調用方法notifyItemXX();
  • 如果我們在RecyclerView第一欄中添加了一行數據,而且希望RecyclerView顯示這一行數據:adapter.notifyItemInserted(0); recyclerView.scrollToPosition(0);
  • 如果我們在RecyclerView最後欄中添加了一行數據,而且希望RecyclerView顯示這一行數據:adapter.notifyItemInserted(contacts.size() - 1); recyclerView.scrollToPosition(mAdapter.getItemCount() - 1); contacts是我們的data數據集;
  下面我們分析LayoutManager對象,LayoutManager的子類比較多,我們不妨以簡單的LinearLayoutManager方法為例進行說明。

LinearLayoutManager.class

  LayoutManager 主要作用是,測量和擺放RecyclerView中itemView,以及當itemView對用戶不可見時循環復用處理。 通過RecycleView.set.LayoutManager()方法將自定義LayoutManager和RecycleView進行綁定。通過設置Layout Manager的屬性,可以實現水平滾動、垂直滾動、方塊表格等列表形式。 Fields
ChildHelper mChildHelper; //負責管理LayoutManager使用的ItemView
RecyclerView mRecyclerView; //與之綁定的RecyclerView
setRecyclerView()@LayoutManager.class
void setRecyclerView(RecyclerView recyclerView) {
            if (recyclerView == null) {
                mRecyclerView = null;
                mChildHelper = null;
                mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
                mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
            } else {
                mRecyclerView = recyclerView;
                mChildHelper = recyclerView.mChildHelper;
                mWidthSpec = MeasureSpec
                        .makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY);
                mHeightSpec = MeasureSpec
                        .makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY);
            }         
}
對LayoutManager中的域完成初始化值的設置,如mChildHelper和mRecyclerView onAdapterChanged()@LayoutManager.class public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { } 空方法沒有子類實現它 dispatchDetachedFromWindow()@LayoutManager.class
void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) {
            mIsAttachedToWindow = false;
            onDetachedFromWindow(view, recycler);
}
onDetachedFromWindow()@LinearLayoutManager.class
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {        
        if (mRecycleChildrenOnDetach) {
            removeAndRecycleAllViews(recycler);
            recycler.clear();
        }
}
removeAndRecycleAllViews()@LayoutManager.class
public void removeAndRecycleAllViews(Recycler recycler) {
            for (int i = getChildCount() - 1; i >= 0; i--) { //內部調用mChildHelper.getChildCount()
                final View view = getChildAt(i); //內部調用mChildHelper.getChildAt(index)
                if (!getChildViewHolderInt(view).shouldIgnore()) { //note1
                    removeAndRecycleViewAt(i, recycler);
                }
            }
 }
1、getChildViewHolderInt(view):通過當前view的LayoutParams值獲取ViewHolder、這裡的LayoutParams是RecyclerView的內部類,由當前View對應的ViewHolder
removeAndRecycleViewAt()@LayoutManager.class
public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index); //內部調用mChildHelper.getChildAt(index)
            removeViewAt(index); //內部調用mChildHelper.removeViewAt(index);
            recycler.recycleView(view);
}
小結:dispatchDetachedFromWindow()方法中主要是調用了removeViewAt(index)、recycler.recycleView(view)和recycler.clear()方法。removeViewAt(index)將RecyclerView不再使用的View移除;recycler.recycleView(view)用於回收該ViewHolder。LayoutManager使用中的View都是交給ChildHelper完成的,Recycler負責回收ViewHolder。   dispatchAttachedToWindow()@LayoutManager.class
void dispatchAttachedToWindow(RecyclerView view) {
            mIsAttachedToWindow = true;
            onAttachedToWindow(view);//note1
}
1、該方法留給子類實現,但是子類基本都沒有重寫該方法   removeAndRecycleAllViews()@LayoutManager.class 該部分已經在dispatchDetachedFromWindow()@LayoutManager.class中介紹過 removeAndRecycleScrapInt()@LayoutManager.class
void removeAndRecycleScrapInt(Recycler recycler) {
            final int scrapCount = recycler.getScrapCount();
            for (int i = scrapCount - 1; i >= 0; i--) {
                final View scrap = recycler.getScrapViewAt(i);
                final ViewHolder vh = getChildViewHolderInt(scrap); //note1
                if (vh.shouldIgnore()) {  continue; } 
                vh.setIsRecyclable(false); //避免因關閉動畫而導致的重復回收
                if (vh.isTmpDetached()) {  mRecyclerView.removeDetachedView(scrap, false);   }
                if (mRecyclerView.mItemAnimator != null) {  mRecyclerView.mItemAnimator.endAnimation(vh);  } //結束動畫
                vh.setIsRecyclable(true);
                recycler.quickRecycleScrapView(scrap);
            }
            recycler.clearScrap();
            if (scrapCount > 0) {   mRecyclerView.invalidate();  } 
}
1、getChildViewHolderInt(view):通過當前view的LayoutParams值獲取ViewHolder、這裡的LayoutParams是RecyclerView的內部類,由當前View對應的ViewHolder   scrollHorizontallyBy()@LayoutManager.calss
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {  return 0;   }
scrollHorizontallyBy()@LinearLayoutManager.calss
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,  RecyclerView.State state) { 
        if (mOrientation == VERTICAL) {   return 0; } 
        return scrollBy(dx, recycler, state);
}
scrollBy()@LinearLayoutManager.calss
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) { //內部調用mChildHelper.getChildCount()方法
            return 0;
        }
        mLayoutState.mRecycle = true;  
        ensureLayoutState(); //mLayoutState和mOrientationHelper初始化設置
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state); //.....
        final int consumed = mLayoutState.mScrollingOffset  + fill(recycler, mLayoutState, state, false); //繪制肯定在fill方法內部
        if (consumed < 0) {
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

fill()@LinearLayoutManager.calss
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { 
        final int start = layoutState.mAvailable;
        ......
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal(); //layoutChunkResult域初始化
            layoutChunk(recycler, state, layoutState, layoutChunkResult); //note1 具體內容見後面
            if (layoutChunkResult.mFinished) { break; } 
            ........
        } //end of While
        return start - layoutState.mAvailable;
 }
layoutChunk()@LinearLayoutManager.calss
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,  LayoutState layoutState, LayoutChunkResult result) { 
        View view = layoutState.next(recycler); //note1
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection  == LayoutState.LAYOUT_START)) { 
                addView(view); //note2
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection  == LayoutState.LAYOUT_START)) { 
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0); //會調用childView.measure方法
        ......
        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                right - params.rightMargin, bottom - params.bottomMargin); //會調用childView.layout方法
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.isFocusable();
    }
next()@ [email protected]
List mScrapList = null;
View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList(); //note1
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;         
}
首先從自身的mScrapList集合中獲取一個View,命中則直接返回 利用recycler的getViewForPosition獲取一個View,該部分我們放到後面講   addView()@LayoutManager.calss 將View添加到RecyclerView中   scrollHorizontallyBy()@LayoutManager.calss
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {   return 0;   }
scrollVerticallyBy()@LinearLayoutManager.calss
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,  RecyclerView.State state) { 
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state); //該方法在前面scrollHorizontallyBy()@LayoutManager.calss已經介紹過了
}
LayoutManager負責測量和擺放RecyclerView中itemView,利用ChildHelper mChildHelper管理LayoutManager使用的ItemView,利用Recycler獲取回收View。簡單講就是從Recycler中取數據然後進行顯示。   上面分析完了Adapter.class和LayoutManager.class,接著我們分析RecyclerView中非常重要的資源回收部分,這部分的操作實體是Recycler.class,下面我們需要重點分析的方法:
  • recycler.clearScrap()
  • recycler.getScrapViewAt(i)
  • recycler.clear()
  • recycler.recycleView(view)
  • recycler.getViewForPosition(mCurrentPosition);
  • recycler.quickRecycleScrapView(scrap)

Recycler.class——視圖資源回收

  A "scrapped" view is a view that is stillattached to its parent RecyclerViewbut thathas been marked for removal or reuse. Fields:
屏幕內緩存
private ArrayList mChangedScrap = null; 
final ArrayList mAttachedScrap = new ArrayList<>(); 
private final List  mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

屏幕外緩存
final ArrayList mCachedViews = new ArrayList();//存儲最近剛被RecyclerView拋棄的ViewHolder,最多存2個
private static final int DEFAULT_CACHE_SIZE = 2;
private int mViewCacheMax = DEFAULT_CACHE_SIZE;

private RecycledViewPool mRecyclerPool = new RecycledViewPool() ; //裡面會給每個類型的ViewHolder建立一個集合,每個這樣的集合存儲的元素最多5個。
private ViewCacheExtension mViewCacheExtension;
clearScrap()@Recycler.class
void clearScrap() {
            mAttachedScrap.clear();
            if (mChangedScrap != null) {
                mChangedScrap.clear();
            }
}
getScrapViewAt()@Recycler.class
View getScrapViewAt(int index) {
      return mAttachedScrap.get(index).itemView;
}
clear()@Recycler.class
public void clear() {
            mAttachedScrap.clear();
            recycleAndClearCachedViews();
}
recycleAndClearCachedViews()@Recycler.class
void recycleAndClearCachedViews() {
            final int count = mCachedViews.size();
            for (int i = count - 1; i >= 0; i--) {
                recycleCachedViewAt(i);
            }
            mCachedViews.clear();
}
recycleCachedViewAt()@Recycler.class
void recycleCachedViewAt(int cachedViewIndex) {
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            addViewHolderToRecycledViewPool(viewHolder);  
            mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool()@Recycler.class
void addViewHolderToRecycledViewPool(ViewHolder holder) {
            ......
            holder.mOwnerRecyclerView = null;
            getRecycledViewPool().putRecycledView(holder); //等於mRecyclerPool.putRecycledView(holder)
}

  putRecycledView()@RecycledViewPool.class
private SparseArray> mScrap =  new SparseArray>(); 
private SparseIntArray mMaxScrap = new SparseIntArray(); //存儲每個類型對應的能夠緩存的最大ViewHolder數量
private int mAttachCount = 0;
private static final int DEFAULT_MAX_SCRAP = 5; //默認每個類型能夠5個ViewHolder
public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType(); //獲取ViewHolder的類型
            final ArrayList scrapHeap = getScrapHeapForType(viewType); //從mScrap集合中獲取viewType類型的集合
            if (mMaxScrap.get(viewType) <= scrapHeap.size()) { //緩存隊列已滿,直接返回
                return;
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
}
private ArrayList getScrapHeapForType(int viewType) {
            ArrayList scrap = mScrap.get(viewType);
            if (scrap == null) { //mScrap沒有當前類型的集合,創建一個集合添加進隊列
                scrap = new ArrayList<>();
                mScrap.put(viewType, scrap);
                if (mMaxScrap.indexOfKey(viewType) < 0) { //如果mMaxScrap中沒有當前類型,存入
                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
                }
            }
            return scrap;         
}
public void setMaxRecycledViews(int viewType, int max) {
            mMaxScrap.put(viewType, max);
            final ArrayList scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null) {
                while (scrapHeap.size() > max) {
                    scrapHeap.remove(scrapHeap.size() - 1);
                }
            }
 }
小結:將ArrayList mAttachedScrap集合中的數據全部清除、對於ArrayList mCachedViews集合中的數據先存入RecycledViewPool中,然後從mCachedViews集合中移除出去。RecyclerViewPool針對每種類型的ViewHolder都有一個對應的ArrayList的集合,該集合存儲的最大ViewHolder數默認為5。不過通過setMaxRecycledViews方法可以修改指定類型存儲的ViewHolder最大值。   recycleView()@Recycler.class
public void recycleView(View view) { 
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()){
                holder.clearReturnedFromScrapFlag();
            }
            recycleViewHolderInternal(holder);
}
recycleViewHolderInternal()@Recycler.class
void recycleViewHolderInternal(ViewHolder holder) {
            if (holder.isScrap() || holder.itemView.getParent() != null) {  throw new IllegalArgumentException(...);  } 

            if (holder.isTmpDetached()) { throw new IllegalArgumentException("Tmp detached view should be removed " ...); } 

            if (holder.shouldIgnore()) {  throw new IllegalArgumentException("Trying to recycle an ignored view ...."); } 
            
            ......
            boolean cached = false;
            boolean recycled = false;
            if (forceRecycle || holder.isRecyclable()) {
                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED  | ViewHolder.FLAG_UPDATE)) { 
                    final int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                    }
                    if (cachedViewSize < mViewCacheMax) {
                        mCachedViews.add(holder);
                        cached = true;
                    }
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder);
                    recycled = true;
                }
            }
            .....
}
void recycleCachedViewAt(int cachedViewIndex) {
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            addViewHolderToRecycledViewPool(viewHolder); //clear()@Recycler.class中已經介紹過,就是把數據存入到池中
            mCachedViews.remove(cachedViewIndex); //當前位置的ViewHolder從Cache中移除出去
}
小結:recycleView()方法會將參數存入到Recycler的 mCachedViews集合中。該集合默認只能存2個ViewHolder,如果當前集合中已經存有兩個ViewHolder,則在將參數ViewHolder存入到集合前,會將0位置ViewHolder放入到回收池中。   getViewForPosition()@Recycler.class
public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
}
getViewForPosition()@Recycler.class
View getViewForPosition(int position, boolean dryRun) {
            ......
            boolean fromScrap = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position); //note1
                fromScrap = holder != null;
            }

            // 1) Find from scrap by position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); //note2
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) { //判斷得到的ViewHolder位置信息是否正確,不正確回收它
                        if (!dryRun) {
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            .... //使得當前View及其子View和Recycler脫離
                            recycleViewHolderInternal(holder);//該方法在 recycleView()@Recycler.class中已經介紹過
                        } //end of if (!dryRun)
                        holder = null;
                    }// end of if (!validateViewHolderForOffsetPosition(holder))
                    else {  fromScrap = true; } 
                } // end of if (holder != null) 
            } // end of  if (holder == null) 

            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                ......
                final int type = mAdapter.getItemViewType(offsetPosition); //獲取該位置的ViewHolder所屬類型
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); //note3
                    if (holder != null) {
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type);  //note4
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) { throw ....}
                        .....
                    }
                }
                if (holder == null) { // fallback to recycler
                    holder = getRecycledViewPool().getRecycledView(type);//note5
                    if (holder != null) {
                        holder.resetInternal();
                        .......
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type); //note6
                }
            }
            //動畫相關的初始化設置
            ......
            
            if (mState.isPreLayout() && holder.isBound()) {
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                holder.mOwnerRecyclerView = RecyclerView.this;
                mAdapter.bindViewHolder(holder, offsetPosition);//note7
                
                if (mState.isPreLayout()) {
                    holder.mPreLayoutPosition = position;
                }
            }
            //ViewHolder的一些初始化設置
            .......
            return holder.itemView;
        }
1、遍歷集合mChangedScrap中的所有ViewHolder,並根據holder.getLayoutPosition() == position是否為真判斷是否是預期的ViewHolder 2、遍歷集合mAttachedScrap中所有的ViewHolder,並根據holder.getLayoutPosition() == position是否為真判斷是否是預期的ViewHolder 3、根據id和type在mAttachedScrap和mCachedViews中嘗試獲取ViewHolder 4、根據id和type在mViewCacheExtension 中嘗試獲取view 5、根據type在RecycledViewPool中嘗試獲取ViewHolder 6、利用Adapter.createViewHolder(Recyclerview, Type)方法獲取一個ViewHolder 7、調用Adapter的bindViewholder進行一次綁定   quickRecycleScrapView()@Recycler.class
void quickRecycleScrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            holder.mScrapContainer = null;
            holder.mInChangeScrap = false;
            holder.clearReturnedFromScrapFlag();
            recycleViewHolderInternal(holder); //該方法在 recycleView()@Recycler.class中已經介紹過
}
 

總結

回收流程 到此為止我們把RecyclerView的整個基本流程分析完畢了,重點了解了RecyclerView的資源回收部分如何實現。再次回顧一下RecyclerView的回收策略: RecyclerView中用於回收視圖的對象有:
  • private ArrayList mChangedScrap
  • private ArrayList mAttachedScrap
  • private ArrayList mCachedViews //ViewHolder緩存列表,一級緩存
  • private ViewCacheExtension mViewCacheExtension //由開發者控制的可以作為View緩存的幫助類,默認是獲取null
  • private RecycledViewPool mRecyclerPool 提供復用ViewHolder池。
  • SparseArray> mScrap ;//每一個Type對應一個List,Type為int型,也就是說我們映射的鍵是int型而非一個對象,這點跟SparseArray這個集合有關!
  • SparseIntArray mMaxScrap = new SparseIntArray();//每個Type對應的最大ViewHolder數量!
  • RecyclerViewPool用於多個RecyclerView之間共享View;
  • RecyclerView默認會創建一個RecyclerViewPool實例。也可通過setRecycledViewPool(RecycledViewPool) 方法,讓RecycleView使用自定義RecyclerViewPool;
RecyclerView的視圖回收流程是:
  1. 調用Recycler.getViewForPosition(int)方法獲取View時,Recycler先檢查mChangedScrap和mAttachedScrap ,沒命中進行如下操作
  2. 調用ViewCacheExtension.getViewForPositionAndType(Recycler, int, int)獲取View,沒命中進行如下操作
  3. 檢查RecyclerViewPool,如果還是沒有命中則內部會調用Adapter的onCreateViewHolder等方法創建一個新的ViewHolder。  
RecyclerView && ListView 最後我們簡單對比一下ListView和RecyclerView的資源回收策略。ListView的視圖資源回收使用的是內部類RecyclBin、而RecyclerView使用的是多級緩存機制。即ListView中只有一個對象負責收集廢棄的View,而RecyclerView有多個集合負責收集廢棄的ViewHolder。針對不同的視圖類型ListView的RecycleBin和RecyclerView的RecycledViewPool有如下兩種不同的存儲方式。 ListView的RecycleBin采用數組+ArrayList的形式:
  1. ListView的setAdapter方法內部會調用mRecycler.setViewTypeCount(mAdapter.getViewTypeCount())方法,方法會給RecycleBin創建mAdapter.getViewTypeCount()個ArrayList集合,每個集合對應一個View類型。android.widget.BaseAdapter的getViewTypeCount()方法默認返回1。表明在將Adapter和ListView綁定的那一刻起,RecycleBin所能緩存的View類型數是一定的。
  2. setViewTypeCount()內容如下:
    private ArrayList[] mScrapViews; public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { thow....} ArrayList[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; }
    RecyclerView的RecycledViewPool采用SparseArray+ArrayList的形式:
  1. private SparseArray> mScrap = new SparseArray>();
  2. final ArrayList scrapHeap = mScrap.get(viewType);
  3. 注意:SparseArray是一個很神奇的數組,可以不按照位置存儲數據,比如當前集合大小為10我們可以在15的位置插上一個數據,通過get(15)獲取到對應的內容!以後有時間我們好好探究其如何實現!!
   
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved