【问题标题】:How to contribute to AppBar from Screen in jetpack compose如何在 Jetpack Compose 中从 Screen 为 AppBar 做贡献
【发布时间】:2022-12-14 22:53:38
【问题描述】:

我想实现一个简单的用户流程,用户可以看到多个屏幕来输入数据。该流程应该共享一个公共导航栏,其中每个屏幕都可以在其处于活动状态时贡献其菜单项(例如添加“搜索”或“下一步”按钮)。导航栏也有概念上属于用户流的按钮,而不是单独的屏幕(如后退按钮和关闭按钮)。屏幕应该可以在其他上下文中重复使用,因此屏幕不应该知道它们在其中运行的流程。

从技术上讲,用户流是作为定义导航栏和使用组合导航的组合函数实现的。每个屏幕都作为一个单独的组合函数实现。 在基于片段/视图的 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("...") {
                        // ...
                    }
                }
            }
        }
    }
}

【问题讨论】:

    标签: android android-jetpack-compose android-jetpack-navigation


    【解决方案1】:

    我想出了一种利用副作用和生命周期监听器来实现我的目标的方法。基本上,只要屏幕变为活动状态 (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 DemoAmountInputRoute(
        onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
        onUnregisterMenuConfiguration:() -> Unit,
        onFinished: (Amount?) -> Unit
    ) {
    
        SimpleMenuConfiguration(
            onRegisterMenuConfiguration = onRegisterMenuConfiguration,
            onUnregisterMenuConfiguration = onUnregisterMenuConfiguration,
            rightButton = {
                Button(
                    onClick = {
                        ...
                    }
                ) {
                    Text(text = stringResource(id = R.string.next))
                }
            }
        )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-07
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 1970-01-01
      • 2023-01-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多