具有 TopAppBar 与导航集成的脚手架

Scaffold with TopAppBar integration with Navigation

如何根据 NavController 中的实际位置使用 ScaffoldTopAppBar 中显示导航图标(后退箭头或菜单)?我正在使用 Navigating with Compose 1.0.0-alpha02。下面是一个示例代码,其中描述了它应该如何工作

@Composable
fun App()
{
    val navController = rememberNavController()

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = "App title") },
                navigationIcon = {

                    /*
                    Check if navController back stack has more
                    than one element. If so show BackButton.
                    Clicking on that button will move back
                     */

                    val canMoveBack = true

                    if (canMoveBack)
                    {
                        IconButton(onClick = {
                            // Move back
                            navController.popBackStack()
                        }) {
                            Icon(asset = Icons.Outlined.ArrowBack)
                        }
                    } 
                    else
                    {
                        IconButton(onClick = {
                            // show NavDrawer
                        }) {
                            Icon(asset = Icons.Outlined.Menu)
                        }
                    }
                },
            )
        },
        bodyContent = {
            AppBody(navController)
        }
    )
}

我想过 navController.backStack.size 之类的东西,但我得到了错误 NavController.getBackStack can only be called from within the same library group (groupId=androidx.navigation)

第二个问题,如果我想更改 TopAppBar 文本,我是否必须提升此文本并为每个“屏幕”提供更改此文本的可能性,或者是否有任何简单的内置方法像在标准视图系统中那样执行此操作?

我今天只是想达到完全一样的效果。我认为这段代码将回答这两个问题:

@Composable
fun NavigationScaffold(
    title: String? = null,
    navController: NavController? = null,
    bodyContent: @Composable (PaddingValues) -> Unit
) {
    val navigationIcon: (@Composable () -> Unit)? =
        if (navController?.previousBackStackEntry != null) {
            {
                IconButton(onClick = {
                    navController.popBackStack()
                }) {
                    Icon(Icons.Filled.ArrowBack)
                }
            }
        } else {
            // this can be null or another component
            // If null, the navigationIcon won't be rendered at all
            null
        }


    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = title.orEmpty())
                },
                navigationIcon = navigationIcon,
            )
        },
        bodyContent = bodyContent
    )
}

如您所见,您可以将标题作为字符串提供,这样您就不必担心传递文本可组合项。

这个脚手架是我所有屏幕的基础,要使用它,只需编写如下内容:

@Composable
fun Home(navController: NavController) {
    NavigationScaffold(
            title = "Home",
            navController = navController
        ) {
        // Screen's content...
    }
}

多亏了 Abdelilah El Aissaoui,我知道了如何用一个 Scaffold 来完成它并且只是改变 bodyContent。在这个方法中,我们不必将 navController 传递给任何 body 元素,一切都在基础 App 可组合中完成。下面是可以在两个物体之间导航的代码(课程 -> 学生)

应用程序:

@Composable
fun App(
    viewModel: MainViewModel
)
{
    val navController = rememberNavController()

    val baseTitle = "" // stringResource(id = R.string.app_name)
    val (title, setTitle) = remember { mutableStateOf(baseTitle) }

    val (canPop, setCanPop) = remember { mutableStateOf(false) }

    val scaffoldState: ScaffoldState = rememberScaffoldState()

    navController.addOnDestinationChangedListener { controller, _, _ ->
        setCanPop(controller.previousBackStackEntry != null)
    }

    // check navigation state and navigate
    if (viewModel.navigateToStudents.value)
    {
        navController.navigate(route = STUDENT_SCREEN_ROUTE)
        viewModel.studentsNavigated()
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = title) },
                navigationIcon = {

                    if (canPop)
                    {
                        IconButton(onClick = {
                            navController.popBackStack()
                        }) {
                            Icon(asset = Icons.Outlined.ArrowBack)
                        }
                    }
                    else
                    {
                        IconButton(onClick = {
                            scaffoldState.drawerState.open()
                        }) {
                            Icon(asset = Icons.Outlined.Menu)
                        }
                    }
                },
            )
        },
        scaffoldState = scaffoldState,
        drawerContent = {
            DrawerContent()
        },
        bodyContent = {
            AppBody(
                viewModel = viewModel,
                navController = navController,
                setTitle = setTitle
            )
        }
    )
}

AppBody

@Composable
fun AppBody(
    viewModel: MainViewModel,
    navController: NavHostController,
    setTitle: (String) -> Unit,
)
{
    NavHost(
        navController,
        startDestination = LESSON_SCREEN_ROUTE
    ) {
        composable(route = LESSON_SCREEN_ROUTE) {
            LessonBody(
                viewModel = viewModel,
                setTitle = setTitle
            )
        }
        composable(
            route = STUDENT_SCREEN_ROUTE
        ) {
            StudentBody(
                viewModel = viewModel,
                setTitle = setTitle
            )
        }
    }
}

在 ViewModel 中,我使用此模式进行导航:

private val _navigateToStudents: MutableState<Boolean> = mutableStateOf(false)
val navigateToStudents: State<Boolean> = _navigateToStudents

fun studentsNavigated()
{
    // here we can add any logic after doing navigation
    _navigateToStudents.value = false
}

所以当我想导航到下一个片段时,我只需设置 _navigateToStudents.value = true