《Android群英傳》讀書筆記2

第3章 Android 控件架構(gòu)與自定義控件詳解

1.Android控件架構(gòu)
控件大致分為兩類,ViewGroup控件和View控件。如圖在UI界面架構(gòu)中,每個Activity都包含一個Window對象,Window對象通常由PhoneWindow實現(xiàn)。PhoneWindow將一個DecorView設(shè)置為整個應用窗口的根View,DecorView作為窗口的頂層視圖,里面的所有View的監(jiān)聽事件都通過WindowManagerService來接收,并通過Activity對象來回調(diào)相應的onClickListener。
其中,DecorView包含兩部分:TitleView和ContentView。
ContentView實際是一個FrameLayout,所以設(shè)置requestWindowFeature
(Window.FEATURE_NOTITLE)一定要在調(diào)用setContentView()方法之前,才能生效。
當程序在onCreate()方法中調(diào)用setContentView()方法后,ActivityManagerService會回調(diào)onResume()方法,此時系統(tǒng)才會將整個DecorView添加到PhoneWindow中,并讓其顯示出來,從而完成界面的繪制。

Android UI界面架構(gòu)圖(圖來自五道口宅男)

擴展: Activity界面繪制過程詳解

2.View的測量
Android的View的測量時通過MeasureSpec類實現(xiàn),它是32位int值,高兩位為測量模式,低30位為測量的大小,有三種測量模式:

  • 精確值模式(EXACTLY),如將控件layout_width或layout_height設(shè)置為具體數(shù)值或match_parent;
  • 最大值模式(AT_MOST),控件layout_width或layout_height設(shè)置為wrap_content,控件大小隨內(nèi)容變化,不超過父控件允許的最大;
  • UNSPECIFIED,不指定控件大小測量模式,View要多大就多大,通常在自定義View時使用;

View默認的onMeasure()方法只支持EXACTLY模式,所以自定義控件不重寫onMeasure(),就只能使用EXACTLY模式(具體值和match_parent屬性),要支持wrap_content屬性就必須重寫onMeasure()方法。重寫onMeasure方法最終工作就是把測量后的寬高值作為參數(shù)設(shè)置在setMeasuredDimension方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  //計算width和height 
  setMeasuredDimension(width, height);
}

例如,ScrollView嵌套ListView或GridView,想要設(shè)置它們不出現(xiàn)滾動條,可以重寫它們的onMeasure方法,設(shè)置測量模式為AT_MOST,如下

    @Override   
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        int expandSpec = MeasureSpec.makeMeasureSpec(   
                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);   
        super.onMeasure(widthMeasureSpec, expandSpec);   
    }   

3.View的繪制
View的onDraw()方法包含一個參數(shù)Canvas對象,使用這個Canvas對象就可以直接繪圖了。但是通常情況下,Canvas對象的創(chuàng)建需要傳入?yún)?shù)Bitmap,為什么呢?
因為傳進去的Bitmap與通過這個Bitmap創(chuàng)建的Canvas畫布是緊緊聯(lián)系在一起的,這個Bitmap用來存儲所有繪制在Canvas上的像素信息,當使用Bitmap創(chuàng)建Canvas之后,后面調(diào)用所有的Canvas.drawXXX方法都發(fā)生在這個Bitmap上。當View需要重繪時,更新bitmap就可以了

4.ViewGroup的測量與繪制
當ViewGroup大小為wrap_content時,需要遍歷子View,調(diào)用measure方法獲取每一個子View測量結(jié)果,然后將子View放到合適位置,也就是Layout的過程;自定義ViewGroup,通常需要重寫onLayout()方法來控制字View顯示邏輯,需要支持wrap_content時,還需要重寫onMeasure方法。

ViewGroup通常不需要繪制,因為它本身沒有需要繪制的東西,如果不指定ViewGroup的背景顏色,那么ViewGroup的onDraw方法都不會被調(diào)用。但是,ViewGroup會調(diào)用dispatchDraw方法來繪制其子view,其過程同樣是通過遍歷所有子view并調(diào)用子view的繪制方法來完成繪制工作的。

5.自定義View
自定義View時,通常會重寫onDraw()方法來繪制View的顯示內(nèi)容,如果需要使用到wrap_content,還必須重寫onMeasure()方法,另外,通過自定義attrs屬性,可以設(shè)置新的屬性配置值。自定義View時,比較重要的回調(diào)方法:

  • onFinishInflate():從XML加載組件后回調(diào);
  • onSizeChanged():組件大小改變時回調(diào);
  • onMeasure():回調(diào)該方法來進行測量;
  • onLayout():回調(diào)該方法來確定顯示的位置;
  • onTouchEvent():監(jiān)聽到觸摸事件時回調(diào);

三種自定義控件的實現(xiàn)方式:
(1). 對現(xiàn)成控件進行擴展
一般可以在onDraw()方法對原生控件進行擴展

@Override
protected void onDraw(Canvas canvas) { 
  //在回調(diào)父類方法之前實現(xiàn)自己的邏輯,對TextView來說就是在繪制文本之前 
  super.onDraw(canvas); 
  //在回調(diào)父類方法之后實現(xiàn)自己的邏輯,對TextView來說就是在繪制文本之后
}

如書中,利用Android的繪圖機制實現(xiàn)動態(tài)文字閃動的效果。利用Paint對象的Shader渲染器,通過設(shè)置不斷變化的LinearGradient,并使用帶有該屬性的Paint對象來繪制要顯示的文字,首先在onSizeChanged()方法進行對象的初始化,并根據(jù)View的寬度設(shè)置一個LinearGradient漸變渲染器,如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
  super.onSizeChanged(w, h, oldw, oldh){
    if(mViewWidth==0){
      mViewWidth=getMeasuredWidth();
      if(mViewWidth>0){
        mPaint=getPaint();  //關(guān)鍵,獲取當前TextView的Paint對象,設(shè)置LinearGradient屬性
        mLinearGradient=new LinearGradient(0, 0, mViewWidth, 0, new            
int[]{Color.BLUE, 0xffffffff, Color.BLUE}, null, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
        mGradientMatrix=new Matrix();
      }
  }
}

@Override
protected void onDraw(Canvas canvas){
  super.onDraw(canvas);
  if(mGradientMatrix!=null){
    mTraslate+=mViewWidth/5;
    if(mTranslate>2*mViewWidth){
      mTranslate=-mViewWidth;
    }
    mGradientMatrix.setTranslate(mTranslate,0);
    mLinearGradient.setLocalMatrix(mGradientMatrix);
    postInvalidateDelayed(100);
  }
}

(2). 通過組合來實現(xiàn)新的控件
這種方式可以用來創(chuàng)建復用性比較強的控件,通常會繼承一個合適的ViewGroup,再添加指定的功能,組合成復合控件,比如頂部標題欄自定義的TopBar
先定義屬性,在attrs.xml文件,添加自定義的屬性,文字、顏色、大小等

<declare-styleable name="TopBar">
  <attr name="title" format="string"/>
  <attr name="titleTextSize" format="dimension"
  <attr name="leftBackground" format="reference|color"
 ……

再組合控件,然后定義和調(diào)用接口,布局中引用(注意要使用完整的包名),最后接口調(diào)用……

(3). 重寫View來實現(xiàn)全新控件
自定義View難于繪制控件和實現(xiàn)交互,通常需要繼承View類,重寫onDraw(), onMeasure()等方法實現(xiàn)繪制邏輯,重寫onTouchEvent()等觸控事件來實現(xiàn)交互邏輯……

自定義控件資料擴展:
TextView預渲染研究
Android自定義控件其實很簡單
AndroidTips-View

The end

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

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

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