在前一章节的实现中,Skeleton: Main structure,我们留下了几个 Jetpack 架构组件,这些组件将在本章中使用,例如 Composables、ViewModels、Navigation 和 Hilt。此外,我们还通过 Scaffold 集成了 TopAppBar 和 BottomAppBar UI 模式的基本结构。为了继续改进这个实现,我们需要添加一个新的关键选项卡,即“应用程序的通用状态”。
App程序的状态:一般状态
定义导航地图
导航从其他UI元素
摘要
应用程序状态:一个通用状态
“设计原则”中我们讨论了状态在现代 Android 应用程序中的重要作用。
设计中可能存在三种类型的状态:属性 UI 状态、组件 UI 状态和屏幕 UI 状态。
除了这些状态之外,我们还可以定义一种新类型的状态,即应用程序状态。
这个新状态将定义应用程序的通用状态。它将用于屏幕之间的导航、自发消息(snackbars)的呈现以及应用程序中其他可用的进程。
在主目录中,我们将定义一个名为 OrderNowState 的类,它将是我们的状态持有者,代表这种类型的状态。
接下来,我们像这样执行OrderNowState的初始实现:
@SuppressLint("RememberReturnType")
@Composable
fun rememberAppState(scaffoldState:ScaffoldState=rememberScaffoldState(),
navController: NavHostController =rememberNavController(),
resources1: Resources = resources(),
coroutineScope: CoroutineScope=rememberCoroutineScope()
)=remember(scaffoldState,navController,resources1,coroutineScope){
OrderNowState(scaffoldState,navController,resources1,coroutineScope)
}
class OrderNowState(val scaffoldState:ScaffoldState,val navController:NavHostController,private val resources:Resources,coroutineScope:CoroutineScope)
@Composable
@ReadOnlyComposable
fun resources():Resources{
LocalConfiguration.current
return LocalContext.current.resources
}
然后,我们修改我们的OrderNowScreen视图以包含之前定义的状态,如下所示:
@Preview
@Composable
fun OrderNowScreen(){
MyTestTheme {
Surface(modifier = Modifier.fillMaxSize(),color=MaterialTheme.colors.background) {
val appState =rememberAppState()
Scaffold(scaffoldState = appState.scaffoldState, topBar = { OrderNowTopBar()}, bottomBar = {OrderNowBottomBar()}){contentPadding->
println(contentPadding)
}
}
}
}
注意:
还需要向OrderNowScreen添加资源函数,它将使用该函数访问App资源。
突出显示更改的代码行如下:
Scaffold(
scaffoldState = appState.scaffoldState,
topBar = { OrderNowTopBar() },
bottomBar = { OrderNowBottomBar() }) { contentPadding ->
println(contentPadding)
}
有了上面的代码,我们现在可以告诉Scaffold它应该把哪个状态作为引用:应用程序的状态。之后,视图之间的导航操作、自发消息的呈现,以及视图只把AppState作为真实源的其他独占任务都将被允许。
既然已经为APP定义了状态,我们就可以继续实现APP的导航了。
定义导航地图
我们在应用程序中使用的导航策略由以下元素组成:
NavHost:它是负责在视图中显示导航结果的组件。导航的结果由NavigationController和导航图中给出的定义决定。
AppSoGraph:它是导航图的实现。它应该根据指定的路由将导航指向哪个视图或可组合对象。
屏幕路由:它们是可以通过导航到达的应用程序的不同屏幕。无论导航是从选项菜单、链接、按钮还是任何其他活动代理激活的,都无关紧要。每个屏幕都有一个与之关联的唯一路由。
一般导航图
我们将继续在OrderNow中包含这些元素
OrderNowScreenRoute
首先,创建一个名为common -> navigation的新遍历目录。在这个包中,我们像这样添加了一个名为OrderNowScreenRoute的类:
在这个类中,可以导航到的屏幕定义如下:
sealed class OrderNowScreenRoute(val route:String){
object Home :OrderNowScreenRoute("home")
object Cart:OrderNowScreenRoute("cart")
object ProductList:OrderNowScreenRoute("product_list")
object ProductDetail:OrderNowScreenRoute("product_detail")
}
OrderNowNavHost 和 AppSoGraph
现在,我们创建OrderNowNavHost类,它将像这样表示应用程序的NavHost:
@Composable
fun OrderNowNavHost(appState:OrderNowState){
NavHost(navController = appState.navController,5,startDestination=1){
appSoGraph(appState)
}
}
fun NavGraphBuilder.appSoGraph(appState: OrderNowState) {
}
从之前的代码片段中,我们应该强调以下定义:
• OrderNowNavHost 需要知道 APP 的状态。
• NavController 是从 APP 状态中托管和获取的。
• Navigation map(appSoGraph)将基于 APP 的状态创建,并且是在 OrderNowNavHost 内定义的扩展。
为了继续完成 OrderNow 中导航的实现,我们必须添加一个描述如下的辅助类。
NavigationBarSection
NavigationBarSection 是一个辅助类,代表应用程序底部菜单中组成筛选组的部分。
请记住,我们可以从选项菜单或其他 UI 组件(如链接、按钮或内部重定向)中的操作开始导航。
在下一节中,我们将对内部重定向(从按钮、链接等)进行更改;现在,让我们专注于从 BottomBar 导航。
我们看到NavigationBarSection帮助器类将如何只对Home和Cart屏幕进行分组,这是我们希望从BottomBar菜单启用的选项。这个类将像这样放置在导航目录中:
它的实现是这样的:
sealed class OrderNowScreenRoute(val route:String){
object Home :OrderNowScreenRoute("home")
object Cart:OrderNowScreenRoute("cart")
object ProductList:OrderNowScreenRoute("product_list")
object ProductDetail:OrderNowScreenRoute("product_detail")
}
sealed class NavigationBarSection(val title: String, val icon: ImageVector, val route:String){
companion object{
val sections = listOf(OrderNowScreenRoute.Home,OrderNowScreenRoute.Cart)
}
object Home:NavigationBarSection(title ="Home" , icon = Icons.Default.Home, route =OrderNowScreenRoute.Home.route)
object Cart:NavigationBarSection(title = "Cart", icon =Icons.Default.ShoppingCart, route = OrderNowScreenRoute.Cart.route)
}
将helper类添加到项目中后,我们继续更新OrderNowNavHost类,如下所示:
@Composable
fun OrderNowNavHost(appState: OrderNowState, paddingValues: PaddingValues) {
NavHost(
navController = appState.navController,
startDestination = NavigationBarSection.Home.route,
modifier = Modifier.padding(paddingValues)
) {
appSoGraph(appState)
}
}
fun NavGraphBuilder.appSoGraph(appState: OrderNowState) {
composable(NavigationBarSection.Home.route) {
// HomeScreen()
}
composable(NavigationBarSection.Cart.route) {
// CartScreen()
}
composable(OrderNowScreenRoute.ProductList.route){
//ProductListScreen()
}
composable(OrderNowScreenRoute.ProductDetail.route){
//ProductDetailScreen()
}
}
appSoGraph函数的实现是NavGraphBuilder的扩展,在那里,我们为App的每个屏幕指定导航地图。另外,通过startDestination参数,指定将首先呈现的默认屏幕,即Home屏幕。采用更改的下一步是更新名为 OrderNowBottomBar 的类,如下所示:
@Composable
fun OrderNowBottomBar(navController: NavHostController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
BottomNavigation(
backgroundColor = MaterialTheme.colors.background,
contentColor = contentColorFor(MaterialTheme.colors.background),
elevation = 10.dp
) {
NavigationBarSection.sections.forEach { section ->
val selected = currentDestination?.hierarchy?.any {
it.route == section.route
} == true
BottomNavigationItem(icon = {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_launcher_foreground),
contentDescription = stringResource(id = R.string.app_name)
)
},
label = { Text(text = stringResource(id =R.string.app_name)) },
selected = selected,
unselectedContentColor = Color.Gray,
selectedContentColor = Color.Red,
onClick = {
navController.navigate(section.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
})
}
}
}
对于添加到 NavigationBarSection 中的每个项目,BottomBar 中都会显示一个选项。OrderNowBottomBar 的这个实现比之前的架构:主要结构中的实现更清晰。
然后,我们再次更新 OrderNowScreen 视图,如下所示:
@Preview
@Composable
fun OrderNowScreeen(){
MyTestTheme(){
Surface {
val appState =rememberAppState()
Scaffold(
scaffoldState = appState.scaffoldState,
topBar = {OrderNowTopBar()},
bottomBar = {OrderNowBottomBar(appState.navController)}
){contentPadding ->
OrderNowNavHost(appState,contentPadding)
}
}
}
}
现在 OrderNowBottomBar 将需要引用负责导航的 navController。
在Scaffold的内容部分,添加了OrderNowNavHost的实例,该实例接收APP的一般状态作为参数。Order Now with simple navigation:
从其他UI元素进行导航
现在我们已经准备好从 BottomBar 选项导航的实现,我们需要定义从其他 UI 元素(例如按钮、链接、DeepLinks)甚至根据应用程序的其他内部组件的请求以编程方式导航应用程序。我们要做的第一个更改是添加一个名为 OrderNowNavigationState 的结构,它允许我们扩展应用程序的一般状态。
OrderNowNavigationState,是APP通用状态的扩展,即一组用于导航目的的OrderNowState状态的扩展。我们还将使用此结构来集中取决于应用程序状态的导航逻辑。
OrderNowNavigationState的实现如下:
fun OrderNowState.popUp(){
navController.popBackStack()
}
fun OrderNowState.navigate(route:String){
navController.navigate(route){
launchSingleTop = true
}
}
fun OrderNowState.navigateAndPopUp(route:String,popUp:String){
navController.navigate(route){
launchSingleTop =true
popUpTo(popUp){
inclusive =true
}
}
}
fun OrderNowState.navigateSaved(route:String,popUp:String){
navController.navigate(route){
launchSingleTop =true
restoreState =true
popUpTo(popUp){
saveState =true
}
}
}
fun OrderNowState.clearAndNavigate(route:String){
navController.navigate(route){
launchSingleTop =true
popUpTo(0){ inclusive =true}
}
}
对Home、ProductList和ProductDetail屏幕做了一些更改,如下图所示:
以in - home视图为例,导航动作通过Button执行,方式如下:
@Composable
fun HomeScreen(goToProductList:()->Unit,modifier:Modifier=Modifier,viewModel2:HomeViewModel =hiltViewModel()){
Column(){
Button(onClick = goToProductList,){
Text(text = stringResource(id = R.string.app_name))
}
}
}
代码的重要部分是“Go to —> ProductList Screen”按钮的动作 onClick = goToProductList 的定义,其中通过设计原理章节中解释的状态提升技术,我们将动作 goToProductList 委托给在 OrderNowNavHost 中定义的 appSoGraph 导航图如下:
un NavGraphBuilder.appSoGraph1(appState:OrderNowState){
val goToListFromHome:()->Unit ={
appState.navigateSaved(OrderNowScreenRoute.ProductList.route,OrderNowScreenRoute.Home.route)
}
composable(NavigationBarSection.Home.route){
HomeScreen(goToProductList =goToListFromHome)
}
}
回想一下,navigatesave函数是OrderNowNavigationState结构中定义的扩展的一部分。同样的实现应用于导航到其他ProductList和ProductDetail屏幕,这样在OrderNowNavHost中的性能如下所示:
fun NavGraphBuilder.appSoGraph2(appState:OrderNowState){
val homeRoute= OrderNowScreenRoute.Home.route
val listRoute= OrderNowScreenRoute.ProductList.route
val detailRoute =OrderNowScreenRoute.ProductDetail.route
val goToListFromHome:()->Unit ={
appState.navigateSaved(listRoute,homeRoute)
}
val goToDetailFromList:()->Unit ={
appState.navigateSaved(detailRoute,listRoute)
}
val goBack:()->Unit ={
appState.popUp()
}
composable(NavigationBarSection.Home.route){
HomeScreen(goToProductList =goToListFromHome)
}
composable(NavigationBarSection.ProductList.route){
ProductListScreen(goToProductDetail =goToDetailFromList,goBack =goBack)
}
composable(NavigationBarSection.ProductDetail.route){
ProductDetailScreen(goBack =goBack)
}
}
总结起来,前面的代码段中配置了以下导航定义(导航图):
- 从主页(Home screen)导航到产品列表(ProductList screen)。
- 从产品列表(ProductList screen)导航到产品详情(ProductDetail screen)。
- 从产品详情(ProductDetail screen)导航回到上一个呈现的屏幕。
- 从底部导航栏菜单导航到主页(Home screen)。
- 从底部导航栏菜单导航到购物车(Cart screen)。
到此为止,我们在 OrderNow 应用中已经实现了一个很好的基本导航。然而,还有一些东西缺失。 您有什么想法可能是缺失的吗? 我们需要在导航中包含数据或信息的传递。 由于在本章中设计了相关的实现,因此很容易实现数据传递。我们将在最后“实现功能”中详细探讨。
总结
本章中,我们完成了应用程序的主要部分的设计,这些将是以后添加功能的基础。 我们知道导航是应用程序的重要组成部分,必须从应用程序设计的开始就考虑它。 在本章中,我们采用了谷歌的建议,将状态纳入导航逻辑中。 同时,读者可以注意到我们的策略中没有直接涉及 ViewModel 或 View,这使得它在测试时更加灵活,可以检查导航。