TextView類的成員函數onMeasure根據上述規則計算好自己的寬度wdith和高度height之后,必須要調用從父類View繼承下來的成員函數setMeasuredDimension來通知父視圖它所要設置的寬度和高度,否則的話,該函數調用結束之后,就會拋出一個類型為IllegalStateException的異常。
2. 布局
前面的測量工作實際上是確定了控件的大小,但是控件的位置還未確定??丶奈恢檬峭ㄟ^布局這個操作來完成的。
我們知道,控件是按照樹形結構組織在一起的,其中,子控件的位置由父控件來設置,也就是說,只有容器類控件才需要執行布局操作,這是通過重寫父類View的成員函數onLayout來實現的。從Activity窗口的結構可以知道,它的頂層視圖是一個DecorView,這是一個容器類控件。Activity窗口的布局操作就是從其頂層視圖開始執行的,每碰到一個容器類的子控件,就調用它的成員函數onLayout來讓它有機會對自己的子控件的位置進行設置,依次類推。
我們常見的FrameLayout、LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout,都是屬于容器類控件,因此,它們都需要重寫父類View的成員函數onLayout。由于TextView控件不是容器類控件,因此,它可以不重寫父類View的成員函數onLayout。
3. 繪制
有了前面兩個操作之后,控件的位置的大小就確定下來了,接下來就可以對它們的UI進行繪制了??丶榱四軌蚶L制自己的UI,必須要重寫父類View的成員函數onDraw。
TextView類的成員函數onDraw的實現如下所示:
[java] view plaincopyprint?
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
protected void onDraw(Canvas canvas) {
//在畫布canvas上繪制UI
......
}
......
}
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
protected void onDraw(Canvas canvas) {
//在畫布canvas上繪制UI
......
}
......
}
這個函數定義在文件frameworks/base/core/java/android/widget/TextView.java中。
參數canvas描述的是一塊畫布,控件的UI就是繪制在這塊畫布上面的。畫布提供了豐富的接口來繪制UI,例如畫線(drawLine)、畫圓(drawCircle)和貼圖(drawBitmap)等等。有了這些UI畫圖接口之后,就可以隨心所欲地繪制控件的UI了。
從前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文可以知道,Java層的Canvas實際上是封裝了C++層的SkCanvas。C++層的SkCanvas內部有一塊圖形緩沖區,這塊圖形繪沖區就是窗口的繪圖表面(Surface)里面的那塊圖形緩沖區。
從前面Android應用程序與SurfaceFlinger服務的關系概述和學習計劃一文可以知道,窗口的繪圖表面里面的那塊圖形緩沖區實際上是一塊匿名共享內存,它是SurfaceFlinger服務負責創建的。SurfaceFlinger服務創建完成這塊匿名共享內存之后,就會將其返回給窗口所運行在的進程。窗口所運行在的進程獲得了這塊匿名共享內存之后,就會映射到自己的進程空間來,因此,窗口的控件就可以在本進程內訪問這塊匿名共享內存了,實際上就是往這塊匿名共享內存填入UI數據。注意,這個過程執行完成之后,控件的UI還沒有反映到屏幕上來,因為這時候將控件的UI數據填入到圖形緩沖區而已。
從前面Android窗口管理服務WindowManagerService的簡要介紹和學習計劃一文可以知道,窗口的UI的顯示是WindowManagerService服務來控制的。因此,當窗口的所有控件都繪制完成自己的UI之后,窗口就會向WindowManagerService服務發送一個Binder進程間程通信請求。WindowManagerService服務接收到這個Binder進程間程通信請求之后,就會請求SurfaceFlinger服務刷新相應的窗口的UI。SurfaceFlinger服務刷新窗口UI的過程可以參考前面Android系統Surface機制的SurfaceFlinger服務渲染應用程序UI的過程分析一文。
從上面的描述就可以看出,控件的UI雖然是在一塊簡單的畫布進行繪制,但是其中蘊含了豐富的知識點,并且需要應用程序進程、WindowManagerService服務和SurfaceFlinger服務三方緊密而有序的配合。
如果我們仔細閱讀Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文,還可以得出以下兩個結論:
(1). 一個窗口的所有控件的UI都是繪制在窗口的繪圖表面上的,也就是說,一個窗口的所有控件的UI數據都是填寫在同一塊圖形緩沖區中;
(2). 一個窗口的所有控件的UI的繪制操作是在主線程中執行的,事實上,所有與UI相關的操作都是必須是要在主線程中執行,否則的話,就會拋出一個類型為CalledFromWrongThreadException的異常來。
為什么要規定所有與UI相關的操作都必須在主線程中執行呢?我們知道,這些與UI相關的操作都涉及到大量的控件內部狀態以及需要訪問窗口的繪圖表面,也就是說,要大量地訪問控件類的成員變量以及窗口繪圖表面里面的圖形緩沖區,因此,如果不將這些與UI相關的操作限定在同一個線程中執行的話,那么就會涉及到線程同步問題。線程同步的開銷是很大的,因此,就要保證那些與UI相關的操作都在同一個線程中執行。這個負責執行UI相關操作的線程便是應用程序進程的主線程,因此我們也將應用程序進程的主線程稱為UI線程。
我們知道,應用程序進程的主線程除了負責執行與UI相關的操作之外,還負責響應用戶的輸入,因此,我們就要盡量地避免執行很耗時的UI操作,否則的話,系統就會由于應用程序進程的主線程無法及時響應用戶輸入而彈出ANR對話框。
那么,有沒有辦法讓某一個控件的UI享有獨立的圖形緩沖區呢?也就是這個控件不將自己的UI數據填入到它的宿主窗口的繪圖表面的圖形繪沖區里面去。如果可以的話,那么我們就可以在另外一個獨立的線程中繪制該控件的UI。這樣做的好處是顯而易見——可以在這個獨立的線程執行相對比較耗時的UI繪制操作而不會導致主線程無法及時響應用戶輸入。答案是肯定的,在接下來的一篇文章中,我們就分析一個可以具有獨立圖形緩沖區的控件——SurfaceView。
原文轉自:http://blog.csdn.net/luoshengyang/article/details/8636153