Android Render(三)supportVersion 27.0.0源碼RecyclerView繪制流程解析


閱讀者三篇Android繪制文章,會讓你對理解Android繪制有幫助:


RecyclerView繪制三步驟.png

RecyclerViewsupportVersion 27.0.0為目前的最新版本,其實Support Library每一個版本之間都有一定的差異,有API Changes或者Bug fixes,可以通過API Diffs來查看其區(qū)別。具體的可以參考:https://developer.android.google.cn/topic/libraries/support-library/revisions.html

就來我們現(xiàn)在要說的 RecyclerView來說,從剛開始出來到現(xiàn)在的我們開發(fā)人員大面積使用,修改了很多次了,里面的繪制流程也是重構(gòu)過很多次,網(wǎng)上有一些講解RecyclerView繪制的文章,大都是老版本的support library,跟目前最新的RecyclerView的繪制有所差異的。

1.基本使用

    @BindView(R.id.recyclerView)
    RecyclerView recyclerView;
    GridLayoutManager gridLayoutManager;

        gridLayoutManager = new GridLayoutManager(ActivityMain.this, 2);
        gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        //設(shè)置LayoutManager
        recyclerView.setLayoutManager(gridLayoutManager);
        //設(shè)置左上邊距
        recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
                super.getItemOffsets(outRect, view, parent, state);
                outRect.left = 4;
                outRect.top = 4;
            }
        });
        //設(shè)置item動畫
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        MAdapter mAdapter = new MAdapter();
        //設(shè)置Adapter
        recyclerView.setAdapter(mAdapter);
        mAdapter.notifyDataSetChanged();

上面是RecyclerView的基本使用,就是設(shè)置LayoutManager,設(shè)置一些裝飾,設(shè)置一些動畫,設(shè)置Adapter,如果在設(shè)置完成之前Adapter中我們沒有填充數(shù)據(jù),UI界面是不會發(fā)生變化的。如果我們設(shè)置完成之后填充數(shù)據(jù)就需要調(diào)用notifyDataSetChanged()方法請求繪制刷新。

2.初始化-繪制之前的準(zhǔn)備

2.1 setAdapter(Adapter adapter)

這一步是必須的,不設(shè)置Adapter ,那么數(shù)據(jù)跟UI就無法關(guān)聯(lián)起來,也就談不上什么繪制了,繪制都是要基于數(shù)據(jù)的。

setAdapter(Adapter adapter)方法源代碼:

    //為RecyclerView設(shè)置Adapter,為RecyclerView提供子itemView
    public void setAdapter(Adapter adapter) {
        // 設(shè)置布局繪制不被凍結(jié),以便刷新界面UI
        setLayoutFrozen(false);
        //設(shè)置Adapter內(nèi)部實現(xiàn)
        setAdapterInternal(adapter, false, true);
        //請求重新布局
        requestLayout();
    }

一共調(diào)用了三個方法,第一個和最后一個方法都很好理解,我們重點(diǎn)要說setAdapterInternal方法,說之前我要知道setAdapter主要是適配數(shù)據(jù),當(dāng)我們設(shè)置的數(shù)據(jù)有變化時,UI界面能夠及時刷新。要實現(xiàn)這樣的邏輯,就必須存在數(shù)據(jù)變更觀察者和被觀察者,就是RecyclerView要觀察Adapter的數(shù)據(jù)變化。

Adapter類中有一個被觀察者:

 private final AdapterDataObservable mObservable = new AdapterDataObservable();  //默認(rèn)就創(chuàng)建好了

RecyclerView中有一個觀察者:

 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();  //默認(rèn)就創(chuàng)建好了

RecyclerViewsetAdapterInternal方法中使觀察者和被觀察者發(fā)生關(guān)系:

    //設(shè)置Adapter內(nèi)部實現(xiàn)
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        //移除之前觀察者和被觀察者之間的關(guān)系
        mAdapter.unregisterAdapterDataObserver(mObserver);
        //移除之前RecyclerView和Adapter之間的關(guān)系
        mAdapter.onDetachedFromRecyclerView(this);
    }
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        //不兼容之前的并且移除所以的itemView
        removeAndRecycleViews();
    }
    //重置刪除、移動、增加輔助工具類AdapterHelper
    mAdapterHelper.reset();
    //記錄之前的Adapter
    final Adapter oldAdapter = mAdapter;
    //賦值新的Adapter
    mAdapter = adapter;
    if (adapter != null) { //新的Adapter不為空
        //注冊觀察者跟被觀察者之間的關(guān)系
        adapter.registerAdapterDataObserver(mObserver);
        //注冊RecyclerView與Adapter之間的關(guān)系
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        //通知LayoutManager Adapter發(fā)生了變化
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    //通知回收復(fù)用管理者Recycler Adapter發(fā)生了變化
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
}

經(jīng)過一系列的重置后調(diào)用adapter.registerAdapterDataObserver(mObserver)使二者發(fā)現(xiàn)關(guān)系。之后請求重繪。

2.2 setLayoutManager(LayoutManager layout)

這一步也是必須的,用什么類型的LayoutManager來繪制RecyclerView,就算設(shè)置了Adapter ,沒設(shè)置LayoutManager ,RecyclerView也是不知道該如何繪制。

setLayoutManager方法源代碼:

    //設(shè)置LayoutManager
   public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) { //設(shè)置的LayoutManager跟之前的一樣就返回
            return;
        }
        stopScroll();  //馬上停止?jié)L動
        // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
        // chance that LayoutManagers will re-use views.
        if (mLayout != null) {
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();  //馬上結(jié)束動畫
            }
            //LayoutManager負(fù)責(zé)移除回收所有的itemView
            mLayout.removeAndRecycleAllViews(mRecycler);
            //LayoutManager負(fù)責(zé)移除回收所有的已經(jīng)廢棄itemView緩存
            mLayout.removeAndRecycleScrapInt(mRecycler);
            //清除所有緩存
            mRecycler.clear();

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            //重置LayoutManager中RecyclerView的狀態(tài)
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();
        }
        // this is just a defensive measure for faulty item animators.
        mChildHelper.removeAllViewsUnfiltered();
        mLayout = layout;
        if (layout != null) {
            if (layout.mRecyclerView != null) {
                throw new IllegalArgumentException("LayoutManager " + layout
                        + " is already attached to a RecyclerView: " + layout.mRecyclerView);
            }

            //把當(dāng)前的RecyclerView交給設(shè)置進(jìn)來的LayoutManager
            mLayout.setRecyclerView(this);

            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        //更新Recycler中的緩存大小
        mRecycler.updateViewCacheSize();
        //請求重繪
        requestLayout();
    }

做了一些重置后調(diào)用調(diào)用LayoutManagersetRecyclerView(RecyclerView recyclerView)方法使LayoutManagerRecyclerView發(fā)生關(guān)聯(lián)。之后請求重繪。

2.3 addItemDecoration(ItemDecoration decor)

這一步不是必須的,給ItemView的繪制增加一些裝飾,比如分割線,懸浮的指示等等,通過重寫ItemDecoration中的幾個方法可以實現(xiàn)很多炫酷的RecyclerView顯示效果。

addItemDecoration(ItemDecoration decor)方法源代碼跟蹤:


    public void addItemDecoration(ItemDecoration decor) {
        //調(diào)用addItemDecoration方法
        addItemDecoration(decor, -1);
    }
            
    public void addItemDecoration(ItemDecoration decor, int index) {
        if (mLayout != null) {
            mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                    + " layout");
        }
        if (mItemDecorations.isEmpty()) { //保存ItemDecoration的ArrayList為空說明是第一次初始化
            setWillNotDraw(false);  //為第一次初始化設(shè)置 etWillNotDraw(false)保證RecyclerView的onDraw(canvas)方法可以調(diào)用,
                                    //應(yīng)為RecyclerView所有Item的裝飾是在RecyclerView的onDraw(canvas)里面被調(diào)用繪制的
        }
        if (index < 0) {  //把ItemDecoration加入ArrayList中
            mItemDecorations.add(decor);  
        } else {
            mItemDecorations.add(index, decor);
        }
        //使每一個itemView的裝飾都可以被繪制
        markItemDecorInsetsDirty();
        //請求重繪
        requestLayout();
    }

經(jīng)過一些的判斷后把需要的裝飾ItemDecoration加入到ArrayList中保存并請求重繪。

2.4 setItemAnimator(ItemAnimator animator)

這一步也不是必須的,RecyclerView的itemView的動畫,一般都是itemView第一次創(chuàng)建展示的時候會播放動畫,或者是刪除移動itemView的時候。如果沒有設(shè)置,也會有一個默認(rèn)的動畫DefaultItemAnimator。

setItemAnimator(ItemAnimator animator)方法源代碼:

    public void setItemAnimator(ItemAnimator animator) {
        if (mItemAnimator != null) {
            //如果之前的動畫不為空就結(jié)束動畫移除監(jiān)聽
            mItemAnimator.endAnimations();
            mItemAnimator.setListener(null);
        }
        //賦值
        mItemAnimator = animator;
        if (mItemAnimator != null) {
            //設(shè)置監(jiān)聽
            mItemAnimator.setListener(mItemAnimatorListener);
        }
    }

經(jīng)過判斷后賦值重新設(shè)置動畫監(jiān)聽。

其實前面可以看到調(diào)用RecyclerViewsetLayoutManager、addItemDecoration、setAdapter三個方法都會去調(diào)用requestLayout()方法請求重繪。如果在創(chuàng)建Adapter時候我們就綁定了數(shù)據(jù)就不需要調(diào)用notifyDataSetChanged()來繪制UI界面了。如果是之后綁定數(shù)據(jù)那就要手動調(diào)用notifyDataSetChanged()方法繪制。

Adapter.notifyDataSetChanged()方法源碼閱讀:

public final void notifyDataSetChanged() {
   mObservable.notifyChanged();
}

我們接著看AdapterDataObservable.notifyChanged()方法實現(xiàn):

//mObservers是保存觀察者的一個ArrayList
public void notifyChanged() {
    for (int i = mObservers.size() - 1; i >= 0; i--) {
        //通知所有的觀察者數(shù)據(jù)發(fā)送改變了
        mObservers.get(i).onChanged();
    }
}

我們再來看RecyclerViewDataObserver.onChanged()方法:

@Override
public void onChanged() {
    ......
    //Adapter目前沒有待更新或者正在更新的操作才可以重新繪制
    if (!mAdapterHelper.hasPendingUpdates()) {
        requestLayout();
    }
}

從上面我們可以看到一個Adapter中可能存在多個觀察者,那就意味中Adapter可以同時被多個RecyclerView公用。

調(diào)用requestLayout()方法后就會走下面的onMeasure onLayout onDraw三個流程了。

3.onMeasure

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {  //LayoutM為空就走默認(rèn)測量然后返回
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.mAutoMeasure) {  //是不是自動測量,我們創(chuàng)建LayoutManager時默認(rèn)為支持自動測量
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            //當(dāng)前RecyclerView的寬高是否都為精確值
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                    && heightMode == MeasureSpec.EXACTLY;
            //委托LayoutManager來測量RecyclerView的寬高(還是走defaultOnMeasure方法)
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            //如果RecyclerView的寬高都是寫死的精確值或者是match_parent并且Adapter還沒有設(shè)置就結(jié)束測量
            if (skipMeasure || mAdapter == null) {
                return;
            }

            //當(dāng)RecyclerView的寬高設(shè)置為wrap_content時,skipMeasure=false 就是不跳過測量
            //當(dāng)寬高為wrap_content時,就先不能確定RecyclerView的寬高,因為需要先測量子itemView的寬高后才可以確定自己的寬高
            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;
            //
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            //通過對子ItemView的測量布局來確定寬高為WARP_CNTENT的RecyclerView的寬高
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
                  //如果是自定義LayoutManager就要自己實現(xiàn)
          }
    }

RecyclerView的onMeasure方法測量是分兩種情況的:

  1. 當(dāng)RecyclerView的寬高設(shè)置為match_parent具體值的時候,skipMeasure=true,此時會只需要測量其自身的寬高就可以知道RecyclerView的大小,這時是onMeasure方法測量結(jié)束。
  2. 當(dāng)RecyclerView的寬高設(shè)置為wrap_content時,skipMeasure=false,onMeasure會繼續(xù)執(zhí)行下面的dispatchLayoutStep2(),其實就是測量RecyclerView的子視圖的大小最終確定RecyclerView的實際大小,這種情況真正的測量操作都是在方法dispatchLayoutStep2()里執(zhí)行的:

下面看一下onMeasure方法中調(diào)用到的dispatchLayoutStep1方法和dispatchLayoutStep2方法。

dispatchLayoutStep1()方法:

private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    mState.mIsMeasuring = false;
    //禁止布局請求
    eatRequestLayout();
    //清空itemView信息保存類
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    //1.重排序所有UpdateOp,比如move操作會排到末尾
    //2.依次執(zhí)行所有UpdateOp事件,更新VH的position(這里是前移mPosition,mPreLayoutPosition不變),如果VH被remove了標(biāo)記它。
    /*在2中,“會決定是否在prelayout之前把更新告訴LM”,
    這里把更新告訴LM指的是把更新反應(yīng)在VH的mPreposition上(VH中有mPosition、mPreLayoutPosition等成員,注意,prelayout中是使用的mPreLayoutPosition)mPosition是一定會更新的,mPreLayoutPosition則不一定。
    如果RV決定不把更新再prelayout之前告訴LM,則會對VH更新時的參數(shù)applyToPreLayout傳入false,mPosition更新了而mPreLayoutPosition則是舊值,反之mPreLayoutPosition則和mPosition同步。
    當(dāng)然,如何“決定”我們就不說了,有興趣可以看下原文和源碼。*/
    processAdapterUpdatesAndSetAnimationFlags();
    processAdapterUpdatesAndSetAnimationFlags();
    //保存焦點(diǎn)信息
    saveFocusInfo();
    //對RecyclerView的情況存儲類State賦值
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    //我們需要展示的所有ItemView個數(shù) 就是我們寫Adapter給的getItemCount返回值
    mState.mItemCount = mAdapter.getItemCount();
    //找到屏幕上可以繪制的最小position和最大position
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

    if (mState.mRunSimpleAnimations) {
        // Step 0: Find out where all non-removed items are, pre-layout

        //獲得界面上所以顯示的itemView的個數(shù)
        int count = mChildHelper.getChildCount();

        for (int i = 0; i < count; ++i) {

            ......略

            //保存所有ViewHolder的動畫信息
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                long key = getChangedHolderKey(holder);
               
                //如果ViewHolder有變更就保存起來
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {

        ......略
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;  //設(shè)置RecyclerView的結(jié)構(gòu)沒有改變,因為這里是為了測量ReyclerView寬高而預(yù)布局子ItemV
        // 讓LayoutManager布局所有ItemView的位置
        mLayout.onLayoutChildren(mRecycler, mState);
        //恢復(fù)本來的狀態(tài)
        mState.mStructureChanged = didStructureChange;
        ......略
        // 其實走到這里把子View的位置和大小測量位置完了,但是沒有去draw子itemView,界面應(yīng)該還是不可見的
        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {

                ......略

                //為每一個ViewHolder創(chuàng)建一個ItemHolderInfo,保存ViewHolder的位置信息動畫信息等
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                        mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                ......略

            }
        }
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    //恢復(fù)繪制鎖定
    resumeRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}

dispatchLayoutStep2()方法:

private void dispatchLayoutStep2() {
    //鎖定布局請求
    eatRequestLayout();
    onEnterLayoutOrScroll();
    //設(shè)置RecyclerView為布局和動畫狀態(tài)
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;  //預(yù)布局已經(jīng)完成,dispatchLayoutStep1方法中此值為true,為false的時候才會去真正的測量子View
    mLayout.onLayoutChildren(mRecycler, mState);  //LayoutManager去測量布局子ItemView

    mState.mStructureChanged = false;  //設(shè)置RecyclerView結(jié)構(gòu)沒變化
    mPendingSavedState = null;

    ......略
    //恢復(fù)布局鎖定
    resumeRequestLayout(false);
}

三步測量解釋:

  • dispatchLayoutStep1: Adapter的更新; 決定該啟動哪種動畫; 保存當(dāng)前View的信息(getLeft(), getRight(), getTop(), getBottom()等); 如果有必要,先跑一次布局并將信息保存下來。
  • dispatchLayoutStep2: 真正對子View做布局的地方。
  • dispatchLayoutStep3: 為動畫保存View的相關(guān)信息; 觸發(fā)動畫; 相應(yīng)的清理工作。
    但是在onMeasure方法中沒有調(diào)用dispatchLayoutStep3方法,下面的onLayout方法中會調(diào)用到這個方法。

3.onLayout

onLayout方法源代碼閱讀:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    //直接調(diào)用dispatchLayout()方法布局
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

dispatchLayout()方法源代碼閱讀:

void dispatchLayout() {
    if (mAdapter == null) {
        Log.e(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "No layout manager attached; skipping layout");
        // leave the state in START
        return;
    }
    //當(dāng)Adapter或者LayoutManager為空的時候就直接返回,還測量布局個毛線球


    mState.mIsMeasuring = false;  //設(shè)置RecyclerView布局完成狀態(tài),前面已經(jīng)設(shè)置預(yù)布局完成了。
    if (mState.mLayoutStep == State.STEP_START) { //如果沒在OnMeasure階段提前測量子ItemView
        dispatchLayoutStep1();  //收集ItemView的ViewHolder的信息并保存
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();  //正在測量布局子ItemView
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {  
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        //有更新操作或者寬期望的寬高跟目前的寬高不一致就重新測量所有的子ItemView
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    //執(zhí)行測量第三部
    dispatchLayoutStep3();
}

可以看到在onLayout階段也是跟onMeasure階段一樣會選擇性地執(zhí)行測量布局三個步驟,那么這三步在RecyclerView的什么情況下會有不同的執(zhí)行呢?

測量布局總結(jié):

就是RecyclerView的子ItemView可能會被測量布局多次,如果是RecyclerView的寬高是寫死或者是match_parent
那么在onMeasure階段不會提前測量布局子ItemView。如果寬或者高是wrap_content的,由于還沒測量布局子ItemView,
所以不知道RecyclerView的內(nèi)容的寬高,那么就要提前在onMeasure階段就測量布局子ItemView確定內(nèi)容顯示區(qū)需要的寬高值來確定RecyclerView的寬高。

dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3這三步都一定會執(zhí)行,只是在RecyclerView的寬高是寫死或者是match_parent的時候會提前執(zhí)行dispatchLayoutStep1 dispatchLayoutStep2者兩個方法。會在onLayout階段執(zhí)行dispatchLayoutStep3第三步。

RecyclerView 寫死寬高的時候onMeasure階段很容易,直接設(shè)定寬高。但是在onLayout階段會把dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3三步依次執(zhí)行。

這就不看dispatchLayoutStep3這個方法了,有興趣的自己去看,博客寫著累,還沒人看。
下面是dispatchLayoutStep3方法的解釋:

The final step of the layout where we save the information about views for animations,trigger animations and do any necessary cleanup.
最后一步的布局,我們保存觸發(fā)動畫和做任何必要的清理。

3.onDraw

終于來到了最后一步繪制了,寫了好幾天,沒人看!扎心了。
onDraw方法源代碼閱讀:

@Override
public void onDraw(Canvas c) {
    super.onDraw(c); //所有的ItemView先繪制

    //子ItemView繪制完了以后再繪制裝飾
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) { //遍歷所有的裝飾依次繪制
        mItemDecorations.get(i).onDraw(c, this, mState);
    }
}

ItemDecoration的onDraw方法其實是開發(fā)者自定義的,我們在創(chuàng)建ItemDecoration的時候需要重新其onDraw來自己繪制裝飾,把更多的靈活性給開發(fā)者。關(guān)于ItemDecoration的自定義請看:http://www.itdecent.cn/p/b46a4ff7c10a

測量布局繪制三個步驟只有onDraw看起來最簡單了,其實正如我前面的文章Android Render(二)7.1源碼硬件加速下draw繪制流程分析所說,onDraw方法如果不要需要一些特殊的效果,在TextView、ImageView這些控件中已經(jīng)繪制完了,沒必要在一些LinearLayout、RecyclerView等容器控件中去處理子View的繪制了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容