建立自訂檢視畫面元件

嘗試 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 時通常不需要這麼做,因為版面配置具有可能正常運作的預設行為。
  • 視需要覆寫其他 on 方法,例如 onKeyDown(),例如:輕觸特定鍵時,從下拉式方塊的彈出式清單中選擇特定預設值。

使用 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. 類別初始化

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

  3. 覆寫的方法

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

    在本範例中,覆寫 onDraw() 方法後,即可在 EditText 檢視畫布上繪製藍線。畫布會傳遞至覆寫的 onDraw() 方法。系統會在方法結束之前呼叫 super.onDraw() 方法。必須叫用父類別方法。在這種情況下,請在繪製要納入的線條後,在最後叫用。

  4. 自訂元件

    既然您已經擁有自訂元件,該怎麼運用呢?在記事本範例中,會直接透過宣告式版面配置使用自訂元件,因此請查看 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 方法,並引入其專屬的輔助方法,大幅自訂其屬性和行為。唯一的限制,就是您的想像力 以及元件需要執行的操作