源碼分析篇 - Android繪製流程(二)measure、layout、draw流程

   performTraversals方法會通過measure、layout和draw三個流程才能將一幀View須要顯示的內容繪製到屏幕上,用最簡化的方式看ViewRootImpl.performTraversals()方法,以下。java

 private void performTraversals() {
     ... 
     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
     ...
     performLayout(lp, mWidth, mHeight); 
     ...
     performDraw();
    . .. 
 }

首先來講這三個流程的意義:android

performMeasure():從根節點向下遍歷View樹,完成全部ViewGroup和View的測量工做,計算出全部ViewGroup和View顯示出來須要的高度和寬度;算法

performLayout():從根節點向下遍歷View樹,完成全部ViewGroup和View的佈局計算工做,根據測量出來的寬高及自身屬性,計算出全部ViewGroup和View顯示在屏幕上的區域;canvas

performDraw():從根節點向下遍歷View樹,完成全部ViewGroup和View的繪製工做,根據佈局過程計算出的顯示區域,將全部View的當前需顯示的內容畫到屏幕上。app

 

再來具體分析這三個流程:less

1. performMeasure()流程異步

  在ViewRootImpl.java中調用performMeasure()方法傳入的參數爲childWidthMeasureSpec和childHeightMeasureSpec。在performTraversals能找到它們初始化的地方以下。ide

  int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  經過getRootMeasureSpec可以計算出一個MeasureSpec值,該值通常是父佈局調用子佈局的measure()是傳入的參數,這個參數是由父佈局的寬高和子佈局的LayoutParams參數計算獲得的,這兩個參數用於子佈局的measure過程,很大程度上決定着子View的寬高。上面代碼出計算出來的childWidthMeasureSpec和childHeightMeasureSpec則是根據Window相關屬性獲得的MeasureSpec,由於DecorView自己沒有父佈局,因此傳入給DecorView進行measure的MeasureSpec值是由Window的尺寸(mWidth、mHeigth)和DecorView的LayoutParams獲得的;而對於普通的View,它的MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來共同決定。oop

  方法中的lp.width和lp.heigth表示佈局類型,這裏指的是DecorView的佈局類型。佈局類型舉例來講,寫佈局xml時android:layout_width="wrap_content"中設置的wrap_content值,共有三種類型MATCH_PARENT、WRAP_CONTENT以及直接寫入了肯定大小。進入getRootMeasureSpec()方法。佈局

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

  實際上getRootMeasureSpec()方法就是根據傳入的窗口大小和類型,經過MeasureSpec.makeMeasureSpec()方法合併成一個int型的measureSpec值。該值表明一個32位 int 值,高2位表明 SpecMode(測量模式),由傳入的佈局類型轉化而來,有三種類型,MeasureSpec.EXACTLY:肯定大小,parent view爲child view指定固定大小;MeasureSpec.AT_MOST:最大大小;child view在parent view中取值;MeasureSpec.UNSPECIFIED:無限制,parent view不約束child view的大小。該值低30位表明 SpecSize,指在某個測量模式下的規格大小。後面的方法中會經過MeasureSpec.getMode()和MeasureSpec.getSize()方法來解析出這兩個值。

  從這裏就能夠看到childWidthMeasureSpec, childHeightMeasureSpec實際上表示的就是DecorView的寬高和佈局類型。進入到performMeasure()方法。

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

  該方法設置完TraceView的起始點和結束點後直接即是進入到了mView.measure()方法,進入對應View.measure()方法代碼。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     ...

//是否有從新Layout的標誌
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.
     
//與上一次的MeasureSpec進行對比肯定是否須要從新繪製
     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();        //key的值是由widthMeasureSpec和heightMeasureSpece
//在MeasureCache中查看在當前傳入的寬高的MeasureSpec是否已經執行過onMeasure計算
        //若是已經執行過,則直接取出結果經過setMeasuredDimemsionRaw()設置測量出的相關參數
//若是沒有執行過,纔會調用onMeasure()方法進行測量工做
//mPrivateFlags3的設置,在後面介紹的View.layout()方法中會用到
//在後面layout()方法中會判斷該flag,若是此時沒有調用,則那layout會再調用measure()
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // 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) { (2) throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }      ... }

  實際的measure過程是在onMeasure()方法中完成的,這裏進入到調用到View.onMeasure()方法。

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

  你們注意這裏要注意performMeasure()方法是final類型的,便是不能被父類重載的,因此不管是對於任何一種Layout(父類爲ViewGroup.java,ViewGroup.java父類爲View.java)的measure()方法調用,或是任何一個控件好比TextView的measure()方法調用,執行代碼都是View.java中的measure()方法;而不一樣的控件measure過程的區別是經過重寫onMeasure()方法來實現的。因此在measure()方法裏調用的onMeasure()方法並未直接走到View.onMeasure()方法,而是走到了View的父類中重寫的onMeasure()方法。可是,這裏咱們要注意到上一段代碼的(2)處,這段代碼是在調用後onMeasure()方法以後的一個判斷,上面的註釋的意思是若是到如今尚未調用過setMeasuredDimension()方法,就會拋出下面的異常,因此在View的父類重寫onMeasure()方法時,必定要執行一次setMeasuredDimension()方法。那這個方法作了什麼事呢?進入View.setMeasuredDimension()方法代碼。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
     //根據本身與父佈局的android:layoutMode的值調整傳入參數
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); }

  直接進入setMeasuredDimensionRaw()方法。

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

  這裏咱們能夠看到setMeasuredDimension()實際上就是將傳入的參數設置到View的變量mMeasuredWidth和mMeasuredHeight中。這也是實際上measure流程所要完成的任務,便是調用到佈局樹上的全部ViewGroup和View的measure(),讓全部的ViewGroup和View計算出對應的寬、高的值保存到本身的mMeasuredWidth、mMeasuredHeight變量中。

  咱們繼續來分析DecorView的measure流程,進入DecorView.onMeasure()方法。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final boolean isPortrait =
                getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;

        final int widthMode = getMode(widthMeasureSpec);
        final int heightMode = getMode(heightMeasureSpec);

        boolean fixedWidth = false;
        mApplyFloatingHorizontalInsets = false;

     //若是SpecMode不是EXACTLY的,則須要在這裏調整爲EXACTLY
if (widthMode == AT_MOST) { final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { final int w;
//根據DecorView屬性,計算出DecorView須要的寬度
if (tvw.type == TypedValue.TYPE_DIMENSION) { w = (int) tvw.getDimension(metrics); } else if (tvw.type == TypedValue.TYPE_FRACTION) { w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); } else { w = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w); final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//根據上面計算出來的須要的寬度生成新的MeasureSpec用於DecorView的測量流程
if (w > 0) { widthMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(w, widthSize), EXACTLY); fixedWidth = true; } else { widthMeasureSpec = MeasureSpec.makeMeasureSpec( widthSize - mFloatingInsets.left - mFloatingInsets.right, AT_MOST); mApplyFloatingHorizontalInsets = true; } } } mApplyFloatingVerticalInsets = false; if (heightMode == AT_MOST) {   ... //邏輯同上 } ...super.onMeasure(widthMeasureSpec, heightMeasureSpec); ... }

  因爲DecorView.java父類是FrameLayout.java,因此調用super.onMeasure()時進入FrameworkLayout.onMeasure()方法。

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

     //若是寬、高的MeasureSpec的Mode有一個不是EXACTLY,這裏就是true
final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0;
     //遍歷子View
for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) {
          //子View的測量,方法內會調用到child.measure(),後文詳解 measureChildWithMargins(child, widthMeasureSpec,
0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          //計算出全部子佈局中寬度和高度最大的值
          //因爲子佈局佔用的尺寸除了自身寬高以外,還包含了其距離父佈局的邊界的值,因此須要加上左右Margin值 maxWidth
= Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState());
          //當前的FrameLayout的MeasureSpec不都是EXACTLY,且其子View爲MATCH_PARENT,
          //則子View保存到mMatchParentChildren中,後面從新測量
         //DecorView不會走這個邏輯,由於進過了DecorView的onMeasure()流程,MeasureSpec必定都爲EXACTLY
          //會走到下面流程的狀況舉例:用戶自佈局一個FrameLayout屬性爲WRAP_CONTENT是,但子佈局爲MATCH_PARENT
if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } }
     //最後計算獲得的maxWidth和maxHeight的值須要保證可以容納下當前Layout下全部子View,因此須要對各種狀況進行處理
     //因此有如下的加上Padding值,用戶設置的Mini尺寸值的對比,設置了背景圖片狀況的圖片大小對比
     
// Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); }
     //設置測量結果,至關於完成本身View的measure setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState
<< MEASURED_HEIGHT_STATE_SHIFT));
     //會走到這裏的狀況見前面註釋 count
= mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) {
            //根據當前FrameLayout已經測量出來的mMeasureWidth,計算出MATCH_PARENT的子View的寬度值
            final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } ... //childHeigthMeasureSpe的設置,邏輯同上一段 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }

  首先是對於maxHeight和maxWith的寬度的計算,爲什麼能經過全部子佈局中最大的子佈局的尺寸來決定本身的尺寸呢?首先要回顧下FrameLayout的佈局模式,簡單來講,就是全部放在佈局裏的控件,都按照層次堆疊在屏幕的左上角,後加進來的控件覆蓋前面的控件。正是由於要實現這樣的佈局模式,才決定了FrameLayout的measure算法,全部的子View都是堆疊在屏幕左上角,天然只須要根據最大的子View的尺寸來設置本身(還須要加一些特殊狀況的處理),便可可以放下全部的子View。若是是LinearLayout的onMeasure()方法,則又會實現不一樣的測量邏輯,在測量時則會考慮到多個View依次擺放的問題。

  再來關注代碼中第一個加粗的方法measureChildWithMargins(),這裏邏輯是依次調用該方法完成FrameLayout全部子View的measure工做。該方法在父類ViewGroup.java類中實現的,進入ViewGroup.measureChildWithMargins()方法代碼。

   protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

  這個方法比較簡單,首先關注下widthUsed和heithUsed的意義,表示的是父佈局已經佔用的大小,上面的FrameLayout.onMeasure()代碼中調用時傳入的是0。爲什麼是0?由於FrameLayout的邏輯是所有堆疊在右上角,因此這個子View放到FrameLayout中時,不會被其它子View提早使用了FrameLayout的空間。

  重點關注下getChildMeasureSpec()方法,經過該方法可以獲得子View的MeasureSpec,因此才能調用子View的measure()方法,完成子View的測量。注意,這裏傳入的lp.width表示的是佈局中的android:layout_width對應的值,多是肯定的值,多是LayoutParams.MATCH_PARENT或LayoutParams.WRAP_CONTENT。進入ViewGroup.getChildMeasureSpec()方法。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
     //父佈局的SpecMode和Size
int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);
     //父佈局中剩下的可以提供給子View使用的尺寸,經過後面計算獲得子View須要多少
int size = Math.max(0, specSize - padding);
     //用於保存子佈局的進行measure的MeasureSpec的兩個參數
int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us
     //若是父佈局是Mode是EXACTY
     case MeasureSpec.EXACTLY: if (childDimension >= 0) {
          //子View賦值了固定大小
          //則子View的SpecSize就是本身想要的大小
//則子View的SpecMode是EXACTY resultSize
= childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it.
          //子View是MATCH_PARENT
          //子View想要父佈局全部大小,則把父佈局剩餘的大小都給子View
          //子View的SpecMode是EXACTLY
          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.
          //子View是WRAP_CONTENT
          //子View想要在後面本身計算本身須要多少大小
          //則把父佈局剩餘的大小存入SpecSize,但SpecMode爲AT_MOST
//表示子佈局在後面measure本身大小的同時不能超過SpecSize的值
          resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us
     //父佈局Mode爲AT_MOST 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.
//子View是MATCH_PARENT
//因爲父佈局沒有肯定大小,因此子佈局在肯定本身須要多少大小前不能給出肯定大小
//則把父佈局剩餘的大小存入SpecSize,但SpecMode爲AT_MOST 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
//若是子View是MATFH_PARENT
          //因爲父佈局沒有限定子佈局大小,則設置SpecSize值爲0 (須要View支持這種模式)
          //設置SpecMode類型仍是爲UNSPECIFIED,這樣最後計算出有多大就給多大,沒有父佈局的限制(下同) 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
//生成MeasureSpec
     return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

  在計算出用於子View進行measure的高和寬的MeasureSpec後,便會進入到子View的measure()方法。若是子View是ViewGroup類型,則會繼續調用到其子View的measure()方法,並調用setMeasuredDimension()方法設置mMeasureHeight和mMeasureWidth完成本身的measure;若是是子View是一個控件(例如TextView),則會根據本身onMeasure()的實現完成自身的測量,也是調用setMeasuredDimension()方法設置變量。經過這樣的方式便個遍歷完View樹上全部的ViewGroup中間節點和View葉節點,完成measure流程。

 

2. performLayout()流程

  進入ViewRootImpl.performLayout()源碼。

   private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
       
     ...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());   
       ...    

      } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }

  這裏調用的到DecorView的layout方法,該方法是其實是調用到到了ViewGroup的layout()方法。上面介紹measure流程時咱們知道了兩個相關方法measure()和onMeasure(),其中measure()方法在全部視圖到基類View中實現的,且不能被重寫;而onMeasure()方法實現了實際的測量過程,須要由繼承了View的視圖子類重寫該方法從而實現了本身的測量邏輯。而對於Layout流程,也有着兩個相關方法layout()和onLayout(),進入View.java代碼看這連個方法。View.layout()方法後面詳講。

 public void layout(int l, int t, int r, int b) {
        ...
        onLayout(changed, l, t, r, b);
     ...
 }

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

  首先這裏的layout()方法沒有final修飾符,表示layout()方法能夠被重寫,另外onLayout()方法是一個空方法,因此須要View的子類根據本身需求來重寫該方法來完成layout流程。再來看下繼承自View的ViewGroup類的layout()和onLayout()方法。

   @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {//過渡動畫相關
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

  ViewGroup的layout()方法加上了final修飾符,而onLayout()方法則是抽象類型的,因此全部繼承自ViewGroup類的佈局類(例如FrameLayout.java)都須要實現onLayout()方法,而不能重寫layout()方法。而且ViewGroup中的layout()的方法只是加上一些對於過渡動畫邏輯的處理,本質上是調回了View的layout()方法。

  在ViewRootImpl.performLayout()方法中調用到了DecorView的layout()的方法,根據上方代碼super.layout()實際調用到了View的layout()方法。注意這裏傳入的四個參數,分別爲0,0, host.getMeasuredWidth(), host.getMeasuredHeight(),後兩個值就是在measure流程中計算出來的mMeasuredWidth和MeasuredHeigth,用於表示須要的寬度和高度。進入View.layout()方法代碼。

   public void layout(int l, int t, int r, int b) {
     //mPrivateFlag3記錄了measure過程是否被跳過,若是被跳過則這時候再調用一次measure(),前文有提
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;
     //layoutoutMode爲Optical則會調到setOpticalFrame()
//setOpticalFrame()會對傳入的參數進行調整,但仍是調用到setFrame()方法
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
     //若是View位置發生了變化或已經設置了從新Layout的標誌
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b);        ... } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }

  首先會調用setFrame()方法,方法的返回值標誌了佈局與上一次是否發生了變化。傳入的四個參數的分別表明了,佈局左、頂部、右、底部的值,這四個值指示了一個矩形區域。

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

       //會回調onSizeChanged()方法 if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            ...
        }
        return changed;
    }

  這個方法比較簡單,主要是將父類傳入的區域保存到View的mLeft、mTop、mRight、mBottom。在執行完setFrame()以後便會執行到onLayout()方法。這裏咱們經過插入一段TextView.java類的onLayout()方法,來探究下layout這個流程到底是要完成什麼工做?TextView的layout也是從其父類傳入四個參數調用到View.layout()方法(上面代碼),而後setFrame(),而後就會進入到進入TextView.onLayout()代碼。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom); if (mDeferScroll >= 0) {
            int curs = mDeferScroll;
            mDeferScroll = -1;
            bringPointIntoView(Math.min(curs, mText.length()));
        }
    }

  能夠看到這裏只是在調用了View.onLayout()後添加了一個DeferScroll的邏輯處理。而回想下View.onLayout()方法其實什麼都沒有作,但這樣TextView的layout流程就結束了。那對於TextView來講,layout流程到底作了什麼事呢?其實主要就是設置了mLeft、mTop、mRight、mBottom這四個變量,這四個變量指示了這個TextView在屏幕上應該出現的座標位置區域;而這四個變量,是由TextView的父佈局View計算好了,再調用到TextView.layout()方法時傳入的。因此整個layout過程,實際遍歷整個View樹,根據measure過程計算出的View須要的寬度和高度值結合本身的LayoutParam屬性,計算出全部View在相對於本身父佈局View的邊界的位置,並保存到mLeft、mTop、mRight、mBottom變量中,用於後面的繪製操做。

  上面能夠知道TextView的layout計算出來的現實區域直接時父佈局傳入的,因此較爲複雜的計算邏輯是處於Layout類onLayout()源碼中的。DecorView繼承自FrameLayout,DecorView.onLayout()方法會調用到其父類FrameLayout類的onLayout()方法,進入FrameLayout.onLayout()方法。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

     //計算當前Layout的邊界padding值
final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍歷該Layout的全部子View
//結合子View的measure值,即本身的屬性,計算出子View的layout區域,並調用子View的layout()方法
for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          //得到該子佈局measure出來的寬、高值
final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;          

          //該Layout水平方向的gravity屬性各類狀況下的處理
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            //水平方向居中
case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break;
//水平方向居右
case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; }
//水平方向居左
case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; }
          //該Layout垂直方法的gravity屬性各類狀況的處理
switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; }
          //調用子View的layout方法,這裏傳入的參數就是父佈局計算好的子View的區域 child.layout(childLeft, childTop, childLeft
+ width, childTop + height); } } }

  這樣隨着Layout的onLayout()調用到全部子View的layout()方法,而子View的layout()又會繼續往下遍歷直至遍歷到View樹到全部節電,這樣就完成了整個layout的流程。

 

3. perfromDraw()流程

  執行完performLayout()方法,便會調用到performDraw()方法,進入到ViewRootImpl.performDraw()方法。

    private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            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); } ... }

  這裏主要關注調用到了draw()方法,進入ViewRootImpl.draw()代碼。  

   private void draw(boolean fullRedrawNeeded) {
        ...

     //若是是第一次繪製,則會回調到sFirstDrawHandlers中的事件
//在ActivityThread.attch()方法中有將回調事件加入該隊列
//回調時會執行ActivityThread.ensureJitEnable來確保即時編譯相關功能
if (!sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete = true; final int count = sFirstDrawHandlers.size(); for (int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i)); } } }      
     //滾動相關處理,若是scroll發生改變,則回調dispatchOnScrollChanged()方法 scrollToRectOrFocus(
null, false); if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); }
//窗口當前是否有動畫須要執行
boolean animating = mScroller != null && mScroller.computeScrollOffset();
     ... //scroll相關處理
     
     
final Rect dirty = mDirty;
     ...
     
     
if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } ...

     
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//mAttachInfo.mHardwareRenderer不爲null,則表示該Window使用硬件加速進行繪製
//執行ViewRootImpl.set()方法會判斷是否使用硬件加速

//若判斷使用會調用ViewRootImpl.enableHardwareAcceleration()來初始化mHardwareRenderer
       //該View設置爲使用硬件加速,且當前硬件加速處於可用狀態
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { ...
//使用硬件加速繪製方式 mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo,
this); } else {// If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance.
//若是當前View要求使用硬件加速,但硬件加速處於disable狀態
//多是因爲硬件加速在銷燬以前的surface實例時會發出無效的宣告致使的
          if (mAttachInfo.mHardwareRenderer != null && !mAttachInfo.mHardwareRenderer.isEnabled() && mAttachInfo.mHardwareRenderer.isRequested()) { try {
//嘗試從新初始化當前window的硬件加速 mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); }
catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } mFullRedrawNeeded = true; scheduleTraversals(); return; }
          //使用軟件渲染繪製方式
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } ... }

  mDirty表示的是當前須要更新的區域,即髒區域。通過一些scroll相關的處理後,若是髒區域不爲空或者有動畫須要執行時,便會執行重繪窗口的工做。有兩種繪製方式,硬件加速繪製方式和軟件渲染繪製方式,在建立窗口流程的ViewRootImpl.setView()中,會根據不一樣狀況,來選擇是否創mAttachInfo.mHardwareRenderer對象。若是該對象不爲空,則會進入硬件加速繪製方式,即調用到ThreadedRenderer.draw();不然則會進入軟件渲染的繪製方式,調用到ViewRootImpl.drawSoftware()方法。可是不管哪一種方式,都會走到mView.draw()方法,即DecorView.draw()方法。該方法是實際調用到到是View.draw(Canvas canvas)方法。

  在調用View.draw()方式時調用該方法時會傳入canvas參數,硬件加速和軟件渲染這兩種方式會建立出不一樣的Canvas用以執行View的draw流程。Canvas顧名思義就是畫布的意思,這個類一方面表明了當前用於繪製的區域及區域屬性相關信息,同時也提供了各種接口用於在這片區域上畫出給類圖形,好比調用canvas.drawCircle(...)方法就根據傳入參數在屏幕上畫出一個特定的圓圈。硬件加速和軟件渲染這兩種方式對於Canvas有着不一樣的底層實現,帶來的效果和對系統性能及內存這些的影響也不同,這裏咱們再也不展開介紹。直接看來View.draw()方法。

    @CallSuper
    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)
         */
 //根據上面註釋能夠知道draw的過程分爲5步
//1.畫出背景background //2.判斷是否須要畫邊緣的漸變效果
//3.畫出當前View須要顯示的內容,調用onDraw()來實現
//4.調用dispatchDraw()方法,進入子視圖的draw邏輯
//5.若是須要花邊緣漸變效果,則在這裏畫
//6.繪製裝飾(如滾動條)

     // 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;
     //若是不須要繪製邊緣漸變效果,跳過了step5
if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content
//繪製本身View的內容
       if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children
       //調起子View的Draw過程 dispatchDraw(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); // we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ ... //有邊緣漸變效果的處理// Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ... //畫出邊緣漸變效果// 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); }

  註釋已經很清楚,View的draw過程分爲了五個步驟,若是沒有邊緣漸變效果則會跳過第五步。這裏咱們關注onDraw()方法實現了本身View內容的繪製和dispatchDraw()方法調起了本身的子View的繪製過程。首先,在調用View中調用onDraw()時,因爲走的時DecorView的流程,因此會調用到DecorView.onDraw()方法,代碼以下。

    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);
//DecorView設置了一個BackgroundFallback,該對象用於應用沒有設置window背景時,會顯示該對象指示的背景 mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent); }

  這裏實際調用到的是父類的onDraw()方法,可是在DecorView的父類FrameLayout及更上一層的ViewGroup上都沒有實現該方法,因此super.onDraw()實際調用到了View.onDraw()方法。

    protected void onDraw(Canvas canvas) {
    }

  可是View.onDraw()方法是一個空實現,這裏能夠看出來,onDraw()方法是用於父類進行重寫來實現畫出本身內容的方法,而ViewGroup及其各類Layout子類都沒有實現該方法,由於對於佈局來講自己就是用來放置控件的,本身是沒有什麼內容好繪製的,因此在onDraw()是並未執行什麼內容。天然做爲FrameLayout子類的DecorView也沒有什麼好畫出來的,除了畫出一個FallbackBackground。但若是當前的View不是DecorView,也不是其它佈局View,而是一個控件類型的View,那在onDraw()時就會根據本身的屬性及本身須要顯示的區,經過調用傳入的Canvas對象的各類方法,來在屏幕上畫出本身須要顯示的內容。

  再來看View.draw()方法裏的Step4調用到disptachDraw()方法,在View.dispatchDraw()方法仍是一個空實現,說明是但願View的子類來實現的。而通常的非ViewGroup的控件子View類則不會實現該方法,由於這裏View是沒有子View的,因此當遍歷到控件類型的View時,這一步其實是什麼都沒有作的。咱們繼續來看DecorView的draw流程,這裏調用到dispatchDraw()實際調用到到是ViewGroup.dispatchDraw()方法,進入該代碼。

    @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

//若是當前的ViewGroup須要執行Layout級別的動畫
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams();
            //將須要執行的動畫設置到子View的對應屬性上 attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); } }
final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; }
       //啓動mLayoutAnimationControlle中設置的動畫 controller.start(); mGroupFlags
&= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE;
       //動畫啓動時的回調
if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
     //若是當前的ViewGroup設置了Padding的屬性
if (clipToPadding) { clipSaveCount = canvas.save();
//將父視圖傳入的canvas裁剪去padding的區域 canvas.clipRect(mScrollX
+ mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } ...
for (int i = 0; i < childrenCount; i++) {        ... //TransientView的處理

       
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } }
     ...

     // Draw any disappearing views that have animations
     //繪製mDisappearingChildren列別中的子視圖,指正在處於消失動畫狀態的子View if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } }
...

     //檢查動畫是否完成,若是完成則發送一個異步消息,通知應用程序
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && mLayoutAnimationController.isDone() && !more) { // We want to erase the drawing cache and notify the listener after the // next frame is drawn because one extra invalidate() is caused by // drawChild() after the animation is over mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; final Runnable end = new Runnable() { @Override public void run() { notifyAnimationListener(); } }; post(end); } }

  在dispatch中會遍歷當前ViewGroup的子視圖,而後調用drawChild()方法來依次調起子視圖的繪製過程,進入ViewGroup.drawChild()代碼。

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

  這裏直接就調用到了child.draw()方法,可是注意,這裏的draw()方法與咱們以前分析過的View.draw()方法參數不同,不只傳入了父佈局視圖的畫布,還把父佈局視圖視圖自身做爲參數傳入了。進入有三個參數的View.draw()方法的代碼。

   /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
   //這個方法是專門用於ViewGroup來調其子View的繪製過程的方法
//方法會傳入當前View的父佈局View,用來進行父佈局canvas畫布的區域移動和裁剪工做
//該方法會根據繪製類型(硬件加速、軟件渲染)結合View屬性進行一些canvas和painter屬性設置工做
   boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
     ...
  
     draw(canvas);
     ...
}

  這個方法很長,但都是邏輯算法類型的,不影響到後面的流程,方法的做用見上訪註釋。咱們回想一下以前的流程,從canvas的建立,到視圖使用canvas進行繪製,並未涉及到canvas的裁剪動做。那是是由於咱們是從DecorView進行分析的,在咱們略過的canvas初始化動做時就根據DecorView在layout佈局時計算出來的顯示區域,進行了canvas畫布區域的實質;而對於其它的ViewGroup在調起其子View的繪製動做時,就會調用到上方有三個參數的View.draw()方法,在該方法中結合layout過程當中的計算結果,完成canvas畫布的裁剪,而後再調用View.draw(Canvas)方法(以前介紹過的包含五個步驟的方法)來完成當前View的繪製過程。經過這樣一個流程,便可以調用到視圖樹上全部ViewGroup和控件View的draw()方法,畫出了全部View須要顯示的內容,完成了一次繪製過程。

相關文章
相關標籤/搜索