MDC-101 Android:Material Design 元件 (MDC) 基本概念 (Java)

logo_components_color_2x_web_96dp.png

開發人員可透過 Material 元件 (MDC) 實作 Material Design。MDC 由 Google 的工程師和 UX 設計師團隊建立,提供數十種美觀實用的 UI 元件,適用於 Android、iOS、網頁和 Flutter。

material.io/develop

什麼是 Material Design 和 Android 適用的 Material 元件?

Material Design 是一套系統,可協助您打造大膽且美觀的數位產品。只要在一致的原則和元件下整合樣式、品牌、互動和動態效果,產品團隊就能發揮最大的設計潛力。

對於 Android 應用程式,Android 適用的 Material 元件(MDC Android) 結合了設計和工程,提供元件程式庫,讓您在應用程式中建立一致性。隨著 Material Design 系統不斷演進,這些元件也會隨之更新,確保實作程序在像素層面保持一致,並符合 Google 的前端開發標準。MDC 也適用於網頁、iOS 和 Flutter。

在本程式碼研究室中,您將使用多個 MDC Android 元件建構登入頁面。

建構項目

本程式碼研究室是 4 個程式碼研究室中的第一個,將引導您建構名為「Shrine」的應用程式,這是一個銷售服飾和居家用品的電子商務 Android 應用程式。並說明如何使用 MDC-Android 自訂元件,反映任何品牌或風格。

在本程式碼研究室中,您將建構 Shrine 的登入頁面,其中包含:

  • 兩個文字欄位,一個用於輸入使用者名稱,另一個用於輸入密碼
  • 兩個按鈕,分別是「取消」和「下一步」
  • 應用程式名稱 (Shrine)
  • Shrine 標誌的圖片

本程式碼研究室中的 MDC Android 元件

  • 文字欄位
  • 按鈕

軟硬體需求

  • 具備 Android 開發的基本知識
  • Android Studio (如果沒有,請在這裡下載)
  • Android 模擬器或裝置 (可透過 Android Studio 取得)
  • 範例程式碼 (請參閱下一個步驟)

您對建構 Android 應用程式的經驗程度為何?

新手 中級 熟練

啟動 Android Studio

開啟 Android Studio 時,應該會顯示名為「Welcome to Android Studio」的視窗。不過,如果您是第一次啟動 Android Studio,請使用預設值完成 Android Studio 設定精靈步驟。下載及安裝必要檔案可能需要幾分鐘的時間,因此您可以讓這項作業在背景執行,同時進行下一節的步驟。

下載程式碼研究室範例應用程式

下載範例應用程式

入門應用程式位於 material-components-android-codelabs-101-starter/java 目錄中。

...或從 GitHub 複製

如要從 GitHub 複製本程式碼研究室,請執行下列指令:

git clone https://github.com/material-components/material-components-android-codelabs
cd material-components-android-codelabs/
git checkout 101-starter

在 Android Studio 中載入範例程式碼

  1. 設定精靈完成後,系統會顯示「Welcome to Android Studio」視窗,請按一下「Open an existing Android Studio project」。前往安裝範例程式碼的目錄,然後選取「java」>「shrine」(或在電腦上搜尋「shrine」) 開啟 Shrine 專案。
  2. 稍待片刻,Android Studio 會建構及同步處理專案,Android Studio 視窗底部的活動指標會顯示進度。
  3. 此時,Android Studio 可能會引發一些建構錯誤,因為您缺少 Android SDK 或建構工具,如下所示。按照 Android Studio 中的操作說明安裝/更新這些項目,然後同步專案。

新增專案依附元件

專案需要 MDC Android 支援程式庫的依附元件。您下載的範例程式碼應該已列出這項依附元件,但建議您執行下列步驟,確保一切正常。

  1. 前往 app 模組的 build.gradle 檔案,確認 dependencies 區塊包含 MDC Android 的依附元件:
api 'com.google.android.material:material:1.1.0-alpha06'
  1. (選用) 視需要編輯 build.gradle 檔案,加入下列依附元件並同步處理專案。
dependencies {
    api 'com.google.android.material:material:1.1.0-alpha06'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:core:1.1.0'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test:runner:1.2.0-alpha05'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha05'
}

執行範例應用程式

  1. 確認「Run」/「Play」按鈕左側的建構設定為 app
  2. 按下綠色的「Run」/「Play」按鈕,即可建構及執行應用程式。
  3. 在「Select Deployment Target」視窗中,如果可用裝置清單中已有 Android 裝置,請跳至步驟 8。如果沒有,請按一下「Create New Virtual Device」
  4. 在「Select Hardware」畫面中,選取手機裝置 (例如 Pixel 2),然後按一下「Next」
  5. 在「系統映像檔」畫面中,選取最新的 Android 版本,最好是最高的 API 級別。如果尚未安裝,請按一下顯示的「下載」連結,然後完成下載。
  6. 點選 [下一步]。
  7. 在「Android Virtual Device (AVD)」畫面上,保留預設設定並按一下「Finish」
  8. 從部署目標對話方塊中選取 Android 裝置
  9. 按一下 [確定]。
  10. Android Studio 會建構及部署應用程式,並在目標裝置上自動開啟。

大功告成!模擬器應會執行 Shrine 登入頁面的入門程式碼。您應該會看到「Shrine」名稱,以及下方的 Shrine 標誌。

我們來看看程式碼。我們在範例程式碼中提供簡單的 Fragment 導覽架構,可顯示片段並在片段之間導覽。

shrine -> app -> src -> main -> java -> com.google.codelabs.mdc.java.shrine 目錄中開啟 MainActivity.java。其中應包含下列項目:

MainActivity.java

package com.google.codelabs.mdc.java.shrine;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;

public class MainActivity extends AppCompatActivity implements NavigationHost {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.shr_main_activity);

       if (savedInstanceState == null) {
           getSupportFragmentManager()
                   .beginTransaction()
                   .add(R.id.container, new LoginFragment())
                   .commit();
       }
   }

   /**
    * Navigate to the given fragment.
    *
    * @param fragment       Fragment to navigate to.
    * @param addToBackstack Whether or not the current fragment should be added to the backstack.
    */
   @Override
   public void navigateTo(Fragment fragment, boolean addToBackstack) {
       FragmentTransaction transaction =
               getSupportFragmentManager()
                       .beginTransaction()
                       .replace(R.id.container, fragment);

       if (addToBackstack) {
           transaction.addToBackStack(null);
       }

       transaction.commit();
   }
}

這項活動會顯示 R.layout.shr_main_activity 版面配置檔案,該檔案定義於 shr_main_activity.xml

您可以看到 onCreate(), MainActivity.java 會啟動 Fragment 交易,以便顯示 LoginFragmentLoginFragment. 我們將在本程式碼研究室中修改這個檔案。活動也會實作 NavigationHost 中定義的 navigateTo(Fragment) 方法,讓任何片段導覽至其他片段。

在活動檔案中按一下 Command 鍵並點選 (或按一下 Control 鍵並點選) shr_main_activity,即可開啟版面配置檔案,或在 app -> res -> layout -> shr_main_activity.xml 中前往版面配置檔案。

shr_main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/container"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity" />

這裡會看到簡單的 <FrameLayout>,做為活動顯示任何片段的容器。開啟 LoginFragment.java

LoginFragment.java

package com.google.codelabs.mdc.java.shrine;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

/**
* Fragment representing the login screen for Shrine.
*/
public class LoginFragment extends Fragment {

   @Override
   public View onCreateView(
           @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       // Inflate the layout for this fragment
       View view = inflater.inflate(R.layout.shr_login_fragment, container, false);

       // Snippet from "Navigate to the next Fragment" section goes here.

       return view;
   }

   // "isPasswordValid" from "Navigate to the next Fragment" section method goes here
}

LoginFragment 會加載 shr_login_fragment 版面配置檔案,並顯示在 onCreateView() 中。讓我們看看 shr_login_fragment.xml 版面配置檔案,瞭解登入頁面的外觀。

shr_login_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<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_width="match_parent"
   android:layout_height="match_parent"
   android:background="@color/loginPageBackgroundColor"
   tools:context=".LoginFragment">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:clipChildren="false"
       android:clipToPadding="false"
       android:orientation="vertical"
       android:padding="24dp"
       android:paddingTop="16dp">

       <ImageView
           android:layout_width="64dp"
           android:layout_height="64dp"
           android:layout_gravity="center_horizontal"
           android:layout_marginTop="48dp"
           android:layout_marginBottom="16dp"
           app:srcCompat="@drawable/shr_logo" />

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_horizontal"
           android:layout_marginBottom="132dp"
           android:text="@string/shr_app_name"
           android:textAllCaps="true"
           android:textSize="16sp" />

       <!-- Snippet from "Add text fields" section goes here. -->

       <!-- Snippet from "Add buttons" section goes here. -->

   </LinearLayout>
</ScrollView>

這裡可以看到頂端有 <ImageView><LinearLayout>,代表「Shrine」標誌。

接著是代表「SHRINE」標籤的 <TextView> 標記。這個標籤的文字是名為 @string/shr_app_name字串資源。如果按住 Command 鍵並點選 (或按住 Control 鍵並點選) 字串資源名稱,或是開啟 app -> res -> values -> strings.xml,您會看到定義字串資源的 strings.xml 檔案。日後新增更多字串資源時,也會在這裡定義。這個檔案中的每個資源都應有 shr_ 前置字串,表示這些資源屬於 Shrine 應用程式。

您已熟悉範例程式碼,接下來要實作第一個元件。

首先,我們會在登入頁面中加入兩個文字欄位,供使用者輸入使用者名稱和密碼。我們會使用 MDC 文字欄位元件,其中內建可顯示浮動標籤和錯誤訊息的功能。

新增 XML

shr_login_fragment.xml 中,於「SHRINE」標籤 <TextView> 下方的 <LinearLayout> 內,新增兩個含有子項 TextInputEditTextTextInputLayout 元素:

shr_login_fragment.xml

<com.google.android.material.textfield.TextInputLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_margin="4dp"
   android:hint="@string/shr_hint_username">

   <com.google.android.material.textfield.TextInputEditText
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:inputType="text"
       android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
   android:id="@+id/password_text_input"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_margin="4dp"
   android:hint="@string/shr_hint_password">

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

上述程式碼片段代表兩個文字欄位,每個欄位都包含 <TextInputLayout> 元素和 <TextInputEditText> 子項。每個文字欄位的提示文字都會在 android:hint 屬性中指定。

我們為文字欄位新增了兩個字串資源:@string/shr_hint_username@string/shr_hint_password。開啟 strings.xml 即可查看這些字串資源。

strings.xml

...
<string name="shr_hint_username">Username</string>
<string name="shr_hint_password">Password</string>
...

新增輸入內容驗證

TextInputLayout 元件內建錯誤意見回饋功能。

如要顯示錯誤意見回饋,請對 shr_login_fragment.xml 進行下列變更:

  • 在「密碼」 TextInputLayout 元素中,將 app:errorEnabled 屬性設為 true。這樣一來,文字欄位下方的錯誤訊息就會有額外邊框間距。
  • 在「密碼」 TextInputEditText 元素中,將 android:inputType 屬性設為「textPassword」。這樣一來,密碼欄位中的輸入文字就會隱藏。

完成這些變更後,shr_login_fragment.xml 中的文字欄位應如下所示:

shr_login_fragment.xml

<com.google.android.material.textfield.TextInputLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_margin="4dp"
   android:hint="@string/shr_hint_username">

   <com.google.android.material.textfield.TextInputEditText
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:inputType="text"
       android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
   android:id="@+id/password_text_input"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_margin="4dp"
   android:hint="@string/shr_hint_password"
   app:errorEnabled="true">

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

現在請嘗試執行應用程式,您應該會看到「使用者名稱」和「密碼」的兩個文字欄位!

查看浮動標籤動畫:

接著,我們會在登入頁面新增兩個按鈕:「取消」和「下一步」。我們將使用 MDC 按鈕元件,內建標誌性的 Material Design 墨水漣漪效果。

新增 XML

shr_login_fragment.xml 中,將 <RelativeLayout> 新增至 <LinearLayout>TextInputLayout 元素下方。然後在 <RelativeLayout> 中新增兩個 <MaterialButton> 元素。

產生的 XML 檔案應如下所示:

shr_login_fragment.xml

<RelativeLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <com.google.android.material.button.MaterialButton
       android:id="@+id/next_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignParentEnd="true"
       android:layout_alignParentRight="true"
       android:text="@string/shr_button_next" />

   <com.google.android.material.button.MaterialButton
       android:id="@+id/cancel_button"
       style="@style/Widget.MaterialComponents.Button.TextButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginEnd="12dp"
       android:layout_marginRight="12dp"
       android:layout_toStartOf="@id/next_button"
       android:layout_toLeftOf="@id/next_button"
       android:text="@string/shr_button_cancel" />

</RelativeLayout>

大功告成!執行應用程式時,輕觸每個按鈕都會顯示墨水漣漪。

最後,我們會在 LoginFragment.java 中新增一些 Java 程式碼,將「NEXT」按鈕連結至另一個片段。您會發現,我們新增至版面的每個元件都已指派 id。我們會使用這些 id 參照程式碼中的元件,並新增一些錯誤檢查和導覽功能。

讓我們在 onCreateView() 下方的 LoginFragment.java 中新增私有布林值 isPasswordValid 方法,並加入判斷密碼是否有效的邏輯。就本示範而言,我們只會確保密碼長度至少為 8 個字元:

LoginFragment.java

/*
   In reality, this will have more complex logic including, but not limited to, actual
   authentication of the username and password.
*/
private boolean isPasswordValid(@Nullable Editable text) {
   return text != null && text.length() >= 8;
}

接著,將點按事件監聽器新增至「Next」按鈕,根據我們剛建立的 isPasswordValid() 方法設定及清除錯誤。在 onCreateView() 中,這個點按事件監聽器應放在膨脹器行和 return view 行之間。

接著,我們在密碼 TextInputEditText 中新增按鍵事件監聽器,監聽會清除錯誤的按鍵事件。這個接聽程式也應使用 isPasswordValid() 檢查密碼是否有效。您可以在 onCreateView() 中,直接在點擊事件監聽器下方新增這項功能。

您的 onCreateView() 方法現在應如下所示:

LoginFragment.java

@Override
public View onCreateView(
       @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
   // Inflate the layout for this fragment
   View view = inflater.inflate(R.layout.shr_login_fragment, container, false);
   final TextInputLayout passwordTextInput = view.findViewById(R.id.password_text_input);
   final TextInputEditText passwordEditText = view.findViewById(R.id.password_edit_text);
   MaterialButton nextButton = view.findViewById(R.id.next_button);

   // Set an error if the password is less than 8 characters.
   nextButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           if (!isPasswordValid(passwordEditText.getText())) {
               passwordTextInput.setError(getString(R.string.shr_error_password));
           } else {
               passwordTextInput.setError(null); // Clear the error
           }
       }
   });

   // Clear the error once more than 8 characters are typed.
   passwordEditText.setOnKeyListener(new View.OnKeyListener() {
       @Override
       public boolean onKey(View view, int i, KeyEvent keyEvent) {
           if (isPasswordValid(passwordEditText.getText())) {
               passwordTextInput.setError(null); //Clear the error
           }
           return false;
       }
   });
   return view;
}            

現在我們可以導覽至另一個片段。更新 onCreateView() 中的 onCreateView(),在錯誤驗證成功時導覽至另一個片段。OnClickListener如要完成這項操作,請在點擊事件監聽器的 ProductGridFragment 案例中新增下列程式行,前往 ProductGridFragmentelse

LoginFragment.java

...
((NavigationHost) getActivity()).navigateTo(new ProductGridFragment(), false); // Navigate to the next Fragment
...

點擊事件監聽器現在應如下所示:

LoginFragment.java

...
MaterialButton nextButton = view.findViewById(R.id.next_button);

// Set an error if the password is less than 8 characters.
nextButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       if (!isPasswordValid(passwordEditText.getText())) {
           passwordTextInput.setError(getString(R.string.shr_error_password));
       } else {
           passwordTextInput.setError(null); // Clear the error
           ((NavigationHost) getActivity()).navigateTo(new ProductGridFragment(), false); // Navigate to the next Fragment
       }
   }
});
...

這行新程式碼會從 MainActivity 呼叫 navigateTo() 方法,前往新的片段 ProductGridFragment。目前這個頁面是空白的,您將在 MDC-102 中使用這個頁面。

接著建構應用程式。請按「下一步」按鈕。

你成功了!這個畫面將是下一個程式碼研究室的起點,您會在 MDC-102 中處理這個畫面。

您使用基本的 XML 標記和約 30 行 Java 程式碼,透過 Android 適用的 Material Design 元件程式庫,建立符合 Material Design 指南的美觀登入頁面,且在所有裝置上外觀和行為都一致。

後續步驟

文字欄位和按鈕是 MDC Android 程式庫中的兩個核心元件,但還有更多元件!您可以探索 MDC Android 中的其餘元件。你也可以前往「MDC 102:Material Design 結構和版面配置」,瞭解頂端應用程式列、資訊卡檢視畫面和格線版面配置。感謝您試用 Material Components。希望您喜歡這個程式碼研究室!

我能在合理的時間和精力內完成本程式碼研究室

非常同意 同意 沒意見 不同意 非常不同意

我希望日後繼續使用 Material Design 元件

非常同意 同意 沒意見 不同意 非常不同意