简体   繁体   English

使用 Compose Navigation 导航时 TopAppBar 闪烁

[英]TopAppBar flashing when navigating with Compose Navigation

I have 2 screens which both have their own Scaffold and TopAppBar .我有 2 个屏幕,它们都有自己的ScaffoldTopAppBar When I navigate between them using the Jetpack Navigation Compose library, the app bar flashes.当我使用 Jetpack Navigation Compose 库在它们之间导航时,应用栏会闪烁。 Why does it happen and how can I get rid of this?为什么会发生这种情况,我怎样才能摆脱这种情况?

在此处输入图像描述

Code:代码:

Navigation:导航:

@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)
                }
            )
        }
    }
}

Todo list screen Scaffold with TopAppBar :带有TopAppBar的 Todo 列表屏幕Scaffold

@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 screen Scaffold with TopAppBar :使用TopAppBar添加/编辑屏幕Scaffold

@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)
        )
    }
}

I think I found an easy solution for that issue (works on Compose version 1.4.0).我想我找到了解决该问题的简单方法(适用于 Compose 1.4.0 版)。

My setup - blinking我的设置 - 闪烁

All of my screens have their own toolbar wrapped in the scaffold:我所有的屏幕都有自己的工具栏包裹在脚手架中:

// Some Composable screnn

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

Main activity which holds the nav host is defined like that:拥有导航主机的主要活动定义如下:

// Activity with NavHost

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

Solution - no blinking!解决方案 - 不要眨眼!

Wrap you NavHost in activity in a Surface:将 NavHost 包裹在 Surface 的活动中:

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

Rest of the screens stay the same. Rest 的屏幕保持不变。 No blinking and transition animation between destinations is almost the same like it was with fragments (subtle fade in/fade out).目的地之间没有闪烁和过渡 animation 几乎与片段相同(微妙的淡入/淡出)。 So far I haven't found any negative side effects of that.到目前为止,我还没有发现任何负面影响。

I got the same issue having a "scaffold-per-screen" architecture.我遇到了同样的问题,有一个“每个屏幕的脚手架”架构。 What helped, to my surprise, was lowering androidx.navigation:navigation-compose version to 2.4.0-alpha04 .令我惊讶的是,将androidx.navigation:navigation-compose版本降低到2.4.0-alpha04

In order not to blink (or to slide if you have AnimatedNavHost ) you should put the TopAppBar in the activity and outside the NavHost , otherwise the TopAppBar is just part of the screen and makes transitions like every other screen elements:为了不闪烁(或者如果你有AnimatedNavHost则滑动)你应该把TopAppBar放在活动中和NavHost之外,否则TopAppBar只是屏幕的一部分并且像其他屏幕元素一样进行转换:

// Activity with your navigation host
setContent {
    MyAppTheme {
        Scaffold(
            topBar = { TopAppBar(...) }
        ) { padding ->
            TodoNavHost(padding, ...) { }
        }
    }
}

From the Scaffold containing the TopAppBar comes the padding parameter, that you should pass to the NavHost and use it in the screen like you have done in your example来自包含TopAppBarScaffoldpadding参数,您应该将其传递给NavHost并在屏幕中使用它,就像您在示例中所做的那样

It is the expected behaviour.这是预期的行为。 You are constructing two separate app bars for both the screens so they are bound to flash.您正在为两个屏幕构建两个单独的应用栏,因此它们一定会闪烁。 This is not the correct way.这不是正确的方法。 The correct way would be to actually put the scaffold in your main activity and place the NavHost as it's content.正确的方法是将脚手架实际放在您的主要活动中,并将 NavHost 作为其内容放置。 If you wish to modify the app bar, create variables to hold state.如果您希望修改应用栏,请创建变量以保持状态。 Then modify them from the Composables.然后从 Composables 修改它们。 Ideally, store then in a viewmodel.理想情况下,然后将其存储在视图模型中。 That is how it is done in compose.这就是在撰写中完成的方式。 Through variables.通过变量。

Thanks谢谢

The flashing is caused by the default cross-fade animation in more recent versions of the navigation-compose library.闪烁是由更新版本的navigation-compose库中的默认交叉淡入淡出动画引起的。 The only way to get rid of it right now (without downgrading the dependency) is by using the Accompanist animation library:现在摆脱它的唯一方法(不降级依赖项)是使用Accompanist动画库:

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

and then replacing the normal NavHost for Accompanist's AnimatedNavHost and disabling the transitions animations:然后替换 Accompanist 的AnimatedNavHost的正常NavHost并禁用转换动画:

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

With the newer library implementation "com.google.accompanist:accompanist-navigation-animation:0.24.1-alpha" you need to have the AnimatedNavHost like this使用较新的库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)

Also

Replace rememberNavController() with rememberAnimatedNavController()rememberNavController()替换为rememberAnimatedNavController()

Replace NavHost with AnimatedNavHostNavHost替换为AnimatedNavHost

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

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

Alternative to removing Animation you can change animations for example:除了删除动画之外,您还可以更改动画,例如:

@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)
    ) {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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