AndroidのRecyclerViewソース分析

18155 ワード

RecyclerViewコンポーネント
  • Adapter提供データ及びデータ変更通知
  • LayoutManagerレイアウト管理、測量、item位置の指定、item回収、
  • ItemAnimator Item変更動画
  • 重要な実装
    1、ViewHolder多重
    3層キャッシュ1層目:RecyclerのmCachedViews
    階層2:開発者によって実現されるViewCacheExtensionのキャッシュポリシー.setViewCacheExtensionによってRecyclerViewに設定できる
    階層3:RecycledViewPoolのmScrapは、複数のRecyclerViewで共有可能なViewキャッシュプール
    Scrapped View(破棄されたView):1つのViewは親ビューRecyclerViewに付属していますが、使用済みまたは移行済みとしてマークされています.このようなViewはScrapped Viewと呼ばれています.
    ビューを取得するプロセス
    //RecyclerView.Recycler
    final ArrayList mAttachedScrap = new ArrayList<>();
            private ArrayList mChangedScrap = null;
    
            final ArrayList mCachedViews = new ArrayList();
    
            private final List
                    mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    //    View     
    public View getViewForPosition(int position) {
                //      
                //   position     View   
                //     false            dryrun
                return getViewForPosition(position, false);
            }
    
    View getViewForPosition(int position, boolean dryRun) {
        //    
        ...
        //          ,   mChangedScrap    
        if (mState.isPreLayout()) {
                    holder = getChangedScrapViewForPosition(position);
                    fromScrap = holder != null;
                }
         //     ,  mAttachedScrap     
         holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
         //     ,     ViewHolder OffsetPosition  
         // ,     OffsetPosition。    stable ids 
         //mAttachedScrap   (  mAdapter  stableIds)。
          if (mAdapter.hasStableIds()) {
                        holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                        if (holder != null) {
                            // update position
                            holder.mPosition = offsetPosition;
                            fromScrap = true;
                        }
                    }
        //      ,  mViewCacheExtension   ,
        //       RecyclerView   ViewCacheExtension
        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);
        ....
        ....
        //      ViewCacheExtension,        ,
        //     RecycledViewPool   。
        //  RecycledViewPool   RecyclerView   ,       ViewHolder,           。
        holder = getRecycledViewPool().getRecycledView(type);
        ...
        ...
        //            ,     
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
         ...
    //    
     mAdapter.bindViewHolder(holder, offsetPosition);
    
         ...
         return holder.itemView; 
    }

    2、データ変更通知(観察者モード)
    観察者AdapterDataObserverは、具体的にはRecyclerViewDataObserverとして実現され、データソースが変更された場合、インタフェースの変化にタイムリーに応答する
    public static abstract class AdapterDataObserver {
            public void onChanged() {
                // Do nothing
            }
    
            public void onItemRangeChanged(int positionStart, int itemCount) {
                // do nothing
            }
    
            public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                // fallback to onItemRangeChanged(positionStart, itemCount) if app
                // does not override this method.
                onItemRangeChanged(positionStart, itemCount);
            }
    
            public void onItemRangeInserted(int positionStart, int itemCount) {
                // do nothing
            }
    
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                // do nothing
            }
    
            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                // do nothing
            }
        }

    被観察者AdapterDataObservable,内部保有観察者AdapterDataObserver集合
    static class AdapterDataObservable extends Observable {
            public boolean hasObservers() {
                return !mObservers.isEmpty();
            }
    
            public void notifyChanged() {
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
    
            public void notifyItemRangeChanged(int positionStart, int itemCount) {
                notifyItemRangeChanged(positionStart, itemCount, null);
            }
    
            public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
                // since onItemRangeChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
                }
            }
    
            public void notifyItemRangeInserted(int positionStart, int itemCount) {
                // since onItemRangeInserted() is implemented by the app, it could do anything,
                // including removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
                }
            }
    
            public void notifyItemRangeRemoved(int positionStart, int itemCount) {
                // since onItemRangeRemoved() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
                }
            }
    
            public void notifyItemMoved(int fromPosition, int toPosition) {
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
                }
            }
        }

    Adapter内部にはAdapterDataObservableオブジェクトがあります.RecyclerViewにAdapterを設定すると、観察者のRecyclerViewDataObserverがAdapterに登録されます.
    adapter.registerAdapterDataObserver(mObserver);

    データが変更されると、notify**メソッドが呼び出されると、Adapter内部の被観察者は登録された観察者の対応メソッドを遍歴して通知し、インタフェースは変更に応答します.
    3、多様なレイアウトタイプ展示ベースクラスLayoutManagerサブクラスはLinearLayoutManager、GridLayoutManager、StaggerdGridLayoutManagerである
    測定入口:RecyclerViewのonMeasureメソッド
    //mAutoMeasure           LayoutManager       。
    //       LayoutManager ,       false
    if (mLayout.mAutoMeasure) {
                final int widthMode = MeasureSpec.getMode(widthSpec);
                final int heightMode = MeasureSpec.getMode(heightSpec);
                //  RecyclerView      EXACTLY(    )
                //              RecyclerView     
                //            onLayout   
                final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                        && heightMode == MeasureSpec.EXACTLY;
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                if (skipMeasure || mAdapter == null) {
                    return;
                }
                if (mState.mLayoutStep == State.STEP_START) {
                    dispatchLayoutStep1();
                }
                // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
                // consistency
                mLayout.setMeasureSpecs(widthSpec, heightSpec);
                mState.mIsMeasuring = true;
                //        LayoutManager       
                dispatchLayoutStep2();
                .....
                .....
     /**
         * The second layout step where we do the actual layout of the views for the final state.
         * This step might be run multiple times if necessary (e.g. measure).
         */
        private void dispatchLayoutStep2() {
        ...
        ...
        //mLayout RecyclerView   LayoutManager
        mLayout.onLayoutChildren(mRecycler, mState);
        ...
        ...
       }
    //LayoutManager           ,           
    //    LayoutManager ,            
    public void onLayoutChildren(Recycler recycler, State state) {
                Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
            }
    //   LinearLayoutManager  ,      
    //                    
    @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            // layout algorithm:
            // 1) by checking children and other variables, find an anchor coordinate and an anchor
            //  item position.
            // 2) fill towards start, stacking from bottom
            // 3) fill towards end, stacking from top
            // 4) scroll to fulfill requirements like stack from bottom.
            ...
            ...
            //   ,            ,       item    
            //   ,          
            //   ,          
            //   ,          ,         
            ...
            ...
            //        ,     view
            //LayoutManager             ,     
            //     ,          
            fill(recycler, mLayoutState, state, false);

    4、Item変更動画
    5、その他
    notifyDataSetChangedで何があったの?
    1.ページに存在するViewHolderとRecyclerのmCachesViewにキャッシュされたViewHolderにフラグを追加する:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
    2、実行待ちの更新操作(add/remove/update/move)があるかどうかを確認します.ない場合は、現在レイアウト状態ではなく、LayoutFrozenを禁止する設定はありません.requestLayout()メソッドを呼び出して再レイアウトします.
    3、実行待ちの更新操作があればどうしますか.更新は許可されていません
    何があった?
    1、RecyclerViewはItemの挿入、削除、移動、変更を抽象的にコマンドUpdateOpにし、AdapterHelperでコマンドプールを維持した.Itemを挿入すると、更新されたコマンドキューに「ADD」コマンドが追加されます.
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));

    2、その後、プロセッサの更新がトリガーされ、新しいコマンドが処理されます.
    3、RecyclerViewは実行待ちのコマンドを処理するためにRunnableを起動します.
        /**
         * Note: this Runnable is only ever posted if:
         * 1) We've been through first layout
         * 2) We know we have a fixed size (mHasFixedSize)
         * 3) We're attached
         */
        private final Runnable mUpdateChildViewsRunnable = new Runnable() {
            public void run() {
                if (!mFirstLayoutComplete || isLayoutRequested()) {
                    // a layout request will happen, we should not do layout here.
                    return;
                }
                if (mLayoutFrozen) {
                    mLayoutRequestEaten = true;
                    return; //we'll process updates when ice age ends.
                }
                consumePendingUpdateOperations();
            }
        };

    でもワンステップデバッグで条件isLayoutRequested()がtrueなのでこのRunnableは何もせずにそのまま戻ってきました???
    isLayoutRequested()がtrueである以上、説明は次に再レイアウトされ、更新が実行される可能性がある場所がlayoutに置かれています.我々はdispatchLayout()法における第2ステップdispatchLayoutStep 1()である.S p r o s s e s A p t e r UpdatesAndSetAnimationFlags()メソッドが見つかった.この方法は主に2つのことをした:第一に、AdapterのItemの位置を操作タイプと数量に応じて更新する(例、新しいItemを挿入し、元のリストのItemのPositionを順次追加する).第2に、RecyclerViewの状態mStateを設定.mRunSimpleAnimationsとmState.mRunPredictiveAnimationsはtrueです.