Android 會根據基本的版面配置類別 View
和 ViewGroup
,提供複雜且強大的元件化模型來建構 UI。首先,這個平台提供了各種預先建構的 View 和 ViewGroup 子類別,分別稱為「小工具」和「版面配置」,可用來建構 UI。
部分可用小工具清單包括 Button
、TextView
、EditText
、ListView
、CheckBox
、RadioButton
、Gallery
、Spinner
,以及專用 AutoCompleteTextView
、ImageSwitcher
和 TextSwitcher
。
可用的版面配置包括 LinearLayout
、FrameLayout
、RelativeLayout
等。如需更多範例,請參閱常見版面配置物件。
如果預先建立的所有小工具和版面配置都不符合您的需求,您可以建立自己的 View 子類別。如果只需要對現有的小工具或版面配置進行微幅調整,只需簡單將小工具或版面配置加入子類別,並覆寫其方法即可。
建立自己的 View 子類別可讓您精確控制畫面元素的外觀和功能。為讓您理解您對自訂檢視區塊的控制權,以下範例展示了您可對自訂檢視區塊執行的操作:
- 您可以建立完全自訂顯示的 View 類型,例如使用 2D 圖形顯示的「音量控制」旋鈕,這類似於模擬的電子控制項。
- 您可以將一組 View 元件合併為一個新的元件,類似於 ComboBox (彈出式視窗清單和可用輸入文字欄位的組合),或者雙窗格選取器控制項 (左側和右側窗格中各有一個清單,您可以重新指派哪個項目應位於哪個清單中),等等。
- 您可以覆寫 EditText 元件在螢幕上的顯示方式 (記事本教學課程使用此方法來建立效果良好的劃線型記事本頁面)。
- 您可以擷取按下按鍵之類的其他事件,並以自訂方式進行處理 (例如在遊戲中)。
以下章節說明了如何建立自訂檢視區塊並在應用程式中使用。如需詳細的參考資訊,請參閱 View
類別。
基本做法
以下總覽大致說明了建立自有檢視區塊的須知事項:
-
使用自己的類別擴充現有
View
類別或子類別。 -
覆寫父類別中的某些方法。要覆寫的父類別方法以「
on
」開頭,例如onDraw()
、onMeasure()
和onKeyDown()
。這與Activity
或ListActivity
中的on...
事件類似,您可以覆寫生命週期和其他功能掛鉤。 - 使用新的擴充功能類別。完成之後,您可以在建立擴充功能類別的檢視區塊中使用它們。
提示:擴充功能類別可定義為使用它們的活動中的內部類別。這種做法可以控制對它們的存取權限,因而十分有用,但並非必要 (因為您或許想要建立新的公開檢視區塊,以在應用程式內的更大範圍內使用)。
完全自訂元件
您可以使用完全自訂元件來建立圖形元件,並依需求顯示。這可以是看起來像傳統模擬量測計的圖形 VU 計量器,或者可以是伴唱文字檢視區塊,會有一個活力球沿著字詞移動,讓您在卡拉 OK 機上唱歌。無論哪種方式,您都可能希望這些內建元件永遠不要執行某些操作,無論您如何組合它們。
幸運的是,您可以輕鬆建立元件,使其具有您想要的外觀和行為,能夠限制它們的可能只有您的想象力、螢幕的大小和可用的處理能力 (請記住,您的應用程式最終需要在某個裝置上執行,而該裝置的處理能力可能大大低於您的電腦工作站)。
如何建立完全自訂的元件:
-
毫不意外,您可以擴充的最常見檢視區塊正是
View
,所以通常是先擴充該檢視區塊,以建立新的上層元件。 - 您可以提供一個建構函式,從 XML 中擷取屬性和參數,也可以使用您自己的此類屬性和參數 (例如 VU 計量器的色彩和範圍,或指針的寬度和阻尼等)。
- 您可以建立自己的事件監聽器、屬性存取子和修飾詞,以及在元件類別中建立更複雜的行為。
-
大部分人都想要覆寫
onMeasure()
,如果您希望元件顯示某些內容,則還需要覆寫onDraw()
。雖然兩者都有預設行為,但預設onDraw()
不執行任何動作,而預設onMeasure()
會一律將大小設定為 100x100,這可能不是您想要的大小。 -
您可以視需要覆寫其他
on...
方法。
擴充 onDraw()
和 onMeasure()
onDraw()
方法提供 Canvas
,您可以在上面實作任何所需項目,例如 2D 圖形、其他標準或自訂元件、樣式化文字,或是您可以想到的任何內容。
注意:這不適用於 3D 圖形。若您想使用 3D 圖形,則必須擴充 SurfaceView
,而非 View,並從另一個執行緒進行繪製。詳情請參閱 GLSurfaceViewActivity 範例。
onMeasure()
有點複雜。onMeasure()
是元件與其容器之間顯示合約的重要部件。您必須覆寫 onMeasure()
,才能有效且準確地報告其所含零件的測量結果。加上父項的限制要求 (這些要求傳遞至 onMeasure()
方法),以及在計算完成後使用測得的寬度和高度呼叫 setMeasuredDimension()
方法的要求,使得情況更加複雜。如果您無法透過覆寫的 onMeasure()
方法呼叫此方法,在測量時就會產生例外狀況。
大致來說,實作 onMeasure()
看起來會像這樣:
-
系統會使用寬度和高度測量規格 (
widthMeasureSpec
和heightMeasureSpec
參數,二者都是代表尺寸的整數代碼) 呼叫覆寫的onMeasure()
方法,這些規格應視為所得寬度和高度測量結果的限制要求。您可以閱讀View.onMeasure(int, int)
的參考說明文件,找到這些規格要求的完整限制 (這份參考說明文件還很好地解釋了整個測量作業)。 -
元件的
onMeasure()
方法應計算顯示元件所需的測量寬度和高度。應盡量遵守傳遞的規格,不過也可以選擇超出規格 (在這種情況下,父項可選擇要採取的動作,例如剪輯、捲動、擲回例外狀況,或要求onMeasure()
使用不同的測量規格再試一次)。 -
計算寬度和高度後,您必須使用計算得出的測量結果呼叫
setMeasuredDimension(int width, int height)
方法。否則將擲回例外狀況。
以下內容概略說明了架構在檢視區塊上呼叫的其他標準方法:
類別 | 方法 | 說明 |
---|---|---|
創作 | 建構函式 | 當透過程式碼建立檢視區塊時,系統會呼叫一種形式的建構函式;當透過版面配置檔案加載檢視區塊時,系統會呼叫另一種形式的建構函式。第二種形式應剖析並套用版面配置檔案中定義的所有屬性。 |
|
將在從 XML 中加載檢視區塊及其所有子項之後呼叫。 | |
版面配置 |
|
呼叫此項以確定該檢視區塊及其所有子項的大小要求。 |
|
當該檢視區塊應當向其所有子項指派大小和位置時呼叫。 | |
|
當該檢視區塊的大小變更時呼叫。 | |
繪圖 |
|
在檢視區塊應顯示其內容時呼叫。 |
事件處理 |
|
當發生新的按鍵事件時呼叫。 |
|
當發生按鍵向上事件時呼叫。 | |
|
當發生軌跡球動作事件時呼叫。 | |
|
當發生觸控螢幕動作事件時呼叫。 | |
焦點 |
|
當檢視區塊獲得或失去焦點時呼叫。 |
|
當包含檢視區塊的視窗獲得或失去焦點時呼叫。 | |
附加 |
|
當檢視區塊附加至視窗時呼叫。 |
|
當檢視區塊從視窗卸離時呼叫。 | |
|
當包含檢視區塊的視窗顯示設定變更時呼叫。 |
複合控制項
如果您不想建立完全自訂的元件,而是想將一組現有控制項組合成可重複使用的元件,則可以建立複合元件 (或複合控制項)。概括而言,這會將多個基礎控制項 (或檢視區塊) 合併為一個邏輯項目群組,該群組可視為單一項目。舉例來說,「下拉式方塊」可視為單行 EditText 欄位和具有附加 PopupList 的鄰近按鈕的組合。如果按下按鈕並從清單中選取項目,該項目就會填入 EditText 欄位,如果使用者願意,他們也可以直接在 EditText 中輸入任何內容。
在 Android 中,已經有兩個其他檢視區塊可以達成此目的:Spinner
和 AutoCompleteTextView
。不過,下拉式方塊的概念更易於理解。
如何建立複合元件:
- 常見的起始點是某種類型的版面配置,可以建立一個擴充版面配置的類別。如果是下拉式方塊,我們可能會使用水平方向的 LinearLayout。請注意,可以在其中內嵌其他版面配置,因此複合元件可以任意構建且可能十分複雜。就像使用「活動」一樣,您可以使用宣告式 (基於 XML) 方法建立內含元件,也可以透過程式化方式利用程式碼進行內嵌。
- 在新類別的建構函式中,擷取父類別所需的參數,並先將其傳遞至父類別建構函式。然後設定要在新元件中使用的其他檢視區塊;您可以在這裡建立 EditText 欄位和 PopupList。您也可以在 XML 中引入自己的屬性及參數,建構函式將從中提取這些資訊並使用。
- 您還可以為內涵檢視區塊可能產生的事件建立事件監聽器,例如用於清單項目點擊事件監聽器的事件監聽器方法,以便在選取清單時更新 TextText 的內容。
- 您也可以建立含有存取子和修飾詞的屬性,例如允許在元件中先設定 EditText 值,並視需要查詢其內容。
-
擴充版面配置時,您無需覆寫
onDraw()
和onMeasure()
方法,因為版面配置的預設行為可能運作正常。不過,您還是可以視需要覆寫這些方法。 -
您可以覆寫
onKeyDown()
之類的其他on...
方法,這樣,當按下特定按鍵時,可以在下拉式方塊的彈出式清單中選擇某些預設值。
總而言之,將版面配置用作自訂控制項的基礎有以下優點:
- 您可以使用宣告式 XML 檔案指定版面配置,方式與活動畫面類似;或者,您也可以透過程式化方式建立檢視區塊,並利用程式碼將其嵌入版面配置中。
-
onDraw()
和onMeasure()
方法 (以及大多數其他on...
方法) 都有可能具有適用的行為,因此您無需覆寫這些方法。 - 總而言之,您可以快速建構任何複雜程度的複合檢視區塊,並將其視為單一元件重複使用。
修改現有檢視區塊類型
在某些情況下,還可以使用一種更簡單的方法來建立自訂檢視區塊。如果元件已經與您想要的效果十分接近,只需擴充元件並覆寫想要變更的行為即可。您能夠對完全自訂元件執行的所有操作,都可以在現有元件上執行,但是從檢視區塊階層中更專業的類別入手,您還可以免費獲得許多行為,它們可能正好能夠滿足您的需求。
舉例來說,記事本應用程式示範了使用 Android 平台的許多方面。其中一個就是擴充 TextText 檢視區塊,以製作劃線的記事本。這並不是一個最佳範例,而且用於執行此操作的 API 可能會變更,但這個範例表明了基本原則。
如果您還沒有這樣做,請將記事本範例匯入 Android Studio (或使用提供的連結查看來源)。請特別留意 NoteEditor.java 檔案中 LinedEditText
的定義。
在這個檔案中,需要留意以下事項:
-
定義
該類別的定義如下:
public static class LinedEditText extends EditText
-
LinedEditText
定義為NoteEditor
活動中的內部類別,但對外公開,因此,在需要時可從NoteEditor
類別外部以NoteEditor.LinedEditText
身分存取該類別。 -
這是
static
,意味著其不會產生所謂「合成方法」,該方法讓其能夠存取父類別中的資料,這進而表示它能夠真正作為單獨的類別行事,而非與NoteEditor
強烈相關的類別。如果內部類別不需要存取外部類別的狀態,則可以使用這種更簡潔的方法來建立內部類別,使產生的類別更小,並允許其他類別輕鬆使用它。 -
它會擴充
EditText
,也就是我們在此範例中選擇自訂的檢視區塊。完成後,新類別將能夠替代常規的EditText
檢視區塊。
-
-
類別初始化
如同往常,系統會先呼叫父類別。此外,這不是預設建構函式,而是參數化建構函式。當從 XML 版面配置檔案加載 EditText 時,使用這些參數建立了 EditText,因此,我們的建構函式必須擷取這些參數,並將其傳遞至父類別建構函式。
-
已覆寫的方法
這個範例僅覆寫一個方法 (
onDraw()
),但在您建立自訂元件時,可能需要覆寫其他方法。在這個範例中,覆寫
onDraw()
方法讓我們能夠在EditText
檢視區塊畫布上繪製藍線 (畫布將傳遞至覆寫的onDraw()
方法)。在方法結束前,系統會呼叫 Super.onDraw() 方法。需要叫用父類別方法,在本範例中,我們會在繪製想要包含的行後再執行此操作。 -
使用自訂元件
現在,我們擁有自訂元件了,但該怎麼使用呢?在記事本範例中,會直接從宣告式版面配置使用自訂元件,所以,我們看一下 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="match_parent" android:layout_height="match_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 中的通用檢視區塊,並使用完整套件指定類別。另請注意,我們使用
NoteEditor$LinedEditText
標記來參照定義的內部類別,這是在 Java 程式設計語言中參照內部類別的標準方法。如果您的自訂檢視區塊元件未定義為內部類別,則可使用 XML 元素名稱宣告檢視區塊元件,並排除
class
屬性。例如:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
可以看到,
LinedEditText
類別現已成為獨立的類別檔案。當類別以巢狀形式嵌入NoteEditor
類別時,這項技術將無法運作。 - 定義中的其他屬性和參數會傳遞至自訂元件建構函式中,然後會傳遞至 EditText 建構函式,這樣您就會為 EditText 檢視區塊使用相同的參數。您也可以自行新增參數,我們會在下方說明這一點。
-
自訂元件建立作為 XML 中的通用檢視區塊,並使用完整套件指定類別。另請注意,我們使用
這樣就大功告成了。當然,這是一個簡單的案例,但關鍵點已經表明,如果您想要更複雜的自訂元件,建立過程也會更加複雜。
更複雜的元件可能會覆寫更多 on...
方法,並引入部分自己的輔助程式方法,同時會大量自訂屬性和行為。唯一的限制就是您的想像力,以及元件需要執行的操作。