Анимация движения с использованием пружинной физики

Попробуйте способ создания
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"
            }
            

    Котлин

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

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

Создайте весеннюю анимацию

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

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

Котлин

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

Ява

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

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

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

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

Котлин

// 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) }

Ява

// 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 для получения и вычисления скорости сенсорных жестов.

Котлин

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

Ява

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 . Обратитесь к следующему примеру кода:

Котлин

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

Ява

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

Установить свойства пружины

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

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

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

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

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

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

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

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

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

Рисунок 2. Высокий отскок

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

Рисунок 4. Низкий отскок

Рисунок 5. Никакого отскока

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

Котлин

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

Ява

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 .

Котлин

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

Ява

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. Демонстрация цепной пружины

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

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

Котлин

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

Ява

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() вызывает визуальный переход.