View的測(cè)量:
在現(xiàn)實(shí)生活中昔善,如果我們要去畫(huà)一個(gè)圖形,就必須知道他的大小和位置人柿。同樣Android系統(tǒng)在繪制View之前柴墩,也必須對(duì)View進(jìn)行測(cè)量,即告訴系統(tǒng)該畫(huà)一個(gè)多大的View凫岖,這個(gè)國(guó)政在onMeasure(0方法中進(jìn)行江咳。
Android系統(tǒng)給我們提供了一個(gè)設(shè)計(jì)短小精悍卻功能強(qiáng)大的類(lèi)------MeasureSpec類(lèi),通過(guò)他來(lái)幫助我們測(cè)量View隘截,MeasureSpec是一個(gè)32位的int值扎阶,其中高2位為測(cè)量的模式,低30位為測(cè)量的大小婶芭,在計(jì)算中使用位運(yùn)算的原因是為了提高并優(yōu)化效率。
測(cè)試的模式可以分為以下三種:
EXACTLY:
即精確值模式着饥,當(dāng)我們將控件的layout_width屬性或layout_height屬性指定為具體數(shù)值時(shí)犀农,比如android:layout_width="100dp",或者指定為match_parent屬性時(shí)(占據(jù)父View的大小)。系統(tǒng)使用的是EXACTLY模式宰掉。
AT_MOST:
即最大值模式呵哨,當(dāng)控件的layout_width屬性或layout_height屬性指定為wrap_content時(shí),控件大小一般隨著控件的子空間或內(nèi)容的變化而變化轨奄,此時(shí)控件的尺寸只要不超過(guò)父控件允許的最大尺寸即可孟害。
UNSPECIFIED:
這個(gè)屬性比較奇怪,挪拟,他不指定其大小測(cè)量模式挨务,View像多大就多大。通常情況下在繪制自定義View時(shí)才會(huì)使用。
View默認(rèn)的onMeasure(0方法只支持EXACTLY模式谎柄,所以如果在自定義控件的時(shí)候不重寫(xiě)onMeasure()方法丁侄,就只能使用EXACTLY模式〕祝控件可以響應(yīng)你指定的具體寬高值或者是match_warp屬性鸿摇,而如果要讓自定義View支持wrap_content屬性,那么就必須重寫(xiě)onMeasure(0方法來(lái)指定wrap_content時(shí)的大小劈猿。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
通過(guò)查看源碼拙吉,可以發(fā)現(xiàn)其最終調(diào)用的是:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension這個(gè)方法。該方法試將測(cè)量后的寬高設(shè)置進(jìn)去揪荣。從而完成測(cè)量工作筷黔。所以我們就可以重寫(xiě)onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
我們調(diào)用自定義的measureWidth()和measureHeight()方法分別對(duì)寬高進(jìn)行重新定義。
private int measureWidth(int measureSpec) {
// TODO Auto-generated method stub
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;
}
上面的方法是通過(guò)判斷測(cè)量的模式变逃,給出不同的測(cè)量值必逆,當(dāng)specMode為EXACTLY時(shí),直接使用指定的specSize即可揽乱,當(dāng)specMode為其他兩種模式石名眉,需要給他一個(gè)默認(rèn)的大小。特別的如果制定wrap_content屬性凰棉,即AT_MOST模式损拢,則需要取出我們指定的大小與soecSize中的最小的一個(gè)來(lái)作為最后的測(cè)量值。上面的代碼基本上可以作為模板代碼撒犀。
運(yùn)行上面代碼福压,當(dāng)指定寬高為400PX的時(shí)候就會(huì)顯示具體的寬高View。如果制定為match_parent屬性時(shí)或舞,就會(huì)match父view荆姆,當(dāng)指定寬高屬性為wrap_content的時(shí)候,如果不重寫(xiě)onMeasure()方法映凳,那么系統(tǒng)就不知道該使用默認(rèn)多大的尺寸胆筒,因此,他就會(huì)默認(rèn)填充整個(gè)父布局诈豌,所以重寫(xiě)仆救,onMeasure()方法的目的,就是為了能夠給View一個(gè)wrap_conteng屬性下的默認(rèn)大小矫渔。(感覺(jué)說(shuō)了這么多還是這句最明白彤蔽。擦。庙洼。顿痪。镊辕。)
View的繪制:
onDraw(),Canvas ,Paint這三個(gè)就是繪制所必需的具體的我就不多說(shuō)了。
如果是在onDraw方法里面就不用創(chuàng)建Canvas對(duì)象员魏,因?yàn)閰?shù)里面就有丑蛤,那么如果在別的地方就需要new出來(lái)一個(gè):
Canvas camvas=new Canvas(bitmap);
為什么要穿進(jìn)去一個(gè)bitmap對(duì)象?如果不傳入一個(gè)bitmap撕阎,IDE編譯雖然不報(bào)錯(cuò)受裹,但是一般我們不會(huì)這樣做。這是因?yàn)榇┻M(jìn)去的bitmap與通過(guò)這個(gè)bitmap穿件的Canvas畫(huà)布是僅僅聯(lián)系在一起的虏束。這個(gè)過(guò)程我們稱(chēng)之為裝載畫(huà)布棉饶。這個(gè)bitmap是用來(lái)存儲(chǔ)所有繪制在Canvas上的像素信息。所以當(dāng)你通過(guò)這種方式創(chuàng)建了Canvas對(duì)戲那個(gè)后镇匀,門(mén)后面調(diào)用所有的Canvas.drawXXX方法都是發(fā)生在這個(gè)bittmap上照藻。如果在View類(lèi)的onDraw方法中,通過(guò)下面這段代碼汗侵,我們可以了解到bitmap與canvas直接的關(guān)系幸缕。首先在onDraw方法中繪制兩個(gè)bitmap:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawBitmap(bitmap1, 0, 0,null);
canvas.drawBitmap(bitmap2, 0, 0,null);
}
而對(duì)于bitmap2我們將他裝載到另一個(gè)Canvas對(duì)象中:
Canvas mCanvas=new Canvas(bitmap2);
在其他地方使用Canvas對(duì)象的繪圖方法在裝載bitmap2的Canvas對(duì)象上進(jìn)行繪圖:
mCamvas.drawXXX
通過(guò)mCanvas將繪制效果作用在了bittmap2上,再刷新View的時(shí)候就會(huì)發(fā)現(xiàn)通過(guò)onDraw方法畫(huà)出來(lái)的bitmap2已經(jīng)發(fā)生了改變晰韵,這就是因?yàn)閎itmap2承載了在mCanvas上所進(jìn)行的繪圖操作发乔。雖然我們也使用了Canvas的繪制API。但其實(shí)并沒(méi)有經(jīng)圖形直接繪制在onDraw()方法指定的那塊畫(huà)布上雪猪,而是通過(guò)改變bitmap栏尚,然后讓View重回,從而顯示改變之后的bittmap只恨。
ViewGroup的測(cè)量:
ViewGroup會(huì)去管理其子View译仗,其中的一個(gè)管理項(xiàng)目就是負(fù)責(zé)子View的顯示大小,當(dāng)Vie我Group的大小為wrap_content時(shí)官觅,ViewGroup就需要對(duì)子View進(jìn)行遍歷纵菌,以便獲得所有子view的大學(xué)哎奧,從而來(lái)決定自己的大小休涤,而在其他模式下則會(huì)通過(guò)具體的指定值來(lái)設(shè)置自身的大小产艾。
ViewGroup在測(cè)量時(shí)通過(guò)遍歷所有子View從而調(diào)用子View 的Measure方法獲得每一個(gè)子View的測(cè)量結(jié)果前面所說(shuō)的對(duì)View的測(cè)量就是在這里進(jìn)行的。
當(dāng)子View測(cè)量完畢后滑绒,就需要將子View放到合適的位置,這個(gè)過(guò)程就是View的Layout過(guò)程隘膘,ViewGroup在執(zhí)行Layout過(guò)程時(shí)疑故,同樣是使用遍歷來(lái)調(diào)用子View的Layout方法,并指定其具體的顯示位置弯菊,從而來(lái)決定其布局的位置纵势。
在自定義ViewGroup時(shí),通常會(huì)去重寫(xiě)onLayout()方法來(lái)控制其子View顯示位置的邏輯。同樣钦铁,如果需要支持wrap_content屬性软舌,那么他還必須重寫(xiě)onMeasure()方法,這點(diǎn)與VIEW是相同的牛曹。
ViewGroup的繪制:
ViewGroup通常情況下不需要繪佛点,因?yàn)樗旧砭蜎](méi)有需要繪制的東西,如果不是指定了ViewGroup的北京顏色黎比,那么ViewGroup的onDraw()方法都不會(huì)被調(diào)用超营。但是,ViewGRoup會(huì)使用dispatchDraw()方法來(lái)繪制其子View阅虫,其過(guò)程同樣是遍歷所有的子View演闭,并調(diào)用子View的繪制方法來(lái)完成繪制工作的。
自定義View:
在View中通常有以下一些比較重要的回調(diào)方法:
onFinishInflate():從XML加載組件后回調(diào)颓帝。
onSizeChanged():組件大小改變時(shí)回調(diào)米碰。
onMeasure():回調(diào)給方法來(lái)進(jìn)行測(cè)量。
onLayout():回調(diào)該方法來(lái)確定顯示的位置购城。
onTouchEvent():監(jiān)聽(tīng)到觸摸事件時(shí)的回調(diào)吕座。
通常情況下,由以下三種方法來(lái)實(shí)現(xiàn)自定義控件:
對(duì)現(xiàn)有控件進(jìn)行拓展
通過(guò)組合來(lái)實(shí)現(xiàn)新的控件
重寫(xiě)View來(lái)實(shí)現(xiàn)全新的控件
對(duì)現(xiàn)有控件進(jìn)行拓展:
以一個(gè)TextView為例工猜,比如米诉,讓一個(gè)TextView的背景更加豐富,給其多繪制幾層背景篷帅。
我們先來(lái)分析一下如何實(shí)現(xiàn)史侣,原生的TextView使用onDraw()方法繪制要顯示的文字,當(dāng)繼承了系統(tǒng)deTextView之后魏身,如果不重寫(xiě)其onDraw()方法惊橱,則不會(huì)修改TextView的任何效果,可以認(rèn)為在自定義的TextView中調(diào)用TextView類(lèi)的onDraw()方法來(lái)繪制了顯示的文字箭昵,代碼如下:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
}
程序調(diào)用super.onDraw(canvas)方法來(lái)實(shí)現(xiàn)原生控件的功能税朴,但是在調(diào)用super.onDraw()方法之前和之后,我們都可以實(shí)現(xiàn)自己的邏輯家制,分別在系統(tǒng)繪制文字前后正林,完成自己的操作,即如下:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//在回調(diào)父類(lèi)方法前颤殴,實(shí)現(xiàn)自己的邏輯觅廓,對(duì)TextView來(lái)說(shuō)即是在繪制文本內(nèi)容前
super.onDraw(canvas);
//在回調(diào)父類(lèi)方法后,實(shí)現(xiàn)自己的邏輯涵但。對(duì)TextVeiw來(lái)說(shuō)既是在繪制文本內(nèi)容后
}
這樣就可以改變?cè)到y(tǒng)繪制的方式杈绸。