一 概述
自定義組件是Android工程師必須瞭解並且經常會使用的知識點,本文就是對該知識點的簡單總結。
具體而言,自定義組件有三種方式:
-
繼承現有組件,拓展其功能
-
組合現有組件,實現模板化
-
直接繼承View,重寫onDraw方法,進行重繪
直接繼承ViewGroup(或其子類),重寫onLayout/onMeasure方法,進行自定義佈局
二 繼承現有組件,拓展其功能
步驟如下:
-
根據要實現的效果,找到功能相近的組件
-
繼承該組件,實現自有功能
具體而言,以下幾個方法需要注意,
構造方法
首先,如果要在XML文件中直接使用自定義組件,需要實現具有兩個參數的構造方法。
在構造方法中,主要實現獲取XML佈局文件中寫入的自定義屬性的值。
自定義屬性的方法如下:
在res/values目錄下定義attrs.xml文件,該文件中定義了自定義組件支持的自定義屬性。
具體寫法爲:
在resources標籤下加入declare-styleable標籤,並給其一個標籤名,一般選擇自定義類名作爲標籤名;
然後,在後面自定義屬性,以標籤attr包裹,內容包括自定義屬性名name與自定義屬性的類型format。
形式如下所示:
<resources> <declare-styleable name="一般爲自定義組件類名"> <attr name="自定義屬性名" format=""> <attr name="自定義屬性名" format=""> ... ... </declare-styleable> </resources>
這就定義了自定義組件的自定義屬性,之後就可以在XML佈局文件中使用這些自定義屬性了。
使用的時候,要在xml文件中寫入xmlns,即xml命名空間,形式爲:
xmlns:custom="http://schemas.android.com/apk/res-auto"
或:
xmlns:custom="http://schemas.android.com/apk/manifest中的package名"
之後就可以“custom:自定義屬性名”來使用自定義屬性了。
在構造方法中,通過操作TypedArray來獲取自定義屬性內容,代碼如下:
TypedArray options = context.obtainStyledAttributes(attrs, R.styleable.PaintDoubleWordsTextView, 0, 0); int numOfOptions = options.getIndexCount(); for (int i = 0; i < numOfOptions; ++i) { int index = options.getIndex(i); switch (i) { case R.styleable.PaintDoubleWordsTextView_word0 : word0 = options.getString(index); break; case R.styleable.PaintDoubleWordsTextView_word0Size : word0Size = options.getInt(index, DEFAULT_TEXT_SIZE); break; case R.styleable.PaintDoubleWordsTextView_word1 : word1 = options.getString(index); break; case R.styleable.PaintDoubleWordsTextView_word1Size : word1Size = options.getInt(index, DEFAULT_TEXT_SIZE); break; } } options.recycle(); // 勿忘
onDraw()
onDraw() 方法的參數是一個Canvas對象,通過該對象即可實現自定義組件的內容繪製。
invalidate()
觸發重繪,onDraw。
requestLayout()
觸發整個繪製流程,onMeasure/onLayout/onDraw。
三 組合組件,模板化使用
這種自定義組件的方式並不創建新的組件,只是將已有組件進行組合,然後將組合好的整體當做模板來使用。
關鍵方法:
LayoutInflater.from(Context).inflate(自定義佈局id, 爲加載好的佈局指定父佈局);
通過以上關鍵代碼來將自定義佈局載入程序中,之後就可以通過findViewById
來獲取自定義佈局文件中的組件了。
步驟如下:
-
根據需求組合組件
根據需求對組件進行組合,構成模板。
此處主要是實現一個XML佈局文件。
-
拓展Layout類
實現Layout類的子類,在其具有兩個參數的構造方法中加載上一步實現的佈局文件,然後獲取其中定義的組件。
-
控制組件顯示,實現事件響應
根據需求,操作上一步獲取的組件,控制組件顯示並實現具體的事件響應。
四 繼承View & ViewGroup
繼承View與繼承ViewGroup是不同的。繼承View是要自定義組件,而繼承ViewGroup則要自定義佈局。
對於自定義組件而言,所需要考慮比較少,只有兩點需要注意:
-
對於Touch事件的處理
根據事件處理機制,當重寫了
onTouch
方法後,要注意返回true之後會導致onTouchEvent
方法不再執行,進而影響到click的執行與滑動的效果;同時,還要注意對ACTION的DOWN處理後返回false時,會導致後續的UP、MOVE事件無法獲取,也就是會阻止事件的層級傳遞。 -
重寫
onDraw
方法繪製需要的UI根據Android繪製原理,自定義組件只需要重寫
onDraw
方法即可,在其中使用所提供的Canvas畫布繪製所需要的效果。至於測量與佈局,自定義組件不需要考慮太多,這是由佈局組件來考慮的問題。實際上,View類中的
measure
方法是一個final方法,子類無法重寫;對於子類,只需要重寫真正進行測量的onMeasure
方法即可。View類的
onMeasure
實現裏,只是去調用了setMeasuredDimension
這個final方法去保存了組件的寬、高信息;直接繼承自View的TextView則重寫了onMeasure
方法去根據padding等信息來計算寬、高數據。View類中的
layout
方法則直接在註釋中說明,子類不需要重寫該方法,具有子組件的子類,需要重寫onLayout
方法,在其中調用子組件的layout
方法。代碼上看,View類中的
onLayout
方法是一個空方法,TextView中也並沒有重寫layout
方法(因爲不需要),所重寫的onLayout
方法也沒有進行重要的操作。所以,在自定義View組件時,爲了實現較精細的測量,一般需要重寫
onMeasure
方法,對於onLayout
方法,一般不用實現。
至於自定義佈局,就要考慮對於測量與佈局的處理問題,
-
測量
這一步根據Android繪製原理,需要重寫
onMeasure
方法,在其中獲取各個子組件的dimension,計算出佈局的dimension,然後調用setMeasuredDimension
方法設置好佈局的尺寸。ViewGroup中沒有重寫
onMeasure
方法,子類必須自己進行實現。 -
佈局
這一步需要重寫
onLayout
方法,在其中調用各個子組件的layout
方法,讓子組件完成對自己的佈局。在ViewGroup中,
onLayout
方法是一個抽象方法,自定義的佈局必須實現該方法,在其中對子組件的layout
方法進行調用。而ViewGroup中的layout
方法是一個final方法,不能被重寫,其中則簡單的調用了View的layout
方法(super.layout()
)。關於View的
layout
方法,一般在進行繼承View自定義組件時不需要重寫,至於View類中的onLayout
方法,是一個空方法,不進行任何操作。至於繪製,則由ViewRootImpl類中的
performTraversals
方法調用performDraw
進而調用組件的onDraw
方法實現,不需要在ViewGroup中考慮。