從源碼角度分析View的繪制流程

在上一篇文章【從源碼角度分析Activity竿报、Window铅乡、View的關(guān)系】中講到了View的加載流程,最終會(huì)調(diào)用ViewRootImpl的invalidate()方法烈菌。如果對(duì)View的加載流程不熟悉的阵幸,可以先去了解下,然后再和這篇文章結(jié)合芽世,這樣理解的會(huì)比較體系化挚赊。下面開始今天的View的繪制流程之源碼之旅。

1济瓢、ViewRootImpl的invalidate()

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //1
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在上面的代碼中荠割, invalidate() 中有一個(gè)mWillDrawSoon標(biāo)識(shí)位,在進(jìn)行繪制流程的時(shí)候mWillDrawSoon=true旺矾,它的作用:在我們調(diào)用invalidate()蔑鹦,其未執(zhí)行完成時(shí),再次調(diào)用invalidate()是無效的箕宙。也就是說在同一時(shí)間我們多次調(diào)用invalidate()嚎朽,只會(huì)執(zhí)行一次。在上面的代碼可以發(fā)現(xiàn)任務(wù)轉(zhuǎn)移到了注釋1的位置柬帕,其中mTraversalRunnable是一個(gè)Runnable對(duì)象哟忍,mChoreographer是一個(gè)Choreographer對(duì)象,先進(jìn)入Choreographer的postCallback()方法看下做了什么事陷寝?

public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

在上面代碼中锅很,通過一系列的跳轉(zhuǎn),我們可以發(fā)現(xiàn)凤跑,最后會(huì)在postCallbackDelayedInternal()方法內(nèi)部通過mHandler發(fā)送消息處理爆安,這里調(diào)用的是mTraversalRunnable的run方法去執(zhí)行,并未啟動(dòng)一個(gè)新的工作線程饶火,從這里可以知道鹏控,ViewRootImpl的invalidate()是同步執(zhí)行的。現(xiàn)在已經(jīng)知道怎么啟動(dòng)的了肤寝,那么我們回到mTraversalRunnable的run方法。

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //繪制流程開始執(zhí)行
            doTraversal();
        }
    }

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //執(zhí)行遍歷
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

2抖僵、ViewRootImpl的performTraversals()方法

由于該方法的代碼量實(shí)在是太大了鲤看,這里就截取關(guān)鍵性的代碼,一些細(xì)節(jié)可能會(huì)忽略掉耍群,但是不會(huì)影響對(duì)整體繪制流程的理解义桂。

private void performTraversals() {
      
             //省略代碼....
            //在這里獲取的是根布局的WidthMeasureSpec和HeightMeasureSpec找筝,
            //根布局的參數(shù)是從window中得到的,看了文章開頭提的那篇文章就可以發(fā)現(xiàn)
             int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
             int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            
             //省略代碼....
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
             
              //省略代碼....
             performLayout(lp, mWidth, mHeight);
             
              //省略代碼....
             performDraw();
             //省略代碼....
 }

在performTraversals() 對(duì)于繪制流程主要的就是上面的三個(gè)方法了慷吊。接下來對(duì)這個(gè)三個(gè)方法進(jìn)行逐個(gè)分析袖裕。

3、 performMeasure()

對(duì)View進(jìn)行測量溉瓶,內(nèi)部主要調(diào)用了View的measure()方法急鳄,源碼如下:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
          //對(duì)View進(jìn)行測量
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            //......1
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //......2
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

從上面代碼可發(fā)現(xiàn),View的measure方法是不可重寫的堰酿。在注釋1疾宏,通過MeasureSpec調(diào)整widthMeasureSpec和heightMeasureSpec;而這個(gè)MeasureSpec是什么呢触创?

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.

上面是Google官方文檔對(duì)MeasureSpec的定義坎藐。大概意思:MeasureSpec內(nèi)部封裝了父View對(duì)子View的布局規(guī)格要求,每一個(gè)MeasureSpec都代表一個(gè)寬度和高度的規(guī)格哼绑,由一個(gè)尺寸和一個(gè)模式組成岩馍。

MeasureSpec的三種模式:

  • UNSPECIFIED: 父View對(duì)子View不施加任何要求,子View想要多大就多大抖韩。
  • EXACTLY :父View對(duì)子View設(shè)定了確切的尺寸蛀恩,不管子View想要多大,都會(huì)是父View給予的大小
  • AT_MOST:子View可以達(dá)到它自己想要的大小帽蝶,但是不會(huì)超過父View所剩余的空間赦肋。

我們回到繪制流程中,繼續(xù)跟蹤代碼励稳,在注釋2佃乘,調(diào)用了onMeasure()并且把widthMeasureSpec和heightMeasureSpec作為參數(shù)傳入。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

可以發(fā)現(xiàn)onMeasure()方法很簡單驹尼,只是調(diào)用了setMeasuredDimension()方法趣避;首先,看下setMeasuredDimension()方法中的getDefaultSize(getSuggestedMinimumWidth())新翎;寬度和高度的設(shè)置都是同一個(gè)原理程帕,所以這里就選一個(gè)寬度來分析。

//measureSpec也就是從父View傳遞過來的widthMeasureSpec
//size是getSuggestedMinimumWidth()返回的值
 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        //獲取父View的測量模式
        int specMode = MeasureSpec.getMode(measureSpec);
        //獲取父View的寬的尺寸大小
        int specSize = MeasureSpec.getSize(measureSpec);

        //根據(jù)父View的測量模式返回一個(gè)默認(rèn)的尺寸
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

//這里先判斷是否設(shè)置了背景地啰,如果沒有就返回mMinWidth (即:android:minWidth)
//如果有愁拭,則返回背景的最小寬度
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
//保存measuredWidth和measuredHeight
 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

到這里,View的基本的measure就已經(jīng)完成了亏吝,但是岭埠,根視圖是DecorView,是一個(gè)ViewGroup,那么對(duì)于ViewGroup而言惜论,其測量又是怎樣的呢许赃?ViewGroup也是繼承自View,不過由于它的特殊性馆类,它存放的是一個(gè)個(gè)子View混聊,所以并不是直接對(duì)ViewGroup進(jìn)行measure,而是通過遍歷的方式對(duì)子View進(jìn)行measure乾巧,等遍歷完所有子View后確認(rèn)自身大小句喜。在ViewGroup中measure的方法有measureChildWithMargins()、measureChildren()卧抗、measureChild()藤滥;measureChildren()這個(gè)方法遍歷時(shí)直接調(diào)用measureChild()對(duì)子View進(jìn)行measure。measureChild()和measureChildWithMargins()的區(qū)別是在于有沒有把margin和padding作為子視圖的大小社裆。對(duì)measureChildren()這個(gè)方法拙绊,里面的實(shí)現(xiàn)比較簡單,通過傳入父View的WidthMeasureSpec泳秀、HeightMeasureSpec和自身的LayoutParams結(jié)合标沪,生成自己的childWidthMeasureSpec;

下面我們看下measureChildWithMargins()這個(gè)方法:

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        //子View的LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        
        //根據(jù)自身的LayoutParams和父view的MeasureSpec生成自生的childMeasureSpec 
        //同時(shí)計(jì)算了Padding 和 Margin值嗜傅,已使用的尺寸
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        //這里會(huì)回調(diào)到我們之前分析的View的onMeasure()方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //獲取父View的Size和Mode
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        //根據(jù)父View不同的模式金句,對(duì)子View進(jìn)行不同的調(diào)整
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

對(duì)于繪制流程的第一步到這里就結(jié)束了,這里對(duì)前面的信息再回顧下吕嘀,通過performMeasure()進(jìn)行測量時(shí)违寞,然后調(diào)用View的measure(),然后又切換onMeasure()偶房;對(duì)于ViewGroup趁曼,是通過遍歷測量子View來確定自身的最終大小的。在測量過程中棕洋,View的大小是有父View的測量規(guī)格和View自身的LayoutParams決定的挡闰。在實(shí)際開發(fā)中,如果我們想要獲取View的寬度掰盘,就必須在onMeasure()后獲取才有效摄悯。

寫的有點(diǎn)飄了~~~補(bǔ)充下查克拉,繼續(xù)

4愧捕、performLayout()

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
       //............省略代碼
        final View host = mView;
        if (host == null) {
            return;
        }  
       //............省略代碼
       host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
      //............省略代碼
}
 public void layout(int l, int t, int r, int b) {
        //已進(jìn)行Measure的會(huì)跳過這里
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //........1
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

在上面的代碼中可以發(fā)現(xiàn)奢驯,任務(wù)轉(zhuǎn)移到到了注釋1的 onLayout(changed, l, t, r, b);l和t是左上角坐標(biāo)次绘,r和b是右下角坐標(biāo)叨橱。接下我們看下onLayout源碼:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

這里竟然是空實(shí)現(xiàn)典蜕,那么我們就找一個(gè)具體的來分析断盛,下面選了RelativeLayout中的onLayout罗洗。

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //  The layout has actually already been performed and the positions
        //  cached.  Apply the cached values to the children.
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                RelativeLayout.LayoutParams st =
                        (RelativeLayout.LayoutParams) child.getLayoutParams();
                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
            }
        }
    }

RelativeLayout中的onLayout的實(shí)現(xiàn),首先獲取子View的總數(shù)钢猛,然后for循環(huán)遍歷伙菜,得到每個(gè)子View的LayoutParams ,再獲取子View的左上角坐標(biāo)命迈,和右下角坐標(biāo)贩绕,開始layout。

Layout過程總結(jié):layout在實(shí)現(xiàn)方面也比較靈活壶愤,因?yàn)閷?duì)于每一個(gè)ViewGroup而言淑倾,絕大多數(shù)時(shí)候,其內(nèi)容都是不同的征椒,通過遍歷每一個(gè)子View去layout娇哆,而相應(yīng)參數(shù)在measure過程已經(jīng)保存了,會(huì)傳遞到layout這個(gè)過程勃救。

5碍讨、 performDraw()

接下來看繪制流程的最后一個(gè),繪制(Draw)蒙秒。首先進(jìn)入performDraw()方法勃黍。

private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        } else if (mView == null) {
            return;
        }

        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        //..........代碼省略 
}

從上面的代碼中,找到關(guān)鍵點(diǎn)draw()晕讲,接著往下看

private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface; //原始圖片的處理緩沖區(qū)
        if (!surface.isValid()) {
            return;
        }      
         //.........省略代碼
        //開始繪制
         if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }

在draw()方法里只選擇了關(guān)鍵性的代碼作為參考覆获,把任務(wù)切換到了drawSoftware()方法。

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                                 boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;
            //.......1
            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                //.......2
                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
              //.......3
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }

在上面的代碼中瓢省,注釋1處弄息,利用mSurface.lockCanvas()鎖定dirty區(qū)域,獲取Canvas净捅。然后在注釋2處疑枯,把canvas作為參數(shù)傳入View的draw()方法進(jìn)行繪制。注釋3蛔六,繪制完成后回收surface荆永。接下來切換到View的draw():

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
        //.........省略代碼
    }

上面代碼中的注釋已經(jīng)很明確了,
step1:進(jìn)行背景的繪制国章,
step2 & step5:保存圖層具钥,繪制邊緣液兽、漸變效果掌动,
step3:繪制view的內(nèi)容宁玫,在這個(gè)地方,不同的view欧瘪,內(nèi)容也不一樣(子View重寫),這也就是我們自定義View重寫的onDraw()佛掖,
step4:同樣也是繪制子View妖碉,不同的View需要各自重寫,
step6:繪制裝飾(前臺(tái)芥被、滾動(dòng)條)欧宜,
step7:繪制默認(rèn)焦點(diǎn)到畫布上。

Android view的繪制流程.png

完篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拴魄,一起剝皮案震驚了整個(gè)濱河市冗茸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羹铅,老刑警劉巖蚀狰,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異职员,居然都是意外死亡麻蹋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門焊切,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扮授,“玉大人,你說我怎么就攤上這事专肪「校” “怎么了贴汪?”我有些...
    開封第一講書人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我牡属,道長库物,這世上最難降的妖魔是什么疫萤? 我笑而不...
    開封第一講書人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任芝囤,我火速辦了婚禮,結(jié)果婚禮上关贵,老公的妹妹穿的比我還像新娘遇骑。我一直安慰自己,他們只是感情好揖曾,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開白布亥啦。 她就那樣靜靜地躺著翔脱,像睡著了一般碍侦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枚驻,一...
    開封第一講書人閱讀 52,821評(píng)論 1 314
  • 那天再登,我揣著相機(jī)與錄音锉矢,去河邊找鬼。 笑死循头,一個(gè)胖子當(dāng)著我的面吹牛卡骂,可吹牛的內(nèi)容都是我干的全跨。 我是一名探鬼主播浓若,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼少办,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼英妓!你這毒婦竟也來了蔓纠?” 一聲冷哼從身側(cè)響起腿倚,我...
    開封第一講書人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤暂筝,失蹤者是張志新(化名)和其女友劉穎焕襟,沒想到半個(gè)月后鸵赖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體它褪,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茫打,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诗越。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚷狞。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡床未,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啃洋,到底是詐尸還是另有隱情问裕,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站秉氧,受9級(jí)特大地震影響亚斋,放射性物質(zhì)發(fā)生泄漏纸泡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一吧兔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧箍土,春花似錦吴藻、人聲如沸弓柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕侠。三九已至绢彤,卻和暖如春茫舶,著一層夾襖步出監(jiān)牢的瞬間刹淌,已是汗流浹背疹启。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來泰國打工喊崖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荤懂,地道東北人节仿。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像眉踱,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子册烈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

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