【前言】Android為開發者提供了很多視圖控件,從TextView,ImageView到ViewPager等,按理說似乎我們也已經夠用了,其實不然,我們在做大型項目的時候,難免會遇到各種各樣的UI需求,又或者這些需求上很多地方用到的UI控件系統自帶的控件無法完全滿足,是以我們就需要封裝自定義的視圖控件。如何封裝?大緻的方向便是組合或繼承系統控件以滿足需求。
如下圖所示,在開發自定義的控件之前,想讓我們來認識一下Android的View層次結構:

簡而言之,Android如此的View層次結構正是設計模式的一種---組合模式。
如何開發自定義控件?
衆所周知,Android的界面布局有兩種方式,XML布局和在java檔案裡寫死布局。這兩種方式各有特點,XML布局能做到視圖和邏輯最大程度的分離,能讓代碼結構更清晰,更易于維護和修改,而寫死相比更加麻煩,不易于維護,但是有個好處就是速度快。(為什麼?因為XML布局歸根結底是會被DecoderView解析并繪制,這個過程整個XML布局的樹型結構會從上到下的被解析)。但是在大型的項目開發中,我們通常還是習慣用XML布局來解決UI開發。
是以,第一個關鍵就是,為了讓我們的自定義控件更加通用和易用,自定義控件必須支援能在XML檔案被使用和解析。是以,我們在自定義控件編寫的時候,必須實作一個帶AttributeSet類型參數的構造函數,這樣我們的控件才能在XML檔案中正常使用。
public class MyImageView extends ImageView implements IThemeView
{
public MyImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
}
無論有沒有自定義的參數,這個構造函數必須實作,否則在運作時報錯。
如何自定義控件參數?
上面我們說了自定義控件可以有自定義參數,這裡的參數指的就是像android:layout_width這種類似的參數,隻是我們自定義控件的參數的命名空間和系統控件的不一樣(這不是廢話?)。這些參數一來是友善我們直接在XML檔案裡設定,二來就是在我們繪制控件的時候檢索出這些參數來自定義的實作我們希望達到的視圖效果。
在項目的res/values/attrs檔案下,我們可以加入自定義的參數:
<declare-styleable name="CtripTitleView">
<attr name="title_text" format="string" localization="suggested" />
<attr name="title_text_appearance" format="integer" />
<attr name="title_btn_left_text" format="string" localization="suggested" />
<attr name="title_btn_left_drawable" format="reference" />
<attr name="title_btn_text" format="string" localization="suggested" />
<attr name="title_btn_drawable" format="reference" />
<attr name="title_btn_text_appearance" format="integer" />
<attr name="title_btn_width" format="dimension" />
<attr name="title_btn_height" format="dimension" />
<attr name="title_btn_bg" format="reference" />
<attr name="title_btn_text_padding" format="dimension" />
<attr name="title_bg_show_shadow" format="boolean" />
</declare-styleable>
如上圖所示,這些自定義參數有着各種的name(參數名),format(參數的格式)等,注意一下format這個屬性我們會用得很多,比如integer格式的屬性代表着參數在xml檔案裡可以被設定成int值,而我們在解析的時候的可以用getInt的方法取出設定的參數,而reference則代表這是一個引用值,如@string/...,@drawable/....等,我們在解析的時候就要視情況而定了,比如getDrawable或者getString來取出對應的參數值。還有就是在attrs檔案裡面我們也可以定義枚舉類型的參數,在解析的時候也是通過getInt來擷取其值的。
<declare-styleable name="CtripTextView">
<attr name="text_drawable_src" format="reference" />
<attr name="text_drawable_direction">
<enum name="left" value="0" />
<enum name="top" value="1" />
<enum name="right" value="2" />
<enum name="bottom" value="3" />
</attr>
<attr name="text_drawable_width" format="dimension" />
<attr name="text_drawable_height" format="dimension" />
</declare-styleable>
比如上面的text_drawable_direction屬性就是枚舉類型,我們在解析的時候
int direction = a.getInt(R.styleable.CtripTextView_text_drawable_direction, 0);
如何解析和繪制?
有了自定義的參數,我們便可以根據這些參數的值來繪制我們的自定義控件了。一種情況是比如我們繼承了一些現成的控件,比如ImageView,TextView或者Linerlayout等,我們便需要在構造函數裡面來處了解析自定義參數和繪制的工作,第二種情況便是我們直接繼承了View,那麼我們便要覆寫onDraw這個方法,在該方法完成上述工作(具體原因不解釋)。
public MyImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.attr.accountType);
//TODO
array.recycle();
}
如上圖所示,我們通過obtainStyleAttributes獲得了xml檔案裡面自定義控件被設定的具體的值,接下來就是根據這些值來繪制我們的視圖了,當然,最後别忘了回收資源。