使用 Compose Navigation 导航时 TopAppBar 闪烁

TopAppBar flashing when navigating with Compose Navigation

我有 2 个屏幕,它们都有自己的 ScaffoldTopAppBar。当我使用 Jetpack Navigation Compose 库在它们之间导航时,应用栏会闪烁。为什么会发生这种情况,我该如何摆脱它?

代码:

导航:

@Composable
fun TodoNavHost(
    navController: NavHostController,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = TodoScreen.TodoList.name,
        modifier = modifier
    ) {
        composable(TodoScreen.TodoList.name) {
            TodoListScreen(
                onTodoEditClicked = { todo ->
                    navController.navigate("${TodoScreen.AddEditTodo.name}?todoId=${todo.id}")
                },
                onFabAddNewTodoClicked = {
                    navController.navigate(TodoScreen.AddEditTodo.name)
                }
            )
        }
        composable(
            "${TodoScreen.AddEditTodo.name}?todoId={todoId}", 
            arguments = listOf(
                navArgument("todoId") {
                    type = NavType.LongType
                    defaultValue = -1L
                }
            )
        ) {
            AddEditTodoScreen(
                onNavigateUp = {
                    navController.popBackStack() 
                },
                onNavigateBackWithResult = { result ->
                    navController.navigate(TodoScreen.TodoList.name)
                }
            )
        }
    }
}

待办事项列表屏幕 ScaffoldTopAppBar:

@Composable
fun TodoListBody(
    todos: List<Todo>,
    todoExpandedStates: Map<Long, Boolean>,
    onTodoItemClicked: (Todo) -> Unit,
    onTodoCheckedChanged: (Todo, Boolean) -> Unit,
    onTodoEditClicked: (Todo) -> Unit,
    onFabAddNewTodoClicked: () -> Unit,
    onDeleteAllCompletedConfirmed: () -> Unit,
    modifier: Modifier = Modifier,
    errorSnackbarMessage: String = "",
    errorSnackbarShown: Boolean = false
) {

    var menuExpanded by remember { mutableStateOf(false) }
    var showDeleteAllCompletedConfirmationDialog by rememberSaveable { mutableStateOf(false) }

    Scaffold(
        modifier,
        topBar = {
            TopAppBar(
                title = { Text("My Todos") },
                actions = {
                    IconButton(
                        onClick = { menuExpanded = !menuExpanded },
                        modifier = Modifier.semantics {
                            contentDescription = "Options Menu"
                        }
                    ) {
                        Icon(Icons.Default.MoreVert, contentDescription = "Show menu")
                    }
                    DropdownMenu(
                        expanded = menuExpanded,
                        onDismissRequest = { menuExpanded = false }) {
                        DropdownMenuItem(
                            onClick = {
                                showDeleteAllCompletedConfirmationDialog = true
                                menuExpanded = false
                            },
                            modifier = Modifier.semantics {
                                contentDescription = "Option Delete All Completed"
                            }) {
                            Text("Delete all completed")
                        }
                    }
                }

            )
        },
[...]

Add/edit 屏幕 ScaffoldTopAppBar:

@Composable
fun AddEditTodoBody(
    todo: Todo?,
    todoTitle: String,
    setTitle: (String) -> Unit,
    todoImportance: Boolean,
    setImportance: (Boolean) -> Unit,
    onSaveClick: () -> Unit,
    onNavigateUp: () -> Unit,
    modifier: Modifier = Modifier
) {
    Scaffold(
        modifier,
        topBar = {
            TopAppBar(
                title = { Text(todo?.let { "Edit Todo" } ?: "Add Todo") },
                actions = {
                    IconButton(onClick = onSaveClick) {
                        Icon(Icons.Default.Save, contentDescription = "Save Todo")
                    }
                },
                navigationIcon = {
                    IconButton(onClick = onNavigateUp) {
                        Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                    }
                }
            )
        },
    ) { innerPadding ->
        BodyContent(
            todoTitle = todoTitle,
            setTitle = setTitle,
            todoImportance = todoImportance,
            setImportance = setImportance,
            modifier = Modifier.padding(innerPadding)
        )
    }
}

这是预期的行为。您正在为两个屏幕构建两个单独的应用程序栏,因此它们必然会闪烁。这不是正确的方法。正确的方法是实际将脚手架放在您的主 activity 中,并按其内容放置 NavHost。如果你想修改应用栏,创建变量来保存状态。然后从可组合项中修改它们。理想情况下,然后存储在视图模型中。这就是它在 compose 中的完成方式。通过变量。

谢谢

我在使用“每屏支架”架构时遇到了同样的问题。令我惊讶的是,有帮助的是将 androidx.navigation:navigation-compose 版本降低到 2.4.0-alpha04

闪烁是由 navigation-compose 库的较新版本中的 默认交叉淡入淡出动画 引起的。现在摆脱它的唯一方法(不降级依赖)是使用 Accompanist 动画库:

implementation "com.google.accompanist:accompanist-navigation-animation:0.20.0"

然后将正常的 NavHost 替换为伴奏者的 AnimatedNavHost,将 rememberNavController() 替换为 rememberAnimatedNavController() 并禁用过渡动画:

AnimatedNavHost(
        navController = navController,
        startDestination = bottomNavDestinations[0].fullRoute,
        enterTransition = { _, _ -> EnterTransition.None },
        exitTransition = { _, _ -> ExitTransition.None },
        popEnterTransition = { _, _ -> EnterTransition.None },
        popExitTransition = { _, _ -> ExitTransition.None },
        modifier = modifier,
    ) {
        [...}
    }

使用较新的库 implementation "com.google.accompanist:accompanist-navigation-animation:0.24.1-alpha" 你需要像这样 AnimatedNavHost

AnimatedNavHost(
            navController = navController,
            startDestination = BottomNavDestinations.TimerScreen.route,
            enterTransition = { EnterTransition.None },
            exitTransition = { ExitTransition.None },
            popEnterTransition = { EnterTransition.None },
            popExitTransition = { ExitTransition.None },
            modifier = Modifier.padding(innerPadding)

还有

rememberNavController()替换为rememberAnimatedNavController()

NavHost替换为AnimatedNavHost

import androidx.navigation.compose.navigation替换为import com.google.accompanist.navigation.animation.navigation

import androidx.navigation.compose.composable替换为import com.google.accompanist.navigation.animation.composable

除了删除动画之外,您还可以更改动画,例如:

@Composable
private fun ScreenContent() {
    val navController = rememberAnimatedNavController()
    val springSpec = spring<IntOffset>(dampingRatio = Spring.DampingRatioMediumBouncy)
    val tweenSpec = tween<IntOffset>(durationMillis = 2000, easing = CubicBezierEasing(0.08f, 0.93f, 0.68f, 1.27f))
    ...
    ) { innerPadding ->
        AnimatedNavHost(
            navController = navController,
            startDestination = BottomNavDestinations.TimerScreen.route,
            enterTransition = { slideInHorizontally(initialOffsetX = { 1000 }, animationSpec = springSpec) },
            exitTransition = { slideOutHorizontally(targetOffsetX = { -1000 }, animationSpec = springSpec) },
            popEnterTransition = { slideInHorizontally(initialOffsetX = { 1000 }, animationSpec = tweenSpec) },
            popExitTransition = { slideOutHorizontally(targetOffsetX = { -1000 }, animationSpec = tweenSpec) },
            modifier = Modifier.padding(innerPadding)
    ) {}

我想我找到了解决该问题的简单方法(适用于 Compose 版本 1.4.0)。

我的设置 - 闪烁

我的所有屏幕都有自己的工具栏包裹在脚手架中:

// Some Composable screnn

Scaffold(
    topBar = { TopAppBar(...) }
) {
    ScreenContent()
}

包含导航主机的主要 activity 定义如下:

// Activity with NavHost

setContent {
    AppTheme {
        NavHost(...) { }
    }
}

解决方案 - 不闪烁!

将您的 NavHost 包裹在 activity 的 Surface 中:

setContent {
    AppTheme {
        Surface {
            NavHost(...) { }
        }
    }
}

其余屏幕保持不变。目的地之间没有闪烁和过渡动画几乎与片段相同(微妙的淡入淡出in/fade)。到目前为止,我还没有发现任何负面影响。