小程序网站导航源码分享(小程序地图导航开发)

这篇文章给大家聊聊关于小程序网站导航源码分享,以及小程序地图导航开发对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。

在JetpackCompose中实现干净的导航

Android世界中的每个人都知道JetpackCompose中的导航并不是它最亮的一面。执行导航需要传递很多回调和navController,如果在发送参数之前需要做一些业务逻辑怎么办?代码变得非常混乱。

但是,如果您不想依赖其他人,或者您公司的政策是不使用外部库怎么办?导航是应用程序中最重要的事情之一。你不能依赖别人,即使那个库是24/7维护的。您需要创建一个效果很好的解决方案。在完成我的干净解决方案之前,让我们深入研究这个问题,以便我们100%了解我们在这里所做的事情。

问题简介

当前最常见的导航实现是这样的:

valnavController=rememberNavController()\n\nNavHost(\nnavController=navController,\nstartDestination=&34;\n){\ncomposable(route=&34;){\nHomeScreen(\nnavigatToUsersScreen={navController.navigate(&34;)},\nnavigatToMessagesScreen={navController.navigate(&34;)},\nnavigatToDetailsScreen={navController.navigate(&34;)}\n)\n}\ncomposable(route=&34;){\nUsersScreen(\nnavigateBack={navController.navigateUp()},\nnavigatToUserDetailsScreen={navController.navigate(&34;)}\n)\n}\ncomposable(route=&34;){\nUserDetailsScreen(\nnavigateBack={navController.navigateUp()}\n)\n}\ncomposable(route=&34;){\nMessagesScreen(\nnavigateBack={navController.navigateUp()}\n)\n}\ncomposable(route=&34;){\nDetailsScreen(\nnavigateBack={navController.navigateUp()}\n)\n}\n}

你传递了lambda,就是这样。第二个选项是你传递navController而不是lambdas。任何一种解决方案看起来都不是那么好。如果屏幕足够复杂,它可能有太多的回调。你的代码变得很乱。也许你围绕路由的逻辑没有像这样硬编码,但你明白了问题的关键。

如果你需要做一些业务逻辑,比如计算一些东西,计算的结果是下一个屏幕的参数怎么办?您需要调用ViewModel来执行业务逻辑(View不应该这样做),观察结果然后调用回调。Screen和ViewModel之间的前后关系过多。

让我们尝试解决所有这些问题,让它变得更干净一点。

JetpackCompose清洁导航

我的解决方案的想法是拥有一个自定义导航器,它将提供给每个ViewModel。通过调用导航器的函数,我们导航到不同的屏幕。所有导航事件都收集在MainScreen中,这样我们就不需要将回调或navController传递给其他屏幕。看完代码就更清楚了。

首先,让我们为路由创建一个特殊的类:

sealedclassDestination(protectedvalroute:String,varargparams:String){\nvalfullRoute:String=if(params.isEmpty())routeelse{\nvalbuilder=StringBuilder(route)\nparams.forEach{builder.append(&34;)}\nbuilder.toString()\n}\n\nsealedclassNoArgumentsDestination(route:String):Destination(route){\noperatorfuninvoke():String=route\n}\n\nobjectHomeScreen:NoArgumentsDestination(&34;)\n\nobjectUsersScreen:NoArgumentsDestination(&34;)\n\nobjectMessagesScreen:NoArgumentsDestination(&34;)\n\nobjectDetailsScreen:NoArgumentsDestination(&34;)\n\nobjectUserDetailsScreen:Destination(&34;,&34;,&34;){\nconstvalFIST_NAME_KEY=&34;\nconstvalLAST_NAME_KEY=&34;\n\noperatorfuninvoke(fistName:String,lastName:String):String=route.appendParams(\nFIST_NAME_KEYtofistName,\nLAST_NAME_KEYtolastName\n)\n}\n}\n\ninternalfunString.appendParams(varargparams:Pair<String,Any?>):String{\nvalbuilder=StringBuilder(this)\n\nparams.forEach{\nit.second?.toString()?.let{arg->\nbuilder.append(&34;)\n}\n}\n\nreturnbuilder.toString()\n}

Destination有一个带有两个参数的构造函数。第一个是基本路由,第二个是该路由的参数。每个目的地都会有路由和全路由。该路由是没有参数的基本路由,将使用该路由创建带有参数名称的fullRoute或带有参数值的fullRoute。

调用Destination将返回其路由。appendParams函数只会将参数添加到路由并返回带有参数值的fullRoute。

接下来是添加一些我们将要使用的导航组合。

@Composable\nfunNavHost(\nnavController:NavHostController,\nstartDestination:Destination,\nmodifier:Modifier=Modifier,\nroute:String?=null,\nbuilder:NavGraphBuilder.()->Unit\n){\nNavHost(\nnavController=navController,\nstartDestination=startDestination.fullRoute,\nmodifier=modifier,\nroute=route,\nbuilder=builder\n)\n}\n\nfunNavGraphBuilder.composable(\ndestination:Destination,\narguments:List<NamedNavArgument>=emptyList(),\ndeepLinks:List<NavDeepLink>=emptyList(),\ncontent:@Composable(NavBackStackEntry)->Unit\n){\ncomposable(\nroute=destination.fullRoute,\narguments=arguments,\ndeepLinks=deepLinks,\ncontent=content\n)\n}

NavHost与androidx.navigation.compose中的NavHost相同,唯一的区别是startDestination参数是Destination类型。

与可组合而不是路由相同的事情:字符串我们有目的地:目的地。

现在让我们实现那个自定义导航器。这是代码:

interfaceAppNavigator{\nvalnavigationChannel:Channel<NavigationIntent>\n\nsuspendfunnavigateBack(\nroute:String?=null,\ninclusive:Boolean=false,\n)\n\nfuntryNavigateBack(\nroute:String?=null,\ninclusive:Boolean=false,\n)\n\nsuspendfunnavigateTo(\nroute:String,\npopUpToRoute:String?=null,\ninclusive:Boolean=false,\nisSingleTop:Boolean=false,\n)\n\nfuntryNavigateTo(\nroute:String,\npopUpToRoute:String?=null,\ninclusive:Boolean=false,\nisSingleTop:Boolean=false,\n)\n}\n\nsealedclassNavigationIntent{\ndataclassNavigateBack(\nvalroute:String?=null,\nvalinclusive:Boolean=false,\n):NavigationIntent()\n\ndataclassNavigateTo(\nvalroute:String,\nvalpopUpToRoute:String?=null,\nvalinclusive:Boolean=false,\nvalisSingleTop:Boolean=false,\n):NavigationIntent()\n}

AppNavigator有navigationChannel将被收集在MainScreen中,它有四个导航功能。NavigationIntent包含所有可能发生的导航意图。您可以在此处添加更多,例如,一个用于深层链接或类似的东西。navController函数需要每个NavigationIntent的参数。

AppNavigator的实现非常简单。只需将NavigationIntents发送到navigationChannel。

classAppNavigatorImpl@Injectconstructor():AppNavigator{\noverridevalnavigationChannel=Channel<NavigationIntent>(\ncapacity=Int.MAX_VALUE,\nonBufferOverflow=BufferOverflow.DROP_LATEST,\n)\n\noverridesuspendfunnavigateBack(route:String?,inclusive:Boolean){\nnavigationChannel.send(\nNavigationIntent.NavigateBack(\nroute=route,\ninclusive=inclusive\n)\n)\n}\n\noverridefuntryNavigateBack(route:String?,inclusive:Boolean){\nnavigationChannel.trySend(\nNavigationIntent.NavigateBack(\nroute=route,\ninclusive=inclusive\n)\n)\n}\n\noverridesuspendfunnavigateTo(\nroute:String,\npopUpToRoute:String?,\ninclusive:Boolean,\nisSingleTop:Boolean\n){\nnavigationChannel.send(\nNavigationIntent.NavigateTo(\nroute=route,\npopUpToRoute=popUpToRoute,\ninclusive=inclusive,\nisSingleTop=isSingleTop,\n)\n)\n}\n\noverridefuntryNavigateTo(\nroute:String,\npopUpToRoute:String?,\ninclusive:Boolean,\nisSingleTop:Boolean\n){\nnavigationChannel.trySend(\nNavigationIntent.NavigateTo(\nroute=route,\npopUpToRoute=popUpToRoute,\ninclusive=inclusive,\nisSingleTop=isSingleTop,\n)\n)\n}\n}

这里有个简短的说明,我使用Dagger-Hilt作为DI框架。随意使用任何DI框架。

现在让我们实现MainScreen:

@Composable\nfunMainScreen(\nmainViewModel:MainViewModel=hiltViewModel()\n){\nvalnavController=rememberNavController()\n\nNavigationEffects(\nnavigationChannel=mainViewModel.navigationChannel,\nnavHostController=navController\n)\nMediumReposTheme{\nSurface(\nmodifier=Modifier.fillMaxSize(),\ncolor=MaterialTheme.colors.background\n){\nNavHost(\nnavController=navController,\nstartDestination=Destination.HomeScreen\n){\ncomposable(destination=Destination.HomeScreen){\nHomeScreen()\n}\ncomposable(destination=Destination.UsersScreen){\nUsersScreen()\n}\ncomposable(destination=Destination.UserDetailsScreen){\nUserDetailsScreen()\n}\ncomposable(destination=Destination.MessagesScreen){\nMessagesScreen()\n}\ncomposable(destination=Destination.DetailsScreen){\nDetailsScreen()\n}\n}\n}\n}\n}\n\n@Composable\nfunNavigationEffects(\nnavigationChannel:Channel<NavigationIntent>,\nnavHostController:NavHostController\n){\nvalactivity=(LocalContext.currentas?Activity)\nLaunchedEffect(activity,navHostController,navigationChannel){\nnavigationChannel.receiveAsFlow().collect{intent->\nif(activity?.isFinishing==true){\nreturn@collect\n}\nwhen(intent){\nisNavigationIntent.NavigateBack->{\nif(intent.route!=null){\nnavHostController.popBackStack(intent.route,intent.inclusive)\n}else{\nnavHostController.popBackStack()\n}\n}\nisNavigationIntent.NavigateTo->{\nnavHostController.navigate(intent.route){\nlaunchSingleTop=intent.isSingleTop\nintent.popUpToRoute?.let{popUpToRoute->\npopUpTo(popUpToRoute){inclusive=intent.inclusive}\n}\n}\n}\n}\n}\n}\n}

在MainScreen中,我们使用自定义NavHost和可组合。我们记得我们传递给NavigationEffects的navController,以及来自MainViewModel的navigationChannel。NavigationEffects只是收集navigationChannel并导航到所需的屏幕。如您所见,它更干净,我们不必传递任何回调或navController。

MainViewModel很简单。刚刚从AppNavigator获取navigationChannel。

@HiltViewModel\nclassMainViewModel@Injectconstructor(\nappNavigator:AppNavigator\n):ViewModel(){\n\nvalnavigationChannel=appNavigator.navigationChannel\n}

唯一要展示的是我们如何调用导航器函数。让我们看一下HomeViewModel的例子。

@HiltViewModel\nclassHomeViewModel@Injectconstructor(\nprivatevalappNavigator:AppNavigator\n):ViewModel(){\n\nfunonNavigateToUsersButtonClicked(){\nappNavigator.tryNavigateTo(Destination.UsersScreen())\n}\n\nfunonNavigateToMessagesButtonClicked(){\nappNavigator.tryNavigateTo(Destination.MessagesScreen())\n}\n\nfunonNavigateToDetailsButtonClicked(){\nappNavigator.tryNavigateTo(Destination.DetailsScreen())\n}\n}

HomeScreen将调用相应的函数,在HomeViewModel中我们只调用AppNavigator函数并作为我们调用Destination的路由的参数。

查看UsersViewModel以查看导航返回并将参数传递给路由的示例。

@HiltViewModel\nclassUsersViewModel@Injectconstructor(\nprivatevalappNavigator:AppNavigator\n):ViewModel(){\n\nprivateval_viewState=MutableStateFlow(UsersViewState())\nvalviewState=_viewState.asStateFlow()\n\nfunonBackButtonClicked(){\nappNavigator.tryNavigateBack()\n}\n\nfunonUserRowClicked(user:User){\nappNavigator.tryNavigateTo(\nDestination.UserDetailsScreen(\nfistName=user.firstName,\nlastName=user.lastName\n)\n)\n}\n}

就是这样!

结论

我认为仍有改进的空间,但这可能是一个很好的起点。它使我们的代码更加简洁,并且无需在屏幕中收集ViewModel的副作用,以便我们可以导航。

我们可以争论是否应该从ViewModel完成导航,但我认为应该这样做。视图应该是“愚蠢的”,并且只显示数据。我们在点击时所做的,应该是ViewModel的责任。

关注七爪网,获取更多APP/小程序/网站源码资源!

好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平