1. 简介
Material Components (MDC) 有助于开发者实现 Material Design。MDC 是由一组 Google 工程师和用户体验设计人员倾心打造的,提供数十种精美实用的界面组件,可用于 Android、iOS、Web 和 Flutter.material.io/develop |
什么是 Material Design 和 Material Components for Android?
Material Design 是一个系统,用于构建醒目、美观的数字产品。它采用一套统一的原则和组件,将风格、品牌、交互和动效结合起来,让产品团队得以充分释放其设计潜能。
对于 Android 应用,Material Components for Android(以下简称 MDC Android)用于将设计和工程与组件库结合在一起,从而在应用之间打造一致的体验。随着 Material Design 系统的发展,这些组件会进行相应更新,以确保一致的像素级完美实现,并遵循 Google 的前端开发标准。MDC 同样适用于 Web、iOS 和 Flutter。
在此 Codelab 中,您将使用 MDC Android 的若干组件构建一个登录页面。
您将构建的内容
我们提供了 4 个 Codelab 来引导您构建一款名为 Shrine 的应用,这是其中的第 1 个。Shrine 是一个销售服装和家居用品的电子商务 Android 应用。此 Codelab 将演示如何使用 MDC Android 来自定义组件以体现任何品牌或样式。
在本 Codelab 中,您将为 Shrine 构建一个包含以下内容的登录页面:
- 两个文本字段,分别用于输入用户名和密码
- 两个按钮,分别对应“Cancel”(取消)和“Next”(下一步)
- 应用名称 (Shrine)
- Shrine 徽标的图片

本 Codelab 中用到的 MDC Android 组件
- 文本字段
- 按钮
所需条件
- 已掌握 Android 开发方面的基础知识
- Android Studio(如果尚未安装,请在此处下载)
- Android 模拟器或设备(可通过 Android Studio 获取)
- 示例代码(参见下一步)
您如何评价自己在构建 Android 应用方面的经验水平?
2. 设置您的开发环境
启动 Android Studio
打开 Android Studio 后,您应该会看到一个标题为“Welcome to Android Studio”的窗口。不过,如果这是您第一次启动 Android Studio,请使用默认值完成 Android Studio 设置向导中的步骤。该步骤可能需要几分钟时间才能下载并安装必要的文件,因此您可以让其在后台运行,同时继续执行下一部分的操作。
下载起始 Codelab 应用
起始应用位于 material-components-android-codelabs-101-starter/kotlin 目录中。
…或从 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 中加载起始代码
- 在设置向导完成且系统显示 Welcome to Android Studio 窗口后,点击 Open an existing Android Studio project。找到您安装了示例代码的目录,然后依次选择 kotlin -> shrine(或在计算机上搜索 shrine),以打开 Shipping 项目。
- 等待 Android Studio 构建和同步项目,如 Android Studio 窗口底部的 activity 指示器所示。
- 此时,由于缺少 Android SDK 或构建工具,因此 Android Studio 可能会显示一些构建错误(如下所示)。按照 Android Studio 中的说明安装/更新这些内容,并同步您的项目。
添加项目依赖项
项目需要一个 MDC Android 支持库的依赖项。您下载的示例代码应该已经列出了此依赖项,但您最好按照以下步骤操作,以确保万无一失。
- 转到
app模块的build.gradle文件,并确保dependencies代码块包含 MDC Android 的依赖项:
api 'com.google.android.material:material:1.1.0-alpha06'
- (可选)如有必要,请修改
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'
}
运行起始应用
|
成功!您的模拟器中现在应该正在运行 Shrine 登录页面的起始代码。您应该会看到名称“Shrine”以及该名称正下方的 Shrine 徽标。

我们来看一下代码。我们在示例代码中提供了一个简单的 Fragment 导航框架,用于显示 fragment 以及在 fragment 之间导航。
打开 shrine -> app -> src -> main -> java -> com.google.codelabs.mdc.kotlin.shrine 目录中的 MainActivity.kt,其中应包含以下代码:
MainActivity.kt
package com.google.codelabs.mdc.kotlin.shrine
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
class MainActivity : AppCompatActivity(), NavigationHost {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.shr_main_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.container, LoginFragment())
.commit()
}
}
override fun navigateTo(fragment: Fragment, addToBackstack: Boolean) {
val transaction = supportFragmentManager
.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.kt 启动了 Fragment 事务,以显示 LoginFragment。在此 Codelab 中,我们将修改 LoginFragment。该 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.kt。
LoginFragment.kt
package com.google.codelabs.mdc.kotlin.shrine
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.shr_login_fragment, container, false)
return view
}
}
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" />
</LinearLayout>
</ScrollView>
在这里,我们从顶部能看到一个 <LinearLayout>,其中包含代表 Shrine 徽标的 <ImageView>。
在此之后,有一个 <TextView> 标记,代表徽标下方的 Shrine 标签。此标签的文本是一个名为 @string/shr_app_name 的字符串资源。如果您在按住 Command 键的同时点击(或按住 Ctrl 键的同时点击)该字符串资源名称,或者打开 app -> res -> values -> strings.xml,则可看到其中定义了字符串资源的 strings.xml 文件。以后添加更多字符串资源时,将在此处进行定义。此文件中的每个资源都应带有 shr_ 前缀,以指明它们是 Shrine 应用的一部分。
现在,您已经熟悉了起始代码,接下来我们要实现第一个组件。
3. 添加文本字段
首先,我们将为登录页面添加两个文本字段,供用户输入用户名和密码。我们将使用 MDC 文本字段组件,其中包括可显示浮动标签和错误消息的内置功能。

添加 XML
在 shr_login_fragment.xml 中,在 <LinearLayout> 内的“SHRINE”标签 <TextView> 的下方,添加两个包含子元素 TextInputEditText 的 TextInputLayout 元素:
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" />
</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”。这会为文本字段下方的错误消息添加额外的内边距。 - 对于 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" />
</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”)的页面!
查看悬浮标签动画:

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

添加 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>
大功告成!运行应用后,点按每个按钮都会显示水墨涟漪效果。

5. 转到下一个 fragment
最后,我们将向 LoginFragment.kt 添加一些 Kotlin 代码,以附加要转换到另一个 fragment 的“NEXT”按钮。
让我们在 LoginFragment.kt 中的 onCreateView() 下添加一个私有布尔值 isPasswordValid 方法,并使用逻辑来确定密码是否有效。在本演示中,我们只需确保密码长度至少为 8 个字符:
LoginFragment.kt
private fun isPasswordValid(text: Editable?): Boolean {
return text != null && text.length >= 8
}
接下来,将点击监听器添加到“Next”按钮,该按钮用于根据我们刚刚创建的 isPasswordValid() 方法设置和清除错误。在 onCreateView() 中,此点击监听器应放置在膨胀器代码行和 return view 代码行之间。
现在,我们来为密码 TextInputEditText 添加一个键监听器,以监听将清除错误的按键事件。此监听器还应使用 isPasswordValid() 检查密码是否有效。您可以直接在 onCreateView() 中的点击监听器下方添加该方法。
您的 onCreateView() 方法现在应如下所示:
LoginFragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment.
val view = inflater.inflate(R.layout.shr_login_fragment, container, false)
// Set an error if the password is less than 8 characters.
view.next_button.setOnClickListener({
if (!isPasswordValid(password_edit_text.text!!)) {
password_text_input.error = getString(R.string.shr_error_password)
} else {
// Clear the error.
password_text_input.error = null
}
})
// Clear the error once more than 8 characters are typed.
view.password_edit_text.setOnKeyListener({ _, _, _ ->
if (isPasswordValid(password_edit_text.text!!)) {
// Clear the error.
password_text_input.error = null
}
false
})
return view
}
}
现在,我们可以导航到另一个 fragment。在 onCreateView() 中,更新 OnClickListener,以在错误验证成功后导航到另一个 fragment。您的 clickListener 代码现在应如下所示:
LoginFragment.kt
// Set an error if the password is less than 8 characters.
view.next_button.setOnClickListener({
if (!isPasswordValid(password_edit_text.text!!)) {
password_text_input.error = getString(R.string.shr_error_password)
} else {
// Clear the error.
password_text_input.error = null
// Navigate to the next Fragment.
(activity as NavigationHost).navigateTo(ProductGridFragment(), false)
}
})
我们向点击监听器的 else 情况添加了 (activityas NavigationHost).navigateTo(ProductGridFragment(), false) 代码行。此代码行会从 MainActivity 调用 navigateTo() 方法,以转到新的 fragment(即 ProductGridFragment)。目前,这是一个空白页面,您将在 MDC-102 中完善此页面。
现在,构建应用。接下来,请按“Next”按钮。
大功告成!此屏幕是我们下一个 Codelab 的起点,您将在 MDC-102 中继续使用它。
6. 大功告成
借助基本的 XML 标记和约 30 行 Kotlin 代码,Material Components for Android 库就可以帮助您创建一个符合 Material Design 准则的精美登录页面,同时让该页面在所有设备上保持一致的外观和行为。
后续措施
文本字段和按钮是 MDC Android 库的两个核心组件,但该库还提供了许多其他组件!您可以在 MDC Android 中探索其余组件。或者,前往 MDC 102:Material Design 结构和布局,了解顶部应用栏、卡片视图和网格布局。感谢您试用 Material Components。希望您喜欢此 Codelab!