深入了解View的繪制流程


1.  ViewRoot

    ViewRoot是連接WindowManager與DecorView的紐帶,View的整個繪制流程的三大步(measure、layout、draw)都是通過ViewRoot完成的。當Activity對象被創建完畢后,會將DecorView添加到Window中(Window是對窗口的抽象,DecorView是一個窗口的頂級容器View,其本質是一個FrameLayout),同時會創建ViewRootImpl(ViewRoot的實現類)對象,並將ViewRootImpl與DecorView建立關聯。關於ViewRoot,我們只需要知道它是聯系GUI管理系統和GUI呈現系統的紐帶。View的繪制流程從ViewRoot的performTraversals方法開始,經過measure、layout、draw三大過程完成對一個View的繪制工作。peformTraversal方法內部會調用measure、layout、draw這三個方法,這三個方法內部又分別調用onMeasure、onLayout、onDraw方法。

    在onMeasure方法中View會對其所有的子元素執行measure過程,此時measure過程就從父容器"傳遞"到了子元素中,接着子元素會遞歸的對其子元素進行measure過程,如此反復完成對整個View樹的遍歷。onLayout與onDraw過程的執行流程與此類似。

    measure過程決定了View的測量寬高,這個過程結束后,就可以通過getMeasuredHeight和getMeasuredWidth獲得View的測量寬高了;

    layout過程決定了View在父容器中的位置和View的最終顯示寬高,getTop等方法可獲取View的top等四個位置參數(View的左上角頂點的坐標為(left, top), 右下角頂點坐標為(right, bottom)),getWidth和getHeight可獲得View的最終顯示寬高(width = right - left;height = bottom - top)。 

    draw過程決定了View最終顯示出來的樣子,此過程完成后,View才會在屏幕上顯示出來。

   

2. MeasureSpec

    MeasureSpec為一個32位的int值,高2位代表SpecMode,低30位代表SpecSize,前者指測量模式,后者指某種測量模式下的規格大小。在一個View的measure過程中,系統會將該View的LayoutParams結合父容器的“要求”生成一個MeasureSpec,這個MeasureSpec說明了應該怎樣測量這個View

(1)三種 SpecMode:

    UNSPECIFIED父容器不對View作任何要求,通常用於系統內部,表示一種測量的狀態。

    EXACTLY父容器已經檢測出View所需要的精確大小,這種測量模式下View的測量值就是SpecSize的值。這個SpecMode對應於LayoutParams中的match_parent和給出具體大小這兩種模式。

    AT_MOST父容器指定了一個可用大小即SpecSize,View的大小不能大於此值,可用大小取決於不同View的具體實現。這個SpecMode對應於LayoutParams中的wrap_content。

(2)對於DecorView,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同確定;對於普通View,他的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同確定。

 

3. View的具體繪制流程

(1)measure過程

a. DecorView的measure過程

    前面我們提到過,DecorView是一個應用窗口的根容器,它本質上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout,這個垂直線性布局管理器包含兩個子元素,一個是TitleView(ActionBar的容器),另一個是ContentView(窗口內容的容器)。關於ContentView,它是一個FrameLayout(android.R.id.content),我們平常用的setContentView就是設置它的子View。如下圖中,我們為TilteView中添加了一個ActionBar,為ContentView中添加了一個RelativeLayout(通過setContentView方法)。

    前面提到,DecorView的MeasureSpec由窗口的尺寸和自身的LayoutParams共同決定。在ViewRootImpl的measureHierarchy方法中,完成了創建DecorView的MeasureSpec的過程,相應的代碼片段如下:

1 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
2 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
3 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    以上代碼片段中的childXxxMeasureSpec即為DecorView的MeasureSpec,lp.width和lp.height被系統賦值為MATCH_PARENT。getRootMeasureSpec的代碼如下:

 1 private int getRootMeasureSpec(int windowSize, int rootDimension) {  
 2     int measureSpec;  
 3     switch (rootDimension) {  
 4         case ViewGroup.LayoutParams.MATCH_PARENT:  
 5             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
 6             break;  
 7         case ViewGroup.LayoutParams.WRAP_CONTENT:  
 8             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
 9             break;  
10         default:  
11             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
12             break;  
13     }  
14     return measureSpec;  
15 } 

    上述代碼中調用了makeMeasureSpec方法來獲取measureSpec,而傳入的rootDimension參數即為lp.width或lp.height,值為MATCH_PARENT,由此可得DecorView的MeasureSpec,其中SpecMode為EXACTLY,SpecSize為windowSize。

 

b. 普通View(非ViewGroup)的measure過程:

    非ViewGroup的View的特點是不能有子元素,因此只需測量好自身就行。普通View的measure通過measure方法來完成:

1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
2     //....  
3   
4     //回調onMeasure()方法    
5     onMeasure(widthMeasureSpec, heightMeasureSpec);  
6      
7     //more  
8 }

    普通View的measure方法是由ViewGroup在measureChild方法中調用的(即完成了measure過程從ViewGroup到子View的傳遞),ViewGroup調用其子View的measure時即傳入了該子View的widthMeasureSpec和heightMeasureSpec。注意到measure是一個final方法,因此要實現自定義的measure過程,需要重寫onMeasure方法,onMeasure方法源碼如下:

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

    setMeasureDimension方法用於設置View的測量寬高,如果不重寫此方法,默認是直接調用getDefaultSize方法獲取尺寸的:

 1 public static int getDefaultSize(int size, int measureSpec) {  
 2     int result = size;  
 3     int specMode = MeasureSpec.getMode(measureSpec);  
 4     int specSize = MeasureSpec.getSize(measureSpec);  
 5     switch (specMode) {  
 6         case MeasureSpec.UNSPECIFIED:  
 7             result = size;  
 8             break;  
 9         case MeasureSpec.AT_MOST:  
10         case MeasureSpec.EXACTLY:  
11             result = specSize;  
12             break;  
13     }  
14     return result;  
15 }  

    由以上代碼可知,正常情況下(SpecMode為AT_MOST或EXACTLY),getDefaultSize獲取的尺寸大小即為specSize。由以上代碼還可知道,直接繼承View的自定義控件需要重寫onMeasure方法並設置wrap_content時的自身大小,否則在布局中使用wrap_content就相當於使用match_parent的效果。示例如下:

 1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 2     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 3     int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 4     int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 5     int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 6     int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 7     if (widthSpecMode == MeasureSpec.AT_MOST  && heightSpecMode == MeasureSpec.AT_MOST) {
 8         setMeasuredDimension(mWidth, mHeight);
 9     } else if (widthSpecMode == MeasureSpec.AT_MOST) {
10         setMeasuredDimension(mWidth, heightSpecSize);
11     } else if (heightSpecMode == MeasureSpec.AT_MOST) {
12         setMeasuredDimension(widthSpecSize, mHeight);
13     }
14 }

 

    上述示例代碼中的mWidth,mHeight是為wrap_content時設定的默認寬高。這個默認寬高可根據實際需要自行設置,比如TextView在wrap_content時的默認寬高是根據其中的所有文字的寬度來設定的,從而實現正好“包裹”文字內容的效果。

 

c. ViewGroup的measure過程:

    ViewGroup需要先完成子View的measure過程,才能完成自身的measure過程,ViewGroup的onMeasure方法根據不同的布局管理器類(LinearLayout、RelativeLayout等等)有不同的實現,比如LinearLayout的onMeasure方法代碼如下:

1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2     if (mOriention == VERTICAL) {
3         measureVertical(widthMeasureSpec, heightMeasureSpec);
4     } else {
5         measureHorizontal(widthMeasureSpec, heightMeasureSpec);
6     }
7 }

 

    measureVertical中測量子元素的主要代碼如下:

 1 //See how tall everyone is. Also remember max width.
 2 for (int i = 0; i < count; ++i) {
 3     final View child = getVirtualChildAt(i);
 4     . . .
 5     //Determine how big this child would like to be. If this or previous children have given a weight, 

       //then we allow it to use all available space (and we will shrink things later if needed).
 6     measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalHeight == 0 ? mTotalLength : 0);
 7     
 8     if (oldHeight != Integer.MIN_VALUE) {
 9         lp.height = oldHeight;
10     }
11 
12     final int childLength = child.getMeasuredHeight();
13     final int totalLength = mTotalLength;
14     mTotalLength = Math.max(totalLength, totalLength+childHeight+lp.topMargin+lp.bottomMargin+getNextLocationOffset(child));
15 }

 

    由上述代碼可以知道,在measureVertical方法中會對每個LinearLayout中的子元素進行遍歷並通過measureChildBeforeLayout方法對每個子元素執行measure過程。在measureChildBeforeLayout方法內部會調用子元素的measure方法,這樣會依次讓每個子元素進入measure過程。mTotalLength表示LinearLayout在豎直方向上的尺寸,每完成一個子元素的measure過程,它的值也會相應增加。測量完子元素后,LinearLayout會測量自身的大小。measureVertical中測量LinearLayout自身的主要代碼如下:

 1 //Add in our padding.
 2 mTotalLength += mPaddingTop + mPaddingBottom;
 3 int heightSize = mTotalLength;
 4 //Check against our minimum height
 5 heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
 6 //Reconcile our calculated size with the heightMeasureSpec
 7 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
 8 heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
 9 . . .
10 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);

 

    對垂直的LinearLayout來說,它在水平方向的測量過程與普通View的measure過程一樣,在豎直方向的measure過程如下:若該垂直LinearLayout的layout_height為match_parent或具體數值,它的measure過程與普通View一樣;若該垂直LinearLayout的layout_height為wrap_content,則它豎直方向的高度為所有子元素占用的高度之和,但不能超過父容器的可用空間大小,最終高度還要考慮到其豎直方向的padding,相關的代碼如下:

 1 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
 2     int result = size;
 3     int specMode = MeasureSpec.getMode(measureSpec);
 4     int specSize = MeasureSpec.getSize(measureSpec);
 5     switch (speczMode) {
 6         case MeasureSpec.UNSPECIFIED:
 7             result = size;
 8             break;
 9         case MeasureSpec.AT_MOST:
10             if (specSize < size) {
11                 result = specSize | MEASURED_STATE_TOO_SMALL;
12             } else {
13                 result = size;
14             }
15             break;
16         case MeasureSpec.EXACTLY:
17             result = specSize;
18             break;
19     }
20     return result | (childMeasuredState & MEASURED_STATE_MASK);
21 }   

    ViewGroup主要通過其measureChildren方法完成其子View的measure過程,上面垂直LinearLayout中調用的measureChildBeforeLayout可以看做是measureChildren的一個“變種”,measureChildren方法代碼如下:

 1 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
 2     final int size = mChildrenCount;  
 3     final View[] children = mChildren;  
 4     for (int i = 0; i < size; ++i) {  
 5             final View child = children[i];  
 6             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { 
 7                 measureChild(child, widthMeasureSpec, heightMeasureSpec);  
 8             }  
 9     }  
10 }  

 

    其中,measureChild方法完成對子View的measure過程:

1 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {  
2     final LayoutParams lp = child.getLayoutParams();  
3     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
4         mPaddingLeft + mPaddingRight, lp.width);  
5     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
6         mPaddingTop + mPaddingBottom, lp.height);  
7     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
8 }  

 

    注意在這里,在執行child.measure方法前,就已經通過getChildMeasureSpec獲取了子View的MeasureSpec。getChildMeasureSpec根據子View的LayoutParams和父容器的MeasureSpec來決定子View的MeasureSpec,getChildMeasureSpec的代碼如下:

 1 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 2     //這里傳入的spec為ViewGroup的MeasureSpec
 3     //specMode和specSize即為父容器的MeasureSpec
 4     int specMode = MeasureSpec.getMode(spec);
 5     int specSize = MeasureSpec.getSize(spec);
 6     //padding為父容器中已使用的空間大小,size為父容器可用空間大小
 7     int size = Math.max(0, specSize - padding);
 8     int resultSize = 0;
 9     int resultMode = 0;
10 
11     switch (specMode) {
12         case MeasureSpec.EXACTLY:
13             if (childDimension >= 0) {
14                 resultSize = childDimension;
15                 resultMode = MeasureSpec.EXACTLY;
16             } else if (childDimension == LayoutParams.MATCH_PARENT) {
17                 //子View想要和父容器一樣大
18                 resultSize = size;
19                 resultMode = MeasureSpec.EXACTLY;
20             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
21                 //子View想自己決定它的大小,但不能比父容器大
22                 resultSize = size;
23                 resultMode = MeasureSpec.AT_MOST;
24             }
25             break;
26         
27         case MeasureSpec.AT_MOST:
28             if (childDimension >= 0) {
29                 resultSize = childDimension;
30                 resultMode = MeasureSpec.EXACTLY;
31             } else if (childDimension == LayoutParams.MATCH_PARENT) {
32                 resultSize = size;
33                 resultMode = MeasureSpec.AT_MOST;
34             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
35                 resultSize = size;
36                 resultMode = MeasureSpec.AT_MOST;
37             } 
38             break;
39         
40         //Parent asked to see how big we want to be
41         case MeasureSpec.UNSPECIFIED:
42             if (childDimension >= 0) {
43                 resultSize = childDimension;
44                 resultMode = MeasureSpec.EXACTLY;
45             } else (childDimension == LayoutParams.MATCH_PARENT) {
46                 resultSize = 0;
47                 resultMode = MeasureSpec.UNSPECIFIED;
48             } else (childDimension == LayoutParams.WRAP_CONTENT) {
49                 resultSize = 0;
50                 resultMode = MeasureSpec.UNSPECIFIED;
51             }
52             break;
53         }
54         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
55 }

 

    以上函數的工作過程可總結如下:

    a. 當childLayoutParams指定為為具體的大小時:若parentSpecMode為EXACTLY,則childSpecMode為EXACTLY,childSpecSize為childSize(layout_width和layout_height中指定的具體大小);若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為EXACTLY和childSize。

    b. 當childLayoutParams為match_parent時:若parentSpecMode為EXACTLY,則childSpecMode和childSpecSize分別為EXACTLY和parentSize(父容器中可用的大小);若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為AT_MOST和parentSize。

    c. 當childLayoutParams為wrap_content時:若parentSpecMode為EXACTLY,則childSpecMode和childSpecSize分別為AT_MOST和parentSize;若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為AT_MOST和parentSize。

     還有一點需要注意的是,View的measure過程和Activity生命周期的回調方法不是同步的,也就是不能保證在某個生命周期的回調方法中measure過程已經執行完畢。

 

(2)layout過程

    layout過程用來確定View在父容器中的位置,因而是由父容器獲取子View的位置參數后,調用child.layout方法並傳入已獲取的位置參數,從而完成對子View的layout。當ViewGroup的位置被確定后,它在onLayout中會遍歷所有子元素並調用其layout方法,在layout方法中子元素的onLayout又會被調用。layout方法確定先View本身的位置,再調用onLayout方法確定所有子元素的位置。layout方法如下:

1 public void layout(int l, int t, int r, int b) {  
 2 …… 3 int oldL = mLeft; 4 int oldT = mTop; 5 int oldB = mBottom; 6 int oldR = mRight; 7 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : set(l, t, r, b); 8 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { 9 onLayout(changed, l, t, r, b); 10 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 11 ListenerInfo li = mListenerInfo; 12 if (li != null && li.mOnLayoutChangeListeners != null) { 13 ArrayList<OnLayoutChangeListener> listenersCopy = 14 (ArrayList<OnLayoutChangeListener>) li.mOnLayoutChangeListeners.clone(); 15 int numListeners = listenersCopy.size(); 16 for (int i = 0; i < numListeners; ++i) { 17 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); 18 } 19 } 20 } 21 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 22 mPrivateFlags3 |= PFLAGS3_IS_LAID_OUT; 23 } 

    layout方法的大致流程:首先通過setFrame方法設定View的四個位置參數,即用傳來的l、t、r、b四個參數初始化mLeft、mTop、mRight、mBottom這四個值,從而確定了該View在父容器中的位置。若位置發生改變就調用onLayout方法,onLayout方法在View類中為空,因為對子元素布局的工作只有容器View才需要做。在ViewGroup中,onLayout是一個抽象方法,因為對於不同的布局管理器類,對子元素的布局方式是不同的。比如,LinearLayout的onLayout方法如下:

1 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2     if (mOriention == VERTIVAL) { 3  layoutVertical(l, t, r, b); 4 } else { 5  layoutHorizontal(l, t, r, b); 6  } 7 }

    以上代碼會根據LinearLayout的orientation為水平或垂直調用相應的函數來完成布局過程,這里以layoutVertical為例分析一下垂直線性布局管理器的布局過程,layoutVertical的主要代碼如下:

 1 void layoutVertical(int left, int top, int right, int bottom) {
 2  . . . 3 final int count = getVirtualChildCount(); 4 for (int i = 0; i < count; i++) { 5 final View child = getVirtualChildAt(i); 6 if (child == null) { 7 childTop += measureNullChild(i); 8 } else if (child.getVisibility() != GONE) { 9 final int childWidth = child.getMeasuredWidth(); 10 final int childHeight = child.getMeasuredHeight(); 11 12 final int LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); 13  . . . 14 if (hasDividerBeforeChildAt(i)) { 15 childTop += mDividerHeight; 16  } 17 18 childTop += lp.topMargin; 19 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); 20 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); 21 22 i += getChildrenSkipCount(child, i); 23  } 24  } 25 }

      以上代碼中,LinearLayout會遍歷它的所有子View,並調用setChildFrame方法設置子View的位置,代碼中的childTop代表當前子View的top位置參數。setChildFrame方法的代碼如下:    

1 private void setChildFrame(View child, int left, int top, int width, int  height) {
2     child.layout(left, top, left + width, top + height); 3 }

      也就是說,在父容器(這里為LinearLayout)完成了對子元素位置參數(top、left、right、bottom)的獲取后,會調用子元素的layout方法,並把獲取到的子元素位置參數傳入,從而完成對子元素的layout過程。子元素在自己的layout方法中,也會先完成對自己的布局(確定四個位置參數),再調用onLayout方法完成對其子View的布局,這樣layout過程就沿着View樹一層層傳了下去。

      layout過程完成后,便可以通過getWidth和getHeight方法獲取View的最終顯示寬高,這倆方法源碼如下:

1 public final int getWidth() {
2     return mRight – mLeft; 3 }    
1 public final int getHeight() {
2     return mBottom – mTop; 3 }

     由此便可以知道,通過getMeasuredWidth/getMeasuredHeight方法獲取的測量寬高與通過getWidth/getHeight方法獲取的最終顯示寬高的區別:即最終顯示寬高是通過View的位置參數相減得到的,正常情況下應該與測量寬高相等。但如果我們重寫View的layout方法如下:

1 public void layout(int l, int t, int r, int b) {
2     super.layout(l, t, r, b, r + 100, b + 100); 3 }

    這樣就會導致最終顯示寬高比測量寬高大100。(除非你很明確的知道自己想要干啥,否則不應該這樣做)

 

(3)draw過程

    主要分為以下六步:
    a. 繪制背景;
    b. 如果要視圖顯示漸變框,這里會做一些准備工作;
    c. 繪制視圖本身,即調用onDraw()函數。在View中onDraw()是個空函數,也就是說具體的視圖都要override該函數來實現自己的顯示,而對於ViewGroup則不需要實現該函數,因為作為容器是“沒有內容“的,其包含了多個子view,而子view已經實現了自己的繪制方法,因此只需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;
    d. 繪制子視圖,即dispatchDraw()函數。在View中這是個空函數,具體的視圖不需要實現該方法,它是專門為容器類准備的,也就是容器類必須實現該方法;
    e. 如果需要, 開始繪制漸變框;
    f. 繪制滾動條;

    draw方法的代碼如下:   

 1 public void draw(Canvas canvas) {  
 2     final int privateFlags = mPrivateFlags; 3 final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && 4 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 5 mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 6 // Step 1, draw the background, if needed 7 int saveCount; 8 if (!dirtyOpaque) { 9  drawBackground(canvas); 10  } 11 //step 2 & 5 12 final int viewFlags = mViewFlags; 13 boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; 14 boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; 15 if (!verticalEdges && !horizontalEdges) { 16 // Step 3, draw the content 17 if (!dirtyOpaque) onDraw(canvas); 18 // Step 4, draw the children 19  dispatchDraw(canvas); 20 // Step 6, draw decorations (scrollbars) 21  onDrawScrollBars(canvas); 22 if (mOverlay != null && !mOverlay.isEmpty()) { 23  mOverlay.getOverlayView().dispatchDraw(canvas); 24 // we're done... 25 return; 26  } 27  } 28 } 

   View的draw過程的傳遞通過diapatchDraw來實現,dispatchDraw會遍歷調用所有子View的draw方法,這樣draw事件就一層層傳了下去。重寫View的onDraw方法可以定制View繪制出來的樣子,例如實現一些特殊的圖形和動畫。

   View有個名為setWillNotDraw的方法,若一個View不需要繪制任何內容,可通過這個方法將相應標記設為true,系統會進行相應優化。ViewGroup默認開啟這個標記,View默認不開啟。

 

以上是我學習View的繪制流程后的簡單總結,很多地方敘述的還不夠清晰准確,如有問題歡迎大家在評論區一起討論 :)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM