Анимация движения с эффектом упругости

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

Физика движения основана на силе. Сила пружины – одна из таких сил, которая определяет интерактивность и движение. Сила пружины имеет следующие свойства: демпфирование и жесткость. В анимации на основе пружины значение и скорость рассчитываются на основе силы пружины, которая применяется к каждому кадру.

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

Жизненный цикл пружинной анимации

В анимации на основе пружины класс SpringForce позволяет настроить жесткость пружины, коэффициент демпфирования и конечное положение. Как только анимация начинается, сила пружины обновляет значение анимации и скорость на каждом кадре. Анимация продолжается, пока пружина не придет в равновесие.

Например, если вы перетаскиваете значок приложения по экрану, а затем отпускаете его, он возвращается на свое место под действием невидимой, но знакомой силы.

На рисунке 1 показан похожий эффект пружины. Знак плюса (+) в центре круга указывает на силу, приложенную при касании.

Весенний выпуск
Рисунок 1. Эффект "Весенний релиз"

Как создать пружинную анимацию

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

В следующих разделах подробно описаны общие шаги по созданию пружинной анимации.

Как добавить библиотеку поддержки

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

  1. Откройте файл build.gradle для модуля приложения.
  2. Добавьте библиотеку поддержки в раздел dependencies.

    Яркий

            dependencies {
                def dynamicanimation_version = '1.0.0'
                implementation "androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version"
            }
            

    Kotlin

            dependencies {
                val dynamicanimation_version = "1.0.0"
                implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version")
            }
            

    Чтобы посмотреть текущие версии этой библиотеки, перейдите на страницу версий и найдите информацию о Dynamicanimation.

Как создать пружинную анимацию

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

Примечание. При создании анимации пружины можно не указывать ее конечное положение. Однако его необходимо задать до начала анимации.

Kotlin

val springAnim = findViewById<View>(R.id.imageView).let { img ->
    // Setting up a spring animation to animate the view’s translationY property with the final
    // spring position at 0.
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f)
}

Java

final View img = findViewById(R.id.imageView);
// Setting up a spring animation to animate the view’s translationY property with the final
// spring position at 0.
final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);

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

  • ALPHA: Представляет альфа-прозрачность на изображении. По умолчанию значение равно 1 (непрозрачный), а значение 0 означает полную прозрачность (невидимый).
  • TRANSLATION_X, TRANSLATION_Y и TRANSLATION_Z. Эти свойства определяют, где находится представление, как разница между его левой и верхней координатами, а также высотой, которые задаются контейнером макета.
    • TRANSLATION_X описывает левую координату.
    • TRANSLATION_Y описывает верхнюю координату.
    • TRANSLATION_Z описывает глубину изображения относительно его высоты.
  • ROTATION, ROTATION_X и ROTATION_Y: эти свойства управляют поворотом в 2D (свойство rotation) и 3D вокруг точки поворота.
  • SCROLL_X и SCROLL_Y – свойства, указывающие смещение прокрутки левого и верхнего края источника в пикселях. Также указывается, насколько прокручена страница.
  • SCALE_X и SCALE_Y: эти свойства управляют масштабированием 2D-представления относительно его точки поворота.
  • X, Y и Z – это основные вспомогательные свойства, описывающие конечное положение элемента в контейнере.
    • X – это сумма левого значения и TRANSLATION_X.
    • Y – это сумма максимального значения и TRANSLATION_Y.
    • Z – это сумма значения высоты и TRANSLATION_Z.

Как зарегистрировать прослушивателей

Класс DynamicAnimation предоставляет два прослушивателя: OnAnimationUpdateListener и OnAnimationEndListener. Эти прослушиватели отслеживают изменения в анимации, например когда меняется значение анимации или когда она заканчивается.

OnAnimationUpdateListener

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

  1. Вызовите метод addUpdateListener() и прикрепите прослушиватель к анимации.

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

  2. Переопределите метод onAnimationUpdate(), чтобы уведомить вызывающую сторону об изменении текущего объекта. В приведенном ниже образце кода показано общее использование OnAnimationUpdateListener.

Kotlin

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 ->
    SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y)
}
val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 ->
    SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y)
}

// Registering the update listener
anim1X.addUpdateListener { _, value, _ ->
    // Overriding the method to notify view2 about the change in the view1’s property.
    anim2X.animateToFinalPosition(value)
}

anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }

Java

// Creating two views to demonstrate the registration of the update listener.
final View view1 = findViewById(R.id.view1);
final View view2 = findViewById(R.id.view2);

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
final SpringAnimation anim1X = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim1Y = new SpringAnimation(view1,
    DynamicAnimation.TRANSLATION_Y);
final SpringAnimation anim2X = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim2Y = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_Y);

// Registering the update listener
anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

// Overriding the method to notify view2 about the change in the view1’s property.
    @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2X.animateToFinalPosition(value);
    }
});

anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

  @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2Y.animateToFinalPosition(value);
    }
});

OnAnimationEndListener

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

  1. Вызовите метод addEndListener() и прикрепите прослушиватель к анимации.
  2. Переопределите метод onAnimationEnd(), чтобы получать уведомления, когда анимация достигает равновесия или отменяется.

Как удалить прослушивателей

Чтобы прекратить получать обратные вызовы для обновления анимации и ее завершения, вызовите методы removeUpdateListener() и removeEndListener() соответственно.

Как задать начальное значение анимации

Чтобы задать начальное значение анимации, вызовите метод setStartValue() и передайте ему начальное значение анимации. Если вы не зададите начальное значение, анимация будет использовать текущее значение свойства объекта.

Как задать диапазон значений анимации

Вы можете задать минимальное и максимальное значения анимации, чтобы ограничить диапазон значений свойства. Также она помогает контролировать диапазон, если вы анимируете свойства, у которых есть собственный диапазон, например альфа-канал (от 0 до 1).

  • Чтобы задать минимальное значение, вызовите метод setMinValue() и передайте минимальное значение свойства.
  • Чтобы задать максимальное значение, вызовите метод setMaxValue() и передайте максимальное значение свойства.

Оба метода возвращают анимацию, для которой задается значение.

Примечание. Если вы задали начальное значение и определили диапазон значений анимации, убедитесь, что начальное значение находится в пределах этого диапазона.

Как задать начальную скорость

Начальная скорость определяет скорость, с которой свойство анимации изменяется в начале анимации. Скорость начала движения по умолчанию равна нулю пикселей в секунду. Скорость можно задать с помощью скорости жестов касания или с помощью фиксированного значения в качестве начальной скорости. Если вы хотите указать фиксированное значение, рекомендуем задать его в единицах dp/с, а затем преобразовать в пиксели/с. Значение в dp/с позволяет сделать скорость независимой от плотности и форм-факторов. Чтобы узнать больше о том, как преобразовать значение в пиксели в секунду, ознакомьтесь с разделом Как преобразовать dp в секунду в пиксели в секунду.

Чтобы задать скорость, вызовите метод setStartVelocity() и передайте скорость в пикселях в секунду. Метод возвращает объект силы пружины, для которого задана скорость.

Примечание. Чтобы получить и вычислить скорость сенсорных жестов, используйте методы класса GestureDetector.OnGestureListener или VelocityTracker.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Compute velocity in the unit pixel/second
        vt.computeCurrentVelocity(1000)
        val velocity = vt.yVelocity
        setStartVelocity(velocity)
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Compute velocity in the unit pixel/second
vt.computeCurrentVelocity(1000);
float velocity = vt.getYVelocity();
anim.setStartVelocity(velocity);

Как преобразовать dp/с в пиксели/с

Скорость пружины должна быть указана в пикселях в секунду. Если вы хотите задать фиксированное значение в качестве начальной скорости, укажите его в единицах dp/с, а затем преобразуйте в пиксели в секунду. Для преобразования используйте метод applyDimension() из класса TypedValue. См. следующий образец кода:

Kotlin

val pixelPerSecond: Float =
    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)

Java

float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());

Как задать свойства пружины

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

Совет. При использовании методов установки можно создать цепочку методов, поскольку все они возвращают объект пружинной силы.

Коэффициент демпфирования

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

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

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

  1. Вызовите метод getSpring(), чтобы получить пружину и добавить коэффициент демпфирования.
  2. Вызовите метод setDampingRatio() и передайте коэффициент демпфирования, который нужно добавить к пружине. Метод возвращает объект силы пружины, для которого задано значение коэффициента демпфирования.

    Примечание. Коэффициент демпфирования должен быть неотрицательным числом. Если задать нулевое значение коэффициента демпфирования, пружина никогда не вернется в исходное положение. Другими словами, он будет колебаться бесконечно.

В системе доступны следующие константы коэффициента демпфирования:

Рисунок 2. Высокий показатель отказов

Рисунок 3. Средний отскок

Рисунок 4. Низкий показатель отказов

Рисунок 5. Отсутствие перехода

Коэффициент демпфирования по умолчанию установлен на DAMPING_RATIO_MEDIUM_BOUNCY.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the damping ratio to create a low bouncing effect.
        spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the damping ratio to create a low bouncing effect.
anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
…

Жесткость

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

  1. Вызовите метод getSpring(), чтобы получить пружину и добавить жесткость.
  2. Вызовите метод setStiffness() и передайте ему значение жесткости, которое нужно добавить к пружине. Метод возвращает объект пружины, для которого задана жесткость.

    Примечание. Жесткость должна быть положительным числом.

В системе доступны следующие константы жесткости:

Рисунок 6. Высокая жесткость

Рисунок 7. Средняя жесткость

Рисунок 8. Низкая жесткость

Рисунок 9. Очень низкая жесткость

Значение по умолчанию – STIFFNESS_MEDIUM.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the spring with a low stiffness.
        spring.stiffness = SpringForce.STIFFNESS_LOW
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the spring with a low stiffness.
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW);
…

Как создать собственную силу пружины

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

  1. Создайте объект SpringForce.

    SpringForce force = new SpringForce();

  2. Назначьте свойства, вызвав соответствующие методы. Также можно создать цепочку методов.

    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);

  3. Вызовите метод setSpring(), чтобы добавить к анимации эффект пружины.

    setSpring(force);

Запустить анимацию

Запустить пружинную анимацию можно двумя способами: вызвав метод start() или метод animateToFinalPosition(). Оба метода должны вызываться в основном потоке.

Метод animateToFinalPosition() выполняет две задачи:

  • Задает конечное положение пружины.
  • Запускает анимацию, если она ещё не началась.

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

На рисунке 10 показана анимация цепочки пружин, в которой анимация одного представления зависит от другого.

Демоверсия цепочки пружин
Рисунок 10. Chained spring demo

Чтобы использовать метод animateToFinalPosition(), вызовите метод animateToFinalPosition() и передайте положение покоя пружины. Вы также можете задать положение покоя пружины, вызвав метод setFinalPosition().

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

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Starting the animation
        start()
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Starting the animation
anim.start();
…

Как отменить анимацию

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

Анимацию можно завершить двумя способами. Метод cancel() останавливает анимацию на текущем значении, а метод skipToEnd() пропускает анимацию до конечного значения и останавливает ее.

Прежде чем завершить анимацию, важно проверить состояние пружины. Если состояние не демпфировано, анимация никогда не достигнет конечного положения. Чтобы проверить состояние пружины, вызовите метод canSkipToEnd(). Если пружина демпфирована, метод возвращает true, в противном случае – false.

Когда вы узнаете состояние пружины, вы можете завершить анимацию, используя метод skipToEnd() или cancel(). Метод cancel() должен вызываться только в основном потоке.

Примечание. Как правило, метод skipToEnd() приводит к визуальному скачку.