複合控制項
如果不想建立一個完全定製的組件,而是想要把一組既存的控制項放到一起,形成一個可重用的組件,那麼建立一個複合組件(或複合控制項)是一個合適的選擇。在一個容器中,複合組件把多個原子化的控制項(或View)組合成一個邏輯組,它能夠處置一件單一的事情。例如,ComboBox控制項,就是通過一個單行文本域、一個按鈕和一個彈出列表組合而成的,如果按下按鈕並且從列表中選擇了一項,那麼選擇項就會填入單行文本域,而且如果使用者喜歡,也可以直接把內容輸入到文本域中。
在Android中,實際上有另外兩View可以很容易的完成ComboBox的工作:Spinner和AutoCompleteTextView,但是不管怎樣,使用ComboBox的例子會使得概念更容易理解。
以下是建立複合組件的步驟:
1. 通常的起點是用某種類型布局,建立一個擴充布局的類。在ComboBox的例子中,可以使用水平方向的LinearLayout布局。其他布局也可以嵌套到內部,以便複合組件能夠隨意的組合。注意,跟Activity一樣,既可以使用基於XML聲明的方法來建立一個複合組件,也可以編程的方式把它們嵌入到代碼中。
2. 在新類的構造器中,可以帶入任何子類需要的參數,並且首先要通過子類的構造器來傳遞它們。然後,就可以建立新群組件中使用的其他的View了;ComboBox組件就是在這個時候建立了文本域和彈出列表。注意,也可以把自己的屬性和參數引入到XML聲明中,這些屬性和參數能夠被構造器使用。
3.
還可以給被包含的View所可能產生的事件建立監聽器,例如,針對清單項目的Click監聽器的方法,如果一個清單項目被選擇了,那麼這個監聽器方法就會更新文本域的內容。
4. 建立自訂帶有訪問器和編輯器的屬性,例如,ComboBox組件中針對文本域的get…和set…方法。
5. 在擴充一個Layout的情況下,不需要重寫onDraw和onMeasure()方法,因為布局的預設行為就可以很好的工作。但是,如果需要你依然可以重寫它們。
6. 還可以重寫其他的on…方法,如可以重寫onKeyDown()方法,當某個鍵被按下時,可以從ComboBox的彈出列表中選擇某些預設值。
總結,使用布局作為定製控制項的基礎有以下好處:
1.
能夠像Activity一樣使用聲明的XML檔案來指定布局,也能夠用編程的方式建立View,並且從代碼中把它們嵌入到布局中。
2. onDraw()和onMeasure()方法(還有其他的大多on…方法)都有適當的行為,因此不用重寫它們。
3. 最後,可以快速的構造任務複雜的複合View,並且可以把它們作為一個單一的組件來重用。
複合控制項的例子
在SDK的API Demos工程中,有兩個List的例子---在Views/Lists目錄下樣本4和樣本6示範了一個SpeechView組件,它擴充了LineraLayout布局讓組件顯示語音查詢結果。對應的類在List4.java和List6.java的範例程式碼中。
修改既存的View類型
在某些情況中,對於建立有用的定製的View甚至有更容易的選項。如果有一個既存的組件與想要的非常相似,就可以簡單的擴充這個組件,並且重寫想要改變的行為。可以用完全定製的組件做所有想做的事情,但是通過從View層次樹中的一個更特殊的類開始,還能夠獲得更多的你所期望的已經實現的行為。
例如,在SDK包含的NotePad應用程式的樣本中,有許多使用Android平台的特徵,其中有擴充EditText控制項的單行記事本。這不是一個完美的例子,但是它示範了組件定製的一些原則。
使用下面連結,把NotePad樣本的代碼匯入到Eclipse中,實際看一下NoteEditor.java檔案中LinedEditText類的定義。
http://developer.android.com/resources/samples/NotePad/index.html
需要注意的一些問題:
1. 定義
使用下面方法來定義類:
public static class LinedEditTextextends EditText
A. 它是作為NoteEditorActivity的一個內部類來定義的,但是它是公用的,這樣如果需要,就可以在NoteEditor類的外部用NoteEditor.MyEditText方式來訪問。
B. 它是靜態,這就;意味著不會產生所謂的允許訪問來自父類的資料的合成方法,反過來意味著它實際是作為一個獨立的類,而沒有跟NoteEditor進行強制的關聯。如果不需要從類的外部存取狀態,要保持產生小的類,並且還要允許在其他類中能夠容易的使用它,那麼這是一種建立內部類的比較清晰的方式。
C. 它擴充了EditText類,它是我們選擇的要定製的View。定製完成後,新的類可以替代普通的EditText類。
2.
類初始化
始終首先要調用的是super方法,它不是預設的構造器,而是參數化的一種方式。EditText是用XML布局檔案中的這些參數來建立的,因此,我們的構造器需要帶入這些參數,並把它們傳遞給父類的構造器。
3. 重寫方法
這個例子中,僅重寫了一個方法:onDraw().
對於NotePad樣本,重寫onDraw()方法允許我們在EditText的畫布上畫藍線(畫布是有重寫的onDraw()方法傳入的)。在這個方法的最後,調用了super.onDraw()方法。父類的方法應該被調用,但是在這個例子中,我們在畫完我們想要的線之後調用了父類的onDraw()方法。
4. 使用定製組件
組件定製完之後,如何使用它呢?在NotePad例子中,定製組件在聲明的布局中被直接使用,因此需要看一下res/layout檔案中的note_editor.xml檔案中的聲明:
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/transparent"
android:padding="5dp"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences" />
定製的組件是用XML中的一個一般的View來建立,並且使用完整的包名來指定類。還要注意定義中引用內部類的方法:NoteEditor$LinedEditText,這是Java程式設計語言標準的引用內部類的方法。如果定製View組件沒有作為內部類來定義,那麼就要選擇用XML元素名來聲明View組件,並且要去除類的屬性,如:
<com.android.notepad.LinedEditText
id="@+id/note"
... />
注意,LinedEditText類是一個獨立的類檔案,當這個類被嵌套在NoteEditor類中時,這種技術是不會工作的。
定義中的其他屬性和參數被傳入定製組件的構造器中,然後傳遞給EditText類的構造器,因此這些屬性和參數與EditText類使用的是相同的。注意,也可以添加自己的參數。