Jetpack ComposeJuniorTechnical

Что такое AnimatedVisibility и как он работает?

AnimatedVisibility анимирует появление и скрытие composable по булевому флагу visible. Дочерний контент удерживается в composition до конца exit-анимации. Поддерживает комбинирование fadeIn, slideIn, expandVertically и других transitions через оператор +.

Что такое AnimatedVisibility

AnimatedVisibility — composable-обёртка, которая анимирует появление и скрытие дочернего контента на основе булевого параметра visible. В отличие от простого if (visible) { ... }, компонент удерживает дочерний composable в дереве composition до полного завершения exit-анимации, после чего удаляет его. Это гарантирует, что пользователь видит плавный переход, а не мгновенное исчезновение.

Как работает внутри

Compose отслеживает изменение visible. При переходе false → true запускается EnterTransition; при true → falseExitTransition. Пока exit-анимация не завершена, content остаётся в composition в специальном состоянии ExitAnimationInProgress. Система transitions строится на Transition<Boolean> под капотом, что позволяет комбинировать несколько эффектов через +.

Стандартные варианты transitions:

  • fadeIn() + slideInVertically() — плавное появление со сдвигом
  • expandVertically() / shrinkVertically() — раскрытие/схлопывание по высоте
  • scaleIn() / scaleOut() — масштабирование

Пример

@Composable
fun NotificationBanner(visible: Boolean, message: String) {
    AnimatedVisibility(
        visible = visible,
        enter = fadeIn(animationSpec = tween(300)) + slideInVertically(
            initialOffsetY = { -it }
        ),
        exit = fadeOut(animationSpec = tween(200)) + slideOutVertically(
            targetOffsetY = { -it }
        )
    ) {
        Surface(
            color = MaterialTheme.colorScheme.primaryContainer,
            modifier = Modifier.fillMaxWidth().padding(8.dp)
        ) {
            Text(
                text = message,
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

// Использование:
var showBanner by remember { mutableStateOf(false) }

Column {
    NotificationBanner(visible = showBanner, message = "Сохранено")
    Button(onClick = { showBanner = !showBanner }) {
        Text("Переключить")
    }
}

AnimatedContent vs AnimatedVisibility

Если нужно анимировать смену содержимого (например, переключение между двумя виджетами), используют AnimatedContent. AnimatedVisibility работает только с одним булевым условием показа/скрытия.

Подводные камни

  • Cleanup в exit-фазе. Если дочерний composable освобождает ресурсы в DisposableEffect, cleanup произойдёт только после завершения exit-анимации, а не в момент visible = false. Это важно при работе с камерой, медиаплеером или сетевыми соединениями.
  • Состояние внутри content. remember-состояние внутри AnimatedVisibility сохраняется, пока идёт exit-анимация. После удаления из composition состояние сбрасывается — не полагайтесь на него после скрытия.
  • Вложенная AnimatedVisibility. Дочерние composable могут использовать animateEnterExit() modifier для независимой анимации внутри родительского перехода, но легко запутаться в порядке запуска.
  • Производительность сложных transitions. Комбинирование 3+ transitions одновременно увеличивает нагрузку на rendering. Профилируйте через Android Studio GPU Profiler.
  • Доступность. AnimatedVisibility не добавляет автоматически semantics-аннотации для screen reader. Скрытый контент с visible = false недоступен, но в момент анимации — доступен. Явно управляйте Modifier.semantics { invisibleToUser() } при необходимости.
  • Лайаут до появления. До первого показа composable не занимает место в лайауте, но после первого скрытия (exit-анимация) он ещё занимает место. Это может вызывать скачки лайаута, если родитель адаптирует размер.
  • key и идентичность. При использовании внутри LazyColumn каждая строка с AnimatedVisibility должна иметь стабильный key, иначе Compose может переиспользовать composable и пропустить анимацию.
  • Нет out-of-the-box spring для visibility. По умолчанию используется tween. Для spring-анимации нужно явно передать spring() в animationSpec каждого перехода.

Common mistakes

  • Объяснять «AnimatedVisibility» только как синтаксис и не описывать поведение runtime/compiler.
  • Игнорировать важный риск: Если хранить resource cleanup только в исчезающем child без учета exit, можно получить неожиданный момент освобождения.
  • Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.

What the interviewer is testing

  • Формулирует суть темы «AnimatedVisibility» своими словами и связывает ее с кодом.
  • Называет механизм: Он удерживает content в composition до завершения exit animation и позволяет комбинировать enter/exit transitions.
  • Видит production-последствие: Если хранить resource cleanup только в исчезающем child без учета exit, можно получить неожиданный момент освобождения.

Sources

Related topics