TopAppBar мигает при навигации с помощью Compose Navigation
У меня есть 2 экрана, у которых есть свои и. Когда я перемещаюсь между ними с помощью библиотеки 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)
}
)
}
}
}
Экран списка задач с:
@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")
}
}
}
)
},
[...]
Экран добавления / редактирования
Scaffold
с
TopAppBar
:
@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)
)
}
}
9 ответов
Мигание вызвано анимацией плавного затухания по умолчанию в более поздних версиях программы.
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,
) {
[...}
}
Кажется, я нашел простое решение этой проблемы (работает в Compose версии 1.4.0).
Моя установка - мигает
Все мои экраны имеют собственную панель инструментов, завернутую в каркас:
// Some Composable screnn
Scaffold(
topBar = { TopAppBar(...) }
) {
ScreenContent()
}
Основное действие, которое содержит навигационный хост, определяется следующим образом:
// Activity with NavHost
setContent {
AppTheme {
NavHost(...) { }
}
}
Решение - не мигать!
Оберните NavHost активностью на поверхности:
setContent {
AppTheme {
Surface {
NavHost(...) { }
}
}
}
Остальные экраны остаются прежними. Нет мерцания, а анимация перехода между пунктами назначения почти такая же, как и с фрагментами (мягкое затухание/нарастание). До сих пор я не нашел каких-либо негативных побочных эффектов этого.
У меня такая же проблема с архитектурой «эшафот на экран». К моему удивлению, помогло снижение
androidx.navigation:navigation-compose
версия для
2.4.0-alpha04
.
Чтобы не моргнуть (или соскользнуть, если у вас естьAnimatedNavHost
) вы должны поместить в действие и вне , иначе это просто часть экрана и делает переходы, как и любой другой элемент экрана:
// Activity with your navigation host
setContent {
MyAppTheme {
Scaffold(
topBar = { TopAppBar(...) }
) { padding ->
TodoNavHost(padding, ...) { }
}
}
}
ИзScaffold
содержащийTopAppBar
приходитpadding
параметр, который вы должны передать вNavHost
и используйте его на экране, как вы сделали в своем примере
Это ожидаемое поведение. Вы создаете две отдельные панели приложений для обоих экранов, поэтому они обязательно должны мигать. Это неправильный путь. Правильный способ - это фактически поместить основу в ваше основное действие и разместить NavHost в качестве его содержимого. Если вы хотите изменить панель приложения, создайте переменные для хранения состояния. Затем измените их из Composables. В идеале хранить в модели просмотра. Вот как это делается в compose. Через переменные.
Спасибо
С более новой библиотекой
implementation "com.google.accompanist:accompanist-navigation-animation:0.24.1-alpha"
у вас должно быть вот так
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
Не добавляя еще один пакет, по состоянию на декабрь 2023 г. вы можете удалить анимацию по умолчанию следующим образом.
NavHost(
navController = navController,
startDestination = HOME_ROUTE,
route = ROOT_ROUTE,
enterTransition = {
EnterTransition.None // removes the blinking/fade effect
},
exitTransition = {
ExitTransition.None // remove the blinking/fade effect
}
) {
// composables
}
Поместите это в свой корневой навигационный хост. Вот версия, которую я использую (по состоянию на 2 декабря 2023 г.)
implementation("androidx.navigation:navigation-compose:2.7.5")
В качестве альтернативы удалению анимации вы можете изменить анимацию, например:
@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)
) {}
Проблема в том, что представление в NavHost имеет анимацию плавного затухания по умолчанию. Вы должны переопределить его, чтобы остановить мигание, как показано в примере ниже.noEnterTransition
иnoExistTransition
дляMainScreen
.
class MainActivity : ComponentActivity() {
private val noEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
{
fadeIn(
animationSpec = tween(durationMillis = 200),
initialAlpha = 0.99f
)
}
private val noExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
{
fadeOut(
animationSpec = tween(durationMillis = 300),
targetAlpha = 0.99f
)
}
/.../
override fun onCreate(savedInstanceState: Bundle?) {
setContent {
val navController = rememberNavController()
YourTheme() {
Surface(color = MaterialTheme.colorScheme.background) {
NavHost(navController = navController, startDestination = "main") {
composable(
route = "main",
popEnterTransition = noEnterTransition,
exitTransition = noExitTransition,
popExitTransition = noExitTransition
) {
// MainScreen(navController = navController)
}
composable(
route = "detail",
enterTransition = {
slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Companion.Left,
animationSpec = tween(200, easing = EaseOut)
)
},
popExitTransition = {
slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right,
animationSpec = tween(150, easing = EaseOut)
)
},
) {
// DetailScreen(navController = navController)
}
}
}
}
}
}
}