[英]How to contribute to AppBar from Screen in jetpack compose
我想实现一个简单的用户流程,用户可以看到多个屏幕来输入数据。 流程应该共享一个公共导航栏,其中每个屏幕都可以在它处于活动状态时为其贡献其菜单项(例如添加“搜索”或“下一步”按钮)。 导航栏也有概念上属于用户流的按钮,而不是单独的屏幕(如后退按钮和关闭按钮)。 屏幕应该在其他上下文中可重用,因此屏幕不应该知道它们在其中运行的流程。
从技术上讲,用户流被实现为定义导航栏并使用组合导航的组合 function。 每个屏幕都实现为单独的组合 function。在基于片段/视图的 Android 中,此场景通过onCreateOptionsMenu
和相关功能开箱即用。 但是我将如何在撰写中做到这一点? 我找不到关于该主题的任何指导。
用代码来说明问题:
@Composable
fun PaymentCoordinator(
navController: NavHostController = rememberNavController()
) {
AppTheme {
Scaffold(
bottomBar = {
BottomAppBar(backgroundColor = Color.Red) {
IconButton(onClick = navController::popBackStack) {
Icon(Icons.Filled.ArrowBack, "Back")
}
Spacer(modifier = Modifier.weight(1f))
// 0..n IconButtons provided by the active Screen
// should be inserted here
// How can we do that, because state should never
// go up from child to parent
// this button (or at least its text and onClick action) should
// be defined by the currently visible Screen as well
Button(
onClick = { /* How to call function of screen? */ }
) {
Text("Next"))
}
}
}
) { padding ->
Box(modifier = Modifier.padding(padding)) {
NavHost(
navController = navController,
startDestination = "selectAccount"
) {
// screens that can contribute items to the menu
composable("selectAccount") {
AccountSelectionRoute(
onAccountSelected = {
navController.navigate("nextScreen")
}
)
}
composable("...") {
// ...
}
}
}
}
}
}
我想出了一种利用副作用和生命周期监听器来实现我的目标的方法。 基本上只要屏幕变为活动状态 (ON_START),它就会通知父级(协调器)其菜单配置。 协调器评估配置并相应地更新导航栏。
该方法基于谷歌关于副作用的文档 ( https://developer.android.com/jetpack/compose/side-effects#disposableeffect ) 该方法感觉复杂和笨拙,我认为 compose 框架缺少一些功能来实现这个这里。 但是,我的实现似乎在我的测试用例中运行良好。
辅助类
// currently I only need to configure a single button, however the approach
// can be easily extended now (you can put anything inside rightButton)
data class MenuConfiguration(
val rightButton: @Composable () -> Unit
)
@Composable
fun SimpleMenuConfiguration(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration: () -> Unit,
rightButton: @Composable () -> Unit
) {
val currentOnRegisterMenuConfiguration by rememberUpdatedState(onRegisterMenuConfiguration)
val currentOnUnregisterMenuConfiguration by rememberUpdatedState(onUnregisterMenuConfiguration)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
currentOnRegisterMenuConfiguration(
MenuConfiguration(
rightButton = rightButton
)
)
} else if (event == Lifecycle.Event.ON_STOP) {
currentOnUnregisterMenuConfiguration()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
协调员级别
@Composable
fun PaymentCoordinator(
navController: NavHostController = rememberNavController()
) {
var menuConfiguration by remember { mutableStateOf<MenuConfiguration?>(null) }
AppTheme {
Scaffold(
bottomBar = {
BottomAppBar(backgroundColor = Color.Red) {
IconButton(onClick = navController::popBackStack) {
Icon(Icons.Filled.ArrowBack, "Back")
}
Spacer(modifier = Modifier.weight(1f))
menuConfiguration?.rightButton?.invoke()
}
}
) { padding ->
Box(modifier = Modifier.padding(padding)) {
PaymentNavHost(
navController = navController,
finishedHandler = finishedHandler,
onRegisterMenuConfiguration = { menuConfiguration = it },
onUnregisterMenuConfiguration = { menuConfiguration = null }
)
}
}
}
}
@Composable
fun PaymentNavHost(
navController: NavHostController = rememberNavController(),
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration:() -> Unit
) {
NavHost(
navController = navController,
startDestination = "selectAccount"
) {
composable("selectAccount") {
DemoAccountSelectionRoute(
onAccountSelected = {
navController.navigate("amountInput")
},
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration
)
}
composable("amountInput") {
AmountInputRoute(
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration,
onFinished = {
...
}
)
}
}
}
屏幕等级
@Composable
internal fun AmountInputRoute(
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration:() -> Unit,
onFinished: (Amount?) -> Unit
) {
SimpleMenuConfiguration(
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration,
rightButton = {
Button(
onClick = {
...
}
) {
Text(text = stringResource(id = R.string.next))
}
}
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.