繁体   English   中英

如何在 Jetpack Compose 中从 Screen 为 AppBar 做贡献

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM