Android控件架構(gòu)與自定義控件(二)

View的測(cè)量:

在現(xiàn)實(shí)生活中,如果我們要去畫一個(gè)圖形,就必須知道他的大小和位置。同樣Android系統(tǒng)在繪制View之前,也必須對(duì)View進(jìn)行測(cè)量,即告訴系統(tǒng)該畫一個(gè)多大的View,這個(gè)國(guó)政在onMeasure(0方法中進(jìn)行。
Android系統(tǒng)給我們提供了一個(gè)設(shè)計(jì)短小精悍卻功能強(qiáng)大的類------MeasureSpec類,通過他來幫助我們測(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í)控件的尺寸只要不超過父控件允許的最大尺寸即可。

UNSPECIFIED:
這個(gè)屬性比較奇怪,,他不指定其大小測(cè)量模式,View像多大就多大。通常情況下在繪制自定義View時(shí)才會(huì)使用。

View默認(rèn)的onMeasure(0方法只支持EXACTLY模式,所以如果在自定義控件的時(shí)候不重寫onMeasure()方法,就只能使用EXACTLY模式。控件可以響應(yīng)你指定的具體寬高值或者是match_warp屬性,而如果要讓自定義View支持wrap_content屬性,那么就必須重寫onMeasure(0方法來指定wrap_content時(shí)的大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

通過查看源碼,可以發(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è)量工作。所以我們就可以重寫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;
    }

上面的方法是通過判斷測(cè)量的模式,給出不同的測(cè)量值,當(dāng)specMode為EXACTLY時(shí),直接使用指定的specSize即可,當(dāng)specMode為其他兩種模式石,需要給他一個(gè)默認(rèn)的大小。特別的如果制定wrap_content屬性,即AT_MOST模式,則需要取出我們指定的大小與soecSize中的最小的一個(gè)來作為最后的測(cè)量值。上面的代碼基本上可以作為模板代碼。

運(yùn)行上面代碼,當(dāng)指定寬高為400PX的時(shí)候就會(huì)顯示具體的寬高View。如果制定為match_parent屬性時(shí),就會(huì)match父view,當(dāng)指定寬高屬性為wrap_content的時(shí)候,如果不重寫onMeasure()方法,那么系統(tǒng)就不知道該使用默認(rèn)多大的尺寸,因此,他就會(huì)默認(rèn)填充整個(gè)父布局,所以重寫,onMeasure()方法的目的,就是為了能夠給View一個(gè)wrap_conteng屬性下的默認(rèn)大小。(感覺說了這么多還是這句最明白。擦。。。。)

View的繪制:

onDraw(),Canvas ,Paint這三個(gè)就是繪制所必需的具體的我就不多說了。

如果是在onDraw方法里面就不用創(chuàng)建Canvas對(duì)象,因?yàn)閰?shù)里面就有,那么如果在別的地方就需要new出來一個(gè):

Canvas camvas=new Canvas(bitmap);

為什么要穿進(jìn)去一個(gè)bitmap對(duì)象?如果不傳入一個(gè)bitmap,IDE編譯雖然不報(bào)錯(cuò),但是一般我們不會(huì)這樣做。這是因?yàn)榇┻M(jìn)去的bitmap與通過這個(gè)bitmap穿件的Canvas畫布是僅僅聯(lián)系在一起的。這個(gè)過程我們稱之為裝載畫布。這個(gè)bitmap是用來存儲(chǔ)所有繪制在Canvas上的像素信息。所以當(dāng)你通過這種方式創(chuàng)建了Canvas對(duì)戲那個(gè)后,門后面調(diào)用所有的Canvas.drawXXX方法都是發(fā)生在這個(gè)bittmap上。如果在View類的onDraw方法中,通過下面這段代碼,我們可以了解到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

通過mCanvas將繪制效果作用在了bittmap2上,再刷新View的時(shí)候就會(huì)發(fā)現(xiàn)通過onDraw方法畫出來的bitmap2已經(jīng)發(fā)生了改變,這就是因?yàn)閎itmap2承載了在mCanvas上所進(jìn)行的繪圖操作。雖然我們也使用了Canvas的繪制API。但其實(shí)并沒有經(jīng)圖形直接繪制在onDraw()方法指定的那塊畫布上,而是通過改變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é)哎奧,從而來決定自己的大小,而在其他模式下則會(huì)通過具體的指定值來設(shè)置自身的大小。

ViewGroup在測(cè)量時(shí)通過遍歷所有子View從而調(diào)用子View 的Measure方法獲得每一個(gè)子View的測(cè)量結(jié)果前面所說的對(duì)View的測(cè)量就是在這里進(jìn)行的。
當(dāng)子View測(cè)量完畢后,就需要將子View放到合適的位置,這個(gè)過程就是View的Layout過程,ViewGroup在執(zhí)行Layout過程時(shí),同樣是使用遍歷來調(diào)用子View的Layout方法,并指定其具體的顯示位置,從而來決定其布局的位置。

在自定義ViewGroup時(shí),通常會(huì)去重寫onLayout()方法來控制其子View顯示位置的邏輯。同樣,如果需要支持wrap_content屬性,那么他還必須重寫onMeasure()方法,這點(diǎn)與VIEW是相同的。

ViewGroup的繪制:

ViewGroup通常情況下不需要繪,因?yàn)樗旧砭蜎]有需要繪制的東西,如果不是指定了ViewGroup的北京顏色,那么ViewGroup的onDraw()方法都不會(huì)被調(diào)用。但是,ViewGRoup會(huì)使用dispatchDraw()方法來繪制其子View,其過程同樣是遍歷所有的子View,并調(diào)用子View的繪制方法來完成繪制工作的。

自定義View:

在View中通常有以下一些比較重要的回調(diào)方法:

onFinishInflate():從XML加載組件后回調(diào)。

onSizeChanged():組件大小改變時(shí)回調(diào)。

onMeasure():回調(diào)給方法來進(jìn)行測(cè)量。

onLayout():回調(diào)該方法來確定顯示的位置。

onTouchEvent():監(jiān)聽到觸摸事件時(shí)的回調(diào)。

通常情況下,由以下三種方法來實(shí)現(xiàn)自定義控件:

對(duì)現(xiàn)有控件進(jìn)行拓展

通過組合來實(shí)現(xiàn)新的控件

重寫View來實(shí)現(xiàn)全新的控件

對(duì)現(xiàn)有控件進(jìn)行拓展:

以一個(gè)TextView為例,比如,讓一個(gè)TextView的背景更加豐富,給其多繪制幾層背景。

我們先來分析一下如何實(shí)現(xiàn),原生的TextView使用onDraw()方法繪制要顯示的文字,當(dāng)繼承了系統(tǒng)deTextView之后,如果不重寫其onDraw()方法,則不會(huì)修改TextView的任何效果,可以認(rèn)為在自定義的TextView中調(diào)用TextView類的onDraw()方法來繪制了顯示的文字,代碼如下:

@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
    }

程序調(diào)用super.onDraw(canvas)方法來實(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)父類方法前,實(shí)現(xiàn)自己的邏輯,對(duì)TextView來說即是在繪制文本內(nèi)容前
        super.onDraw(canvas);
        //在回調(diào)父類方法后,實(shí)現(xiàn)自己的邏輯。對(duì)TextVeiw來說既是在繪制文本內(nèi)容后
    }

這樣就可以改變?cè)到y(tǒng)繪制的方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容