Обзор анимации свойств

Попробуйте Compose
Jetpack Compose – это рекомендуемый набор инструментов для создания пользовательского интерфейса в Android. Узнайте, как использовать анимацию в Compose.

Система анимации свойств – это надежная платформа, которая позволяет анимировать практически все. Вы можете определить анимацию, чтобы со временем изменить любое свойство объекта, независимо от того, рисуется оно на экране или нет. Анимация свойств изменяет значение свойства (поля в объекте) за указанный промежуток времени. Чтобы анимировать объект, укажите его свойство, которое вы хотите анимировать, например положение объекта на экране, длительность анимации и значения, между которыми вы хотите анимировать.

Система анимации свойств позволяет определить следующие характеристики анимации:

  • Длительность. Вы можете указать длительность анимации. По умолчанию установлено значение 300 мс.
  • Интерполяция времени. Вы можете указать, как рассчитываются значения свойства в зависимости от текущего времени анимации.
  • Количество повторов и поведение. Вы можете указать, нужно ли повторять анимацию после ее завершения и сколько раз. Вы также можете указать, нужно ли воспроизводить анимацию в обратном порядке. Если задать значение reverse, анимация будет воспроизводиться сначала в прямом, а затем в обратном направлении, пока не будет достигнуто заданное количество повторов.
  • Наборы анимаций. Вы можете объединять анимации в логические наборы, которые воспроизводятся вместе, последовательно или с определенной задержкой.
  • Задержка обновления кадров. Вы можете указать, как часто должны обновляться кадры анимации. По умолчанию обновление происходит каждые 10 мс, но скорость, с которой приложение может обновлять кадры, в конечном счете зависит от того, насколько загружена система в целом и как быстро она может обслуживать базовый таймер.

Полный пример анимации свойств можно найти в классе ChangeColor в примере CustomTransition на GitHub.

Как работает анимация свойств

Сначала давайте рассмотрим, как работает анимация, на простом примере. На рисунке 1 показан гипотетический объект, анимированный с помощью свойства x, которое представляет его горизонтальное положение на экране. Длительность анимации составляет 40 мс, а расстояние – 40 пикселей. Каждые 10 мс (частота обновления кадра по умолчанию) объект перемещается по горизонтали на 10 пикселей. Через 40 мс анимация останавливается, и объект оказывается в горизонтальном положении 40. Это пример анимации с линейной интерполяцией, то есть объект движется с постоянной скоростью.

Рисунок 1. Пример линейной анимации

Вы также можете задать нелинейную интерполяцию. На рисунке 2 показан гипотетический объект, который ускоряется в начале анимации и замедляется в конце. Объект по-прежнему перемещается на 40 пикселей за 40 мс, но нелинейно. В начале анимация ускоряется до середины, а затем замедляется до конца. Как показано на рисунке 2, расстояние, пройденное в начале и в конце анимации, меньше, чем в середине.

Рисунок 2. Пример нелинейной анимации

Давайте подробно рассмотрим, как важные компоненты системы анимации свойств будут рассчитывать анимацию, подобную показанной выше. На рисунке 3 показано, как основные классы взаимодействуют друг с другом.

Рисунок 3. Как рассчитывается анимация

Объект ValueAnimator отслеживает временные параметры анимации, например время ее выполнения и текущее значение анимируемого свойства.

ValueAnimator содержит TimeInterpolator, который определяет интерполяцию анимации, и TypeEvaluator, который определяет, как рассчитывать значения для анимируемого свойства. Например, на рисунке 2 в качестве TimeInterpolator используется AccelerateDecelerateInterpolator, а в качестве TypeEvaluator – IntEvaluator.

Чтобы запустить анимацию, создайте объект ValueAnimator и укажите начальное и конечное значения для свойства, которое вы хотите анимировать, а также продолжительность анимации. При вызове start() начинается анимация. На протяжении всей анимации ValueAnimator вычисляет прошедшую долю от 0 до 1 на основе продолжительности анимации и прошедшего времени. Доля прошедшего времени представляет собой процент времени, в течение которого анимация была завершена, где 0 означает 0%, а 1 означает 100%. Например, на рисунке 1 прошедшая доля при t = 10 мс будет равна 0,25, поскольку общая продолжительность составляет t = 40 мс.

После того как ValueAnimator рассчитает долю прошедшего времени, он вызывает текущий TimeInterpolator, чтобы рассчитать долю интерполяции. Интерполированная доля сопоставляет прошедшую долю с новой долей, которая учитывает заданную временную интерполяцию. Например, на рисунке 2, поскольку анимация медленно ускоряется, интерполированная доля (около 0,15) меньше, чем прошедшая доля (0,25) при t = 10 мс. На рисунке 1 интерполированная доля всегда совпадает с прошедшей долей.

Когда интерполированная дробь вычисляется, ValueAnimator вызывает соответствующий TypeEvaluator, чтобы вычислить значение свойства, которое вы анимируете, на основе интерполированной дроби, начального значения и конечного значения анимации. Например, на рисунке 2 интерполированная доля составляла 0,15 при t = 10 мс, поэтому значение свойства в этот момент времени будет равно 0,15 × (40 – 0), то есть 6.

Чем анимация свойств отличается от анимации представлений

Система анимации представлений позволяет анимировать только объекты View. Если вы хотите анимировать объекты, не относящиеся к View, вам придется написать собственный код. Кроме того, система анимации представлений позволяет анимировать только некоторые аспекты объекта View, например масштабирование и поворот представления, но не цвет фона.

Ещё один недостаток системы анимации представлений заключается в том, что она изменяет только место отрисовки представления, а не само представление. Например, если вы анимировали кнопку для перемещения по экрану, кнопка отрисовывается правильно, но фактическое местоположение, в котором вы можете нажать кнопку, не меняется, поэтому вам необходимо реализовать собственную логику для обработки этого.

Система анимации свойств полностью снимает эти ограничения. Вы можете анимировать любое свойство любого объекта (как View, так и других), и сам объект будет изменен. Система анимации свойств также более надежна в том, как она выполняет анимацию. В общих чертах, вы назначаете аниматоры для свойств, которые хотите анимировать, таких как цвет, положение или размер, и можете определять аспекты анимации, такие как интерполяция и синхронизация нескольких аниматоров.

Однако система анимации представлений требует меньше времени на настройку и меньше кода. Если анимация представления позволяет вам сделать все, что нужно, или если существующий код уже работает так, как вы хотите, нет необходимости использовать систему анимации свойств. Также может иметь смысл использовать обе системы анимации для разных ситуаций, если возникнет необходимость.

Обзор API

Большинство API системы анимации свойств можно найти в android.animation. Поскольку система анимации представлений уже определяет множество интерполяторов в android.view.animation, вы также можете использовать эти интерполяторы в системе анимации свойств. В следующих таблицах описаны основные компоненты системы анимации свойств.

Класс Animator предоставляет базовую структуру для создания анимаций. Обычно этот класс не используется напрямую, поскольку он предоставляет только минимальную функциональность, которую необходимо расширить для полной поддержки анимированных значений. Следующие подклассы расширяют Animator:

Таблица 1. Аниматоры

Класс Описание
ValueAnimator Основной механизм синхронизации для анимации свойств, который также вычисляет значения для анимируемого свойства. Он содержит все основные функции, которые вычисляют значения анимации и содержат сведения о времени каждой анимации, информацию о том, повторяется ли анимация, прослушиватели, которые получают события обновления, и возможность устанавливать пользовательские типы для оценки. Анимация свойств состоит из двух частей: вычисление анимированных значений и установка этих значений для объекта и свойства, которые анимируются. ValueAnimator не выполняет вторую часть, поэтому вы должны прослушивать обновления значений, рассчитанных ValueAnimator, и изменять объекты, которые хотите анимировать, с помощью собственной логики. Подробную информацию можно найти в разделе Анимация с помощью ValueAnimator.
ObjectAnimator Подкласс ValueAnimator, который позволяет задать целевой объект и его свойство для анимации. Этот класс обновляет свойство соответствующим образом, когда вычисляет новое значение для анимации. В большинстве случаев лучше использовать ObjectAnimator, поскольку это значительно упрощает процесс анимации значений целевых объектов. Однако иногда вам может потребоваться использовать ValueAnimator напрямую, поскольку у ObjectAnimator есть несколько дополнительных ограничений, например требование наличия определенных методов доступа в целевом объекте.
AnimatorSet Предоставляет механизм для группировки анимаций, чтобы они выполнялись по отношению друг к другу. Вы можете настроить анимацию так, чтобы она воспроизводилась одновременно, последовательно или с задержкой. Подробнее читайте в разделе Как управлять несколькими анимациями с помощью AnimatorSet.

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

Таблица 2. Оценщики

Класс/интерфейс Описание
IntEvaluator Оценщик по умолчанию для расчета значений свойств int.
FloatEvaluator Оценщик по умолчанию для расчета значений свойств float.
ArgbEvaluator Оценщик по умолчанию для вычисления значений свойств цвета, представленных в виде шестнадцатеричных значений.
TypeEvaluator Интерфейс, позволяющий создать собственный оценщик. Если вы анимируете свойство объекта, которое не является int, float или цветом, необходимо реализовать интерфейс TypeEvaluator, чтобы указать, как вычислять анимированные значения свойства объекта. Вы также можете указать собственный TypeEvaluator для значений int, float и цвета, если хотите обрабатывать эти типы иначе, чем режим работы по умолчанию. Дополнительную информацию о том, как написать собственный оценщик, см. в разделе Использование TypeEvaluator.

Интерполятор времени определяет, как рассчитываются конкретные значения в анимации как функция времени. Например, можно задать линейную анимацию, которая будет воспроизводиться равномерно, или нелинейную, которая будет ускоряться в начале и замедляться в конце. В таблице 3 описаны интерполяторы, содержащиеся в android.view.animation. Если ни один из предоставленных интерполяторов не соответствует вашим потребностям, реализуйте интерфейс TimeInterpolator и создайте свой собственный. Подробнее о том, как написать собственный интерполятор, читайте в разделе Использование интерполяторов.

Таблица 3. Интерполяторы

Класс/интерфейс Описание
AccelerateDecelerateInterpolator Интерполятор, скорость изменения которого начинается и заканчивается медленно, но ускоряется в середине.
AccelerateInterpolator Интерполятор, скорость изменения которого сначала низкая, а затем увеличивается.
AnticipateInterpolator Интерполятор, изменение которого начинается в обратном направлении, а затем переходит в прямом направлении.
AnticipateOvershootInterpolator Интерполятор, изменение которого начинается в обратном направлении, затем переходит вперед и превышает целевое значение, а затем возвращается к конечному значению.
BounceInterpolator Интерполятор, изменение которого отскакивает в конце.
CycleInterpolator Интерполятор, анимация которого повторяется заданное количество циклов.
DecelerateInterpolator Интерполятор, скорость изменения которого сначала высока, а затем снижается.
LinearInterpolator Интерполятор, скорость изменения которого постоянна.
OvershootInterpolator Интерполятор, изменение которого выходит за пределы последнего значения, а затем возвращается к нему.
TimeInterpolator Интерфейс, позволяющий реализовать собственный интерполятор.

Анимация с помощью ValueAnimator

Класс ValueAnimator позволяет анимировать значения определенного типа в течение анимации, указывая набор значений int, float или цвета для анимации. Чтобы получить объект ValueAnimator, вызовите один из его фабричных методов: ofInt(), ofFloat() или ofObject(). Пример:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

В этом коде ValueAnimator начинает вычислять значения анимации от 0 до 100 в течение 1000 мс, когда выполняется метод start().

Вы также можете указать собственный тип анимации, выполнив следующие действия:

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

В этом коде ValueAnimator начинает вычислять значения анимации между startPropertyValue и endPropertyValue, используя логику, предоставленную MyTypeEvaluator, в течение 1000 мс, когда выполняется метод start().

Вы можете использовать значения анимации, добавив AnimatorUpdateListener к объекту ValueAnimator, как показано в следующем коде:

Kotlin

ValueAnimator.ofObject(...).apply {
    ...
    addUpdateListener { updatedAnimation ->
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        textView.translationX = updatedAnimation.animatedValue as Float
    }
    ...
}

Java

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

В методе onAnimationUpdate() вы можете получить доступ к обновленному значению анимации и использовать его в свойстве одного из ваших представлений. Подробнее о прослушивателях анимации…

Анимация с помощью ObjectAnimator

ObjectAnimator – это подкласс ValueAnimator (описан в предыдущем разделе), который сочетает в себе механизм синхронизации и вычисление значений ValueAnimator с возможностью анимировать именованное свойство целевого объекта. Это значительно упрощает анимацию любого объекта, поскольку вам больше не нужно реализовывать ValueAnimator.AnimatorUpdateListener, так как анимированное свойство обновляется автоматически.

Создание экземпляра ObjectAnimator похоже на создание экземпляра ValueAnimator, но вы также указываете объект и имя свойства этого объекта (в виде строки) вместе со значениями для анимации:

Kotlin

ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
    duration = 1000
    start()
}

Java

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

Чтобы ObjectAnimator правильно обновлял свойства, выполните следующие действия:

  • Свойство объекта, которое вы анимируете, должно иметь функцию setter (в верблюжьем регистре) в форме set<PropertyName>(). Поскольку ObjectAnimator автоматически обновляет свойство во время анимации, у него должен быть доступ к свойству с помощью этого метода. Например, если свойство называется foo, то метод должен называться setFoo(). Если такого метода нет, у вас есть три варианта:
    • Добавьте метод setter в класс, если у вас есть на это права.
    • Используйте класс-оболочку, который вы можете изменять. Он должен получать значение с помощью допустимого метода setter и передавать его исходному объекту.
    • Вместо него используйте ValueAnimator.
  • Если вы укажете только одно значение для параметра values... в одном из фабричных методов ObjectAnimator, оно будет считаться конечным значением анимации. Поэтому у свойства объекта, которое вы анимируете, должна быть функция получения, которая используется для получения начального значения анимации. Функция получения должна иметь следующий вид: get<PropertyName>(). Например, если свойство называется foo, то метод должен называться getFoo().
  • Методы получения (при необходимости) и установки свойства, которое вы анимируете, должны работать с тем же типом, что и начальное и конечное значения, которые вы указываете для ObjectAnimator. Например, если вы создаете следующий объект ObjectAnimator, то вам потребуются targetObject.setPropName(float) и targetObject.getPropName():
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • В зависимости от того, какое свойство или объект вы анимируете, вам может потребоваться вызвать метод invalidate() для View, чтобы принудительно перерисовать экран с обновленными анимированными значениями. Это делается в обратном вызове onAnimationUpdate(). Например, анимация свойства цвета объекта Drawable вызывает обновление экрана только тогда, когда этот объект перерисовывает себя. Все методы задания свойств в View, такие как setAlpha() и setTranslationX(), правильно аннулируют View, поэтому вам не нужно аннулировать View при вызове этих методов с новыми значениями. Подробнее о прослушивателях анимации…

Как создать несколько анимаций с помощью AnimatorSet

Во многих случаях вам нужно воспроизвести анимацию, которая зависит от того, когда начинается или заканчивается другая анимация. Система Android позволяет объединять анимации в AnimatorSet, чтобы вы могли указать, запускать ли анимации одновременно, последовательно или после указанной задержки. Объекты AnimatorSet также можно вкладывать друг в друга.

В приведенном ниже фрагменте кода объекты Animator воспроизводятся следующим образом:

  1. Прозвучит bounceAnim.
  2. Одновременно воспроизводятся squashAnim1, squashAnim2, stretchAnim1 и stretchAnim2.
  3. Прозвучит bounceBackAnim.
  4. Прозвучит fadeAnim.

Kotlin

val bouncer = AnimatorSet().apply {
    play(bounceAnim).before(squashAnim1)
    play(squashAnim1).with(squashAnim2)
    play(squashAnim1).with(stretchAnim1)
    play(squashAnim1).with(stretchAnim2)
    play(bounceBackAnim).after(stretchAnim2)
}
val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
}
AnimatorSet().apply {
    play(bouncer).before(fadeAnim)
    start()
}

Java

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

Прослушиватели анимации

Вы можете отслеживать важные события во время анимации с помощью описанных ниже прослушивателей.

  • Animator.AnimatorListener
    • onAnimationStart() – вызывается при запуске анимации.
    • onAnimationEnd() – вызывается по окончании анимации.
    • onAnimationRepeat() – вызывается при повторе анимации.
    • onAnimationCancel() – вызывается при отмене анимации. Если анимация отменена, также вызывается onAnimationEnd(), независимо от того, как она была завершена.
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate() – вызывается для каждого кадра анимации. Прослушивайте это событие, чтобы использовать значения, рассчитанные ValueAnimator во время анимации. Чтобы использовать значение, запросите объект ValueAnimator, переданный в событие, и получите текущее анимированное значение с помощью метода getAnimatedValue(). Если вы используете ValueAnimator, необходимо реализовать этот прослушиватель.

      В зависимости от того, какое свойство или объект вы анимируете, вам может потребоваться вызвать invalidate() для View, чтобы принудительно перерисовать эту область экрана с новыми анимированными значениями. Например, анимация свойства цвета объекта Drawable вызывает обновление экрана только тогда, когда этот объект перерисовывает себя. Все методы задания свойств в View, такие как setAlpha() и setTranslationX(), правильно аннулируют View, поэтому вам не нужно аннулировать View при вызове этих методов с новыми значениями.

Если вы не хотите реализовывать все методы интерфейса Animator.AnimatorListener, то можете расширить класс AnimatorListenerAdapter вместо реализации интерфейса Animator.AnimatorListener. Класс AnimatorListenerAdapter предоставляет пустые реализации методов, которые вы можете переопределить.

Например, следующий фрагмент кода создает AnimatorListenerAdapter только для обратного вызова onAnimationEnd():

Kotlin

ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            balls.remove((animation as ObjectAnimator).target)
        }
    })
}

Java

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}

Анимация изменений макета для объектов ViewGroup

Система анимации свойств позволяет анимировать изменения объектов ViewGroup, а также предоставляет простой способ анимации самих объектов View.

Вы можете анимировать изменения макета в ViewGroup с помощью класса LayoutTransition. Объекты View внутри ViewGroup могут проходить через анимацию появления и исчезновения, когда вы добавляете их в ViewGroup или удаляете из него, а также когда вы вызываете метод setVisibility() объекта View с параметром VISIBLE, INVISIBLE или GONE. Остальные представления в ViewGroup также могут анимироваться в новых позициях при добавлении или удалении представлений. Вы можете определить следующие анимации в объекте LayoutTransition, вызвав setAnimator() и передав объект Animator с одной из следующих констант LayoutTransition:

  • APPEARING – флаг, указывающий на анимацию, которая воспроизводится для элементов, появляющихся в контейнере.
  • CHANGE_APPEARING – флаг, указывающий на анимацию, которая запускается для элементов, изменяющихся из-за появления нового элемента в контейнере.
  • DISAPPEARING – флаг, указывающий на анимацию, которая запускается для элементов, исчезающих из контейнера.
  • CHANGE_DISAPPEARING – флаг, указывающий на анимацию, которая запускается для элементов, меняющихся из-за исчезновения элемента из контейнера.

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

Чтобы задать для атрибута android:animateLayoutchanges значение true для ViewGroup, выполните следующие действия:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

Если задать для этого атрибута значение true, то представления, которые добавляются в ViewGroup или удаляются из него, а также оставшиеся представления будут анимироваться автоматически.

Анимация изменений состояния представления с помощью StateListAnimator

Класс StateListAnimator позволяет определить аниматоры, которые запускаются при изменении состояния представления. Этот объект действует как оболочка для объекта Animator, вызывая анимацию при изменении указанного состояния представления (например, "нажато" или "в фокусе").

StateListAnimator можно определить в XML-ресурсе с корневым элементом <selector> и дочерними элементами <item>, каждый из которых указывает на другое состояние представления, определенное классом StateListAnimator. Каждый элемент <item> содержит определение набора анимаций свойств.

Например, следующий файл создает аниматор списка состояний, который изменяет масштаб представления по осям X и Y при нажатии:

res/xml/animate_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

Чтобы прикрепить аниматор списка состояний к представлению, добавьте атрибут android:stateListAnimator следующим образом:

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

Теперь при изменении состояния кнопки будут использоваться анимации, определенные в файле animate_scale.xml.

Чтобы назначить аниматор списка состояний представлению в коде, используйте метод AnimatorInflater.loadStateListAnimator() и назначьте аниматор представлению с помощью метода View.setStateListAnimator().

Вместо анимации свойств представления можно воспроизводить анимацию графического объекта между изменениями состояния с помощью AnimatedStateListDrawable. Некоторые системные виджеты в Android 5.0 используют эти анимации по умолчанию. В следующем примере показано, как определить AnimatedStateListDrawable в качестве XML-ресурса:

<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- provide a different drawable for each state-->
    <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
        android:state_pressed="true"/>
    <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
        android:state_focused="true"/>
    <item android:id="@id/default"
        android:drawable="@drawable/drawableD"/>

    <!-- specify a transition -->
    <transition android:fromId="@+id/default" android:toId="@+id/pressed">
        <animation-list>
            <item android:duration="15" android:drawable="@drawable/dt1"/>
            <item android:duration="15" android:drawable="@drawable/dt2"/>
            ...
        </animation-list>
    </transition>
    ...
</animated-selector>

Используйте TypeEvaluator

Если вы хотите анимировать тип, неизвестный системе Android, вы можете создать свой собственный оценщик, реализовав интерфейс TypeEvaluator. Типы, известные системе Android, – это int, float или цвет, которые поддерживаются оценщиками типов IntEvaluator, FloatEvaluator и ArgbEvaluator.

В интерфейсе TypeEvaluator нужно реализовать только один метод – evaluate(). Это позволяет аниматору, который вы используете, возвращать соответствующее значение для вашего анимированного свойства в текущей точке анимации. В классе FloatEvaluator показано, как это сделать:

Kotlin

private class FloatEvaluator : TypeEvaluator<Any> {

    override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
        return (startValue as Number).toFloat().let { startFloat ->
            startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
        }
    }

}

Java

public class FloatEvaluator implements TypeEvaluator {

    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

Примечание. При запуске ValueAnimator (или ObjectAnimator) вычисляется текущая прошедшая доля анимации (значение от 0 до 1), а затем вычисляется ее интерполированная версия в зависимости от того, какой интерполятор вы используете. Интерполированная доля – это то, что ваш TypeEvaluator получает через параметр fraction, поэтому вам не нужно учитывать интерполятор при расчете анимированных значений.

Использование интерполяторов

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

Интерполяторы в системе анимации получают от аниматоров долю, которая представляет собой истекшее время анимации. Интерполяторы изменяют эту долю, чтобы она совпадала с типом анимации, которую они стремятся обеспечить. В системе Android есть набор стандартных интерполяторов в android.view.animation package. Если ни один из них вам не подходит, вы можете реализовать интерфейс TimeInterpolator и создать собственный класс.

Ниже показано, как интерполятор по умолчанию AccelerateDecelerateInterpolator и LinearInterpolator рассчитывают интерполированные доли. Значение LinearInterpolator не влияет на долю прошедшего времени. AccelerateDecelerateInterpolator ускоряется в начале анимации и замедляется в конце. Логика этих интерполяторов определяется следующими методами:

AccelerateDecelerateInterpolator

Kotlin

override fun getInterpolation(input: Float): Float =
        (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f

Java

@Override
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

@Override
public float getInterpolation(float input) {
    return input;
}

В таблице ниже приведены приблизительные значения, рассчитанные этими интерполяторами для анимации длительностью 1000 мс.

мс прошло Доля прошедшего времени/Доля интерполированного времени (линейная) Интерполированная дробь (ускорение/замедление)
0 0 0
200 .2 .1
400 .4 0,345
600 .6 0,654
800 .8 .9
1000 1 1

Как видно из таблицы, LinearInterpolator изменяет значения с одинаковой скоростью: 0,2 за каждые 200 мс. AccelerateDecelerateInterpolator изменяет значения быстрее, чем LinearInterpolator, в интервале от 200 до 600 мс и медленнее – в интервале от 600 до 1000 мс.

Укажите ключевые кадры

Объект Keyframe состоит из пары "время/значение", которая позволяет определить конкретное состояние в определенный момент анимации. У каждого ключевого кадра может быть собственный интерполятор, который управляет поведением анимации в интервале между предыдущим и текущим ключевыми кадрами.

Чтобы создать экземпляр объекта Keyframe, необходимо использовать один из фабричных методов (ofInt(), ofFloat() или ofObject()), позволяющих получить нужный тип Keyframe. Затем вызовите фабричный метод ofKeyframe(), чтобы получить объект PropertyValuesHolder. Получив объект, вы можете получить аниматор, передав объект PropertyValuesHolder и объект для анимации. Это показано в следующем фрагменте кода:

Kotlin

val kf0 = Keyframe.ofFloat(0f, 0f)
val kf1 = Keyframe.ofFloat(.5f, 360f)
val kf2 = Keyframe.ofFloat(1f, 0f)
val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
    duration = 5000
}

Java

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
rotationAnim.setDuration(5000);

Анимация представлений

Система анимации свойств позволяет оптимизировать анимацию объектов View и предлагает несколько преимуществ по сравнению с системой анимации представлений. Система анимации представлений преобразовывала объекты View, изменяя способ их отрисовки. Это обрабатывалось в контейнере каждого представления, поскольку у самого представления не было свойств для манипулирования. В результате объект View анимировался, но сам по себе не менялся. Это приводило к тому, что объект оставался в исходном местоположении, даже если он был нарисован в другом месте на экране. В Android 3.0 были добавлены новые свойства и соответствующие методы получения и установки, чтобы устранить этот недостаток.

Система анимации свойств позволяет анимировать объекты View на экране, изменяя их фактические свойства. Кроме того, представления автоматически вызывают метод invalidate() для обновления экрана при изменении его свойств. Новые свойства в классе View, которые упрощают анимацию свойств:

  • translationX и translationY. Эти свойства определяют, где находится представление, как дельта от его левой и верхней координат, которые задаются его контейнером макета.
  • rotation, rotationX и rotationY. Эти свойства управляют поворотом в 2D (свойство rotation) и 3D вокруг опорной точки.
  • scaleX и scaleY. Эти свойства управляют 2D-масштабированием представления вокруг его опорной точки.
  • pivotX и pivotY. Эти свойства определяют расположение опорной точки, вокруг которой происходит поворот и масштабирование. По умолчанию опорная точка находится в центре объекта.
  • x и y – простые служебные свойства, описывающие конечное положение View в контейнере как сумму значений left и top и значений translationX и translationY.
  • alpha – альфа-прозрачность в представлении. По умолчанию используется значение 1 (непрозрачный), а значение 0 соответствует полной прозрачности (невидимый).

Чтобы анимировать свойство объекта View, например его цвет или значение поворота, вам нужно лишь создать аниматор свойства и указать свойство View, которое вы хотите анимировать. Пример:

Kotlin

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

Java

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

Дополнительную информацию о создании аниматоров можно найти в разделах, посвященных анимации с помощью ValueAnimator и ObjectAnimator.

Анимация с помощью ViewPropertyAnimator

ViewPropertyAnimator позволяет легко анимировать несколько свойств View параллельно, используя один базовый объект Animator. Он работает так же, как ObjectAnimator, поскольку изменяет фактические значения свойств представления, но более эффективен при одновременной анимации многих свойств. Кроме того, код для использования ViewPropertyAnimator намного более лаконичен и его легче читать. В следующих фрагментах кода показаны различия в использовании нескольких объектов ObjectAnimator, одного ObjectAnimator и ViewPropertyAnimator при одновременном анимировании свойств x и y представления.

Несколько объектов ObjectAnimator

Kotlin

val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
AnimatorSet().apply {
    playTogether(animX, animY)
    start()
}

Java

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

Один ObjectAnimator

Kotlin

val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()

Java

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();

ViewPropertyAnimator

Kotlin

myView.animate().x(50f).y(100f)

Java

myView.animate().x(50f).y(100f);

Подробную информацию о ViewPropertyAnimator можно найти в блоге для разработчиков Android.

Объявление анимаций в XML

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

Чтобы отличать файлы анимации, в которых используются новые API анимации свойств, от файлов, в которых используется устаревший фреймворк анимации представлений, начиная с Android 3.1, вы должны сохранять XML-файлы для анимации свойств в каталоге res/animator/.

Следующие классы анимации свойств поддерживают XML-декларацию с помощью следующих XML-тегов:

Чтобы узнать, какие атрибуты можно использовать в XML-декларации, ознакомьтесь со статьей Ресурсы анимации. В следующем примере два набора анимаций объектов воспроизводятся последовательно, причем в первом вложенном наборе две анимации объектов воспроизводятся вместе:

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

Чтобы запустить эту анимацию, необходимо в коде преобразовать XML-ресурсы в объект AnimatorSet, а затем задать целевые объекты для всех анимаций перед запуском набора анимаций. Вызов setTarget() задает один целевой объект для всех дочерних элементов AnimatorSet. Ниже показано, как это сделать.

Kotlin

(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
    setTarget(myObject)
    start()
}

Java

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

Вы также можете объявить ValueAnimator в XML, как показано в следующем примере:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

Чтобы использовать предыдущий объект ValueAnimator в коде, необходимо заполнить объект, добавить AnimatorUpdateListener, получить обновленное значение анимации и использовать его в свойстве одного из представлений, как показано в следующем коде:

Kotlin

(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
    addUpdateListener { updatedAnimation ->
        textView.translationX = updatedAnimation.animatedValue as Float
    }

    start()
}

Java

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

Информацию о синтаксисе XML для определения анимации свойств можно найти в статье Ресурсы анимации .

Возможное влияние на производительность интерфейса

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

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