1. 开始之前
简介
到目前为止,您已经学习了使用 Compose 构建 Android 应用的所有知识。这很好!Compose 是一个非常强大的工具,可以简化开发过程。但是,Android 应用并非总是使用声明式 UI 构建的。Compose 是 Android 应用历史上一个非常新的工具。Android UI 最初是使用视图构建的。因此,在您继续成为 Android 开发者的过程中,您很可能会遇到视图。在本 Codelab 中,您将学习 Android 应用在 Compose 出现之前是如何构建的——使用 XML、视图、视图绑定和片段。
先决条件
- 完成 第 7 单元 的 Android 基础 Compose 教程。
您需要什么
- 一台连接互联网的电脑和 Android Studio
- 一台设备或模拟器
- Juice Tracker 应用的起始代码
您将构建什么
在本 Codelab 中,您将完成 Juice Tracker 应用。此应用允许您通过构建包含详细项目的列表来跟踪重要的果汁。您将添加和修改片段和 XML 来完成 UI 和起始代码。具体来说,您将构建用于创建新果汁的输入表单,包括 UI 和任何相关的逻辑或导航。最终结果是一个带有空列表的应用,您可以向其中添加自己的果汁。
2. 获取起始代码
- 在 Android Studio 中,打开
basic-android-kotlin-compose-training-juice-tracker
文件夹。 - 在 Android Studio 中打开 Juice Tracker 应用代码。
3. 创建布局
使用 视图
构建应用时,您会在 布局 内构建 UI。布局通常使用 XML 声明。这些 XML 布局文件位于资源目录下的 res > layout 中。布局包含构成 UI 的组件;这些组件称为 视图
。XML 语法由标签、元素和属性组成。有关 XML 语法的更多详细信息,请参阅 为 Android 创建 XML 布局 Codelab。
在本节中,您将为所示的“果汁类型”输入对话框构建 XML 布局。
- 在 main > res > layout 目录中创建一个名为
fragment_entry_dialog
的新的 布局资源文件。
fragment_entry_dialog.xml
布局包含应用向用户显示的 UI 组件。
请注意,根元素是 ConstraintLayout
。这种类型的布局是一个 ViewGroup
,它允许您使用约束以灵活的方式定位和调整视图的大小。ViewGroup
是一种包含其他 视图
(称为子项或子 视图
)的 视图
。接下来的步骤将更详细地介绍此主题,但您可以在 使用 ConstraintLayout 构建响应式 UI 中了解有关 ConstraintLayout
的更多信息。
- 创建文件后,在
ConstraintLayout
中定义应用命名空间。
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
- 将以下准则添加到
ConstraintLayout
中。
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="16dp" />
这些 Guideline
用作其他视图的填充。这些准则约束“果汁类型”标题文本。
- 创建一个
TextView
元素。此TextView
表示详细信息片段的标题。
- 将
TextView
的id
设置为header_title
。 - 将
layout_width
设置为0dp
。布局约束最终定义此TextView
的宽度。因此,仅定义宽度只会增加 UI 绘制期间不必要的计算;将宽度定义为0dp
可避免额外的计算。 - 将
TextView text
属性设置为@string/juice_type
。 - 将
textAppearance
设置为@style/TextAppearance.MaterialComponents.Headline5
。
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />
最后,您需要定义约束。与使用维度作为约束的 Guideline
不同,准则本身约束此 TextView
。为了实现此结果,您可以引用您要以此约束视图的 Guideline
的 id。
- 将标题的顶部约束到
guideline_top
的底部。
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top" />
- 将末尾约束到
guideline_middle
的开头,并将开头约束到guideline_left
的开头,以完成TextView
的放置。请记住,约束给定视图的方式完全取决于您希望 UI 的外观。
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top"
app:layout_constraintEnd_toStartOf="@+id/guideline_middle"
app:layout_constraintStart_toStartOf="@+id/guideline_left" />
尝试根据屏幕截图构建其余的 UI。您可以在 fragment_entry_dialog.xml
文件中找到完整的 解决方案。
4. 使用视图创建片段
在 Compose 中,您可以使用 Kotlin 或 Java 声明式地构建布局。您可以通过导航到不同的 Composable 来访问不同的“屏幕”,通常在同一个 Activity 中。使用视图构建应用时,承载 XML 布局的片段取代了 Composable“屏幕”的概念。
在本节中,您将创建一个 Fragment
来承载 fragment_entry_dialog
布局并向 UI 提供数据。
- 在
juicetracker
包中,创建一个名为EntryDialogFragment
的新类。 - 使
EntryDialogFragment
扩展BottomSheetDialogFragment
。
EntryDialogFragment.kt
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class EntryDialogFragment : BottomSheetDialogFragment() {
}
DialogFragment
是一个显示浮动对话框的 Fragment
。BottomSheetDialogFragment
继承自 DialogFragment
类,但会显示一个宽度为屏幕宽度且固定在屏幕底部的表单。此方法与前面所示的设计相匹配。
- 重新构建项目,这将导致基于
fragment_entry_dialog
布局的视图绑定文件自动生成。视图绑定允许您访问和交互与 XML 声明的视图
,您可以在 视图绑定 文档中阅读有关它们的更多信息。 - 在
EntryDialogFragment
类中,实现onCreateView()
函数。顾名思义,此函数为此Fragment
创建视图
。
EntryDialogFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
onCreateView()
函数返回一个 视图
,但现在它没有返回有用的 视图
。
- 返回通过膨胀
FragmentEntryDialogViewBinding
生成的视图
,而不是返回super.onCreateView()
。
EntryDialogFragment.kt
import com.example.juicetracker.databinding.FragmentEntryDialogBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
- 在
onCreateView()
函数之外,但在EntryDialogFragment
类内,创建一个EntryViewModel
的实例。 - 实现
onViewCreated()
函数。
膨胀视图绑定后,您可以访问和修改布局中的 视图
。onViewCreated()
方法在生命周期中的 onCreateView()
后调用。onViewCreated()
方法是访问和修改布局中 视图
的推荐位置。
- 通过对
FragmentEntryDialogBinding
调用bind()
方法来创建视图绑定的实例。
此时,您的代码应如下例所示
EntryDialogFragment.kt
import androidx.fragment.app.viewModels
import com.example.juicetracker.ui.AppViewModelProvider
import com.example.juicetracker.ui.EntryViewModel
class EntryDialogFragment : BottomSheetDialogFragment() {
private val entryViewModel by viewModels<EntryViewModel> { AppViewModelProvider.Factory }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
}
}
您可以通过绑定访问和设置视图。例如,您可以通过 setText()
方法设置 TextView
。
binding.name.setText("Apple juice")
输入对话框 UI 用作用户创建新项目的地方,但您也可以使用它来修改现有项目。因此,片段需要检索被点击的项目。导航组件有助于导航到 EntryDialogFragment
并检索被点击的项目。
EntryDialogFragment
尚未完成,但不用担心!现在,继续下一节,了解如何在使用 视图
的应用中使用导航组件。
5. 修改导航组件
在本节中,您将使用导航组件启动输入对话框,并在适用时检索项目。
Compose 提供了一种简单的方法来渲染不同的可组合项,只需调用它们即可。但是,Fragment 的工作方式有所不同。 导航组件 协调 Fragment 的“目的地”,提供了一种在不同的 Fragment 及其包含的视图之间轻松移动的方法。
使用导航组件协调导航到您的 EntryDialogFragment
。
- 打开
nav_graph.xml
文件,并确保选中“设计”选项卡。 - 点击 图标以添加新的目的地。
- 选择
EntryDialogFragment
目的地。此操作在导航图中声明entryDialogFragment
,使其可用于导航操作。
您需要从 TrackerFragment
启动 EntryDialogFragment
。因此,需要一个导航操作来完成此任务。
- 将光标悬停在
trackerFragment
上。选择灰色点并将线条拖动到entryDialogFragment
。 - 导航图设计视图允许您通过选择目的地并点击 图标(位于“参数”下拉菜单旁边)来为目的地声明参数。使用此功能向
entryDialogFragment
添加类型为Long
的itemId
参数;默认值应为0L
。
请注意,TrackerFragment
包含 Juice
项目列表——如果点击其中一个项目,则会启动 EntryDialogFragment
。
- 重建项目。
itemId
参数现在可在EntryDialogFragment
中访问。
6. 完成 Fragment
使用导航参数中的数据,完成输入对话框。
- 在
EntryDialogFragment
的onViewCreated()
方法中检索navArgs()
。 - 从
navArgs()
中检索itemId
。 - 实现
saveButton
以使用ViewModel
保存新的/修改后的果汁。
回想一下输入对话框 UI 中的默认颜色值为红色。目前,将其作为占位符传递。
调用 saveJuice()
时,传递来自参数的项目 ID。
EntryDialogFragment.kt
import androidx.navigation.fragment.navArgs
import com.example.juicetracker.data.JuiceColor
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
val juiceId = args.itemId
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
}
}
}
- 保存数据后,使用
dismiss()
方法关闭对话框。
EntryDialogFragment.kt
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
dismiss()
}
}
}
请记住,以上代码并未完成 EntryDialogFragment
。您仍然需要实现许多内容,例如使用现有 Juice
数据填充字段(如果适用)、从 colorSpinner
中选择颜色、实现 cancelButton
等等。但是,此代码并非 Fragment
独有,您可以自行实现此代码。尝试实现其余功能。作为最后手段,您可以参考此 codelab 的解决方案代码。
7. 启动输入对话框
最后一个任务是使用导航组件启动输入对话框。当用户点击浮动操作按钮 (**FAB**) 时,需要启动输入对话框。当用户点击项目时,也需要启动它并传递相应的 ID。
- 在 FAB 的
onClickListener()
中,在导航控制器上调用navigate()
。
TrackerFragment.kt
import androidx.navigation.findNavController
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
)
}
//...
- 在 navigate 函数中,传递从跟踪器导航到输入对话框的操作。
TrackerFragment.kt
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment()
)
}
//...
- 在
JuiceListAdapter
中onEdit()
方法的 lambda 体中重复此操作,但这次传递Juice
的id
。
TrackerFragment.kt
//...
onEdit = { drink ->
findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment(drink.id)
)
},
//...
8. 获取解决方案代码
要下载完成的 codelab 的代码,可以使用以下 git 命令
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout views
或者,您可以将存储库下载为 zip 文件,解压缩它,然后在 Android Studio 中打开它。
如果您想查看解决方案代码,请在 GitHub 上查看。