0Pricing
Android Academy · Урок

Основы animate*AsState

Плавно анимируйте отдельные значения

«Основы animate*AsState» — бесплатный урок Android Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.

Анимация одного значения

Самый простой способ анимировать в Compose — использовать семейство animate*AsState. Вы передаёте ему целевое значение, а он возвращает State, которое плавно изменяется от текущего значения к целевому при каждом изменении цели.

Для каждого распространённого типа есть свой вариант: animateFloatAsState, animateDpAsState, animateColorAsState, animateOffsetAsState и другие.

animateFloatAsState

animateFloatAsState анимирует одно значение типа Float. Частый вариант применения — прозрачность (альфа-канал). Когда значение visible меняется, возвращаемая альфа плавно переходит между 0f и 1f.

Обратите внимание на ключевое слово by: оно позволяет напрямую читать анимированное State<Float> как обычный Float.

@Composable
fun FadingLabel(visible: Boolean) {
    val alpha by animateFloatAsState(
        targetValue = if (visible) 1f else 0f,
        label = "alpha"
    )
    Text(
        "Hello",
        modifier = Modifier.graphicsLayer { this.alpha = alpha }
    )
}

animateDpAsState

Размеры и смещения интерфейса используют Dp, поэтому для анимации внутренних отступов, размера или положения применяйте animateDpAsState.

Здесь при переключении прямоугольная область увеличивается с 80dp до 160dp. Поскольку анимированное значение Dp передаётся в size(), размер области плавно изменяется.

@Composable
fun GrowingBox(big: Boolean) {
    val size by animateDpAsState(
        targetValue = if (big) 160.dp else 80.dp,
        label = "size"
    )
    Box(
        Modifier
            .size(size)
            .background(Color.Blue)
    )
}

animateColorAsState

Для анимации цветов используйте animateColorAsState. Он выполняет интерполяцию в цветовом пространстве, поэтому переходы выглядят плавно и естественно, без мерцания необычными промежуточными оттенками.

Здесь индикатор состояния анимируется между красным и зелёным в зависимости от логического значения.

@Composable
fun StatusDot(online: Boolean) {
    val color by animateColorAsState(
        targetValue = if (online) Color(0xFF4CAF50) else Color(0xFFF44336),
        label = "status-color"
    )
    Box(
        Modifier
            .size(16.dp)
            .clip(CircleShape)
            .background(color)
    )
}

Настройка с помощью animationSpec

По умолчанию эти вспомогательные функции используют мягкую пружину. Передайте animationSpec, чтобы управлять временем анимации. Используйте tween, чтобы задать фиксированную длительность и кривую плавности.

Эта панель скользит в течение 400 мс по плавной кривой с ускорением, за которым следует замедление.

@Composable
fun SlidingPanel(open: Boolean) {
    val offset by animateDpAsState(
        targetValue = if (open) 0.dp else 250.dp,
        animationSpec = tween(
            durationMillis = 400,
            easing = FastOutSlowInEasing
        ),
        label = "offset"
    )
    Box(Modifier.offset(x = offset)) { /* content */ }
}

Кривые плавности

Кривая плавности определяет, как анимация ускоряется и замедляется. Compose предоставляет несколько вариантов:

  • LinearEasing — постоянная скорость (механическое ощущение).
  • FastOutSlowInEasing — стандартный вариант Material: быстрое начало и мягкое завершение.
  • LinearOutSlowInEasing — для элементов, появляющихся на экране.
  • FastOutLinearInEasing — для элементов, исчезающих с экрана.

Правильно выбранная кривая плавности делает движение осмысленным, а не роботизированным.

val progress by animateFloatAsState(
    targetValue = if (done) 1f else 0f,
    animationSpec = tween(500, easing = LinearOutSlowInEasing),
    label = "progress"
)

Параметры пружины

Вместо фиксированной длительности можно использовать пружину, которая ощущается физичной и естественной. Пружина определяется параметрами dampingRatio (степень упругости) и stiffness (скорость).

Пружины особенно удобны тем, что плавно адаптируются даже при изменении цели во время анимации.

val scale by animateFloatAsState(
    targetValue = if (active) 1.2f else 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioMediumBouncy,
        stiffness = Spring.StiffnessLow
    ),
    label = "scale"
)

Реакция на завершение

Иногда после завершения анимации нужно выполнить действие, например вызвать обратный вызов, когда значение достигнет цели. Для этого animate*AsState принимает finishedListener.

val alpha by animateFloatAsState(
    targetValue = if (gone) 0f else 1f,
    label = "alpha",
    finishedListener = { finalValue ->
        if (finalValue == 0f) onFullyHidden()
    }
)

Анимация нескольких свойств

Вы можете объединить несколько вызовов animate*AsState, чтобы одновременно анимировать несколько свойств. Каждый вызов отслеживает собственное значение, а вместе они создают более выразительный эффект.

Здесь чип одновременно масштабируется и меняет цвет при выборе.

@Composable
fun SelectableChip(selected: Boolean, text: String) {
    val scale by animateFloatAsState(if (selected) 1.1f else 1f, label = "s")
    val bg by animateColorAsState(
        if (selected) Color(0xFF3F51B5) else Color.LightGray,
        label = "bg"
    )
    Box(
        Modifier
            .graphicsLayer { scaleX = scale; scaleY = scale }
            .clip(RoundedCornerShape(16.dp))
            .background(bg)
            .padding(horizontal = 12.dp, vertical = 6.dp)
    ) { Text(text) }
}

Пружина на чистом Kotlin

Чтобы понять, что именно «делает» пружина, рассмотрим небольшую симуляцию на чистом Kotlin. На каждом шаге она приближает значение к цели с некоторым затуханием — примерно так же, как пружина Compose приближается к целевому значению за множество кадров.

fun main() {
    var value = 0f
    val target = 100f
    var velocity = 0f
    val stiffness = 0.1f
    val damping = 0.6f
    repeat(6) { step ->
        val force = (target - value) * stiffness
        velocity = (velocity + force) * damping
        value += velocity
        println("step $step -> ${"%.1f".format(value)}")
    }
}

Анимация Int и Offset

Семейство функций поддерживает не только числа с плавающей точкой и цвета. Используйте animateIntAsState для целочисленных значений, например целевого количества элементов на значке, а animateOffsetAsState или animateIntOffsetAsState — для анимации двумерного положения одним вызовом.

Здесь чип перемещается по диагонали благодаря анимации IntOffset.

@Composable
fun SlidingChip(moved: Boolean) {
    val pos by animateIntOffsetAsState(
        targetValue = if (moved) IntOffset(120, 60) else IntOffset.Zero,
        label = "pos"
    )
    Box(
        Modifier
            .offset { pos }
            .size(40.dp)
            .background(Color.Magenta)
    )
}

Быстрая проверка

Вы хотите плавно анимировать ширину прямоугольной области, измеряемую в Dp, при изменении логического значения. Какой API лучше всего подходит?

Итоги: animate*AsState

Семейство animate*AsState — основной инструмент для анимации одного значения:

  • Выберите вариант нужного типа: animateFloatAsState, animateDpAsState, animateColorAsState и т. д.
  • Задайте targetValue; результат будет анимированно приближаться к нему при каждом изменении.
  • Настройте движение с помощью animationSpec, используя tween с кривой плавности или spring.
  • Объединяйте несколько вызовов для одновременной анимации нескольких свойств.
  • Используйте finishedListener, чтобы реагировать на завершение анимации.

Далее: анимация появления и исчезновения содержимого с помощью AnimatedVisibility и переходов.

Часто задаваемые вопросы

Урок «Основы animate*AsState» бесплатный?

Да — полный текст урока «Основы animate*AsState» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.

Чему я научусь в уроке «Основы animate*AsState»?

Плавно анимируйте отдельные значения Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Android Academy?

Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Основы animate*AsState»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Android Academy?

Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Зачем нужны анимации
  2. Основы animate*AsState
  3. AnimatedVisibility и переходы
  4. Анимации жестов и пружин
← Назад к Android Academy