Tina Tang's Blog

在哪裡跌倒了,就在哪裡躺下來

0%

Android筆記(10)-建立適用於Android的XML版面配置

瞭解新版的 UI 元件,例如可編輯的文字欄位、圓形按鈕,以及為小費計算器應用程式建立版面配置的切換按鈕。你將會使用 XML,而不使用 Android Studio 中的版面配置編輯器,來編輯應用程式的版面配置。

學習目標

  • 如何在 Android 中讀取及寫入 XML 版面配置
  • 如何建構簡易表單,用於使用者文字輸入和選項

啟動專案

使用 Android 應用程式建立簡易的計算機簡易版。
完成後小費計算機應用程式會如下所示:

您需要使用 Android 提供的下列 UI 元素:

  • EditText - 用於輸入及傳送訊息
  • TextView - 顯示服務條款和小費金額等文字
  • RadioButton:每個小費選項的可選取圓形按鈕
  • RadioGroup - 將圓形按鈕選項分組
  • Switch - 開啟/關閉切換按鈕,選擇是否將小費四捨五入
建立一個空白活動專案
  1. 首先,在 Android Studio 中使用 空白活動 範本來建立新品的 Kotlin 專案。
  2. 呼叫應用程式「Tip Time」,最低 API 級別為 19 (KitKat)。套件名稱為 com.example.tiptime。

閱讀及瞭解 XML

如果不想使用自己熟悉的版面配置編輯器,您可以修改描述使用者介面的 XML 來建構應用程式的版面配置。瞭解使用 XML 瞭解及修改 UI 版面配置對 Android 開發人員的重要性。

您將會查看並編輯 XML 檔案,為這個應用程式定義 UI 版面配置。XML 是可延伸標記語言的一種,這是一種利用文字文件來描述資料的方式。由於 XML 是可擴充且極具彈性的功能,所以有許多種不同的用途,包括定義 Android 應用程式的 UI 版面配置。您或許可以在先前的程式碼研究室中回想起,「strings.xml」這個 XML 檔案中的其他資源也定義了其他資源。

Android 應用程式的使用者介面是由元件(小工具)的元件階層組成,以及這些元件的螢幕版面配置。請注意,這些版面配置本身是 UI 元件。

您必須說明畫面上的 UI 元素檢視畫面階層。舉例來說,ConstraintLayout (父項) 可包含 ButtonsTextViewsImageViews 或其他檢視畫面 (子項)。請注意,ConstraintLayoutViewGroup 的子類別。可讓您靈活設定子項的位置或調整大小。

每個 UI 元素都會在 XML 檔案中以 XML 元素出現。每個元素的開頭和結尾都是代碼,而每個標記的開頭都是 <,結尾則是 >。就像是您可以使用版面配置編輯器 (設計) 設定 UI 元素的屬性,XML 元素也可以具有「屬性」。簡單來說,上述 UI 元素的 XML 可能如下所示:

1
2
3
4
5
<ConstraintLayout>
<TextView
text="Hello World!">
</TextView>
</ConstraintLayout>

以下是一個實際範例。

  1. 開啟 activity_main.xml (「app」>「res」>「layout」>「activity_main.xml」)
  2. 您可能會注意到應用程式顯示包含「Hello World!」的 TextView。如 ConstraintLayout 中所述,您在使用這個範本建立的先前的專案中看過。
  3. 在版面配置編輯器的右上方,找到「Code」、「Split」和「Design」檢視畫面的選項。
  4. 選取「Code」檢視。

activity_main.xml 中的 XML 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. 請注意縮排。Android Studio 會自動執行上述作業,顯示元素階層。TextView 已縮排,因為 ConstraintLayout 裡包含這個項目。ConstraintLayout 是父項,TextView 是子節點。每個元素的屬性會以縮排的方式顯示,表示該元素屬於該元素的一部分。
  2. 請注意色彩編碼:檔案的類似部分會繪製成同一顏色,方便您比對。請特別注意,Android Studio 會繪製用相同顏色元素的開始和結尾。
XML 標記、元素和屬性

以下是 TextView 元素的簡化版本,方便您查看部分重要部分:

1
2
3
<TextView
android:text="Hello World!"
/>

包含 <TextView 的行則是標記的開頭,而 /> 行則是標記結尾。標有 android:text="Hello World!" 的行是標記的屬性。這代表 TextView 將要顯示的文字。這 3 行是常用的元素 (又稱為空白元素代碼),也就是說,如果您利用獨立的「start-tag」和「end-tag」編寫程式碼,例如:

1
2
3
<TextView
android:text="Hello World!"
></TextView>

另一個常見的例子是空白元素標記,編寫時盡可能減少這類標記的行數,然後將代碼的結尾與行前的行結合。因此,您可能會在兩行中看到空白元素標記 (沒有屬性的話,甚至只有一行):

1
2
3
<!-- with attributes, two lines -->
<TextView
android:text="Hello World!" />

由於 ConstraintLayout 元素是以獨立開始和結束標記寫成,因此必須含有其他元素。以下是含有 TextView 元素的 ConstraintLayout 元素簡化版本:

1
2
3
4
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:text="Hello World!" />
</androidx.constraintlayout.widget.ConstraintLayout>

如要將另一個 View 新增為 ConstraintLayout 的子項 (例如 TextView 下的 Button),其會在 TextView 標記 /> 的結尾,並且在 ConstraintLayout 的結尾標記之前,如下所示:

1
2
3
4
5
6
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:text="Hello World!" />
<Button
android:text="Calculate" />
</androidx.constraintlayout.widget.ConstraintLayout>
進一步瞭解版面配置的 XML
  1. 查看 ConstraintLayout 的標記,您會發現這顯示為 androidx.constraintlayout.widget.ConstraintLayout,而非和 TextView 一樣只有 ConstraintLayout。這是因為 ConstraintLayoutAndroid Jetpack 的一部分,而其中的程式碼程式庫在 Android 核心平台之外提供了額外功能。Jetpack 提供了一些實用功能,可協助您輕鬆建構應用程式。您會發現此 UI 元件因為是以「androidx」開頭,所以屬於 Jetpack 的一部分。

  2. 您可能會發現開頭為 xmlns: 的行數,後接 androidapptools

1
2
3
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"

xmlns 代表 XML 命名空間,而每一行分別定義一個「結構定義」或相關詞彙。舉例來說,android: 命名空間會標示 Android 系統定義的屬性。版面配置 XML 中的所有屬性都是從其中一個命名空間開始執行。

  1. XML 元素之間的空白字元不會對電腦產生意義,但可讓 XML 更易於閱讀。

  2. 您可以對 XML 新增註解,就跟使用 Kotlin 程式碼一樣。開頭是 <!--,結尾是-->

1
2
3
4
5
6
7
<!-- this is a comment in XML -->

<!-- this is a
multi-line
Comment.
And another
Multi-line comment -->
  1. 請注意檔案的第一行:
1
<?xml version="1.0" encoding="utf-8"?>

用於表示檔案是 XML 檔案,但並非所有 XML 檔案都會包含這類檔案。


在 XML 中建立版面配置

  1. activity_main.xml 中,請切換至「Split」 畫面,即可查看 設計編輯器 旁的 XML。「設計編輯器」可讓您預覽 UI 版面配置。
  1. 請嘗試點選其他行 (ConstraintLayout 下和 TextView下一行),然後可注意到會選取到「Design Editor」中對應的畫面。反之亦然。舉例來說,如果您在「Design Editor」中按一下 TextView,系統就會醒目顯示對應的 XML。
刪除 TextView
  1. 您現在不需要 TextView,請將其刪除。請務必從 <TextView/> 結尾全部刪除。

  2. ConstraintLayout 中加入 16dp 邊框間距,避免 UI 與螢幕邊緣過度擁擠。

1
2
3
4
5
6
<androidx.constraintlayout.widget.ConstraintLayout
...
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
新增服務費用文字欄位

在這個步驟中,您必須新增 UI 元素,才能在應用程式中輸入服務費用。您必須使用 EditText 元素,讓使用者在應用程式中輸入或修改文字。

目前的版面配置檔案應該會如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">

<EditText
android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:inputType="text"/>

</androidx.constraintlayout.widget.ConstraintLayout>
  1. 請注意,EditText 會加上紅色底線。

  2. 將滑鼠游標移到指標上,系統就會顯示「檢視畫面不受限」的錯誤訊息,您對在舊版程式碼研究室中的這類程式碼應該十分熟悉。先前提過,ConstraintLayout 的子項設有限制,因此版面配置會熟悉該如何排列。

  1. 將這些限制新增至 EditText 可固定在父項的左上角。
1
2
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

加入新限制條件後,EditText 元素會如下所示:

1
2
3
4
5
6
7
<EditText
android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:inputType="text"/>
查看編輯文字屬性

請仔細檢查您貼上的所有 EditText 屬性,確保它們與您應用程式中的方式搭配運作。

  1. 找出設為 @+id/plain_text_inputid 屬性。
  2. id 屬性變更為較為適當的名稱 @+id/cost_of_service

注意: 資源 ID 是元素專屬的資源名稱,當您透過「Layout Editor」新增 View 或其他資源時,Android Studio 會自動為資源指派資源 ID。手動輸入 XML 時,您必須手動宣告資源 ID。XML 檔案中的新資料檢視 ID 必須以 @+id 前置字元來定義,這樣 Android Studio 才能將該 ID 新增為新的資源 ID。

為資源取個清楚易懂的名稱,以便您知道資源的參照對象,但全都應以小寫字母輸入,並以底線分隔字詞。

如要參照應用程式程式碼中的資源 ID,請使用 R..;例如 R.string.roll。如為 View ID,<type> 則為 id,例如 R.id.button

  1. 查看 layout_height 屬性。已設為 wrap_content,表示高度會和當中的內容高度相同。沒關係,因為只有 1 行文字。

  2. 查看 layout_width 屬性。已設為 match_parent,但你無法在 ConstraintLayout 的子項中設定 match_parent。此外,文字欄位不需要那麼寬。設為 160dp 的固定寬度,但應預留足夠空間,讓使用者輸入服務費用。

  1. 找到新的 inputType 屬性,這是一項新功能。屬性值為 "text",表示使用者可在畫面上的欄位中輸入任何文字字元 (英數字元、符號等)。
1
android:inputType="text"

不過,假如您只想在 EditText 中輸入數字,因為該欄位代表貨幣金額。

  1. 清除 text 字詞,但保留引號。
  2. 輸入 number 的位置。輸入「n」後,Android Studio 會顯示包含「n」的可能完成項目清單。
  1. 選擇 numberDecimal,這個類型可限制只顯示含小數點的數字。
1
android:inputType="numberDecimal"

如要查看輸入類型的其他選項,請參閱開發人員說明文件中的 指定輸入法類型

  1. EditText 中加入 hint 屬性,藉此說明使用者應在欄位中輸入的內容。
1
android:hint="Cost of Service"

您也會看到「Design Editor」將其更新。

  1. 在模擬器中執行應用程式。看起來應該像這樣:
新增服務問題

在這個步驟中,您必須新增 TextView 問題說明:”How was the service?”請嘗試輸入,不要複製/貼上。你可以透過 Android Studio 提供建議的做法。

  1. 關閉 EditText 標記 /> 後,新增一行然後開始輸入 <TextView
  2. 從建議中選取 TextView,Android Studio 就會自動為 TextView 新增 layout_widthlayout_height 屬性。
  3. 請為這兩個屬性選擇 wrap_content,因為 TextView 只需要與當中的文字內容一樣大。
  4. 新增含有 "How was the service?"text 屬性
  5. 使用 /> 關閉標記。
  6. 請注意,在「Design Editor」中 TextView 會與 EditText 重疊。

垂直部分 TextView 應低於服務費用文字欄位。水平部分要讓 TextView 對齊父項的邊緣。

  1. TextView 加上水平限制,限制它的起點到父項的起點。
1
app:layout_constraintStart_toStartOf="parent"
  1. TextView 中加入垂直限制,將 TextView 的上緣限制在服務費用 View 的底部邊緣。
1
app:layout_constraintTop_toBottomOf="@id/cost_of_service"
  1. TextView 新增資源 ID。您稍後必須參考這個資料檢視,因為我們建議您新增更多檢視點,並將這些項目互相衝突。
1
android:id="@+id/service_question"

新增提示選項

接下來,您需要為各個使用者選項提供圓形按鈕(RadioButton)選項。

共有三個選項:

  • 極佳 (20%)
  • 不錯 (18%)
  • 一般 (15%)
  1. 瀏覽圓形按鈕(RadioButton)指南,瞭解 RadioGroup 是父項,以及 RadioButtons 是子項。

在建立每個圓形按鈕選項時,請在版面配置中建立 RadioButton。 不過,由於圓形按鈕彼此互斥,因此必須在 RadioGroup 內將這些按鈕設成一個群組。把它們設成一個群組,系統即可確保一次只能選取一個圓形按鈕。

  1. 返回 Android Studio 中的版面配置,將 RadioGroupRadioButton 新增至應用程式。
  2. TextView 元素之後,仍顯示在 ConstraintLayout 中,開始輸入 <RadioGroup
  1. RadioGrouplayout_widthlayout_height 設為 wrap_content
  2. 新增設為 @+id/tip_options 的資源 ID。
  3. 使用 > 關閉起始標記。
  4. Android Studio 會加上 </RadioGroup>。和 ConstraintLayout 一樣,RadioGroup 元素內部也會包含其他元素,因此建議您將元素移到單獨的線條。
  1. RadioGroup 限制在服務問題下方 (垂直),並限制父項的開頭 (水平)。
  2. android:orientation 屬性設為 vertical。如要指定某列的 RadioButtons,可以將方向設為 horizontal
新增圓形按鈕
  1. RadioGroup 的最後一個屬性後方,在 </RadioGroup> 結束標記之前加上 RadioButton
1
2
3
4
5
6
7
8
9
10
11
<RadioGroup
android:id="@+id/tip_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/service_question">

<!-- add RadioButtons here -->

</RadioGroup>
  1. layout_widthlayout_height 設為 wrap_content
  2. @+id/option_twenty_percent 的資源 ID 指派給 RadioButton
  3. 將文字設為 Amazing (20%)
  4. 使用 /> 關閉標記。

接著為 Good (18%)Okay (15%) 選項新增 2 個圓形按鈕(RadioButton)。

以下是新增完成的XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<RadioGroup
android:id="@+id/tip_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/service_question"
app:layout_constraintStart_toStartOf="parent"
android:orientation="vertical">

<RadioButton
android:id="@+id/option_twenty_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amazing (20%)" />

<RadioButton
android:id="@+id/option_eighteen_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Good (18%)" />

<RadioButton
android:id="@+id/option_fifteen_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Okay (15%)" />

</RadioGroup>
新增預設選項

目前未選取任何提示選項。系統預設會選取其中一個圓形按鈕選項。
RadioGroup 上有一個屬性可讓您指定要先檢查哪一個按鈕。名稱是 checkedButton,並設為所選圓形按鈕的按鈕資源。

  1. RadioGroup 上,將 android:checkedButton 屬性設為 @id/option_twenty_percent
1
2
3
4
<RadioGroup
android:id="@+id/tip_options"
android:checkedButton="@id/option_twenty_percent"
...

根據預設,系統會選取 20% 的小費選項!現在這開始看起來像小費計算機了!


完成版面配置的其餘部分

現在已進入版面配置的最後一個部分。請新增 SwitchButtonTextView 以顯示小費金額。

新增切換鈕,讓小費四捨五入

接下來,您將使用 Switch 小工具,讓使用者選取「Yes」或「No」是否要將小費四捨五入。

您想要 Switch 的寬度和父項一樣,因此您可能會認為寬度應該設為 match_parent。如前文所述,您無法在 ConstraintLayout 的 UI 元素上設定 match_parent。相反地,您需要限制檢視畫面的開始和結束時間,並將寬度設為 0dp。將寬度設為 0dp 可讓系統不要計算寬度,只要嘗試與檢視畫面中的限制條件相符即可。

注意:您無法在 match_parent 中使用 ConstraintLayout 的任何資料檢視。而是使用 0dp,也就是相符條件。

  1. RadioGroup 的 XML 後方加入 Switch 元素。
  2. 如上所述,將 layout_width 設為 0dp
  3. layout_height 設為 wrap_content 這會將 Switch 觀看內容與內部內容高度一樣。
  4. id 屬性設為 @+id/round_up_switch
  5. text 屬性設為 Round up tip?。這會做為 Switch 的標籤使用。
  6. Switch 的起始點限制為 tip_options 的起始邊緣,並將結尾點限制為父項的終點邊緣。
  7. Switch 的頂端限制在 tip_options 的底部。
  8. 使用 /> 關閉標記。

如果切換按鈕預設為開啟,且有 android:checked 的屬性,則可能的值為 true (開啟) 或 false (關閉)。

  1. android:checked 屬性設為 true
1
2
3
4
5
6
7
8
9
<Switch
android:id="@+id/round_up_switch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="true"
android:text="Round up tip?"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tip_options"
app:layout_constraintTop_toBottomOf="@id/tip_options" />

注意:如果您將滑鼠遊標懸停在 XML 版面配置中的 Switch 元素上,Android Studio 會顯示以下建議:「使用 AppCompatSwitchCompat,或 Material 資料庫的 SwitchMaterial」。稍後您將在建構小費計算機應用程式的程式碼研究室中實作此建議,因此您可以忽略這則警告。

新增計算按鈕

接著,您必須新增 Button 來向使用者說明小費的計算方式。您想將按鈕設為與上層最寬,因此水平限制和寬度與 Switch 相同。

  1. Switch 後方加上 Button
  2. 請將寬度設為 0dp,方法與 Switch 相同。
  3. 將高度設為 wrap_content
  4. 為資源 ID @+id/calculate_button 輸入文字,格式為「"Calculate"」
  5. Button 的上方邊緣固定到Switch的底部邊緣
  6. 將起始邊緣限制為上層的起點,以及將結尾邊緣設為父項的終點邊緣。
  7. 使用 /> 關閉標記。
1
2
3
4
5
6
7
8
<Button
android:id="@+id/calculate_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Calculate"
app:layout_constraintTop_toBottomOf="@id/round_up_switch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
新增小費計算結果

就快完成了版面配置!在這個步驟中,您會為小費的結果加上 TextView、將其放在「Calculate」按鈕下方,並且與結尾處對齊,而非與其他 UI 元素一樣放在開始處。

  1. 新增具有 ID 為 tip_result 且 ID 為 Tip AmountTextView
  2. TextView 的結尾邊緣限制在父項的終點邊緣。
  3. 限制「Calculate」按鈕的上方與下方的邊緣。
1
2
3
4
5
6
7
<TextView
android:id="@+id/tip_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/calculate_button"
android:text="Tip Amount" />
  1. 執行應用程式。看起來應該要像這個螢幕截圖一樣。

實際執行畫面:


採用完善程式設計做法

擷取字串
  1. 按一下字串;將滑鼠游標懸停在顯示的黃色燈泡圖示上,然後按一下旁邊的三角形圖示;選擇「Extract String Resource」。字串資源的預設名稱沒有問題。您可以視需要根據提示選擇使用 amazing_servicegood_serviceok_service,讓名稱更加描述性。

  2. 如果畫面上未顯示「Project」視窗,請按一下視窗左側的「Project」分頁標籤。

  3. 開啟 app > res > values > string.xml 查看所有 UI 字串資源。

1
2
3
4
5
6
7
8
9
10
11
<resources>
<string name="app_name">Tip Time</string>
<string name="cost_of_service">Cost of Service</string>
<string name="how_was_the_service">How was the service?</string>
<string name="amazing_service">Amazing (20%)</string>
<string name="good_service">Good (18%)</string>
<string name="ok_service">Okay (15%)</string>
<string name="round_up_tip">Round up tip?</string>
<string name="calculate">Calculate</string>
<string name="tip_amount">Tip Amount</string>
</resources>
重新格式化 XML
  1. activity_main.xml 中,選擇「Edit」>「Select All」。
  2. 依序選擇「Code」>「Reformat Code」。

以確保縮排一致,且可能會將 UI 元素的部分 XML 重新排序,例如將單一元素的所有 android: 屬性放在一起。


總結

  • XML (擴充標記語言) 可用來管理文字,由標記、元素和屬性構成。
  • 使用 XML 定義 Android 應用程式的版面配置。
  • 使用 EditText 可讓使用者輸入內容或編輯文字。
  • EditText 可提示用戶指定該欄位的內容。
  • 指定 android:inputType 屬性,以限制用戶可輸入 EditText 欄位的文字類型。
  • 列出包含 RadioButtons 的獨家選項清單 (使用 RadioGroup 分組)。
  • RadioGroup 可以是垂直或水平,而您也可以指定要最初選取的 RadioButton
  • 使用 Switch 可讓用戶在兩個選項之間切換。
  • 您不必使用個別的 TextView,就可以將標籤新增至 Switch
  • ConstraintLayout 中的每個子項都必須包含垂直和水平限制條件。
  • 使用「start」和「end」限制條件來處理由左至右 (LTR) 和右至左 (RTL) 的語言。
  • 限制屬性名稱會依照 layout_constraint<Source>_to<Target>Of 的格式提供。
  • 如要讓 View 盡可能適用於目標 ConstraintLayout,請將開頭和結尾限制為父項的開頭和結尾,然後將寬度設為 0dp