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" />
这些 准则
用作其他视图的填充。准则约束了“果汁类型”标题文本。
- 创建一个
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" />
最后,您需要定义约束。与使用尺寸作为约束的 准则
不同,准则本身约束了此 TextView
。为了实现此结果,您可以引用要约束视图的 准则
的 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(通常位于同一个活动内)来访问不同的“屏幕”。在使用视图构建应用时,托管 XML 布局的片段将取代 Composable“屏幕”的概念。
在本节中,您将创建一个 片段
来托管 fragment_entry_dialog
布局,并为 UI 提供数据。
- 在
juicetracker
包中,创建一个名为EntryDialogFragment
的新类。 - 使
EntryDialogFragment
扩展BottomSheetDialogFragment
。
EntryDialogFragment.kt
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class EntryDialogFragment : BottomSheetDialogFragment() {
}
该 DialogFragment
是一个显示浮动对话框的 片段
。BottomSheetDialogFragment
继承自 DialogFragment
类,但会显示一个与屏幕宽度相同的表格,固定在屏幕底部。这种方法与之前显示的设计相匹配。
- 重建项目,这将导致基于
fragment_entry_dialog
布局的视图绑定文件自动生成。视图绑定让您可以访问和交互与 XML 声明的视图
,您可以在 视图绑定 文档中了解更多信息。 - 在
EntryDialogFragment
类中,实现onCreateView()
函数。顾名思义,此函数将为该片段
创建视图
。
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 提供了通过简单地调用它们来渲染不同 Composable 的机会。但是,片段的工作原理有所不同。导航组件 协调片段“目的地”,提供了一种在不同片段及其包含的视图之间轻松移动的方法。
使用导航组件来协调导航到您的 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(
)
}
//...
- 在导航函数中,传递从跟踪器导航到输入对话框的操作。
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 上查看.