日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Android視圖繪制流程完全解析,帶你一步步深入了解View(二)

 quasiceo 2015-06-22
分類: Android疑難解析 2013-12-26 08:30 51213人閱讀 評論(89) 收藏 舉報

目錄(?)[+]

轉(zhuǎn)載請注明出處:http://blog.csdn.net/guolin_blog/article/details/16330267

在上一篇文章中,我?guī)е蠹乙黄鹌饰隽艘幌翷ayoutInflater的工作原理,可以算是對View進行深入了解的第一步吧。那么本篇文章中,我們將繼續(xù)對View進行深入探究,看一看它的繪制流程到底是什么樣的。如果你還沒有看過我的上一篇文章,可以先去閱讀 Android LayoutInflater原理分析,帶你一步步深入了解View(一)  。

相信每個Android程序員都知道,我們每天的開發(fā)工作當中都在不停地跟View打交道,Android中的任何一個布局、任何一個控件其實都是直接或間接繼承自View的,如TextView、Button、ImageView、ListView等。這些控件雖然是Android系統(tǒng)本身就提供好的,我們只需要拿過來使用就可以了,但你知道它們是怎樣被繪制到屏幕上的嗎?多知道一些總是沒有壞處的,那么我們趕快進入到本篇文章的正題內(nèi)容吧。

要知道,任何一個視圖都不可能憑空突然出現(xiàn)在屏幕上,它們都是要經(jīng)過非??茖W(xué)的繪制流程后才能顯示出來的。每一個視圖的繪制過程都必須經(jīng)歷三個最主要的階段,即onMeasure()、onLayout()和onDraw(),下面我們逐個對這三個階段展開進行探討。

一. onMeasure()

measure是測量的意思,那么onMeasure()方法顧名思義就是用于測量視圖的大小的。View系統(tǒng)的繪制流程會從ViewRoot的performTraversals()方法中開始,在其內(nèi)部調(diào)用View的measure()方法。measure()方法接收兩個參數(shù),widthMeasureSpec和heightMeasureSpec,這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小。

MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規(guī)格。specMode一共有三種類型,如下所示:

1. EXACTLY

表示父視圖希望子視圖的大小應(yīng)該是由specSize的值來決定的,系統(tǒng)默認會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當然也可以按照自己的意愿設(shè)置成任意的大小。

2. AT_MOST

表示子視圖最多只能是specSize中指定的大小,開發(fā)人員應(yīng)該盡可能小得去設(shè)置這個視圖,并且保證不會超過specSize。系統(tǒng)默認會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當然也可以按照自己的意愿設(shè)置成任意的大小。

3. UNSPECIFIED

表示開發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

那么你可能會有疑問了,widthMeasureSpec和heightMeasureSpec這兩個值又是從哪里得到的呢?通常情況下,這兩個值都是由父視圖經(jīng)過計算后傳遞給子視圖的,說明父視圖會在一定程度上決定子視圖的大小。但是最外層的根視圖,它的widthMeasureSpec和heightMeasureSpec又是從哪里得到的呢?這就需要去分析ViewRoot中的源碼了,觀察performTraversals()方法可以發(fā)現(xiàn)如下代碼:

  1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
  2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
可以看到,這里調(diào)用了getRootMeasureSpec()方法去獲取widthMeasureSpec和heightMeasureSpec的值,注意方法中傳入的參數(shù),其中l(wèi)p.width和lp.height在創(chuàng)建ViewGroup實例的時候就被賦值了,它們都等于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. }  

可以看到,這里使用了MeasureSpec.makeMeasureSpec()方法來組裝一個MeasureSpec,當rootDimension參數(shù)等于MATCH_PARENT的時候,MeasureSpec的specMode就等于EXACTLY,當rootDimension等于WRAP_CONTENT的時候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT時的specSize都是等于windowSize的,也就意味著根視圖總是會充滿全屏的。

介紹了這么多MeasureSpec相關(guān)的內(nèi)容,接下來我們看下View的measure()方法里面的代碼吧,如下所示:

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.             widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.             heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.         mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  6.         if (ViewDebug.TRACE_HIERARCHY) {  
  7.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  8.         }  
  9.         onMeasure(widthMeasureSpec, heightMeasureSpec);  
  10.         if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  11.             throw new IllegalStateException("onMeasure() did not set the"  
  12.                     + " measured dimension by calling"  
  13.                     + " setMeasuredDimension()");  
  14.         }  
  15.         mPrivateFlags |= LAYOUT_REQUIRED;  
  16.     }  
  17.     mOldWidthMeasureSpec = widthMeasureSpec;  
  18.     mOldHeightMeasureSpec = heightMeasureSpec;  
  19. }  
注意觀察,measure()這個方法是final的,因此我們無法在子類中去重寫這個方法,說明Android是不允許我們改變View的measure框架的。然后在第9行調(diào)用了onMeasure()方法,這里才是真正去測量并設(shè)置View大小的地方,默認會調(diào)用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. }  

這里傳入的measureSpec是一直從measure()方法中傳遞過來的。然后調(diào)用MeasureSpec.getMode()方法可以解析出specMode,調(diào)用MeasureSpec.getSize()方法可以解析出specSize。接下來進行判斷,如果specMode等于AT_MOST或EXACTLY就返回specSize,這也是系統(tǒng)默認的行為。之后會在onMeasure()方法中調(diào)用setMeasuredDimension()方法來設(shè)定測量出的大小,這樣一次measure過程就結(jié)束了。

當然,一個界面的展示可能會涉及到很多次的measure,因為一個布局中一般都會包含多個子視圖,每個視圖都需要經(jīng)歷一次measure過程。ViewGroup中定義了一個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. }  
這里首先會去遍歷當前布局下的所有子視圖,然后逐個調(diào)用measureChild()方法來測量相應(yīng)子視圖的大小,如下所示:
  1. protected void measureChild(View child, int parentWidthMeasureSpec,  
  2.         int parentHeightMeasureSpec) {  
  3.     final LayoutParams lp = child.getLayoutParams();  
  4.     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  5.             mPaddingLeft + mPaddingRight, lp.width);  
  6.     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  7.             mPaddingTop + mPaddingBottom, lp.height);  
  8.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  9. }  
可以看到,在第4行和第6行分別調(diào)用了getChildMeasureSpec()方法來去計算子視圖的MeasureSpec,計算的依據(jù)就是布局文件中定義的MATCH_PARENT、WRAP_CONTENT等值,這個方法的內(nèi)部細節(jié)就不再貼出。然后在第8行調(diào)用子視圖的measure()方法,并把計算出的MeasureSpec傳遞進去,之后的流程就和前面所介紹的一樣了。

當然,onMeasure()方法是可以重寫的,也就是說,如果你不想使用系統(tǒng)默認的測量方式,可以按照自己的意愿進行定制,比如:

  1. public class MyView extends View {  
  2.   
  3.     ......  
  4.       
  5.     @Override  
  6.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  7.         setMeasuredDimension(200200);  
  8.     }  
  9.   
  10. }  

這樣的話就把View默認的測量流程覆蓋掉了,不管在布局文件中定義MyView這個視圖的大小是多少,最終在界面上顯示的大小都將會是200*200。

需要注意的是,在setMeasuredDimension()方法調(diào)用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調(diào)用這兩個方法得到的值都會是0。

由此可見,視圖大小的控制是由父視圖、布局文件、以及視圖本身共同完成的,父視圖會提供給子視圖參考的大小,而開發(fā)人員可以在XML文件中指定視圖的大小,然后視圖本身會對最終的大小進行拍板。

到此為止,我們就把視圖繪制流程的第一階段分析完了。

二. onLayout()

measure過程結(jié)束后,視圖的大小就已經(jīng)測量好了,接下來就是layout的過程了。正如其名字所描述的一樣,這個方法是用于給視圖進行布局的,也就是確定視圖的位置。ViewRoot的performTraversals()方法會在measure結(jié)束后繼續(xù)執(zhí)行,并調(diào)用View的layout()方法來執(zhí)行此過程,如下所示:

  1. host.layout(00, host.mMeasuredWidth, host.mMeasuredHeight);  
layout()方法接收四個參數(shù),分別代表著左、上、右、下的坐標,當然這個坐標是相對于當前視圖的父視圖而言的??梢钥吹剑@里還把剛才測量出的寬度和高度傳到了layout()方法中。那么我們來看下layout()方法中的代碼是什么樣的吧,如下所示:
  1. public void layout(int l, int t, int r, int b) {  
  2.     int oldL = mLeft;  
  3.     int oldT = mTop;  
  4.     int oldB = mBottom;  
  5.     int oldR = mRight;  
  6.     boolean changed = setFrame(l, t, r, b);  
  7.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  8.         if (ViewDebug.TRACE_HIERARCHY) {  
  9.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  10.         }  
  11.         onLayout(changed, l, t, r, b);  
  12.         mPrivateFlags &= ~LAYOUT_REQUIRED;  
  13.         if (mOnLayoutChangeListeners != null) {  
  14.             ArrayList<OnLayoutChangeListener> listenersCopy =  
  15.                     (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();  
  16.             int numListeners = listenersCopy.size();  
  17.             for (int i = 0; i < numListeners; ++i) {  
  18.                 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  19.             }  
  20.         }  
  21.     }  
  22.     mPrivateFlags &= ~FORCE_LAYOUT;  
  23. }  

在layout()方法中,首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化,以確定有沒有必要對當前的視圖進行重繪,同時還會在這里把傳遞過來的四個參數(shù)分別賦值給mLeft、mTop、mRight和mBottom這幾個變量。接下來會在第11行調(diào)用onLayout()方法,正如onMeasure()方法中的默認行為一樣,也許你已經(jīng)迫不及待地想知道onLayout()方法中的默認行為是什么樣的了。進入onLayout()方法,咦?怎么這是個空方法,一行代碼都沒有?!

沒錯,View中的onLayout()方法就是一個空方法,因為onLayout()過程是為了確定視圖在布局中所在的位置,而這個操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置。既然如此,我們來看下ViewGroup中的onLayout()方法是怎么寫的吧,代碼如下:

  1. @Override  
  2. protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  
可以看到,ViewGroup中的onLayout()方法竟然是一個抽象方法,這就意味著所有ViewGroup的子類都必須重寫這個方法。沒錯,像LinearLayout、RelativeLayout等布局,都是重寫了這個方法,然后在內(nèi)部按照各自的規(guī)則對子視圖進行布局的。由于LinearLayout和RelativeLayout的布局規(guī)則都比較復(fù)雜,就不單獨拿出來進行分析了,這里我們嘗試自定義一個布局,借此來更深刻地理解onLayout()的過程。

自定義的這個布局目標很簡單,只要能夠包含一個子視圖,并且讓子視圖正常顯示出來就可以了。那么就給這個布局起名叫做SimpleLayout吧,代碼如下所示:

  1. public class SimpleLayout extends ViewGroup {  
  2.   
  3.     public SimpleLayout(Context context, AttributeSet attrs) {  
  4.         super(context, attrs);  
  5.     }  
  6.   
  7.     @Override  
  8.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  9.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  10.         if (getChildCount() > 0) {  
  11.             View childView = getChildAt(0);  
  12.             measureChild(childView, widthMeasureSpec, heightMeasureSpec);  
  13.         }  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  18.         if (getChildCount() > 0) {  
  19.             View childView = getChildAt(0);  
  20.             childView.layout(00, childView.getMeasuredWidth(), childView.getMeasuredHeight());  
  21.         }  
  22.     }  
  23.   
  24. }  
代碼非常的簡單,我們來看下具體的邏輯吧。你已經(jīng)知道,onMeasure()方法會在onLayout()方法之前調(diào)用,因此這里在onMeasure()方法中判斷SimpleLayout中是否有包含一個子視圖,如果有的話就調(diào)用measureChild()方法來測量出子視圖的大小。

接著在onLayout()方法中同樣判斷SimpleLayout是否有包含一個子視圖,然后調(diào)用這個子視圖的layout()方法來確定它在SimpleLayout布局中的位置,這里傳入的四個參數(shù)依次是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),分別代表著子視圖在SimpleLayout中左上右下四個點的坐標。其中,調(diào)用childView.getMeasuredWidth()和childView.getMeasuredHeight()方法得到的值就是在onMeasure()方法中測量出的寬和高。

這樣就已經(jīng)把SimpleLayout這個布局定義好了,下面就是在XML文件中使用它了,如下所示:

  1. <com.example.viewtest.SimpleLayout xmlns:android="http://schemas./apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.       
  5.     <ImageView   
  6.         android:layout_width="wrap_content"  
  7.         android:layout_height="wrap_content"  
  8.         android:src="@drawable/ic_launcher"  
  9.         />  
  10.       
  11. </com.example.viewtest.SimpleLayout>  
可以看到,我們能夠像使用普通的布局文件一樣使用SimpleLayout,只是注意它只能包含一個子視圖,多余的子視圖會被舍棄掉。這里SimpleLayout中包含了一個ImageView,并且ImageView的寬高都是wrap_content?,F(xiàn)在運行一下程序,結(jié)果如下圖所示:

                               

OK!ImageView成功已經(jīng)顯示出來了,并且顯示的位置也正是我們所期望的。如果你想改變ImageView顯示的位置,只需要改變childView.layout()方法的四個參數(shù)就行了。

在onLayout()過程結(jié)束后,我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。說到這里,我相信很多朋友長久以來都會有一個疑問,getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別呢?它們的值好像永遠都是相同的。其實它們的值之所以會相同基本都是因為布局設(shè)計者的編碼習慣非常好,實際上它們之間的差別還是挺大的。

首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了,而getWidth()方法要在layout()過程結(jié)束后才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設(shè)置的,而getWidth()方法中的值則是通過視圖右邊的坐標減去左邊的坐標計算出來的。

觀察SimpleLayout中onLayout()方法的代碼,這里給子視圖的layout()方法傳入的四個參數(shù)分別是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),因此getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,所以此時getWidth()方法和getMeasuredWidth() 得到的值就是相同的,但如果你將onLayout()方法中的代碼進行如下修改:

  1. @Override  
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  3.     if (getChildCount() > 0) {  
  4.         View childView = getChildAt(0);  
  5.         childView.layout(00200200);  
  6.     }  
  7. }  
這樣getWidth()方法得到的值就是200 - 0 = 200,不會再和getMeasuredWidth()的值相同了。當然這種做法充分不尊重measure()過程計算出的結(jié)果,通常情況下是不推薦這么寫的。getHeight()與getMeasureHeight()方法之間的關(guān)系同上,就不再重復(fù)分析了。

到此為止,我們把視圖繪制流程的第二階段也分析完了。

三. onDraw()

measure和layout的過程都結(jié)束后,接下來就進入到draw的過程了。同樣,根據(jù)名字你就能夠判斷出,在這里才真正地開始對視圖進行繪制。ViewRoot中的代碼會繼續(xù)執(zhí)行并創(chuàng)建出一個Canvas對象,然后調(diào)用View的draw()方法來執(zhí)行具體的繪制工作。draw()方法內(nèi)部的繪制過程總共可以分為六步,其中第二步和第五步在一般情況下很少用到,因此這里我們只分析簡化后的繪制過程。代碼如下所示:

  1. public void draw(Canvas canvas) {  
  2.     if (ViewDebug.TRACE_HIERARCHY) {  
  3.         ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
  4.     }  
  5.     final int privateFlags = mPrivateFlags;  
  6.     final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
  7.             (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
  8.     mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
  9.     // Step 1, draw the background, if needed  
  10.     int saveCount;  
  11.     if (!dirtyOpaque) {  
  12.         final Drawable background = mBGDrawable;  
  13.         if (background != null) {  
  14.             final int scrollX = mScrollX;  
  15.             final int scrollY = mScrollY;  
  16.             if (mBackgroundSizeChanged) {  
  17.                 background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  18.                 mBackgroundSizeChanged = false;  
  19.             }  
  20.             if ((scrollX | scrollY) == 0) {  
  21.                 background.draw(canvas);  
  22.             } else {  
  23.                 canvas.translate(scrollX, scrollY);  
  24.                 background.draw(canvas);  
  25.                 canvas.translate(-scrollX, -scrollY);  
  26.             }  
  27.         }  
  28.     }  
  29.     final int viewFlags = mViewFlags;  
  30.     boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
  31.     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
  32.     if (!verticalEdges && !horizontalEdges) {  
  33.         // Step 3, draw the content  
  34.         if (!dirtyOpaque) onDraw(canvas);  
  35.         // Step 4, draw the children  
  36.         dispatchDraw(canvas);  
  37.         // Step 6, draw decorations (scrollbars)  
  38.         onDrawScrollBars(canvas);  
  39.         // we're done...  
  40.         return;  
  41.     }  
  42. }  
可以看到,第一步是從第9行代碼開始的,這一步的作用是對視圖的背景進行繪制。這里會先得到一個mBGDrawable對象,然后根據(jù)layout過程確定的視圖位置來設(shè)置背景的繪制區(qū)域,之后再調(diào)用Drawable的draw()方法來完成背景的繪制工作。那么這個mBGDrawable對象是從哪里來的呢?其實就是在XML中通過android:background屬性設(shè)置的圖片或顏色。當然你也可以在代碼中通過setBackgroundColor()、setBackgroundResource()等方法進行賦值。

接下來的第三步是在第34行執(zhí)行的,這一步的作用是對視圖的內(nèi)容進行繪制??梢钥吹?,這里去調(diào)用了一下onDraw()方法,那么onDraw()方法里又寫了什么代碼呢?進去一看你會發(fā)現(xiàn),原來又是個空方法啊。其實也可以理解,因為每個視圖的內(nèi)容部分肯定都是各不相同的,這部分的功能交給子類來去實現(xiàn)也是理所當然的。

第三步完成之后緊接著會執(zhí)行第四步,這一步的作用是對當前視圖的所有子視圖進行繪制。但如果當前的視圖沒有子視圖,那么也就不需要進行繪制了。因此你會發(fā)現(xiàn)View中的dispatchDraw()方法又是一個空方法,而ViewGroup的dispatchDraw()方法中就會有具體的繪制代碼。

以上都執(zhí)行完后就會進入到第六步,也是最后一步,這一步的作用是對視圖的滾動條進行繪制。那么你可能會奇怪,當前的視圖又不一定是ListView或者ScrollView,為什么要繪制滾動條呢?其實不管是Button也好,TextView也好,任何一個視圖都是有滾動條的,只是一般情況下我們都沒有讓它顯示出來而已。繪制滾動條的代碼邏輯也比較復(fù)雜,這里就不再貼出來了,因為我們的重點是第三步過程。

通過以上流程分析,相信大家已經(jīng)知道,View是不會幫我們繪制內(nèi)容部分的,因此需要每個視圖根據(jù)想要展示的內(nèi)容來自行繪制。如果你去觀察TextView、ImageView等類的源碼,你會發(fā)現(xiàn)它們都有重寫onDraw()這個方法,并且在里面執(zhí)行了相當不少的繪制邏輯。繪制的方式主要是借助Canvas這個類,它會作為參數(shù)傳入到onDraw()方法中,供給每個視圖使用。Canvas這個類的用法非常豐富,基本可以把它當成一塊畫布,在上面繪制任意的東西,那么我們就來嘗試一下吧。

這里簡單起見,我只是創(chuàng)建一個非常簡單的視圖,并且用Canvas隨便繪制了一點東西,代碼如下所示:

  1. public class MyView extends View {  
  2.   
  3.     private Paint mPaint;  
  4.   
  5.     public MyView(Context context, AttributeSet attrs) {  
  6.         super(context, attrs);  
  7.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  8.     }  
  9.   
  10.     @Override  
  11.     protected void onDraw(Canvas canvas) {  
  12.         mPaint.setColor(Color.YELLOW);  
  13.         canvas.drawRect(00, getWidth(), getHeight(), mPaint);  
  14.         mPaint.setColor(Color.BLUE);  
  15.         mPaint.setTextSize(20);  
  16.         String text = "Hello View";  
  17.         canvas.drawText(text, 0, getHeight() / 2, mPaint);  
  18.     }  
  19. }  
可以看到,我們創(chuàng)建了一個自定義的MyView繼承自View,并在MyView的構(gòu)造函數(shù)中創(chuàng)建了一個Paint對象。Paint就像是一個畫筆一樣,配合著Canvas就可以進行繪制了。這里我們的繪制邏輯比較簡單,在onDraw()方法中先是把畫筆設(shè)置成黃色,然后調(diào)用Canvas的drawRect()方法繪制一個矩形。然后在把畫筆設(shè)置成藍色,并調(diào)整了一下文字的大小,然后調(diào)用drawText()方法繪制了一段文字。

就這么簡單,一個自定義的視圖就已經(jīng)寫好了,現(xiàn)在可以在XML中加入這個視圖,如下所示:

  1. <LinearLayout xmlns:android="http://schemas./apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.   
  5.     <com.example.viewtest.MyView   
  6.         android:layout_width="200dp"  
  7.         android:layout_height="100dp"  
  8.         />  
  9.   
  10. </LinearLayout>  
將MyView的寬度設(shè)置成200dp,高度設(shè)置成100dp,然后運行一下程序,結(jié)果如下圖所示:

                      

圖中顯示的內(nèi)容也正是MyView這個視圖的內(nèi)容部分了。由于我們沒給MyView設(shè)置背景,因此這里看不出來View自動繪制的背景效果。

當然了Canvas的用法還有很多很多,這里我不可能把Canvas的所有用法都列舉出來,剩下的就要靠大家自行去研究和學(xué)習了。

到此為止,我們把視圖繪制流程的第三階段也分析完了。整個視圖的繪制過程就全部結(jié)束了,你現(xiàn)在是不是對View的理解更加深刻了呢?感興趣的朋友可以繼續(xù)閱讀 Android視圖狀態(tài)及重繪流程分析,帶你一步步深入了解View(三) 。

第一時間獲得博客更新提醒,以及更多技術(shù)信息分享,歡迎關(guān)注我的微信公眾號,掃一掃下方二維碼或搜索微信號guolin_blog,即可關(guān)注。

132
12
主題推薦
android 開發(fā)人員 relativelayout microsoft arraylist
猜你在找
查看評論
68樓 櫻花_殤 3天前 11:45發(fā)表 [回復(fù)]
我發(fā)現(xiàn)這篇相較于上一篇就已經(jīng)開始看不懂了,臺面上的平常接觸的都能看的懂,但是臺面下的源碼看起來就比較費勁了...研讀并且理解了安卓源代碼的,深感佩服...
67樓 easy_money_sniper 2015-06-14 17:46發(fā)表 [回復(fù)]
對于View繪制實在是看不懂啊。。??!
66樓 xiongmaozhijin 2015-06-04 11:04發(fā)表 [回復(fù)]
學(xué)習了,謝謝。
65樓 dong5488 2015-05-28 10:29發(fā)表 [回復(fù)]
很厲害,但是看不懂
64樓 易水南風 2015-05-26 16:13發(fā)表 [回復(fù)]
寫得很好很詳細,但是不容易懂流程。建議博主畫個流程圖。
63樓 mjkbsun 2015-05-20 15:31發(fā)表 [回復(fù)]
62樓 cm1320274921 2015-04-10 18:03發(fā)表 [回復(fù)]
哎。。。先謝謝郭哥。。。然后接著看。。
61樓 hoperxt 2015-03-12 22:34發(fā)表 [回復(fù)]
ViewRoot在安卓的API中并沒有找到,是被舍棄了嗎?
60樓 monkey__liu 2014-12-26 16:59發(fā)表 [回復(fù)]
感覺郭神你這篇文章講的不夠清晰呀..看著很亂沒頭緒
59樓 neosdong 2014-12-24 16:46發(fā)表 [回復(fù)]
內(nèi)層View的onMeasure是否測量完畢,才會運行外層View的onMeasure?

為什么onMeasure會被多次調(diào)用?是什么條件觸發(fā)多次調(diào)用?
58樓 安卓狗劉小立 2014-12-07 09:48發(fā)表 [回復(fù)]
郭大,你這個measure的流程死活看不懂.后來我又找了其他的博文,自己也做了點實驗,然后有了一些想法.不知道理解的對不對,還請指點http://blog.csdn.net/u012990751/article/details/41776985
57樓 修- 2014-12-01 11:00發(fā)表 [回復(fù)]
樓主有對常見方法的總結(jié)么,比如 getWidth,getMeasureWidth 這些等等,因為我現(xiàn)在總感覺View基礎(chǔ)都行了,但是我自己看一個APP 一個效果,我就分析,每次寫到onMeasure 和 onLayout的時候就寫不下去了,特別是Measure.Spec里面的3種類型,知道他們什么意思,就是不會用
56樓 lz_00 2014-11-23 20:55發(fā)表 [回復(fù)]
今天剛買了你的書,看你寫的博客,確實很棒,腦子不好使,有些還是沒辦法弄明白,看了繼續(xù)看。謝謝您的博文。
55樓 goodgoodstudy吧 2014-11-12 15:51發(fā)表 [回復(fù)]
已經(jīng)買了你的書了 確實有層次,進階的勇士~
54樓 suntukang 2014-11-08 21:58發(fā)表 [回復(fù)]
寫的都很好,樓主可以介紹一下有哪些途徑可以自學(xué)到這些嗎?多謝
53樓 Dragon_Zin 2014-10-20 17:57發(fā)表 [回復(fù)]
郭老師出進階的書吧
Re: hixin 2015-04-07 11:15發(fā)表 [回復(fù)]
回復(fù)kiss_zhan:支持出進階的書
52樓 yjiyjige 2014-10-18 16:42發(fā)表 [回復(fù)]
這一篇寫得相當不錯!
51樓 xingty 2014-10-18 09:34發(fā)表 [回復(fù)]
博主你好,我在api 19里并沒有找到ViewRoot這個類,是不是有了新的機制?斷開了這一塊其他的看起來一頭霧水啊。
50樓 小菜鳥csdn 2014-10-17 23:09發(fā)表 [回復(fù)]
很喜歡博主的文章,剛剛用豆約翰博客備份專家備份了您的全部博文。
49樓 ronaldong99 2014-10-16 15:20發(fā)表 [回復(fù)]
謝謝郭哥的文章,繼續(xù)學(xué)習中
48樓 erhu0618 2014-10-11 17:56發(fā)表 [回復(fù)]
建議圖書出kindle版本,提高銷量哦~方便更環(huán)保。
47樓 erhu0618 2014-10-11 17:55發(fā)表 [回復(fù)]
建議圖書出kindle版本,提高銷量哦~方便更環(huán)保。
46樓 reality_jie 2014-09-27 17:32發(fā)表 [回復(fù)]
郭大哥,請教一個問題。在android系統(tǒng)的源碼中,ViewRootImpl類中的performTraversals中,draw()方法比layout先調(diào)用到,那為什么在自定義的控件中,調(diào)用順序是:onMeasure() ---> onLayout() ----> onDraw()呢
45樓 Wonderful_jerry 2014-09-12 20:08發(fā)表 [回復(fù)]
大神 佩服佩服 要是能學(xué)成您這樣的“功力” 我大學(xué)畢業(yè)就不怕找工作了 呵呵 ~~~
44樓 gdrgn 2014-09-11 18:16發(fā)表 [回復(fù)]
樓主的思維能力可以去搞些前沿的研究,不要浪費時間碼代碼了。
43樓 tanxingchun3 2014-09-11 17:02發(fā)表 [回復(fù)]
ViewRoot這個類在哪里,怎么找不到啊
42樓 hanghang1840 2014-09-05 20:11發(fā)表 [回復(fù)]
博主,請教個問題。
在某個界面發(fā)起了文件上傳,需顯示文件上傳進度。但當退出這個界面以后,再次進入這個界面查看上傳進度,安卓里有什么好的實現(xiàn)?
41樓 ScorpioNeal 2014-08-29 11:37發(fā)表 [回復(fù)]
LZ這些東西自己看源碼分析的嗎? 還是有什么好書推薦下啊?
40樓 abelabel 2014-08-26 17:43發(fā)表 [回復(fù)]
你好,非常感謝你的文章,有一個問題我想問下
<com.example.viewtest.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
當有自定義view時,我沒有指定 android:layout_width="200dp"而是設(shè)置為wrap_content,當運行時黃色填充了整個屏幕,而我只是想包含幾個文字的大小,不想要填充整個屏幕,請問我要怎么處理呢?謝謝
Re: 她做了一個夢 2014-08-27 13:30發(fā)表 [回復(fù)]
回復(fù)abelabel:你可以根據(jù)textview的寬高 設(shè)置MyView的寬高吧
Re: abelabel 2014-08-27 17:29發(fā)表 [回復(fù)]
回復(fù)liumeng123321:謝謝你的回復(fù), 我定義的方法里面沒有textView,只有一個文本字符串,
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(Color.BLUE);
mPaint.setTextSize(20);
String text = "Hello View";
canvas.drawText(text, 0, getHeight() / 2, mPaint);
}
我要怎么根據(jù)文本的長度來設(shè)置MyView的寬度呢?
Re: 她做了一個夢 2014-08-28 10:28發(fā)表 [回復(fù)]
回復(fù)abelabel:bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int textWidth = bounds.width();
int textHeight = bounds.height();
Re: abelabel 2014-08-29 11:22發(fā)表 [回復(fù)]
回復(fù)liumeng123321:太好了,非常感謝你的幫助!
39樓 chuyun923 2014-08-21 23:04發(fā)表 [回復(fù)]
"在layout()方法中,首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化"
"View中的onLayout()方法就是一個空方法,因為onLayout()過程是為了確定視圖在布局中所在的位置,而這個操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置。"
其實這個地方會存在一些誤解,確定視圖在布局中所在位置的并不是onLayout,而是setFrame,onLayout只是當View是ViewGroup時,去調(diào)用子View的layout(),當然也可以理解為在onLayout中改變了默認的四個邊界值而達到作者所說的覺得子視圖的顯示位置。
38樓 chuyun923 2014-08-21 23:02發(fā)表 [回復(fù)]
"在layout()方法中,首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化"
"View中的onLayout()方法就是一個空方法,因為onLayout()過程是為了確定視圖在布局中所在的位置,而這個操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置。"
其實這個地方會存在一些誤解,確定視圖在布局中所在位置的并不是onLayout,而是setFrame,onLayout只是當View是ViewGroup時,去調(diào)用子View的layout(),當然也可以理解為在onLayout中改變了默認的四個邊界值而達到作者所說的覺得子視圖的顯示位置。
37樓 John_Guo 2014-08-20 15:16發(fā)表 [回復(fù)]
關(guān)注中。
36樓 qq_18265641 2014-08-17 18:41發(fā)表 [回復(fù)]
measure流程寫的不好,沒有第一篇inflater寫的清晰,這篇文章我看了5次了還是不太懂measure的流程。后來自己想了想,主要是被SpecMode給搞糊涂了。
參看了http:///questions/16022841/when-will-measurespec-unspecified-and-measurespec-at-most-be-applied

如果給個例子比貼代碼好多了,例如
如果子控件是TextView, layout_width和layout_height都是wrap content,那么parent view調(diào)用textView.measure的時候,傳遞的SpecMode都是AT_MOST, 也就是告訴textView, 我是你父親,我的width和height都和屏幕一樣大,因此你最大不能比我大(AT_MOST),至于你到底多大,你自己看。

textView根據(jù)自己當前什么字體,字號大小,計算一個width和height,剛好能容納內(nèi)容(wrap_content),最終看看這個這個width和height有沒有超過parent view的width和height, 沒超過,就用這兩個值,超過了,就用父親的值.
min(textView.width, parent_suggested_width).

另外textView setMeasureDimesion,傳遞自己的width和height,相當于保存width和height到自己內(nèi)部變量,以后就可以調(diào)用getMeasuredWidth和getMeasuredHeight了

謝謝博主,從你的博客里學(xué)到了很多的東西,你的書今天到了(京東買的),我買了,支持你!
Re: 易水南風 2015-05-26 16:19發(fā)表 [回復(fù)]
回復(fù)qq_18265641:我有疑問:如果SimpleLayout中的onMeasure不重寫,內(nèi)部是如何運行的?viewGroup有自己的onMeasure方法么?
Re: 易水南風 2015-05-26 16:11發(fā)表 [回復(fù)]
回復(fù)qq_18265641:你說的很不錯啊,確實這樣舉例子效果很好,贊。
35樓 lfdfhl 2014-08-04 16:10發(fā)表 [回復(fù)]
花了3天時間看完了,自己也寫了demo。多謝,郭大俠。
34樓 luechenying 2014-07-18 11:01發(fā)表 [回復(fù)]
1. onMeasure()
View系統(tǒng)的繪制流程會從ViewRoot的performTraversals()方法中開始,在其內(nèi)部調(diào)用View的measure()方法。
2. onLayout()
ViewRoot的performTraversals()方法會在measure結(jié)束后繼續(xù)執(zhí)行,并調(diào)用View的layout()方法來執(zhí)行此過程。
樓主這兩句好像有點矛盾啊
Re: TATTOO20008 2014-07-29 17:28發(fā)表 [回復(fù)]
回復(fù)luechenying:不矛盾,ViewRoot的performTraversals()方法比較長,measure方法和layout方法都在之個方法中,所以于在measure方法完了后,繼續(xù)往下layout方法
33樓 warm_200 2014-07-03 03:00發(fā)表 [回復(fù)]
愛我草 還是那么的好!
32樓 zxflbbl 2014-06-03 16:44發(fā)表 [回復(fù)]
33個贊, 很享受這種學(xué)習的感覺
31樓 qiang360 2014-05-22 16:53發(fā)表 [回復(fù)]
獲益匪淺!無盡感激!
30樓 linhui9010 2014-05-04 11:34發(fā)表 [回復(fù)]
幾個月后,今天又溫故了一遍,果然有新的收獲
29樓 me1019637049 2014-04-25 15:33發(fā)表 [回復(fù)]
很牛,膜拜了。樓主加油!
28樓 angerlie 2014-03-20 22:21發(fā)表 [回復(fù)]
請問,onMeasure,onLayout,onDraw方法跟Activity的各個生命周期方法的關(guān)系是什么?
Re: lt2288666 2014-04-01 16:57發(fā)表 [回復(fù)]
回復(fù)angerlie:activity里面么的這三個方法。。我想在onCreate方法執(zhí)行結(jié)束前對應(yīng)的子View的這三個方法都應(yīng)該執(zhí)行完了吧
Re: 往事飛sky 2014-07-24 23:05發(fā)表 [回復(fù)]
回復(fù)lt2288666:樓主你自己做個實驗就會發(fā)現(xiàn)oncreate,onresume,onstart執(zhí)行完之后才執(zhí)行onMeasure,onLayout,onDraw這三個方法,以我的理解,oncreate中的setcontentView只是把布局文件加載進去,然后解析各個控件的參數(shù),也就是LayoutParams,然后知道了各個控件所需要的值也就可以給各個控件的onMeasure了你可以看下這篇博客,http://blog.sina.com.cn/s/blog_61fbf8d101016eve.html
共同交流
Re: angerlie 2014-04-04 19:57發(fā)表 [回復(fù)]
回復(fù)lt2288666:那如何獲取某個ImageView的寬高呢?目的是為了把一個Bitmap壓縮到ImageView的寬高,這樣不是能解決內(nèi)存嘛!
Re: behappy0_o 2014-08-13 14:56發(fā)表 [回復(fù)]
回復(fù)angerlie:有個粗俗的辦法 view寬高寫在dimens里面你就可以取到了
27樓 angerlie 2014-03-19 00:29發(fā)表 [回復(fù)]
系統(tǒng)在View的什么時候setMeasuredDimension呢?我們能在什么時候getMeasuredWidth和getMeasuredHeight拿到寬高值呢?
26樓 張小超 2014-03-14 13:39發(fā)表 [回復(fù)]
郭哥 郭哥 我們愛你
25樓 花仙子Mary 2014-03-03 15:15發(fā)表 [回復(fù)]
博主 你好 我想問下你有沒有辦法把Fragment像類似動態(tài)添加linearLayout那樣動態(tài)去添加Fragment啊,希望能得到你的回復(fù),謝謝
24樓 _阿童木 2014-02-12 15:50發(fā)表 [回復(fù)]
嗯 寫的很好。贊一個
23樓 angerlie 2014-02-10 18:03發(fā)表 [回復(fù)]
不愧是博客之星
22樓 zxw136511485 2014-01-09 16:14發(fā)表 [回復(fù)]
不錯!學(xué)習了!??!期待更好的文章!我轉(zhuǎn)載你的文章了!
21樓 xyang0917 2014-01-02 11:19發(fā)表 [回復(fù)]
溫故而知新
20樓 北落師們 2014-01-01 00:37發(fā)表 [回復(fù)]
很喜歡博主的文章,剛剛用豆約翰博客備份專家備份了您的全部博文。
19樓 北落師們 2014-01-01 00:25發(fā)表 [回復(fù)]
LZ不要上班了,開培訓(xùn)班去吧,我第一個報名,哈哈
18樓 ha_cjy 2013-12-31 10:36發(fā)表 [回復(fù)]
真心贊,頂
17樓 wangwangheng 2013-12-30 16:47發(fā)表 [回復(fù)]
很喜歡博主的文章,剛剛用豆約翰博客備份專家備份了您的全部博文。
16樓 chcucl 2013-12-29 16:13發(fā)表 [回復(fù)]
我在用C++ 和OPENGL開發(fā)一個醫(yī)療UI系統(tǒng),正好學(xué)習下android ui的調(diào)用原理和機制,從中汲取設(shè)計和架構(gòu)知識。謝謝樓主的辛勞。
15樓 gjm1961129 2013-12-28 11:37發(fā)表 [回復(fù)]
寫的太好了,投一票。
Re: guolin 2013-12-28 22:47發(fā)表 [回復(fù)]
回復(fù)u013242450:多謝支持
14樓 劉佳翰 2013-12-27 23:24發(fā)表 [回復(fù)]
厲害!
13樓 藍淋楓 2013-12-27 19:22發(fā)表 [回復(fù)]
頂了!寫得很好,很受用!多謝!
12樓 dodoniao 2013-12-27 10:24發(fā)表 [回復(fù)]
頂一個!!
11樓 若魚1919 2013-12-27 10:14發(fā)表 [回復(fù)]
撥云見日,雨過天晴!膜拜的五體投地四腳朝天!
10樓 zhkx123 2013-12-27 09:57發(fā)表 [回復(fù)]
mark
9樓 mcdonaldliu 2013-12-26 15:17發(fā)表 [回復(fù)]
這一篇寫的很詳細,有點難消化啊,頂
8樓 Ailuwolf 2013-12-26 13:42發(fā)表 [回復(fù)]
請問能不能給我介紹幾本基礎(chǔ)的android編程方面的書啊,我以前沒接觸過這方面的知識學(xué)過c與c++!謝謝!
Re: guolin 2013-12-26 20:28發(fā)表 [回復(fù)]
回復(fù)Ailuwolf:從基礎(chǔ)開始學(xué)唄,先從Java語言開始吧。后面找些安卓入門的書,很多的。
7樓 鸛貍媛 2013-12-26 11:44發(fā)表 [回復(fù)]
您的文章已被推薦到CSDN首頁,感謝您的分享。
6樓 晨遇 2013-12-26 11:43發(fā)表 [回復(fù)]
真心不錯,給1000個贊
5樓 sunweijie1234 2013-12-26 10:24發(fā)表 [回復(fù)]
給100個贊!寫得這么好都有人踩,喪心病狂。
4樓 pchenghao 2013-12-26 09:36發(fā)表 [回復(fù)]
贊一個,,另外,郭哥,給推薦本書唄,關(guān)于游戲或者自定義控件方面內(nèi)容比較好的書,先謝了~
Re: guolin 2013-12-26 19:55發(fā)表 [回復(fù)]
回復(fù)pchenghao:額,如果開發(fā)Android游戲的話,和java基本就沒啥關(guān)系了,一般都是用cocos2d-x框架,是用c++開發(fā)的。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多