一、Android的控件架構(gòu):
1、每個Activity包含一個Window對象(Window是abstract的,一般是由PhoneWindow實現(xiàn))。
2、PhoneWindow將DecorView設(shè)置為整個應(yīng)用的根View。DecorView將具體的內(nèi)容呈現(xiàn)在了PhoneWindow上。
3、DecorView里面所有View的監(jiān)聽事件都通過WindowManagerService來接收,通過Activity對象來回調(diào)對應(yīng)的OnClickListener。
4、DecorView把顯示分成兩部分:TiitleView 和 ContentView。
5、當(dāng)我們在Activity中setContentView時,實際就是把activityxxx.xml設(shè)置在ID為content的FrameLayout中。
6、當(dāng)程序在onCreate()方法中調(diào)用setContentView后,ActivityManagerService會回調(diào)onResume()方法,此時系統(tǒng)才會把整個DecorView添加到PhoneWindow中,并讓其顯示,從而完成界面的繪制。
二、View的測量:
關(guān)于View測量必須要知道的:
1、MeasureSpec類,其是一個32位的int值,低30位為測量的大小,高2位是測量模式,在計算中用位運算是為了提高效率。
2、EXACTLY:精確模式,當(dāng)layout_width或layout_height設(shè)為具體數(shù)值或match_parent時。
3、AT_MOST::最大值模式,當(dāng)layout_width或layout_height指定為wrap_content時,其隨著子控件的內(nèi)容變化而變化。
4、UNSPECIFIED:View想多大就多大,多在自定義View時用。
5、View類默認的onMeasure() 方法只支持EXACTLY模式,如果要讓自定義View支持wrap_content,必須重寫onMeasure方法指定wrap_content時的大小。
開始測量:
我們最終要做的工作就是把測量后的寬高值作為參數(shù)設(shè)置給setMeasuredDimension()方法
第一步:重寫onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
第二步:以measureWidth為例,從MeasureSpec對象中提取出具體的測量模式和大小。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
第三步:通過判斷測量的模式,給出不同的測量值。
當(dāng)specMode為EXACTLY時,直接使用指定的specSize即可,當(dāng)specMode為其他兩種模式時,需要給它一個默認的大小。
特別地,如果指定wrap_content屬性,即AT_MOST模式,則需要取出我們指定的大小與specSize中最小的一個來作為最后的測量值。
以下可作為模版代碼:
private int measureWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result.specSize);
}
}
return result;
}
三、View的繪制:
關(guān)于View繪制必須要知道的:
1、當(dāng)測量好一個View后,我們就可以簡單地重寫onDraw()方法,并在Canvas對象上來繪制所需要的圖形。
2、Canvas是畫板,用Paint在上面作畫。
第一步:創(chuàng)建一個Canvas對象
Canvas canvas = new Canvas(bitmap);
第二步:開始繪制
canvas.drawXXX();
1、不提倡創(chuàng)建時不傳入bitmap,因為這個bitmap要和Canvas畫布緊緊聯(lián)系在一起,此過程稱之為:裝載畫布,這個bitmap用來存儲所有繪制在Canvas上的像素信息。
2、當(dāng)用以上方式創(chuàng)建好后,后面調(diào)用所有的Canvas.drawXXX方法都發(fā)生在這個bitmap上。
3、雖然我們也使用了Canvas的繪制API,但其實并沒有將圖形直接繪制在onDraw()方法指定的那塊畫布上,而是通過改變bitmap,然后讓View重繪,從而顯示改變之后的bitmap。
三、ViewGroup的測量:
1、ViewGroup會去管理其子View,其中一個任務(wù)就是負責(zé)View的顯示大小。
2、當(dāng)ViewGroup大小為wrap_content時,ViewGroup就需要對子View進行遍歷,已便獲得所有子View的大小,從而決定自己的大小。
3、ViewGroup在測量時遍歷子View,從而調(diào)用子View的Measure方法獲得每個子View的測量結(jié)果,前面說的對View的測量,就是在這進行的。(ViewGroup的Layout過程與其相似)
4、當(dāng)子View測量完畢后,就需要將子View放到合適的位置,這個過程就是View的Layout過程。
5、在自定義ViewGroup時,通常會去重寫onLayout()方法控制子View顯示位置的邏輯。
6、如果還需支持wrap_content,必須重寫onMeasure()方法自己實現(xiàn),這點與View是相同的。
四、ViewGroup的繪制
1、ViewGroup通常不需要繪制,除了指定ViewGroup的背景色,它的onDraw()方法會調(diào)用之外,其他情況都不會調(diào)用。
2、ViewGroup會使用dispatchDraw()方法來繪制其子View,其過程同樣是遍歷所有子view,并調(diào)用子View的繪制方法來完成繪制工作。(這點與ViewGroup的測量和Layout()過程雷同)
參考資料:
1、《Android群英傳》第3章Android控件架構(gòu)與自定義控件詳解。