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),因為這有助於應用程式在各種尺寸的裝置上正確顯示。如需可接受的測量類型,請參閱版面配置資源

版面配置位置

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

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

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

大小、邊框間距和邊界

檢視畫面的大小會以寬度和高度表示。檢視畫面提供兩組寬度值和高度值。

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

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

為了評估維度,檢視畫面會考量到邊框間距。邊框間距會以像素的左側、頂端、右側和底部部分表示 (以像素為單位)。您可以使用邊框間距,以特定像素數偏移檢視畫面的內容。舉例來說,兩個左側邊框間距會將檢視畫面的內容推送到左側邊緣右側兩個像素。您可以使用 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,藉此填入 AdapterViewListViewAdapterView,這樣就能從外部來源擷取資料,並建立代表各個資料項目的 ViewGridView

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 試用版應用程式瞭解版面配置的使用方式。