为Android构建现代应用——应用导航设计
在前一章节的实现中,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.currentreturn 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?.destinationBottomNavigation(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} == trueBottomNavigationItem(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 = truerestoreState = 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 =truepopUpTo(popUp){inclusive =true}}
}fun OrderNowState.navigateSaved(route:String,popUp:String){navController.navigate(route){launchSingleTop =truerestoreState =truepopUpTo(popUp){saveState =true}}
}fun OrderNowState.clearAndNavigate(route:String){navController.navigate(route){launchSingleTop =truepopUpTo(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.routeval listRoute= OrderNowScreenRoute.ProductList.routeval detailRoute =OrderNowScreenRoute.ProductDetail.routeval 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,这使得它在测试时更加灵活,可以检查导航。
相关文章:
为Android构建现代应用——应用导航设计
在前一章节的实现中,Skeleton: Main structure,我们留下了几个 Jetpack 架构组件,这些组件将在本章中使用,例如 Composables、ViewModels、Navigation 和 Hilt。此外,我们还通过 Scaffold 集成了 TopAppBar 和 BottomA…...
聊聊 Docker 和 Dockerfile
目录 一、前言 二、了解Dockerfile 三、Dockerfile 指令 四、多阶段构建 五、Dockerfile 高级用法 六、小结 一、前言 对于开发人员来说,会Docker而不知道Dockerfile等于不会Docker,上一篇文章带大家学习了Docker的基本使用方法:《一文…...
element表格+表单+表单验证结合u
一、结果展示 1、图片 2、描述 table中放form表单,放输入框或下拉框或多选框等; 点击添加按钮,首先验证表单,如果存在没填的就验证提醒,都填了就向下添加一行表单表格; 点击当前行删除按钮,…...
数据库:MYSQL参数max_allowed_packet 介绍
1、参数作用 max_allowed_packet参数是指mysql服务器端和客户端在一次传送数据包的过程当中最大允许的数据包大小。如果超过了设置的最大长度,则会数据库保持数据失败。 2、问题场景 ● 有时候业务的需要,可能会存在某些字段数据长度非常大(比如富文本编辑器里面的内容),…...
基于DiscordMidjourney API接口实现文生图
https://discord.com/api/v9/interactions 请求头: authorization:取自 浏览器中discord 文生图请求头中的 authorization 的值 Content-Type:application/json 请求体: {“type”:2,“application_id”:“93692956130267xxxx”,“guild_id”:“1135900…...
springboot+vue农产品特产商城销售平台_50kf2 多商家
随着我国经济的高速发展与人们生活水平的日益提高,人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下,人们更趋向于足不出户解决生活上的问题,南阳特产销售平台展现了其蓬勃生命力和广阔的前景。与此同时,为解决…...
【深度学习_TensorFlow】感知机、全连接层、神经网络
写在前面 感知机、全连接层、神经网络是什么意思? 感知机: 是最简单的神经网络结构,可以对线性可分的数据进行分类。 全连接层: 是神经网络中的一种层结构,每个神经元与上一层的所有神经元相连接,实现全连接。 神经…...
软件测试(功能、接口、性能、自动化)详解
一、软件测试功能测试 测试用例编写是软件测试的基本技能;也有很多人认为测试用例是软件测试的核心;软件测试中最重要的是设计和生成有效的测试用例;测试用例是测试工作的指导,是软件测试的必须遵守的准则。 黑盒测试常见测试用…...
Oracle表段中的高水位线HWM
在Oracle数据的存储中,可以把存储空间想象为一个水库,数据想象为水库中的水。水库中的水的位置有一条线叫做水位线,在Oracle中,这条线被称为高水位线(High-warter mark, HWM)。在数据库表刚建立的时候&…...
【福建事业单位-推理判断】03类别推理
【福建事业单位-推理判断】03类别推理 一、类别推理1.1语义关系考点一、近义反义关系(不需要严格的,意思相近即可)近义反义的二级辨析(感情色彩)考点二:比喻义、象征义 1.2 逻辑关系1.2.1全同关系ÿ…...
Leetcode-每日一题【剑指 Offer 05. 替换空格】
题目 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 示例 1: 输入:s "We are happy."输出:"We%20are%20happy." 限制: 0 < s 的长度 < 10000 解题思路 前置知识 Str…...
zookeeper+kafka分布式消息队列集群的部署
目录 一、zookeeper 1.Zookeeper 定义 2.Zookeeper 工作机制 3.Zookeeper 特点 4.Zookeeper 数据结构 5.Zookeeper 应用场景 (1)统一命名服务 (2)统一配置管理 (3)统一集群管理 (4&…...
VR全景旅游,智慧文旅发展新趋势!
引言: VR全景旅游正在带领我们踏上一场全新的旅行体验。这种沉浸式的旅行方式,让我们可以足不出户,却又身临其境地感受世界各地的美景。 一.VR全景旅游是什么? VR全景旅游是一种借助虚拟现实技术,让用户…...
详解EMBER数据集中对PE文件提取ByteEntropyHistogram特征
1. 引入 在我们对PE文件提取特征时,经常会在PE特征工程的项目中,看到如下这段代码 class ByteEntropyHistogram(FeatureType): 2d byte/entropy histogram based loosely on (Saxe and Berlin, 2015).This roughly approximates the joint probability…...
垃圾回收机制和常用的算法
一.什么是垃圾回收? 垃圾回收主要针对堆和方法区(非堆),程序计数器,虚拟机栈,本地方法栈这三个区域属于线程私有,随着线程的销毁,自然就会雄安会了,因此不需要堆着三个区域进行垃圾…...
【PostgreSQL】系列之 一 schema详解(二)
🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的…...
性能优化-react路由懒加载和组件懒加载
背景 随着项目越来越大,打包后的包体积也越来越大,严重影响了首屏加载速度,需要对路由和组件做懒加载处理 主要用到了react中的lazy和Suspense。 废话不多说,直接上干货 路由懒加载 核心代码 import React, { lazy, Suspens…...
静态网页加速器:优化性能和交付速度的 Node.js 最佳实践
如何使用 Node.js 发布静态网页 在本文中,我们将介绍如何使用 Node.js 来发布静态网页。我们将创建一个简单的 Node.js 服务器,将 HTML 文件作为响应发送给客户端。这是一个简单而灵活的方法,适用于本地开发和轻量级应用。 1、创建静态网页…...
Spring 非自定义Bean注解
Spring 非自定义Bean注解 1.概述 在xml中配置的Bean都是自己定义的, 例如:UserDaolmpl,UserServicelmpl。但是,在实际开发中有些功能类并不是我们自己定义的, 而是使用的第三方jar包中的,那么,…...
微信小程序:点击按钮实现数据加载(带模糊查询)
效果图 代码 wxml: <!-- 搜索框--> <form action"" bindsubmit"search_all_productiond"><view class"search_position"><view class"search"><view class"search_left">工单号:</view…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
