打造更優質的使用者體驗

1. 事前準備

如同先前的程式碼研究室所述,Material 是 Google 打造的設計系統,提供相關指南、元件和工具,方便您遵循使用者介面設計的最佳做法。在本程式碼研究室中,您將更新小費計算機應用程式 (出自先前的程式碼研究室),提供更優質的使用者體驗,如下方最終螢幕截圖所示。您也可以在其他情境中測試應用程式,確保使用者享有流暢的使用體驗。

5743ac5ee2493d7.png

必要條件

  • 熟悉常見的使用者介面小工具,例如 TextViewImageViewButtonEditTextRadioButtonRadioGroupSwitch
  • 熟悉 ConstraintLayout 及藉由設定限制條件來定位子項檢視畫面
  • 輕鬆修改 XML 版面配置
  • 瞭解點陣圖圖像與向量可繪項目的差異
  • 能夠設定主題中的主題屬性
  • 可在裝置上開啟深色佈景主題
  • 先前曾修改應用程式的 build.gradle 檔案以取得專案依附元件

課程內容

  • 如何在應用程式中使用 Material Design 元件
  • 如何從 Image Asset Studio 匯入 Material 圖示,以在應用程式中使用 Material 圖示
  • 如何建立及套用新樣式
  • 如何設定顏色以外的佈景主題屬性

建構項目

  • 根據建議的 UI 最佳做法,打造優質的小費計算機應用程式

需求條件

  • 電腦已安裝最新的穩定版 Android Studio
  • 完成先前在此課程及此課程中的程式碼研究室,取得 Tip Time 應用程式的程式碼

2. 範例應用程式總覽

您在先前的程式碼研究室已建構 Tip Time 應用程式,這是一款小費計算機應用程式,且提供可自訂小費的選項。您的應用程式使用者介面看似像如下螢幕截圖。功能可以運作,但看起來更像原型。這些欄位看起來並未完全對齊。就提升樣式和間距的一致性,以及使用 Material Design 元件而言,肯定還有改進空間。

6685eaafba30960a.png

3. Material Design 元件

Material 元件是常見的使用者介面小工具,可協助您輕鬆在應用程式中導入 Material 樣式。這份說明文件說明如何使用及自訂質感設計元件。每個元件都有通用的 Material Design 指南,而 Android 適用的元件則有 Android 平台專屬的指南。如果所選平台沒有某個元件,您可以從已加標籤的圖表取得充足資訊,重新建立該元件。

c4a4db857bb36c3f.png

透過 Material Design 元件,您的應用程式能夠以更一致的方式,與使用者裝置上的其他應用程式搭配運作。如此一來,在某個應用程式中學到的使用者介面操作模式即可沿用到其他應用程式,這樣使用者就能更快瞭解如何使用您的應用程式。因此,建議您盡可能使用 Material 元件,而非使用 Material 小工具。Material 元件更加靈活且可自訂,而且您將在下一個工作中學到這些知識。

您需要將 Material Design 元件 (MDC) 程式庫做為依附元件加入專案中。根據預設,您的專案應該已經包含這一行。請在應用程式的 build.gradle 檔案中,確認最新版程式庫包含這項依附元件。詳情請參閱 Material Design 網站的入門指南頁面。

app/build.gradle

dependencies {
    ...
    implementation 'com.google.android.material:material:<version>'
}

文字欄位

在小費計算機應用程式中,版面配置頂端會顯示目前服務費的 EditText 欄位。此 EditText 欄位可正常運作,但文字欄位的外觀和行為不符合最新質感設計南要求。

如果想使用新元件,請先在 Material Design 網站上瞭解相關資訊。根據文字欄位指南所述,文字欄位分成兩種類型:

實心的文字欄位

29fab63417a5e9ed.png

含外框的文字欄位

3f085f837e146150.png

若要建立上述文字欄位,請使用 MDC 程式庫中內含 TextInputEditTextTextInputLayout。您可以輕鬆自訂 Material Design 文字欄位,執行以下操作:

  • 顯示始終可見的文字或標籤
  • 在文字欄位中顯示圖示
  • 顯示小幫手或錯誤訊息

在本程式碼研究室的第一項工作中,您要將服務費 EditText 替換為 Material Design 文字欄位 (由 TextInputLayoutTextInputEditText 所組成)。

  1. 在 Android Studio 中開啟 Tip Time 應用程式,前往 activity_main.xml 版面配置檔案。其中應包含有小費計算機版面配置的 ConstraintLayout
  2. 如要查看 Material Design 文字欄位的 XML 樣式示例,請返回文字欄位的 Android 指南。您應該會看到類似下列內容的程式碼片段:
<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/textField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>
  1. 看到這個示例後,請插入 Material Design 文字欄位做為 ConstraintLayout 的第一個子項 (在EditText 欄位前)。您將在後續步驟中移除 EditText 欄位。

您可以在 Android Studio 中輸入此內容,並使用自動完成功能來簡化輸入程序。您也可以從說明文件頁面複製範例 XML,然後貼到像這樣的版面配置中。請注意,TextInputLayout 含有子項檢視畫面 TextInputEditText。請注意,刪節號 (...) 表示省略了部分程式碼片段,可讓您專注於實際上有所變更的 XML 行。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    ...>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textField"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/label">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

    </com.google.android.material.textfield.TextInputLayout>

    <EditText
        android:id="@+id/cost_of_service" ... />

    ...

您應該會看到 TextInputLayout 元素有錯誤。您尚未在父項 ConstraintLayout 中適當的限制此檢視畫面。系統也無法辨識字串資源。您會在後續步驟中修正這些錯誤。

344a98d866c7f68c.png

  1. 在文字欄位中加入垂直和水平限制條件,以便正確放置於父項 ConstraintLayout 中。您尚未刪除 EditText,因此請從 EditText 剪下及貼上下列屬性,並加入 TextInputLayout 中:限制、資源 ID cost_of_service160dp 的版面配置寬度、wrap_content 的版面配置高度,以及提示文字 @string/cost_of_service
...

<com.google.android.material.textfield.TextInputLayout
   android:id="@+id/cost_of_service"
   android:layout_width="160dp"
   android:layout_height="wrap_content"
   android:hint="@string/cost_of_service"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent">

   <com.google.android.material.textfield.TextInputEditText
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</com.google.android.material.textfield.TextInputLayout>

...

畫面上可能會顯示錯誤訊息,指出 cost_of_service ID 與 EditText 的資源 ID 相同,但您目前可以忽略這項錯誤 (將用幾個步驟移除 EditText)。

  1. 接著確認 TextInputEditText 元素具有所有適當的屬性。從 EditText 的輸入類型剪下並貼上至 TextInputEditText。將 TextInputEditText 元素資源 ID 變更為 cost_of_service_edit_text
<com.google.android.material.textfield.TextInputLayout ... >

   <com.google.android.material.textfield.TextInputEditText
       android:id="@+id/cost_of_service_edit_text"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:inputType="numberDecimal" />

</com.google.android.material.textfield.TextInputLayout>

保持原本 match_parent 的寬度和 wrap_content 的高度即可。設定 match_parent 的寬度時,TextInputEditText 的寬度會與父項 TextInputLayout 相同,也就是 160dp

  1. 您現在已從 EditText 複製所有相關資訊,請進一步刪除版面配置中的 EditText
  2. 版面配置的「Design」檢視畫面應該會顯示以下預覽內容。「Cost of Service」欄位現在會以 Material Design 文字欄位的形式呈現。

28582b8e4103beeb.png

  1. 您目前無法執行這個應用程式,因為 calculateTip() 方法的 MainActivity.kt 檔案發生錯誤。回顧先前的程式碼研究室,如果您的專案已啟用檢視區塊繫結,Android 會根據資源 ID 名稱在繫結物件中建立屬性。擷取服務費的來源欄位已在 XML 版面配置中有所變更,因此 Kotlin 程式碼也需要隨之更新。

您現在將從資源 ID 為 cost_of_service_edit_textTextInputEditText 元素擷取使用者輸入內容。在 MainActivity 中,使用 binding.costOfServiceEditText 存取儲存於其中的文字字串。calculateTip() 方法的其餘部分則不受影響。

private fun calculateTip() {
    // Get the decimal value from the cost of service text field
    val stringInTextField = binding.costOfServiceEditText.text.toString()
    val cost = stringInTextField.toDoubleOrNull()

    ...
}
  1. 真厲害!接著,執行應用程式並測試是否仍然正常運作。請注意,輸入時,「服務費」標籤顯示在您輸入的內容上方的方式。小費仍會照常計算。

b4a27e58f63417b7.png

切換鈕

Material Design 指南中也提供切換鈕的說明。切換鈕這種小工具可用來開啟或關閉設定。

  1. 請參閱 Android 指南以瞭解 Material 的切換按鈕。您將會瞭解提供切換鈕 Material 樣式的 SwitchMaterial 小工具 (來自 MDC 程式庫)。如果您持續捲動瀏覽指南,就會看到 XML 範例。
  2. 若要使用 SwitchMaterial,您必須在版面配置中明確指定 SwitchMaterial,且使用完整的路徑名稱。

activity_main.xml 版面配置中,將 XML 標記從 Switch 變更為 com.google.android.material.switchmaterial.SwitchMaterial

...

<com.google.android.material.switchmaterial.SwitchMaterial
    android:id="@+id/round_up_switch"
    android:layout_width="0dp"
    android:layout_height="wrap_content" ... />

...
  1. 執行應用程式,確認應用程式是否仍可編譯。應用程式沒有明顯異動。不過,使用 MDC 程式庫中的 SwitchMaterial (而非 Android 平台的 Switch) 的好處便是當更新 SwitchMaterial 程式庫的實作時 (例如質感設計指南變更),您不需要執行變更,就能免費取得更新版小工具。這有助於應用程式永不過時。

現在您已看到兩個例子,瞭解使用現成的 Material Design 元件對使用者介面有何好處,也明白這種做法如何讓應用程式更符合 Material Design 指南。請注意,您隨時可以前往這個網站,探索 Android 提供的其他 Material Design 元件。

4. 圖示

圖示是一種符號,可透過視覺呈現的方式協助使用者瞭解使用者介面的預定功能,而且通常會以使用者預期在實體世界遇到的物體為靈感。圖示設計往往會將詳細資料精細程度降至供使用者熟悉所需的最低程度。舉例來說,現實生活中的鉛筆是用來寫字,因此對應的圖示通常表示建立、新增或編輯項目。

在打開的筆記本上有更尖銳的尖鉛筆。相片來源:Angelina Litvin 發表於 Unsplash 網站上

黑白鉛筆圖示

有時候,圖示會與過時的實體世界遇到物體連結,如同磁碟片圖示一樣。此圖示普遍表示儲存檔案或資料庫記錄;不過,雖然磁碟片在 1970 年代相當普及,但在 2000 年以後並不常見。然而,現今仍持續使用說明強烈的視覺效果如何超越其物理形式的生命週期。

磁碟片平放相片來源:Vincent Botta 發表於 Unsplash 網站上

磁碟片圖示

在應用程式中呈現圖示

就應用程式中的圖示而言,建議您採用向量可繪項目,而不是針對不同螢幕密度提供不同版本的點陣圖圖像。向量可繪項目是以 XML 檔案表示,用於儲存如何建立圖像的操作說明,而非儲存組成該圖像的實際像素。向量可繪項目可以任意縮放,而不會減損圖像品質或增加檔案大小。

提供的圖示

Material Design 提供多種圖示,並依常見類別排列,方便您視需求選擇使用。查看圖示清單

bfdb896506790c69.png

繪製這些圖示時,也可以使用實心、含外框、圓角、雙色和銳利這五種主題,並加上顏色。

實心

含外框

圓角

雙色

銳利

新增圖示

在這項工作中,您要在應用程式中新增三個向量可繪項目圖示:

  1. 服務費文字欄位旁邊的圖示
  2. 服務問題旁邊的圖示
  3. 小費四捨五入提示旁邊的圖示

以下是應用程式最終版本的螢幕截圖。新增圖示後,您將配合這些圖示的位置調整版面配置。請注意,新增圖示會造成欄位和計算按鈕向右移動。

8c4225390dd1fb20.png

新增向量可繪項目素材資源

您可以直接在 Android Studio 的 Asset Studio 中建立這些圖示,做為向量可繪項目。

  1. 開啟應用程式視窗左側的「Resource Manager」分頁
  2. 按一下「+」圖示,然後選取「Vector Asset」。

6dabda0f4bc1f6ed.png

  1. 確認已選取「Asset Type」中標示「Clip Art」的圓形按鈕。

914786d2d8b4025.png

  1. 按一下「Clip Art:」旁的按鈕,選取其他插圖圖像。在隨即顯示的提示中,在顯示的視窗中輸入「"call made」。您將使用這個箭頭圖示來代表「round up tip」選項。請選取該圖示,然後點選「OK」

e7f607e4f576d75c.png

  1. 將圖示重新命名為 ic_round_up。在為圖示檔案命名時,建議使用前置字串 ic_。將「Size」保留為 24 dp x 24 dp,並將「Color」保留為黑色 000000。
  2. 按一下「Next」
  3. 接受預設目錄位置,然後按一下「Finish」

200aed40ee987672.png

  1. 針對另外兩個圖示重複步驟 2 到 7:
  • 服務問題圖示:搜尋「room service」圖示,並將其儲存為 ic_service
  • 服務費圖示:搜尋「store」圖示,並將其儲存為 ic_store
  1. 完成後,「Resource Manager」分頁會顯示以下螢幕截圖所列的畫面。此外,res/drawable 資料夾也會列出三個向量可繪項目,即 ic_round_upic_serviceic_store

c2d8b22f0fb55ce0.png

支援舊版 Android

您剛剛在應用程式中新增了向量可繪項目,但請特別注意,Android 5.0 (API 級別 21) 版本之前的 Android 平台不支援向量可繪項目。

根據專案設定方式,Tip Time 應用程式的最低 SDK 版本為 API 19。這表示該應用程式可以在執行 Android 平台 19 以上版本的 Android 裝置上執行。

如要讓您的應用程式在舊版 Android 裝置上運作 (稱為回溯相容性),請在應用程式的 build.gradle 檔案中新增 vectorDrawables 元素。這樣您就能在 API 21 以下版本的平台上使用向量可繪項目,而不需要在建構專案時轉換成 PNG。詳情請參閱這個頁面

app/build.gradle

android {
  defaultConfig {
    ...
    vectorDrawables.useSupportLibrary = true
   }
   ...
}

專案設定完成後,您就可以開始在版面配置中新增圖示。

插入圖示和位置元素

您將使用 ImageViews 在應用程式中顯示圖示。這是最終顯示 UI 的方式。

5ed07dfeb648bd62.png

  1. 開啟 activity_main.xml 版面配置。
  2. 先找出服務費文字欄位旁邊的商店圖示。在 TextInputLayout 前插入新的 ImageView,做為 ConstraintLayout 的第一個子項。
<androidx.constraintlayout.widget.ConstraintLayout
   ...>

   <ImageView
       android:layout_width=""
       android:layout_height=""

   <com.google.android.material.textfield.TextInputLayout
       android:id="@+id/cost_of_service"
       ...
  1. ImageView 上設定適當的屬性來保留 ic_store 圖示。將 ID 設定為 icon_cost_of_service。將 app:srcCompat 屬性設定為可繪製資源 @drawable/ic_store,而 XML 該行旁邊會顯示圖示的預覽。

此外,由於圖片僅用於裝飾用途,請設定 android:importantForAccessibility="no"

<ImageView
    android:id="@+id/icon_cost_of_service"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:importantForAccessibility="no"
    app:srcCompat="@drawable/ic_store" />

ImageView 應該會出現錯誤,因為檢視區塊尚未受到限制。接下來,您將修復此問題。

  1. 以兩個步驟定位 icon_cost_of_service。首先新增 ImageView 的限制 (此步驟),然後更新旁邊 TextInputLayout 的限制 (步驟 5)。下圖說明設定限制條件的方式。

d982b1b1f0131630.png

ImageView 上,您想要將起始邊緣限制為父項檢視畫面 (app:layout_constraintStart_toStartOf="parent") 的起始邊緣。

比起旁邊的文字欄位,該圖示看起來是垂直置中,因此應將此 ImageView 的頂端 (layout_constraintTop_toTopOf) 限制在文字欄位頂端,並將此 ImageView 的底部 (layout_constraintBottom_toBottomOf) 限制為文字欄位的底部。如要參照文字欄位,請使用資源 ID @id/cost_of_service。預設行為是指將兩個限制條件套用至相同維度 (例如頂端和底部限制條件) 的小工具時,系統會同樣套用這兩個限制條件。這會讓圖示以服務費欄位為基準垂直置中。

<ImageView
    android:id="@+id/icon_cost_of_service"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:importantForAccessibility="no"
    app:srcCompat="@drawable/ic_store"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/cost_of_service"
    app:layout_constraintBottom_toBottomOf="@id/cost_of_service" />

在「Design」檢視畫面中,圖示和文字欄位仍會重疊。這將在下一個步驟中修正。

  1. 新增圖示前,文字欄位定位於父項的開頭,現在,必須移到右側。請更新 cost_of_service 文字欄位與 icon_cost_of_service 相關的限制條件。

bb55ea0cddaa2a12.png

TextInputLayout 的起始邊緣應限制為 ImageView 的結束邊緣 (@id/icon_cost_of_service)。若要在兩個檢視畫面之間加入間距,請在 TextInputLayout 上加入 16dp 的起始邊界。

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/cost_of_service"
    ...
    android:layout_marginStart="16dp"
    app:layout_constraintStart_toEndOf="@id/icon_cost_of_service">

    <com.google.android.material.textfield.TextInputEditText ... />

</com.google.android.material.textfield.TextInputLayout>

完成上述所有變更後,圖示應正確放置在文字欄位旁邊。

23dcae5c3931903f.png

  1. 接著在「How was the service?」旁插入服務鈴鐺圖示 TextView。雖然您可以在 ConstraintLayout 內的任何位置宣告 ImageView,但如果在 XML 版面配置中的 TextInputLayout 後方、service_question TextView 前方插入新 ImageView,XML 版面配置會更清楚易懂。

為新的 ImageView 指派 @+id/icon_service_question 資源 ID。針對 ImageView 和服務問題 TextView 設定適當的限制條件。

38c2dcb4cb18b5a.png

此外,請為 service_question TextView 加上 16dp 上邊界,讓服務問題與上方的服務費欄位之間有更多垂直空間。

...

   <ImageView
        android:id="@+id/icon_service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:importantForAccessibility="no"
        app:srcCompat="@drawable/ic_service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/service_question"
        app:layout_constraintBottom_toBottomOf="@id/service_question" />

    <TextView
        android:id="@+id/service_question"
        ...
        android:layout_marginTop="16dp"
        app:layout_constraintStart_toStartOf="@id/cost_of_service"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>

...
  1. 此時,「Design」檢視畫面應如下圖所示。「Cost of Service」欄位和服務問題 (以及各自的圖示) 看起來很清楚,但圓形按鈕的位置並不適當,而且不會與上方的內容垂直對齊。

578834f5bd3a2d2a.png

  1. 將圓形按鈕向右移至服務問題下方,改善圓形按鈕的位置。這表示更新 RadioGroup 限制條件。將 RadioGroup 的起始邊緣限制為 service_question TextView 的起始邊緣。RadioGroup 上的所有其他屬性都將維持不變。

bf454f3f1617024d.png

...

<RadioGroup
    android:id="@+id/tip_options"
    ...
    app:layout_constraintStart_toStartOf="@id/service_question">

...
  1. 接下來,請將 ic_round_up 的圖示加到「Round up tip?」切換鈕旁的版面配置。請嘗試自行完成此操作,如果遇到困難,可以參考下方的 XML。您可以將新的 ImageView 資源 ID 指派給 icon_round_up
  2. 在版面配置 XML 中的 RadioGroup 後方、SwitchMaterial 小工具前方插入新 ImageView
  3. 指派 icon_round_up 的資源 ID 給 ImageView,然後將 srcCompat 設為圖示 @drawable/ic_round_up 的可繪項目。請將 ImageView 的開頭限制為父項的開頭,並讓圖示以 SwitchMaterial 為基準垂直置中。
  4. 更新 SwitchMaterial 更新,將其放於圖示旁邊,並將起始邊界設為 16dp。針對 icon_round_upround_up_switch 產生的 XML 應如下所示。
...

   <ImageView
        android:id="@+id/icon_round_up"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:importantForAccessibility="no"
        app:srcCompat="@drawable/ic_round_up"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/round_up_switch"
        app:layout_constraintBottom_toBottomOf="@id/round_up_switch" />

    <com.google.android.material.switchmaterial.SwitchMaterial
        android:id="@+id/round_up_switch"
        ...
        android:layout_marginStart="16dp"
        app:layout_constraintStart_toEndOf="@id/icon_round_up" />

...
  1. 「Design」檢視畫面應如下圖所示。這三個圖示均已正確定位。

8781ecbd11859cc4.png

  1. 如果比較此畫面與最終的應用程式螢幕截圖,就會發現計算按鈕也會移動,並垂直對齊服務費欄位、服務問題、圓形按鈕選項和小費四捨五入問題。只要將「Calculate」按鈕的起始位置限制為 round_up_switch 的起始位置,就能達到此效果。此外,在計算按鈕與上方切換鈕之間,也會加入 8dp 垂直邊界。

84348568e13d9e32.png

...

<Button
   android:id="@+id/calculate_button"
   ...
   android:layout_marginTop="8dp"
   app:layout_constraintStart_toStartOf="@id/round_up_switch" />

...
  1. 最後,也務必為 TextView 的上邊界增加 8dp,設定 tip_result 的位置。

8e21f52be710340d.png

...

<TextView
   android:id="@+id/tip_result"
   ...
   android:layout_marginTop="8dp" />

...
  1. 步驟很多!建議您逐步完成這些步驟。需要非常注重細節才能使元素在版面配置中正確對齊,使最終結果看起來更好!執行應用程式,畫面應該看起來像下方的螢幕截圖。使元素垂直對齊並增加元素之間的間距,這些元素就不會擁擠。

1f2ef2c0c9a9bdc7.png

程序尚未完成!您可能已注意到,服務問題及小費金額的字型大小和顏色看起來與圓形按鈕及切換鈕中的文字不同。在下一個工作中,請使用樣式和佈景主題來使這些元素保持一致。

5. 樣式與佈景主題

樣式是單一類型小工具的檢視畫面屬性值集合。舉例來說,TextView 樣式可指定字型顏色、字型大小和背景顏色等。只要將這些屬性擷取為樣式,就能輕鬆將樣式套用至版面配置的多個檢視畫面,並在同一處維護樣式。

在這項工作中,您要先建立文字檢視區塊、圓形按鈕和切換鈕小工具的樣式。

建立樣式

  1. 如果在「res > values」目錄沒有名為 styles.xml 的新檔案,請建立該檔案。在「values」目錄上按一下滑鼠右鍵,然後依序選取「New」>「Values Resource File」,即可建立檔案。呼叫 styles.xml。新檔案包含下列內容。
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
  1. 建立新的 TextView 樣式,使整個應用程式中的文字保持一致。只需在 styles.xml 中定義樣式一次,就能將該樣式套用至版面配置中的所有 TextViews。雖然您可以從頭開始定義樣式,但您可以從 MDC 程式庫中現有的 TextView 樣式延伸。

設定元件樣式時,一般而言,您應從所用小工具類型的父項樣式中延伸。這麼做有兩個非常重要的理由:第一是確認已在元件中設定所有重要的預設值,第二是樣式會持續繼承日後的父項樣式變更。

您可以視需要為樣式命名,但建議採用以下慣例。若是繼承父項 Material 樣式,請將 MaterialComponents 替換成您的應用程式名稱 (TipTime),藉此以類似方式命名樣式。這麼做會將您的變更移到各自的命名空間,進而避免在 Material 元件日後推出新樣式時發生衝突。示例:

您的樣式名稱 Widget.TipTime.TextView 繼承父項樣式 Widget.MaterialComponents.TextView

請此內容加至 styles.xml 檔案中 resources 的開頭標記與結尾標記之間。

<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
</style>
  1. 設定 TextView 樣式,使其會覆寫屬性 android:minHeight,android:gravity,android:textAppearance.

android:minHeight 設定 TextView 上的高度下限為 48dp。根據 Material Design 指南的規定,任一列的最小高度應為 48 dp。

設定 android:gravity 屬性就可將 TextView 中的文字垂直置中。(請參閱下方的螢幕截圖。)重力可控制檢視畫面中內容本身的定位方式。由於實際文字內容的高度不會佔據整個 48dp,所以 center_vertical 值會將 TextView 內的文字垂直置中 (但不變更水平位置)。其他可能的重力值包括 centercenter_horizontaltopbottom。歡迎嘗試其他重心值,瞭解這對文字的影響。

6a7ecc6a49a858e9.png

將文字外觀屬性值設為 ?attr/textAppearanceBody1。TextAppearance 是一組預先製作的樣式,用於文字大小、字型和其他文字屬性。如要查看 Material Design 提供的其他可用文字外觀,請參閱字體排版比例清單

<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
    <item name="android:minHeight">48dp</item>
    <item name="android:gravity">center_vertical</item>
    <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
  1. activity_main.xml 的每個 TextView 上新增樣式屬性,即可將 Widget.TipTime.TextView 樣式套用到 service_question TextView
<TextView
    android:id="@+id/service_question"
    style="@style/Widget.TipTime.TextView"
    ... />

在新增此樣式前,TextView 的字型看起來很小,且字型顏色是灰色:

4d54a3179f0c6f8d.png

新增樣式之後,TextView 外觀如下。現在,TextView 看起來與版面配置的其餘部分更一致。

416d3928f9c3d3de.png

  1. 將相同的 Widget.TipTime.TextView 樣式套用到 tip_result TextView
<TextView
    android:id="@+id/tip_result"
    style="@style/Widget.TipTime.TextView"
    ... />

3ebe16aa8c5bc010.png

  1. 相同的文字樣式應套用至切換按鈕中的文字標籤。不過,您無法將 SwitchMaterial 小工具設定為 TextView 樣式。TextView 樣式只能套用到 TextViews。因此需要建立用於切換按鈕的新樣式。屬性的 minHeightgravitytextAppearance 皆相同。差別在於樣式名稱與父項不同,因為您現在要繼承 MDC 程式庫的 Switch 樣式。樣式名稱也應與父項樣式名稱相同。

您的樣式名稱 Widget.TipTime.CompoundButton.Switch 繼承父項樣式 Widget.MaterialComponents.CompoundButton.Switch.

<style name="Widget.TipTime.CompoundButton.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
   <item name="android:minHeight">48dp</item>
   <item name="android:gravity">center_vertical</item>
   <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>

您也可以在此樣式中指定切換鈕特有的其他屬性,但在應用程式中就不需要這麼做。

  1. 如果想讓文字視覺效果保持一致,圓形按鈕是最後一項需要留意的元素。您無法將 TextView 樣式或 Switch 樣式套用至 RadioButton 小工具。而是,您必須建立用於圓形按鈕的新樣式。您可以從 MDC 程式庫的 RadioButton 樣式延伸。

建立此樣式時,也在圓形按鈕文字與圓形影像內容之間加入邊框間距。paddingStart 是您尚未使用的新屬性。邊框間距是指檢視區塊內容與檢視區塊邊界之間的空間大小。paddingStart 屬性只會在元件的開頭設定邊框間距。以下是圓形按鈕的 paddingStart 分別設為 0 dp 與 8 dp 的差異。

4c1aa37bbdadab1d.png

35a96c994b82539e.png

<style name="Widget.TipTime.CompoundButton.RadioButton"
parent="Widget.MaterialComponents.CompoundButton.RadioButton">
   <item name="android:paddingStart">8dp</item>
   <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
  1. (選用) 建立 dimens.xml 檔案以更方便管理常用值。您可以採用與上述 styles.xml 檔案相同的方式建立檔案。選取「values」目錄、按一下滑鼠右鍵,然後依序選取「New」>「Values Resource File」

在這個小型應用程式中,您重複設定最小高度兩次。目前當然可以管理,但要是有 4 個、6 個、10 個或更多個元件共用相同的值,就會迅速失控。記住逐一變更每個項目的做法不僅繁瑣,而且容易出錯。您可以依序點選「res」>「values」,建立另一個名為 dimens.xml 的實用資源檔案,保留可命名的常用尺寸。將常用值標準化為已命名的尺寸,就能更輕鬆管理應用程式。TipTime 很小,因此我們不會在此選用步驟外使用此應用程式。不過,如果您在正式環境中擁有較複雜的應用程式,且可能會與設計團隊合作,則 dimens.xml 可讓您輕鬆地頻繁變更這些值。

dimens.xml

<resources>
   <dimen name="min_text_height">48dp</dimen>
</resources>

與其直接設定 48dp,您可以改為更新 styles.xml 檔案,使用 @dimen/min_text_height

...
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
    <item name="android:minHeight">@dimen/min_text_height</item>
    <item name="android:gravity">center_vertical</item>
    <item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
...

將這些樣式加入您的佈景主題

您可能已經注意到,您尚未將新的 RadioButtonSwitch 樣式套用至各別的小工具。這是因為您需要使用佈景主題屬性來設定應用程式佈景主題中的 radioButtonStyleswitchStyle。讓我們複習一下何為主題。

佈景主題是一組具名資源 (名為佈景主題屬性),方便之後在樣式、版面配置等中參照。您可以指定整個應用程式、活動或檢視區塊階層的佈景主題,而不只是指定個別 View。您先前在 themes.xml 中設定了 colorPrimarycolorSecondary 等佈景主題屬性,藉此修改應用程式的佈景主題,然後在應用程式和其元件中使用這些佈景主題。

radioButtonStyleswitchStyle 是其他可設定的主題屬性。您為這些佈景主題屬性提供的樣式資源,會套用至每個圓形按鈕以及佈景主題所套用的檢視區塊階層中的每個切換按鈕。

此外,還有適用於 textInputStyle 的主題屬性,當中指定的樣式資源會套用至應用程式中的所有文字輸入欄位。如要讓 TextInputLayout 呈現加上外框的文字欄位外觀 (如 Material Design 指南所述),可以使用 MDC 程式庫中定義為 Widget.MaterialComponents.TextInputLayout.OutlinedBoxOutlinedBox 樣式。這是您將使用的樣式。

2b2a5836a5d9bedf.png

  1. 修改 themes.xml 檔案,讓主題參照所需樣式。設定佈景主題屬性的方式與先前在程式碼研究室中宣告 colorPrimarycolorSecondary 佈景主題屬性的方式相同。不過,相關的佈景主題屬性為 textInputStyleradioButtonStyleswitchStyle。您會使用先前為 RadioButtonSwitch 建立的樣式,以及 Material OutlinedBox 的文字欄位的樣式。

將下列內容複製到 res/values/themes.xml 而複製到應用程式佈景主題的樣式標記。

<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
  1. 您的 res/values/themes.xml 檔案看起來會像這樣。您可以視需要在 XML 中加入註解 (以 <!---> 表示)。
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Base application theme. -->
    <style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        ...
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Text input fields -->
        <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
        <!-- Radio buttons -->
        <item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
        <!-- Switches -->
        <item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
    </style>

</resources>
  1. 請務必對 themes.xml (night) 中的深色佈景主題進行相同變更。您的 res/values-night/themes.xml 檔案看起來會像這樣。
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Application theme for dark theme. -->
    <style name="Theme.TipTime" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        ...
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Text input fields -->
        <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
        <!-- For radio buttons -->
        <item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
        <!-- For switches -->
        <item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
    </style>

</resources>
  1. 執行應用程式並查看變更。文字欄位的 OutlinedBox 樣式看起來更好,所有文字現在看起來都一致!

31ac15991713b031.png 3e861407146c9ed4.png

6. 加強使用者體驗

隨著您即將完成應用程式,除了應該使用預期的工作流程測試應用程式外,也應在其他使用者情境中進行測試。您可能會發現,稍微修改程式碼就能大幅提升使用者體驗。

旋轉裝置

  1. 旋轉裝置,切換為橫向模式。您可能需要先啟用「自動旋轉」設定,這項設定位於裝置的「快速設定選單中。此外,也可以依序前往「設定」>「螢幕」>「進階」,就能找到「自動旋轉螢幕」選項。

f2edb1ae9926d5f1.png

在模擬器中,您可以使用靠近裝置右上角的模擬器選項,將畫面向右或向左旋轉。

2bc08f73d28968cb.png

  1. 您會發現部分使用者介面元件 (包括「Calculate」按鈕) 遭到截斷。這顯然使您無法使用應用程式!

d73499f9c9d2b330.png

  1. 若要解決這個問題,請在 ConstraintLayout 四周加入 ScrollView。您的 XML 看起來有些像這樣。
<ScrollView
   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_height="match_parent"
   android:layout_width="match_parent">

   <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:padding="16dp"
       tools:context=".MainActivity">

       ...
   </ConstraintLayout>

</ScrollView>
  1. 再次執行並測試應用程式。將裝置旋轉為橫向模式時,您應能捲動 UI 來存取計算按鈕,並查看小費計算結果。此修正功能不僅可用於橫向模式,而且也適用於其他不同尺寸的 Android 裝置。現在無論裝置螢幕大小為何,使用者都可以捲動瀏覽版面配置。

按下 Enter 鍵隱藏鍵盤

您可能已注意到,輸入服務費後,鍵盤仍會保持顯示狀態。每次使用「calculate」按鈕都需要手動隱藏鍵盤會有點麻煩。改為按下 Enter 鍵時,讓鍵盤自動隱藏。

e2c3a3dbc40218a2.png

您可以定義文字欄位的鍵事件監聽器,使得可回應於使用者輕觸特定按鍵的事件。鍵盤上每個可能的輸入項目選項都有相關聯的按鍵碼,包括 Enter 鍵。請注意,螢幕小鍵盤 (又稱為螢幕鍵盤) 與實體鍵盤不同。

1c95d7406d3847fe.png

在這項工作中,您要在文字欄位中設定按鍵事件監聽器,用於監聽使用者何時按下 Enter 鍵。偵測到這項事件後,就要隱藏鍵盤。

  1. 複製以下輔助方法,並貼到 MainActivity 類別中。您可以插入在 MainActivity 類別的大括號之前。handleKeyEvent() 是一種私人輔助函式,可以在 keyCode 輸入參數等於 KeyEvent.KEYCODE_ENTER 時隱藏螢幕小鍵盤。InputMethodManager 可控制是否要顯示螢幕鍵盤、隱藏螢幕鍵盤,及讓使用者能夠自行選擇要顯示的螢幕鍵盤。如果系統處理了按鍵事件,此方法會傳回 true,否則會傳回 false。

MainActivity.kt

private fun handleKeyEvent(view: View, keyCode: Int): Boolean {
   if (keyCode == KeyEvent.KEYCODE_ENTER) {
       // Hide the keyboard
       val inputMethodManager =
           getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
       inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
       return true
   }
   return false
}
  1. 現在,請在 TextInputEditText 小工具上附加按鍵事件監聽器。請記住,您可以透過 binding.costOfServiceEditText 等繫結物件存取 TextInputEditText 小工具。

請在 costOfServiceEditText 上呼叫 setOnKeyListener() 方法,然後傳入 OnKeyListener。這個做法類似在應用程式中使用 binding.calculateButton.setOnClickListener { calculateTip() },設定「calculate」按鈕點擊事件監聽器。

在檢視畫面上設定按鍵事件監聽器的程式碼較為複雜,但大致來說,OnKeyListener 具有可在使用者按下按鍵時觸發的 onKey() 方法。onKey() 方法採用 3 個輸入引數:檢視畫面、已按下按鍵的程式碼,及按鍵事件 (您不會用到,因此您可以將其稱作「_」)。呼叫 onKey() 方法時,應呼叫 handleKeyEvent() 方法,並傳遞檢視畫面和按鍵碼引數。編寫此功能的語法為 view, keyCode, _ -> handleKeyEvent(view, keyCode)。其實這種語法稱為 lambda 運算式,您將在後續單元中進一步瞭解 lambda。

請新增程式碼,設定活動 onCreate() 方法內文字欄位的按鍵事件監聽器。這樣一來,就能在建立版面配置後,以及使用者開始與活動互動前,立即附加按鍵事件監聽器。

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
   ...

   setContentView(binding.root)

   binding.calculateButton.setOnClickListener { calculateTip() }

   binding.costOfServiceEditText.setOnKeyListener { view, keyCode, _ -> handleKeyEvent(view, keyCode)
   }
}
  1. 測試新的變更是否正常運作。執行應用程式並輸入服務費。按下鍵盤上的 Enter 鍵就會隱藏螢幕鍵盤。

在啟用 TalkBack 的情況下測試應用程式

完成本課程後,您會想要建構可供眾多使用者存取的應用程式。部分使用者可能會使用 Talkback 存取及瀏覽您的應用程式。TalkBack 是 Android 裝置隨附的 Google 螢幕閱讀器。透過 TalkBack 的互動朗讀功能,不看螢幕也能輕鬆使用裝置。

啟用 TalkBack 後,請確保使用者能夠在應用程式中完成計算小費的用途。

  1. 按照這篇文章的說明,在裝置上啟用 TalkBack。
  2. 返回 Tip Time 應用程式。
  3. 按照這篇文章的說明,使用 TalkBack 探索應用程式。向右滑動即可逐一瀏覽螢幕元素,向左滑動後即可往反方向瀏覽。在任意位置輕觸兩下即可選取。確認您可以透過滑動手勢找到應用程式中的所有元素。
  4. 確認 Talkback 使用者可以導覽至畫面上每個項目、輸入服務費、變更小費選項、計算小費,並聽到小費計算結果。請注意,系統不會為圖示提供互動朗讀內容,因為您已將圖示標記為 importantForAccessibility="no"

如要進一步瞭解如何打造更符合無障礙設計的應用程式,請參閱這些原則和這堂學習課程

(選用) 調整向量可繪項目的色調

在此選用工作中,您將依據主題的主要顏色為圖示上色,讓淺色主題和深色主題中的圖示看起來不同 (如下所示)。這項變更是為了美化 UI,使圖示更貼合應用程式主題。

77092f702beb1cfb.png 80a390087905eb29.png

如先前所述,與點陣圖圖像相比,VectorDrawables 的優點之一就是能夠縮放圖像及上色。下方的 XML 代表鈴鐺圖示。請留意 android:tintandroid:fillColor 這兩種顏色屬性。

ic_service.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
   android:width="24dp"
   android:height="24dp"
   android:viewportWidth="24"
   android:viewportHeight="24"
   android:tint="?attr/colorControlNormal">
 <path
     android:fillColor="@android:color/white"
     android:pathData="M2,17h20v2L2,19zM13.84,7.79c0.1,-0.24 0.16,-0.51 0.16,-0.79 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,0.28 0.06,0.55 0.16,0.79C6.25,8.6 3.27,11.93 3,16h18c-0.27,-4.07 -3.25,-7.4 -7.16,-8.21z"/>
</vector>

aab70a5d4eaabdc7.png

如果有色調,則會覆寫可繪項目的任何 fillColor 指令。在這種情況下,系統會用 colorControlNormal 佈景主題屬性覆寫白色顏色。colorControlNormal 是小工具的「一般」(未選取/未啟用狀態) 顏色。目前是灰色。

我們針對應用程式執行的一項視覺強化操作,就是根據應用程式佈景主題的主要顏色為可繪項目調色。在淺色主題中,圖示會顯示為 @color/green;在深色主題中,圖示則會顯示為 @color/green_light,也就是 ?attr/colorPrimary。根據應用程式主題的主要顏色為可繪項目上色,可以讓版面配置中的元素看起來更融合一致。這樣也不必複製淺色主題和深色主題的各組圖示。只要準備 1 組向量可繪項目就好,而且色調會根據 colorPrimary 主題屬性而改變。

  1. 變更 ic_service.xmlandroid:tint 屬性的值
android:tint="?attr/colorPrimary"

在 Android Studio 中,圖示現在有適當的色調。

f0b8f59dbf00a20b.png

colorPrimary 主題屬性指向的值會因淺色主題和深色主題而有所不同。

  1. 重複上述步驟,變更其他向量可繪項目的色調。

ic_store.xml

<vector ...
   android:tint="?attr/colorPrimary">
   ...
</vector>

ic_round_up.xml

<vector ...
   android:tint="?attr/colorPrimary">
   ...
</vector>
  1. 執行應用程式,確認圖示在淺色和深色主題中的顯示方式不同。
  2. 作為最後的清理步驟,請記得重新設定應用程式中所有 XML 和 Kotlin 程式碼檔案的格式。

恭喜,您已完成小費計算機應用程式!您應該對於自己建構的作品感到自豪。希望本研究室能協助您打造更精美、功能更齊全的應用程式!

7. 解決方案程式碼

本程式碼研究室的解決方案程式碼位於下列 GitHub 存放區中。

5743ac5ee2493d7.png ab4acfeed8390465.png

如要取得這個程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:

取得程式碼

  1. 按一下上面顯示的網址。系統會在瀏覽器中開啟專案的 GitHub 頁面。
  2. 檢查並確認分支版本名稱與程式碼研究室中指定的版本相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

8cf29fa81a862adb.png

  1. 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。

1debcf330fd04c7b.png

  1. 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
  2. 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
  3. 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open」

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。

8d1fda7396afe8e5.png

  1. 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 8de56cba7583251f.png,即可建構並執行應用程式。請確認應用程式的建構作業符合預期。

8. 摘要

  • 盡可能使用 Material Design 元件,以便遵循 Material Design 指南,並進一步充分運用自訂功能。
  • 新增圖示以提供視覺化提示,讓使用者瞭解應用程式中各個部分的運作方式。
  • 使用 ConstraintLayout 將元素放在版面配置中。
  • 針對極端情況測試應用程式 (例如在橫向模式下旋轉應用程式畫面),並視需要做出改進。
  • 請撰寫程式碼的註解,協助閱讀程式碼的其他人瞭解您的做法。
  • 重新設定程式碼格式,並清理程式碼,盡可能力求精簡。

9. 瞭解詳情

10. 自行練習

  • 接續先前的程式碼研究室,使用您在此處學到的最佳做法 (例如使用質感設計元件) 來更新烹飪單位轉換器應用程式,確保應用程式更加符合 Material 指南要求。