建立自訂檢視畫面元件

試用 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用版面配置。

Android 會根據基本的版面配置類別 ViewViewGroup,提供複雜且強大的元件化模型來建構 UI。平台包含各種預先建構的 ViewViewGroup 子類別,分別稱為小工具和版面配置,可用來建構 UI。

部分可用小工具清單包括 ButtonTextViewEditTextListViewCheckBoxRadioButtonGallerySpinner,以及專用 AutoCompleteTextViewImageSwitcherTextSwitcher

可用的版面配置包括 LinearLayoutFrameLayoutRelativeLayout 等。如需更多範例,請參閱「常見版面配置」。

如果所有預先建構的小工具或版面配置都不符合您的需求,您可以自行建立 View 子類別。如果您只需要稍微調整現有的小工具或版面配置,可以將小工具或版面配置加入子類別,並覆寫其方法。

建立自己的 View 子類別可讓您精確控管畫面元素的外觀和功能。以下舉例說明自訂檢視區塊的功用,有助您瞭解自訂檢視區塊的功能:

  • 您可以建立完全自訂的 View 類型,例如使用 2D 圖形算繪的「音量控制」旋鈕,類似類比電子控制項。
  • 您可以將一組 View 元件合併成一個新的單一元件,像是建立組合方塊 (包含彈出式清單和任意輸入文字欄位的組合)、雙窗格選取器控制項 (左右窗格,每個窗格內均含有清單,可讓您重新指派該清單所屬的項目),以此類推。
  • 您可以覆寫 EditText 元件在畫面上的算繪方式。在 NotePad 範例應用程式中,我們可以運用這項特效建立內嵌筆記本頁面。
  • 您可以擷取其他事件 (例如按鍵操作),並以自訂方式處理,例如遊戲。

以下章節將說明如何建立自訂檢視畫面,並在應用程式中使用。如需詳細的參考資訊,請參閱 View 類別。

基本方法

以下是建立自己的 View 元件的重點總覽:

  1. 使用您自己的類別擴充現有的 View 類別或子類別,
  2. 覆寫父類別中的某些方法。要覆寫的父類別方法開頭是 on,例如 onDraw()onMeasure()onKeyDown()。這與 ActivityListActivity 中的 on 事件類似,您可以覆寫生命週期和其他功能掛鉤。
  3. 使用新的擴充功能類別。完成後,您可以使用新的擴充功能類別取代其基於的檢視畫面。

完全自訂元件

您可以建立完全自訂的圖形元件,以您想要的方式顯示。您可能會希望圖形 VU 計量器看起來像是舊式的類比量表,或者是跟唱文字檢視區塊,當您邊唱卡拉 OK 機器時,彈跳球會隨著字詞移動。您可能會希望內建元件無法執行某些操作,無論您採用的方式為何。

幸好,您可以建立具有您所需外觀和行為的元件,而僅限於您的想像力、螢幕尺寸以及可用的處理能力。請注意,應用程式可能需要在耗電量遠比桌面工作站上執行。

如要建立完全自訂的元件,請考慮下列幾點:

  • 可擴充的一般檢視區塊是 View,因此通常您可以先擴充該檢視區塊來建立新的超級元件。
  • 您可以提供建構函式,其中包含來自 XML 的屬性和參數,以及使用自己的屬性和參數,例如 VU 計量器的顏色和範圍,或是針的寬度和阻斷。
  • 您可能會想要在元件類別中建立自己的事件監聽器、屬性存取子和修飾符,以及更複雜的行為。
  • 您幾乎當然會想要覆寫 onMeasure(),但如果您希望元件顯示某些內容,可能也需要覆寫 onDraw()。雖然兩者都有預設行為,但預設的 onDraw() 不會執行任何動作,而預設的 onMeasure() 一律會設定 100x100 的大小,這您可能不想要這個大小。
  • 您還可以視需要覆寫其他 on 方法。

擴充 onDraw() 和 onMeasure()

onDraw() 方法提供 Canvas,您可在上面實作任何所需項目,例如 2D 圖形、其他標準或自訂元件、樣式化文字,或是您能想到的任何項目。

onMeasure() 有點複雜。onMeasure() 是元件及其容器之間轉譯合約的重要環節。必須覆寫 onMeasure(),才能有效且準確地回報所含零件的測量結果。這會因父項的限制要求 (傳遞至 onMeasure() 方法) 稍複雜,而後者需要在計算後,使用測量的寬度和高度呼叫 setMeasuredDimension() 方法。如果您未從覆寫的 onMeasure() 方法呼叫此方法,會在測量時產生例外狀況。

大致來說,實作 onMeasure() 看起來會像這樣:

  • 系統會以寬度和高度規格呼叫覆寫的 onMeasure() 方法,系統會將其視為您產生的寬度和高度測量結果設有限制的要求。widthMeasureSpecheightMeasureSpec 參數都是代表尺寸的整數代碼。如需這些規格要求的完整限制清單,請參閱「View.onMeasure(int, int)」的參考說明文件。本參考說明文件也會說明整個測量作業。
  • 元件的 onMeasure() 方法會計算測量寬度和高度,這是轉譯元件所需的項目。即使超過傳遞規格,仍必須盡量避免超過傳遞規格。在這種情況下,父項可以選擇要執行的操作,包括裁剪、捲動、擲回例外狀況,或要求 onMeasure() 再試一次,這可能需要採用不同的測量規格。
  • 計算寬度和高度時,請使用計算出的測量結果呼叫 setMeasuredDimension(int width, int height) 方法。否則就會發生例外狀況。

以下摘要列出架構在檢視區塊上呼叫的其他標準方法:

類別 方法 說明
創作 建構函式 透過程式碼建立檢視畫面時,系統會呼叫一種形式的建構函式;當透過版面配置檔案加載檢視畫面時,系統會呼叫一種形式。第二種形式會剖析並套用版面配置檔案中定義的屬性。
onFinishInflate() 在從 XML 加載檢視區塊及其所有子項之後呼叫。
版面配置 onMeasure(int, int) 呼叫此項以確定該檢視區塊及其所有子項的大小要求。
onLayout(boolean, int, int, int, int) 當這個檢視區塊必須將大小和位置指派給其所有子項時呼叫。
onSizeChanged(int, int, int, int) 當這個檢視區塊的大小變更時呼叫。
繪圖 onDraw(Canvas) 當檢視區塊必須轉譯其內容時呼叫。
事件處理 onKeyDown(int, KeyEvent) 在發生按鍵向下事件時呼叫。
onKeyUp(int, KeyEvent) 在發生按鍵抬起事件時呼叫。
onTrackballEvent(MotionEvent) 發生軌跡球動作事件時呼叫。
onTouchEvent(MotionEvent) 在觸控螢幕動作事件發生時呼叫。
焦距 onFocusChanged(boolean, int, Rect) 當檢視區塊獲得或失去焦點時呼叫。
onWindowFocusChanged(boolean) 當包含檢視畫面的視窗獲得或失去焦點時呼叫。
附加 onAttachedToWindow() 當檢視區塊附加至視窗時呼叫。
onDetachedFromWindow() 當檢視區塊從視窗卸離時呼叫。
onWindowVisibilityChanged(int) 當包含檢視區塊的視窗可見性變更時呼叫。

複合控制項

如果您不想建立完全自訂的元件,而是希望將一組現有控制項納入可重複使用的元件,那麼建立複合元件 (或複合控制項) 可能是最合適的做法。總而言之,這會將一些不可分割的控制項或檢視畫面整合成一個邏輯項目群組,也就是可視為單一項目的項目。舉例來說,組合方塊可以結合單行 EditText 欄位和帶有彈出式清單的相鄰按鈕組合。當使用者輕觸按鈕並從清單中選取內容時,系統就會填入 EditText 欄位,但使用者如果有需要,也可以直接在 EditText 中輸入文字。

在 Android 中,還有兩種檢視畫面可以用來執行此操作:SpinnerAutoCompleteTextView。無論如何,這個組合方塊的概念就是個很好的例子。

如要建立複合元件,請按照下列步驟操作:

  • Activity 一樣,請採用宣告式 (基於 XML) 方法建立內含的元件,或是透過程式輔助方式使用程式碼建立元件。常見的起點是某種類型的 Layout,因此請建立擴充 Layout 的類別。如果是下拉式方塊,您可以使用水平方向的 LinearLayout。您可以在內部建立其他版面配置的巢狀結構,因此複合元件可以任意複雜且結構化。
  • 在新類別的建構函式中,擷取父類別預期使用的任何參數,並先將其傳遞至父類別建構函式。接著,您可以設定要在新元件中使用的其他檢視畫面。您可以在這裡建立 EditText 欄位和彈出式清單。建議您在 XML 中導入自己的屬性和參數,以供建構函式提取及使用。
  • 或者,您也可以為內含的檢視區塊可能產生的事件建立事件監聽器。一個例子,清單項目點擊事件監聽器是一種事件監聽器方法,可在選取清單時更新 EditText 的內容。
  • 您可以選擇使用存取子和修飾符建立自己的屬性。例如,讓元件一開始就設定 EditText 值,並視需要查詢其內容。
  • 視需要覆寫 onDraw()onMeasure()。擴充 Layout 時,通常不必進行這項操作,因為版面配置預設行為應該都能正常運作。
  • 視需要覆寫 onKeyDown() 等其他 on 方法,例如在輕觸特定按鍵時,從下拉式方塊的彈出式清單中選擇特定預設值。

使用 Layout 做為自訂控制項的基礎有很多好處,包括:

  • 您可以使用宣告式 XML 檔案指定版面配置,就像使用活動畫面一樣;也可以用程式輔助方式建立檢視畫面,然後從程式碼中以巢狀方式將其嵌入版面配置中。
  • onDraw()onMeasure() 方法,以及大多數其他 on 方法都有合適的行為,因此您不必覆寫這些方法。
  • 您可以快速建構任意複雜的複合檢視畫面,並將這些檢視畫面當做單一元件重複使用。

修改現有檢視畫面類型

如果有元件與所需元件類似,您可以擴充該元件,並覆寫想變更的行為。您可以利用完全自訂元件執行所有操作,但先在 View 階層中使用更專業的類別,就可以免費獲得某些行為。

舉例來說,NotePad 範例應用程式示範了使用 Android 平台的許多層面。其中之一是擴充 EditText 檢視區塊,以建立內嵌記事。這並不是一個完美的範例,而且執行此操作的 API 可能會改變,但示範了這些原則。

如果您還沒有將 NotePad 範例匯入 Android Studio,或透過提供的連結查看來源,請這麼做。請特別參閱 NoteEditor.java 檔案中 LinedEditText 的定義。

在這個檔案中,需要留意以下事項:

  1. 定義

    該類別的定義如下:
    public static class LinedEditText extends EditText

    LinedEditText 定義為 NoteEditor 活動中的內部類別,但會對外公開,因此可以從 NoteEditor 類別外部以 NoteEditor.LinedEditText 形式存取。

    此外,LinedEditTextstatic,表示它不會產生所謂的「合成方法」,供其存取父項類別的資料。這表示其行為為個別類別,而不是與 NoteEditor 密切相關的類別。如果內部類別不需要從外部類別存取狀態,這是建立內部類別更為簡潔的方法。這會使產生的類別較小,且可輕鬆與其他類別搭配使用。

    LinedEditText 會擴充 EditText,這是在本例中要自訂的檢視畫面。完成後,新類別可以取代一般的 EditText 檢視畫面。

  2. 類別初始化

    如同往常,系統會先呼叫父類別。這不是預設建構函式,但它屬於參數化的建構函式。EditText 從 XML 版面配置檔案加載時,會使用這些參數建立。因此,建構函式必須擷取這些變數,並傳遞至父類別建構函式。

  3. 已覆寫的方法

    這個範例只會覆寫 onDraw() 方法,但您可能需要在建立自訂元件時覆寫其他方法。

    在這個範例中,覆寫 onDraw() 方法可讓您在 EditText 檢視畫面畫布上繪製藍線。畫布會傳遞至覆寫的 onDraw() 方法。系統會在方法結束前呼叫 super.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="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 檢視畫面的參數相同。您也可以新增自己的參數。

建立自訂元件也依需求那麼複雜。

更複雜的元件可以覆寫更多 on 方法,並導入其自己的輔助方法,大幅自訂屬性和行為。唯一的限制是您的想像力,以及元件需要執行的操作。