MDC-101 Android:Material Components (MDC) 基础知识 (Java)

1. 简介

logo_components_color_2x_web_96dp.png

Material Components (MDC) 有助于开发者实现 Material Design。MDC 是由一组 Google 工程师和用户体验设计人员倾心打造的,提供数十种精美实用的界面组件,可用于 Android、iOS、Web 和 Flutter.material.io/develop

什么是 Material Design 和 Material Components for Android?

Material Design 是一个系统,用于构建醒目、美观的数字产品。通过采用一套一致的原则和组件将样式、品牌、交互和动效结合起来,产品团队能够充分发挥其设计潜能。

对于 Android 应用,适用于 Android 的 Material 组件 (MDC Android) 将设计和工程与组件库相结合,以在应用内实现一致性。随着 Material Design 系统不断发展,这些组件会进行更新,从而可确保一致且每个像素都无比完美的实现,并确保遵循 Google 的前端开发标准。MDC 也适用于 Web、iOS 和 Flutter。

在此 Codelab 中,您将使用 MDC Android 的若干组件构建一个登录页面。

您将构建的内容

我们提供了 4 个 Codelab 来引导您构建一款名为 Shrine 的应用,这是其中的第 1 个。Shrine 是一个销售服装和家居用品的电子商务 Android 应用。本文将演示如何使用 MDC-Android 自定义组件以反映任何品牌或风格。

在此 Codelab 中,您将为 Shrine 构建一个包含以下内容的登录页面:

  • 两个文本字段,分别用于输入用户名和密码
  • 两个按钮,分别对应“Cancel”(取消)和“Next”(下一步)
  • 应用名称 (Shrine)
  • Shrine 徽标的图片

4cb0c218948144b4.png

本 Codelab 中用到的 MDC Android 组件

  • 文本字段
  • 按钮

所需条件

  • 已掌握 Android 开发方面的基础知识
  • Android Studio(如果尚未安装,请在此处下载)
  • Android 模拟器或设备(可通过 Android Studio 获取)
  • 示例代码(参见下一步)

您如何评价自己在构建 Android 应用方面的经验水平?

<ph type="x-smartling-placeholder"></ph> 新手 中级 熟练

2. 设置您的开发环境

启动 Android Studio

打开 Android Studio 后,您应该会看到一个标题为“Welcome to Android Studio”的窗口。不过,如果这是您第一次启动 Android Studio,请使用默认值完成 Android Studio 设置向导中的步骤。该步骤可能需要几分钟时间才能下载并安装必要的文件,因此您可以让其在后台运行,同时继续执行下一部分的操作。

下载起始 Codelab 应用

起始应用位于 material-components-android-codelabs-101-starter/java 目录中。

…或从 GitHub 克隆

如需从 GitHub 克隆此 Codelab,请运行以下命令:

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 窗口底部的 activity 指示器所示。
  3. 此时,由于缺少 Android SDK 或构建工具,因此 Android Studio 可能会显示一些构建错误(如下所示)。按照 Android Studio 中的说明安装/更新这些内容,并同步您的项目。

F5H6srsw_5xOPGFpKrm1RwgewatxA_HUbDI1PWoQUAoJcT6DpfBOkAYwq3S-2vUHvweUaFgAmG7BtUKkGouUbhTwXQh53qec8tO5eVecdlo7QIoLc8rNxFEBb8l7RlS-KzBbZOzVhA

添加项目依赖项

项目需要一个 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”按钮左侧的 build 配置是 app
  2. 按绿色的“Run / Play”按钮,以构建并运行该应用。
  3. Select Deployment Target 窗口中,如果已有 Android 设备列在可用设备列表中,请跳至第 8 步。否则,请点击 Create New Virtual Device
  4. Select Hardware 屏幕中,选择一个手机设备(如 Pixel 2),然后点击 Next
  5. System Image 屏幕中,选择较新的 Android 版本,最好选择最高的 API 级别。如果相应版本尚未安装,请点击随即显示的 Download 链接,然后完成下载。
  6. 点击 Next
  7. Android Virtual Device (AVD) 屏幕上,让各项设置保持不变,然后点击 Finish
  8. 从部署目标对话框中选择一个 Android 设备
  9. 点击 Ok
  10. Android Studio 会构建并部署该应用,然后自动在目标设备上将其打开。

成功!您的模拟器中现在应该正在运行 Shrine 登录页面的起始代码。您应该会看到名称“Shrine”以及该名称正下方的 Shrine 徽标。

e7ed014e84755811.png

我们来看一下代码。我们在示例代码中提供了一个简单的 Fragment 导航框架,用于显示 fragment 以及在 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();
   }
}

此 activity 会显示 shr_main_activity.xml 中定义的 R.layout.shr_main_activity 布局文件。

您可以看到,在 onCreate(), 中,MainActivity.java 启动了 Fragment 事务,以显示 LoginFragmentLoginFragment. 这就是我们将在此 Codelab 中修改的内容。该 activity 还实现了一个在 NavigationHost 中定义的 navigateTo(Fragment) 方法,可让任何 fragment 转到其他 fragment。

按住 Command 键的同时点击(或按住 Ctrl 键的同时点击)该 activity 文件中的 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>,充当 activity 所显示 fragment 的容器。我们打开 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 键的同时点击(或按住 Ctrl 键的同时点击)该字符串资源名称,或者打开 app -> res -> values -> strings.xml,则可看到其中定义了字符串资源的 strings.xml 文件。以后添加更多字符串资源时,将在此处进行定义。此文件中的每个资源都应带有 shr_ 前缀,以指明它们是 Shrine 应用的一部分。

现在,您已经熟悉了起始代码,接下来我们要实现第一个组件。

3. 添加文本字段

首先,我们将为登录页面添加两个文本字段,供用户输入用户名和密码。我们将使用 MDC 文本字段组件,其中包括可显示浮动标签和错误消息的内置功能。

d83c47fb4aed3a82.png

添加 XML

shr_login_fragment.xml 中,在 <LinearLayout> 内的“SHRINE”标签 <TextView> 的下方,添加两个包含子元素 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 进行以下更改:

  • 对于 Password TextInputLayout 元素,将 app:errorEnabled 属性设置为 true。这会为文本字段下方的错误消息添加额外的内边距。
  • 对于 Password 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>

现在,请尝试运行该应用。您应该会看到一个包含两个文本字段(“Username”和“Password”)的页面!

查看悬浮标签动画:

333184b615aed4f7.gif

4. 添加按钮

接下来,我们将为登录页面添加两个按钮:“Cancel”和“Next”。我们将使用 MDC 按钮组件,该组件内置了标志性的 Material Design 水墨涟漪效果。

4cb0c218948144b4.png

添加 XML

shr_login_fragment.xml 中,将 <RelativeLayout> 添加到 TextInputLayout 元素下方的 <LinearLayout> 中。然后,向 <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>

大功告成!运行应用后,点按每个按钮都会显示水墨涟漪效果。

9dd162d65e4a92a2.gif

5. 转到下一个 fragment

最后,我们将向 LoginFragment.java 添加一些 Java 代码,以连接“NEXT”按钮添加到另一个 fragment。您会发现,我们添加到布局中的每个组件都分配了一个 id。我们将使用这些 id 引用代码中的组件,并添加一些错误检查和导航。

让我们在 LoginFragment.java 中的 onCreateView() 下添加一个私有布尔值 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;
}            

现在,我们可以导航到另一个 fragment。更新 onCreateView() 中的 OnClickListener,以便在错误验证成功时导航到另一个 fragment。为此,您可以添加以下代码行,以将 ProductGridFragment 导航到点击监听器的 else 情况:

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() 方法,以导航到新的 fragment - ProductGridFragment。目前,这是一个空白页面,您将在 MDC-102 中完善此页面。

现在,构建应用。接下来,请按“Next”按钮。

大功告成!此屏幕是我们下一个 Codelab 的起点,您将在 MDC-102 中继续使用它。

6. 大功告成

借助基本的 XML 标记和约 30 行 Java 代码,Material Components for Android 库就可以帮助您创建一个符合 Material Design 准则的精美登录页面,同时让该页面在所有设备上保持一致的外观和行为。

后续步骤

文本字段和按钮是 MDC Android 库的两个核心组件,但该库还提供了许多其他组件!您可以在 MDC Android 中探索其余组件。或者,前往 MDC 102:Material Design 结构和布局,了解顶部应用栏、卡片视图和网格布局。感谢您试用 Material Components。希望您喜欢此 Codelab!

我能够用合理的时间和精力完成此 Codelab

非常赞同 赞同 中立 不赞同 非常不赞同

我希望日后继续使用 Material Components

<ph type="x-smartling-placeholder"></ph> 非常赞同 赞同 一般 不赞同 非常不赞同