View 中的版面配置

嘗試 Compose 方法
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中處理版面配置。

版面配置定義了應用程式中使用者介面的結構,例如活動。版面配置中的所有元素都是以 ViewViewGroup 物件的階層建構而成。View 通常會繪製使用者可查看和互動的內容。ViewGroup 是一種隱藏容器,用於定義 View 和其他 ViewGroup 物件的版面配置結構,如圖 1 所示。

圖 1 插圖:定義 UI 版面配置的檢視區塊階層。

View 物件通常稱為「小工具」,可以是許多子類別的其中一個,例如 ButtonTextViewViewGroup 物件通常稱為「版面配置」,可以是提供不同版面配置結構的多種類型之一,例如 LinearLayoutConstraintLayout

您可以透過兩種方式宣告版面配置:

  • 在 XML 中宣告 UI 元素。Android 提供與 View 類別和子類別相對應的簡單 XML 詞彙,例如適用於小工具和版面配置的詞彙。您也可以使用 Android Studio 的版面配置編輯器,透過拖曳介面建立 XML 版面配置。

  • 在執行階段將版面配置元素執行個體化。您的應用程式可以建立 ViewViewGroup 物件,並透過程式輔助方式操控其屬性。

只要在 XML 中宣告 UI,就能將應用程式的呈現方式與控制應用程式行為的程式碼分開。使用 XML 檔案也能更輕鬆地為不同螢幕大小和螢幕方向提供不同的版面配置。詳情請參閱「支援不同的螢幕大小」一文。

Android 架構可讓您靈活運用這兩種方法,或同時使用這兩種方法建構應用程式的 UI。舉例來說,您可以在 XML 中宣告應用程式的預設版面配置,然後在執行階段修改版面配置。

寫入 XML

使用 Android 的 XML 詞彙,您可以快速設計 UI 版面配置和其中所含的畫面元素,就像在 HTML 中利用一系列巢狀元素建立網頁一樣。

每個版面配置檔案都必須包含一個根元素,必須是 ViewViewGroup 物件。定義根元素後,您可以新增其他版面配置物件或小工具做為子項元素,逐步建構定義版面配置的 View 階層。例如,以下 XML 版面配置使用垂直 LinearLayout 以保留 TextViewButton

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

在 XML 中宣告版面配置後,請將檔案儲存為 .xml 副檔名,然後儲存到 Android 專案的 res/layout/ 目錄中,以便正確編譯。

如要進一步瞭解版面配置 XML 檔案的語法,請參閱「版面配置資源」。

載入 XML 資源

編譯應用程式時,每個 XML 版面配置檔案都會編譯為 View 資源。在應用程式的 Activity.onCreate() 回呼實作中載入版面配置資源。方法是呼叫 setContentView(),並以下列格式向其傳送版面配置資源的參照:R.layout.layout_file_name。舉例來說,如果將 XML 版面配置儲存為 main_layout.xml,請為其載入 Activity 版面配置,如下所示:

Kotlin

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Java

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

Activity 啟動時,Android 架構會呼叫 Activity 中的 onCreate() 回呼方法。如要進一步瞭解活動生命週期,請參閱「活動簡介」一文。

屬性

每個 ViewViewGroup 物件都支援自己的各種 XML 屬性。部分屬性專屬於特定 View 物件。舉例來說,TextView 支援 textSize 屬性。不過,所有擴充此類別的 View 物件也會繼承這些屬性。有些 View 物件很常見,因為這些物件是繼承自根 View 類別 (例如 id 屬性)。其他屬性則視為「版面配置參數」,也就是描述 View 物件特定版面配置方向的屬性 (由該物件的父項 ViewGroup 物件定義)。

ID

任何 View 物件都可以有與之相關聯的整數 ID,以識別樹狀結構中的 View。應用程式編譯時,這個 ID 會參照為整數,但 ID 通常是在版面配置 XML 檔案中指派為 id 屬性的字串。這是所有 View 物件通用的 XML 屬性,由 View 類別定義。建議您經常使用XML 標記內 ID 的語法如下:

android:id="@+id/my_button"

字串開頭的 at 符號 (@) 表示 XML 剖析器會剖析及展開其餘 ID 字串,並將其識別為 ID 資源。「加號」符號 (+) 表示這是必須在 R.java 檔案中建立並新增至資源的新資源名稱。

Android 架構提供許多其他 ID 資源。參照 Android 資源 ID 時,您不需要加上 plus 符號,但必須依照下列方式新增 android 套件命名空間:

android:id="@android:id/empty"

android 套件命名空間表示您參照的是來自 android.R 資源類別的 ID,而非本機資源類別。

如要從應用程式建立檢視畫面並參照,可以使用下列常見模式:

  1. 在版面配置檔案中定義檢視畫面,並為其指派專屬 ID,如以下範例所示:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. 建立檢視畫面物件的例項,並從版面配置擷取,通常位於 onCreate() 方法中,如以下範例所示:

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

建立 RelativeLayout 時,請務必定義檢視畫面物件的 ID。在相對版面配置中,同層級檢視畫面可以定義相對於其他同層級檢視畫面的版面配置,而該檢視畫面會由專屬 ID 參照。

ID 在整個樹狀結構中可以重複,但不得在搜尋樹狀結構的一部分內使用。通常是整個樹狀結構,因此建議您盡可能使用獨特的樹狀結構。

版面配置參數

名為 layout_something 的 XML 版面配置屬性為 View 定義了適合其所在的 ViewGroup 的版面配置參數。

每個 ViewGroup 類別都會實作可擴充 ViewGroup.LayoutParams 的巢狀類別。這個子類別包含定義每個子檢視畫面大小和位置的資源類型,適用於檢視區塊群組。如圖 2 所示,父項檢視區塊群組會為每個子項檢視畫面定義版面配置參數,包括子項檢視區塊群組。

圖 2. 以視覺化方式呈現檢視區塊階層,以及與每個檢視區塊相關聯的版面配置參數。

每個 LayoutParams 子類別設定值的專屬語法。每個子項元素都必須定義適用於其父項的 LayoutParams,但也可能為本身的子項定義不同的 LayoutParams

所有檢視區塊群組都會使用 layout_widthlayout_height 加入寬度和高度,而且每個檢視畫面都必須定義這些群組。許多 LayoutParams 都包含選用的邊界和框線。

您可以使用確切的測量值指定寬度和高度,但我們不建議您經常這麼做。您通常會使用以下常數設定寬度或高度:

  • wrap_content:指示檢視畫面將自身調整為內容所需的尺寸。
  • match_parent:指示您的檢視畫面,使其大小與父項檢視區塊群組允許的大小一樣大。

一般而言,我們不建議使用絕對單位 (例如像素) 指定版面配置的寬度和高度。更好的做法是使用相對測量,例如密度獨立像素單位 (dp)、wrap_contentmatch_parent,這樣應用程式就能在多種裝置螢幕大小上正確顯示。您可以在「版面配置資源」中定義可接受的測量類型。

版面配置位置

檢視畫麵包含矩形幾何圖形。它有一個位置 (以「左」和「上方」座標表示),以及兩個維度 (以寬度和高度表示)。位置和尺寸的單位為像素。

您可以叫用 getLeft()getTop() 方法,擷取檢視畫面的位置。前者會傳回代表檢視畫面的矩形左側座標 (x)。後者會傳回代表檢視畫面的矩形頂端座標 (y)。這些方法會傳回檢視區塊相對於其父項的位置。舉例來說,如果 getLeft() 傳回 20,表示檢視畫面位於其直接父項左側邊緣右側 20 像素。

此外,您也可以透過下列便利方法,避免不必要的運算:getRight()getBottom()。這些方法會傳回代表檢視畫面的矩形右側邊緣和底部邊緣的座標。舉例來說,呼叫 getRight() 與以下計算方法類似:getLeft() + getWidth()

大小、邊框間距和邊界

檢視畫面大小會以寬度和高度表示。檢視畫面有兩個寬度和高度值組合。

第一組稱為「測量寬度」和「測量高度」。這些維度可定義檢視畫面父項大小。您可以呼叫 getMeasuredWidth()getMeasuredHeight() 取得測量的維度。

第二個組合稱為「寬度」和「高度」,或有時「繪製寬度」和「繪製高度」。這些維度可定義螢幕上、繪製時及版面配置後的實際檢視畫面大小。這些值可能與測量的寬度和高度不同,但不一定需要。您可以呼叫 getWidth()getHeight() 來取得寬度和高度。

檢視畫面在評估維度時會考量邊框間距。邊框間距會以像素的左側、頂端、右側和底部部分以像素表示。您可以使用邊框間距,將檢視畫面內容偏移特定像素數。舉例來說,如果左側邊框間距為兩,系統會將檢視區塊的內容向左側邊緣推 2 像素。您可以使用 setPadding(int, int, int, int) 方法設定邊框間距,並呼叫 getPaddingLeft()getPaddingTop()getPaddingRight()getPaddingBottom() 進行查詢。

雖然檢視畫面可以定義邊框間距,但不支援邊界。不過,檢視區塊群組支援邊界。詳情請參閱 ViewGroupViewGroup.MarginLayoutParams

如要進一步瞭解維度,請參閱維度一文。

除了透過程式輔助設定邊界和邊框間距外,您也可以在 XML 版面配置中設定邊界,如以下範例所示:

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
      <TextView android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

上例說明套用的邊界和邊框間距。TextView 在整個邊界套用統一邊界和邊框間距,而 Button 會顯示如何獨立套用至不同的邊緣。

常見版面配置

ViewGroup 類別的每個子類別都提供特定方式,方便您查看其中的巢狀結構。最具彈性的版面配置類型,以及最能讓您將版面配置階層保持淺層狀態的工具為 ConstraintLayout

以下是 Android 平台內建的部分常見版面配置類型。

建立線性版面配置

將子項整理成單一水平或垂直列,如果視窗長度超出螢幕長度,則建立捲軸。

建立動態名單

如果版面配置的內容為動態或未預先決定,您可以使用 RecyclerViewAdapterView 的子類別。RecyclerView 一般來說是更好的選項,因為其使用記憶體的效率比 AdapterView 更有效率。

使用 RecyclerViewAdapterView 時可使用的常見版面配置如下:

清單

顯示捲動單一欄清單。

格線

顯示欄和列的捲動格線。

RecyclerView 提供更多可能性,以及建立自訂版面配置管理工具的選項。

在轉接器檢視畫面中填入資料

您可以將 AdapterView 例項繫結至 Adapter,藉此填入 ListViewGridViewAdapterView,這樣就能從外部來源擷取資料,並建立 View 代表每個資料項目。

Android 提供了 Adapter 的多個子類別,這些子類別可用於擷取不同類型的資料,並為 AdapterView 建構檢視畫面。最常見的兩種轉接器如下:

ArrayAdapter
如果資料來源是陣列,請使用這個轉接程式。根據預設,ArrayAdapter 會針對每個項目呼叫 toString(),並將內容放入 TextView 中,藉此為每個陣列項目建立檢視畫面。

舉例來說,如果想在 ListView 中顯示字串陣列,請使用建構函式初始化新的 ArrayAdapter,指定每個字串和字串陣列的版面配置:

Kotlin

    val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
    

Java

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, myStringArray);
    

此建構函式的引數如下:

  • 您的應用程式 Context
  • 包含陣列中每個字串的 TextView 的版面配置。
  • 字串陣列

然後在 ListView 上呼叫 setAdapter()

Kotlin

    val listView: ListView = findViewById(R.id.listview)
    listView.adapter = adapter
    

Java

    ListView listView = (ListView) findViewById(R.id.listview);
    listView.setAdapter(adapter);
    

如要自訂每個項目的外觀,您可以覆寫陣列中物件的 toString() 方法。或者,如要為每個 TextView 以外的項目建立檢視畫面 (例如您希望每個陣列項目都有 ImageView),只要擴充 ArrayAdapter 類別並覆寫 getView(),即可為每個項目傳回所需的檢視畫面類型。

SimpleCursorAdapter
如果資料來自 Cursor,請使用這個轉接程式。使用 SimpleCursorAdapter 時,請指定用於 Cursor 中每個資料列的版面配置,以及您要將 Cursor 中的哪些資料欄插入所需版面配置的檢視畫面中。舉例來說,如要建立使用者名稱和電話號碼的清單,您可以執行查詢來傳回 Cursor,其中包含每個人的列,以及名稱和數字的資料欄。然後建立字串陣列,指定每個結果在 Cursor 的版面配置中的哪些資料欄,以及一個整數陣列,用於指定各欄需要放置的對應檢視畫面:

Kotlin

    val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                              ContactsContract.CommonDataKinds.Phone.NUMBER)
    val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
    

Java

    String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.Phone.NUMBER};
    int[] toViews = {R.id.display_name, R.id.phone_number};
    

SimpleCursorAdapter 執行個體化時,請傳遞每個結果要使用的版面配置、包含結果的 Cursor,以及以下兩個陣列:

Kotlin

    val adapter = SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
    val listView = getListView()
    listView.adapter = adapter
    

Java

    SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
    ListView listView = getListView();
    listView.setAdapter(adapter);
    

接著,SimpleCursorAdapter 會使用提供的版面配置,將每個 fromColumns 項目插入對應的 toViews 檢視畫面,為 Cursor 中的每個資料列建立檢視畫面。

如果您在應用程式的生命週期中,變更轉換器讀取的基礎資料,請呼叫 notifyDataSetChanged()。這會通知附加的檢視畫面,說明資料已變更,並會自行重新整理。

處理點擊事件

藉由實作 AdapterView.OnItemClickListener 介面,您可以回應 AdapterView 中的每個項目的點擊事件。例如:

Kotlin

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click.
}

Java

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click.
    }
};

listView.setOnItemClickListener(messageClickedHandler);

其他資源

瞭解如何在 GitHub 的 Sunflower 示範應用程式中使用版面配置。