为Android构建现代应用——应用导航设计

在前一章节的实现中,Skeleton: Main structure,我们留下了几个 Jetpack 架构组件,这些组件将在本章中使用,例如 Composables、ViewModels、Navigation 和 Hilt。此外,我们还通过 Scaffold 集成了 TopAppBar 和 BottomAppBar UI 模式的基本结构。为了继续改进这个实现,我们需要添加一个新的关键选项卡,即"应用程序的通用状态"。

App程序的状态:一般状态

定义导航地图

导航从其他UI元素

摘要


应用程序状态:一个通用状态

"设计原则"中我们讨论了状态在现代 Android 应用程序中的重要作用。

设计中可能存在三种类型的状态:属性 UI 状态、组件 UI 状态和屏幕 UI 状态。

除了这些状态之外,我们还可以定义一种新类型的状态,即应用程序状态。

这个新状态将定义应用程序的通用状态。它将用于屏幕之间的导航、自发消息(snackbars)的呈现以及应用程序中其他可用的进程。

在主目录中,我们将定义一个名为 OrderNowState 的类,它将是我们的状态持有者,代表这种类型的状态。

接下来,我们像这样执行OrderNowState的初始实现:

Kotlin 复制代码
@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视图以包含之前定义的状态,如下所示:
Kotlin 复制代码
@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资源。

突出显示更改的代码行如下:

Kotlin 复制代码
  Scaffold(
                scaffoldState = appState.scaffoldState,
                topBar = { OrderNowTopBar() },
                bottomBar = { OrderNowBottomBar() }) { contentPadding ->
                println(contentPadding)

            }

有了上面的代码,我们现在可以告诉Scaffold它应该把哪个状态作为引用:应用程序的状态。之后,视图之间的导航操作、自发消息的呈现,以及视图只把AppState作为真实源的其他独占任务都将被允许。

既然已经为APP定义了状态,我们就可以继续实现APP的导航了。

定义导航地图

我们在应用程序中使用的导航策略由以下元素组成:

NavHost:它是负责在视图中显示导航结果的组件。导航的结果由NavigationController和导航图中给出的定义决定。

AppSoGraph:它是导航图的实现。它应该根据指定的路由将导航指向哪个视图或可组合对象。

**屏幕路由:**它们是可以通过导航到达的应用程序的不同屏幕。无论导航是从选项菜单、链接、按钮还是任何其他活动代理激活的,都无关紧要。每个屏幕都有一个与之关联的唯一路由。

一般导航图

我们将继续在OrderNow中包含这些元素

OrderNowScreenRoute

首先,创建一个名为common -> navigation的新遍历目录。在这个包中,我们像这样添加了一个名为OrderNowScreenRoute的类:

在这个类中,可以导航到的屏幕定义如下:

Kotlin 复制代码
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:

Kotlin 复制代码
@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菜单启用的选项。这个类将像这样放置在导航目录中:

它的实现是这样的:

Kotlin 复制代码
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类,如下所示:

Kotlin 复制代码
@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 的类,如下所示:

Kotlin 复制代码
@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 视图,如下所示:

Kotlin 复制代码
@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的实现如下:

Kotlin 复制代码
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执行,方式如下:

Kotlin 复制代码
@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 导航图如下:

Kotlin 复制代码
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中的性能如下所示:

Kotlin 复制代码
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)
    }
    
}

总结起来,前面的代码段中配置了以下导航定义(导航图):

  1. 从主页(Home screen)导航到产品列表(ProductList screen)。
  2. 从产品列表(ProductList screen)导航到产品详情(ProductDetail screen)。
  3. 从产品详情(ProductDetail screen)导航回到上一个呈现的屏幕。
  4. 从底部导航栏菜单导航到主页(Home screen)。
  5. 从底部导航栏菜单导航到购物车(Cart screen)。

到此为止,我们在 OrderNow 应用中已经实现了一个很好的基本导航。然而,还有一些东西缺失。 您有什么想法可能是缺失的吗? 我们需要在导航中包含数据或信息的传递。 由于在本章中设计了相关的实现,因此很容易实现数据传递。我们将在最后"实现功能"中详细探讨。

总结

本章中,我们完成了应用程序的主要部分的设计,这些将是以后添加功能的基础。 我们知道导航是应用程序的重要组成部分,必须从应用程序设计的开始就考虑它。 在本章中,我们采用了谷歌的建议,将状态纳入导航逻辑中。 同时,读者可以注意到我们的策略中没有直接涉及 ViewModel 或 View,这使得它在测试时更加灵活,可以检查导航。

相关推荐
Gary Studio1 小时前
Android AIDL HAL工程结构示例
android
y = xⁿ2 小时前
MySQL八股知识合集
android·mysql·adb
andr_gale2 小时前
04_rc文件语法规则
android·framework·aosp
祖国的好青年3 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴4 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭4 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首4 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil5 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙5 小时前
echarts,3d堆叠图
android·3d·echarts