一、 实验名称
使用 Compose 实现多屏幕导航。
二、 参考资料
《Android开发者官方网站:Android 移动应用开发者工具 – Android 开发者 | Android Developers》、第6章课件。
三、 实验目的
本实验通过完成以下任务练习在Android应用中使用Compose实现多屏幕导航:
· 创建 NavHost 可组合项以定义应用中的路线和屏幕。
· 使用 NavHostController 在屏幕之间导航。
· 操控返回堆栈,以切换到之前的屏幕。
· 使用 intent 与其他应用共享数据。
· 自定义应用栏,包括标题和返回按钮。
四、 实验内容
1. 应用演示
Cupcake 应用与您到目前为止所使用过的应用略有不同。该应用不是在单个屏幕上显示所有内容,而是采用四个单独的屏幕,并且用户可以在订购纸杯蛋糕时在各个屏幕之间切换。如果您运行应用,您将无法查看任何内容,也无法在这些屏幕之间进行导航,因为 navigation 组件尚未添加到应用代码中。不过,您仍可以检查每个屏幕的可组合项预览,并将它们与下方的最终应用屏幕配对。
Start Order 屏幕
第一个屏幕向用户显示三个按钮,这些按钮对应于要订购的纸杯蛋糕数量。
|
|
在代码中,这由 StartOrderScreen.kt
中的 StartOrderScreen
可组合项表示。
该屏幕包含一个列(包含图片和文字)以及三个用于订购不同数量的纸杯蛋糕的自定义按钮。自定义按钮由同样位于 StartOrderScreen.kt
中的 SelectQuantityButton
可组合项实现。
Choose Flavor 屏幕
选择数量后,应用会提示用户选择纸杯蛋糕的口味。应用使用单选按钮来显示不同的选项。用户可以从多种可选口味中选择一种口味。
|
|
可选口味列表以字符串资源 ID 列表的形式存储在 data.DataSource.kt
中。
Choose Pickup Date 屏幕
用户选择口味后,应用会向用户显示另一些单选按钮,用于选择自提日期。自提选项来自 OrderViewModel
中的 pickupOptions()
函数返回的列表。
|
|
Choose Flavor 屏幕和 Choose Pick-Date 屏幕均由 SelectOptionScreen.kt
中的相同可组合项 SelectOptionScreen
表示。为什么要使用相同的可组合项?因为这些屏幕的布局完全相同!唯一的区别在于数据,但您可以使用相同的可组合项来同时显示口味屏幕和自提日期屏幕。
Order Summary 屏幕
用户选择自提日期后,应用会显示 Order Summary 屏幕,用户可以在其中检查和完成订单。
|
|
此屏幕由 SummaryScreen.kt
中的 OrderSummaryScreen
可组合项实现。
布局包含一个 Column
(包含订单的所有信息)、一个 Text
可组合项(用于显示小计),以及用于将订单发送到其他应用或取消订单并返回第一个屏幕的多个按钮。
如果用户选择将订单发送到其他应用,Cupcake 应用会显示 Android ShareSheet,其中显示了不同的分享选项。
应用的当前状态存储在 data.OrderUiState.kt
中。OrderUiState
数据类包含用于存储在每个屏幕中为用户提供的可用选项的属性。
应用的屏幕将显示在 CupcakeApp
可组合项中。不过,在起始项目中,应用仅显示第一个屏幕。目前还无法在应用的所有屏幕之间导航,不过别担心,本课程的目的就是实现这种导航!您将学习如何定义导航路线,设置用于在屏幕(也称为目标页面)之间导航的 NavHost 可组合项、执行 intent 以与共享屏幕等系统界面组件集成,并让应用栏能够响应导航更改。
可重复使用的可组合项
在适当的情况下,本课程中的示例应用可实现最佳实践。Cupcake 应用也不例外。在 ui.components 软件包中,您会看到一个名为 CommonUi.kt
的文件,其中包含一个 FormattedPriceLabel
可组合项。应用中的多个屏幕使用此可组合项来统一设置订单价格的格式。您无需重复定义具有相同格式和修饰符的相同 Text
可组合项,而是只需定义一次 FormattedPriceLabel
,然后根据需要将其重复用于其他屏幕。
同样,口味屏幕和自提日期屏幕也使用可重复使用的 SelectOptionScreen
可组合项。此可组合项接受名为 options
且类型为 List<String>
的参数,该参数表示要显示的选项。这些选项显示在 Row
中,后者由一个 RadioButton
可组合项和一个包含各个字符串的 Text
可组合项组成。整个布局周围有一个 Column
,还包含一个用于显示格式化价格的 Text
可组合项、一个 Cancel 按钮和一个 Next 按钮。
2. 定义路线并创建 NavHostController
导航组件的组成部分
Navigation 组件有三个主要部分:
-
NavController:负责在目标页面(即应用中的屏幕)之间导航。
-
NavGraph:用于映射要导航到的可组合项目标页面。
-
NavHost:此可组合项充当容器,用于显示 NavGraph 的当前目标页面。
在此 Codelab 中,您将重点关注 NavController 和 NavHost。在 NavHost 中,您将定义 Cupcake 应用的 NavGraph 的目标页面。
在应用中为目标页面定义路线
在 Compose 应用中,导航的一个基本概念就是路线。路线是与目标页面对应的字符串。这类似于网址的概念。就像不同网址映射到网站上的不同页面一样,路线是可映射至目标页面并作为其唯一标识符的字符串。目标页面通常是与用户看到的内容相对应的单个可组合项或一组可组合项。Cupcake 应用需要显示“Start Order”屏幕、“Flavor”屏幕、“Pickup Date”屏幕和“Order Summary”屏幕的目标页面。
应用中的屏幕数量有限,因此路线数量也是有限的。您可以使用枚举类来定义应用的路线。Kotlin 中的枚举类具有一个名称属性,该属性会返回具有属性名称的字符串。
首先,定义 Cupcake 应用的四个路线。
-
Start
:从三个按钮之一选择纸杯蛋糕的数量。 -
Flavor
:从选项列表中选择口味。 -
Pickup
:从选项列表中选择自提日期。 -
Summary
:检查所选内容,然后发送或取消订单。
添加一个枚举类来定义路线。
在 CupcakeScreen.kt
中的 CupcakeAppBar
可组合项上方,添加一个名称为 CupcakeScreen
的枚举类。
enum class CupcakeScreen() {
}
在枚举类中添加四种情况:Start
、Flavor
、Pickup
和 Summary
。
enum class CupcakeScreen() {
Start,
Flavor,
Pickup,
Summary
}
为应用添加 NavHost
NavHost 是一个可组合项,用于根据给定路线来显示其他可组合项目标页面。例如,如果路线为 Flavor
,NavHost
会显示用于选择纸杯蛋糕口味的屏幕。如果路线为 Summary
,则应用会显示摘要屏幕。
NavHost
的语法与任何其他可组合项的语法一样。
有两个参数值得注意:
-
navController
:NavHostController
类的实例。您可以使用此对象在屏幕之间导航,例如,通过调用navigate()
方法导航到另一个目标页面。您可以通过从可组合函数调用rememberNavController()
来获取NavHostController
。 -
startDestination
:此字符串路线用于定义应用首次显示NavHost
时默认显示的目标页面。对于 Cupcake 应用,这应该是Start
路线。
与其他可组合项一样,NavHost
也接受 modifier
参数。
注意:NavHostController 是 NavController 类的子类,可提供与 NavHost
可组合项搭配使用的额外功能。
您将向 CupcakeScreen.kt
中的 CupcakeApp
可组合项添加一个 NavHost
。首先,您需要建立一个到导航控制器的引用。您现在添加的 NavHost
以及将在后续步骤中添加的 AppBar
中都可以使用该导航控制器。因此,您应在 CupcakeApp()
可组合项中声明该变量。
打开 CupcakeScreen.kt
。
在 Scaffold
中的 uiState
变量下方,添加一个 NavHost
可组合项。
import androidx.navigation.compose.NavHost
Scaffold(
...
) { innerPadding ->
val uiState by viewModel.uiState.collectAsState()
NavHost()
}
为 navController
参数传入 navController
变量,并为 startDestination
参数传入 CupcakeScreen.Start.name
。传递之前传入到修饰符参数的 CupcakeApp()
中的修饰符。为最后一个参数传入空的尾随 lambda。
import androidx.compose.foundation.layout.padding
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
}
在 NavHost
中处理路线
与其他可组合项一样,NavHost
接受函数类型作为其内容。
在 NavHost
的内容函数中,调用 composable()
函数。composable()
函数有两个必需参数。
-
route
:与路线名称对应的字符串。这可以是任何唯一的字符串。您将使用CupcakeScreen
枚举的常量的名称属性。 -
content
:您可以在此处调用要为特定路线显示的可组合项。
您将针对这四个路线分别调用一次 composable()
函数。
注意:composable()
函数是 NavGraphBuilder 的扩展函数。
调用 composable()
函数,为 route
传入 CupcakeScreen.Start.name
。
import androidx.navigation.compose.composable
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
}
}
在尾随 lambda 中,调用 StartOrderScreen
可组合项,并为 quantityOptions
属性传入 quantityOptions
。对于 Modifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium))
中的 modifier
卡券
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.res.dimensionResource
import com.example.cupcake.ui.StartOrderScreen
import com.example.cupcake.data.DataSource
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
注意:quantityOptions
属性来自 DataSource.kt 中的 DataSource
单例对象。
在对 composable()
的第一次调用下方,再次调用 composable()
,为 route
传入 CupcakeScreen.Flavor.name
。
composable(route = CupcakeScreen.Flavor.name) {
}
在尾随 lambda 中,获取对 LocalContext.current
的引用,并将其存储到名称为 context
的变量中。Context 是一个抽象类,其实现由 Android 系统提供。它允许访问应用专用资源和类,以及向上调用应用级别的操作(例如启动 activity 等)。您可以使用此变量从视图模型中的资源 ID 列表中获取字符串以