Android難點之——自定義View(上)

一、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)與自定義控件詳解。

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

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

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