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

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

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

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

  • Длительность: Вы можете указать длительность анимации. Длительность по умолчанию составляет 300 мс.
  • Интерполяция времени: Вы можете указать, как значения свойства рассчитываются в зависимости от текущего прошедшего времени анимации.
  • Количество повторов и поведение: Вы можете указать, следует ли повторять анимацию по истечении заданного времени, а также сколько раз это нужно сделать. Вы также можете указать, хотите ли вы, чтобы анимация воспроизводилась в обратном порядке. В обратном порядке анимация будет воспроизводиться сначала вперед, затем назад, многократно, пока не будет достигнуто заданное количество повторов.
  • Наборы анимаций: Вы можете группировать анимации в логические наборы, которые воспроизводятся одновременно, последовательно или с заданной задержкой.
  • Задержка обновления кадров: Вы можете указать, как часто следует обновлять кадры вашей анимации. По умолчанию установлено обновление каждые 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 , а в TypeEvaluatorIntEvaluator .

Чтобы запустить анимацию, создайте 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 Класс ObjectAnimator является подклассом ValueAnimator и позволяет задать целевой объект и свойство объекта для анимации. Этот класс обновляет свойство соответствующим образом при вычислении нового значения для анимации. В большинстве случаев рекомендуется использовать ObjectAnimator , поскольку он значительно упрощает процесс анимации значений целевых объектов. Однако иногда может потребоваться использовать ValueAnimator напрямую, поскольку ObjectAnimator имеет несколько дополнительных ограничений, например, требует наличия определенных методов доступа у целевого объекта.
AnimatorSet Предоставляет механизм для группировки анимаций таким образом, чтобы они воспроизводились относительно друг друга. Вы можете настроить воспроизведение анимаций одновременно, последовательно или с заданной задержкой. Дополнительную информацию см. в разделе « Хореография нескольких анимаций с помощью наборов аниматоров» .

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

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

Класс/Интерфейс Описание
IntEvaluator Инструмент по умолчанию для вычисления значений int свойств.
FloatEvaluator Инструмент по умолчанию для вычисления значений свойств float .
ArgbEvaluator Инструмент по умолчанию для вычисления значений цветовых свойств, представленных в шестнадцатеричном формате.
TypeEvaluator Интерфейс, позволяющий создавать собственный оценщик. Если вы анимируете свойство объекта, которое не является целым int ), float ) или цветом (color), вам необходимо реализовать интерфейс TypeEvaluator , чтобы указать, как вычислять анимируемые значения свойства объекта. Вы также можете указать собственный TypeEvaluator для значений int , float и color, если хотите обрабатывать эти типы иначе, чем это предусмотрено по умолчанию. Дополнительную информацию о написании пользовательского оценщика см. в разделе «Использование 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() . Например:

Котлин

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() .

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

Котлин

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 , как показано в следующем коде:

Котлин

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 , но при этом вы также указываете объект и имя свойства этого объекта (в виде строки), а также значения, между которыми нужно анимировать:

Котлин

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

Java

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

Для корректного обновления свойств объектом ObjectAnimator необходимо выполнить следующие действия:

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

С помощью AnimatorSet можно создать несколько анимаций одновременно.

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

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

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

Котлин

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, чтобы принудительно перерисовать эту область экрана с новыми анимированными значениями. Например, анимация свойства color объекта Drawable приводит к обновлению экрана только тогда, когда этот объект перерисовывается. Все методы установки свойств View, такие как setAlpha() и setTranslationX() корректно аннулируют View, поэтому вам не нужно аннулировать View при вызове этих методов с новыми значениями.

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

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

Котлин

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 . Элементы внутри ViewGroup могут проходить анимацию появления и исчезновения при добавлении или удалении из ViewGroup, а также при вызове метода setVisibility() элемента с параметрами 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, а также остальные представления в ViewGroup.

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

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

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

Например, следующий файл создает аниматор списка состояний, который изменяет масштаб представления по осям 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 или color, которые поддерживаются оценщиками типов IntEvaluator , FloatEvaluator и ArgbEvaluator .

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

Котлин

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 ускоряет движение в начале анимации и замедляет его в конце. Логика работы этих интерполяторов определяется следующими методами:

УскоритьЗамедлитьИнтерполятор

Котлин

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;
}

Линейный интерполятор

Котлин

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 .345
600 .6 .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 и объект для анимации. Следующий фрагмент кода демонстрирует, как это сделать:

Котлин

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, поскольку сам View не имел свойств для манипулирования. В результате View анимировался, но не изменял сам объект View. Это приводило к такому поведению, как сохранение объекта в исходном положении, даже если он был отрисован в другом месте на экране. В Android 3.0 были добавлены новые свойства и соответствующие методы getter и setter для устранения этого недостатка.

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

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

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

Котлин

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

Котлин

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();

Один ОбъектАниматор

Котлин

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

Котлин

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 . Следующий код показывает, как это сделать:

Котлин

(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 , получить обновленное значение анимации и использовать его в свойстве одного из ваших представлений, как показано в следующем коде:

Котлин

(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 для определения анимаций свойств см. в разделе «Ресурсы анимации» .

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

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

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