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

news2024/11/14 18:50:10

在前一章节的实现中,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)
    }
    
}

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

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

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/834503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文阅读- Uncovering Coordinated Networks on Social Media:Methods and Case Studies

链接:https://arxiv.org/pdf/2001.05658.pdf 目录 摘要: 引言 Methods Case Study 1: Account Handle Sharing Coordination Detection 分析 Case Study 2: Image Coordination Coordination Detection Analysis Case Study 3: Hashtag Sequen…

Leetcode-每日一题【剑指 Offer 39. 数组中出现次数超过一半的数字】

题目 数组中有一个数字出现的次数超过数组长度的一半&#xff0c;请找出这个数字。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]输出: 2 限制&#xff1a; 1 < 数组长度 < 50000 解题思路 前置知…

手把手教新手最新Maven3.9.3下载安装配置

当前文档地址&#xff1a;有道云笔记 手把手教新手最新Maven3.9.3下载安装配置 1.下载 Maven官网&#xff1a;Maven – Maven Documentation 1.1打开主页&#xff0c;点击 Download 1.2 选择二进制压缩包&#xff0c;点击apache-maven-3.9.3-bin.zip进行下载 1.3 选择合适的…

租赁类小程序定制开发|租赁管理系统源码|免押租赁系统开发

随着互联网的发展&#xff0c;小程序成为了一种重要的移动应用开发方式。租赁小程序作为其中的一种类型&#xff0c;可以为很多行业提供便利和创新。下面我们将介绍一些适合开发租赁小程序的行业。   房屋租赁行业&#xff1a;租房小程序可以帮助房东和租户快速找到合适的租赁…

史上最全docker启动命令

docker Docker 启动镜像 一、查看当前docker中下载的镜像&#xff0c;如下图&#xff0c;当前我的Docker容器中存在两个镜像 &#xff0c;tomcat、mysql 二、启动镜像 (因启动命令参数过多&#xff0c;同时各种镜像启动时可以增加额外的参数&#xff0c;本次以启动mysql5.6为例…

Manage repositories按钮没有找到

今天安装PyCharm 2022.2.5版本没有发现Manage Repositories&#xff0c;如下图&#xff1a; 解决办法&#xff1a; 第一步&#xff1a;打开pycharm 根据图示按顺序点击底部内容 第二步 &#xff1a;点击加号&#xff0c;添加国内镜像源即可 第三步&#xff1a;国内镜像源地址 …

SpringBoot之Actuator基本使用

SpringBoot之Actuator基本使用 引入分类常用接口含义healthbeansconditionsheapdumpmappingsthreaddumploggersmetrics 引入 <!-- actuator start--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

【C++入门到精通】C++入门 —— string类(STL)

目录 一、STL简介 1.STL是什么 2.STL的内容 3.STL的使用前提 二、string类 1.string类 是什么 2.string类的特点和操做 ⭕构造和初始化 ⭕字符串大小和容量 ⭕字符访问和修改 ⭕字符串连接和拼接 ⭕子串操作 ⭕字符串比较 ⭕字符串修改 ⭕获取字符的ASCII码 ⭕…

音频光耦合器

音频光耦合器是一种能够将电信号转换为光信号并进行传输的设备。它通常由发光二极管&#xff08;LED&#xff09;和光敏电阻&#xff08;光电二极管或光敏电阻器&#xff09;组成。 在音频光耦合器中&#xff0c;音频信号经过放大和调节后&#xff0c;被转换为电流信号&#xf…

RISCV 5 RISC-V调用规则

RISCV 5 RISC-V调用规则 1 Register Convention1.1 Integer Register Convention1.2 Floating-point Register Convention 2. Procedure Calling Convention2.1 Integer Calling Convention2.2 Hardware Floating-point Calling Convention2.3 ILP32E Calling Convention2.4 Na…

web前端转正工作总结范文5篇

web前端转正工作总结&#xff08;篇1&#xff09; 来到__有限公司已经三个月了&#xff0c;目前的工作是前端开发&#xff0c;我是一名应届毕业生&#xff0c;之前没有过工作经验&#xff0c;在刚来到__这个大家庭的时候&#xff0c;我就被这里的工作气氛深深地吸引&#xff0…

【Spring】(三)Spring 使用注解存储和读取 Bean对象

文章目录 前言一、使用注解储存 Bean 对象1.1 配置扫描路径1.2 类注解储存 Bean 对象1.2.1 Controller&#xff08;控制器存储&#xff09;1.2.2 Service&#xff08;服务储存&#xff09;1.2.3 Repository&#xff08;仓库存储&#xff09;1.2.4 Component&#xff08;组件储存…

java+vue+springboot基于Web的研究生信息管理系统_u0974

随着社会的不断进步与发展&#xff0c;人们对生活质量要求逐步提升。如果开发一款基于web的研究生管理系统&#xff0c;可以让管理员、研究生和导师在最短的时间里享受到最好的服务&#xff1b;而开发本系统&#xff0c;又能够提高系统整体工作水平&#xff0c;简化工作程序&am…

TiDB Serverless 正式商用,全托管的云服务带来数据管理和应用程序开发的全新体验

八 年 前 &#xff0c;我们构建了 TiDB&#xff0c;一个开源分布式关系型数据库。 我们的目标是重新定义开发者和企业处理数据的方式&#xff0c;满足不断增长的可扩展性、灵活性和性能需求。 从那时起&#xff0c;PingCAP 便致力于为开发者和企业提供快速、灵活和规模化的数据…

flutter-GridView使用

先看效果 代码实现 import package:app/common/util/k_log_util.dart; import package:app/gen/assets.gen.dart; import package:app/pages/widget/top_appbar.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_…

机器学习深度学习——卷积的多输入多输出通道

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——从全连接层到卷积 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮…

GPT神器级插件Code Interpreter开放,这里有一份保姆级教程

# 关注并星标腾讯云开发者# 每周4 | 鹅厂一线程序员&#xff0c;为你“试毒”新技术# 第2期 | 腾讯李洛勤&#xff1a;测评 GPT4 Code Interpreter 体验 自 3 月份以来&#xff0c;人们对 GPT-4 API 的兴趣激增&#xff0c;“有数百万开发人员请求访问”。OpenAI 在一篇博客文章…

MySQL日志——慢查询日志

1.慢查询日志 默认情况下&#xff0c;不会记录管理语句&#xff0c;也不会记录不使用索引进行查找的查询。可以 使用log_slow_admin_statements和更改此行为log_queries_not_using_indexes&#xff0c;如下所述。 #慢查询日志 slow_query_log1 #执行时间参数 long_query_time…

Redis面试题-3

Redis面试题-3 14、什么是缓存击穿&#xff1f;该如何解决 缓存击穿是指一个Key非常热点&#xff0c;在不停的扛着大并发&#xff0c;大并发集中对这一个点进行访问&#xff0c;当这个Key在失效的瞬间&#xff0c;持续的大并发就穿破缓存&#xff0c;直接请求数据库&#xff…

SpringBoot整合Nacos实现配置中心

目录 1.Nacos定位 2.Nacos的安装配置 3.整合Nacos 3.1.Nacos安装 3.2.Nacos登录 3.3.配置Nacos 4.Nacos使用 4.1.引入 Maven 依赖 4.2.配置bootstrap.yml 4.3.Nacos配置类 4.4.启用Nacos服务 4.5.测试获取配置 5.加载多个配置文件 datasource-dev.yaml myba…