Animuj ruch za pomocą fizyki sprężyny

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak korzystać z animacji w Compose

Ruch oparty na fizyce jest napędzany siłą. Siła sprężyny jest jedną z takich sił, które sterują interakcją i ruchem. Siła sprężyny ma takie właściwości: tłumienie i sztywność. W animacji opartej na sprężynach wartość i prędkość są obliczane na podstawie siły sprężystej zastosowanej do każdej klatki.

Jeśli chcesz, by animacje w aplikacji były spowolnione tylko w jednym kierunku, rozważ użycie animacji przesuwania opartej na tarciu.

Cykl życia animacji wiosennej

W animacji opartej na sprężynach klasa SpringForce umożliwia dostosowanie sztywności sprężyny, współczynnika tłumienia drgań i jej ostatecznego położenia. Gdy tylko rozpocznie się animacja, siła sprężyny zaktualizuje wartość animacji i prędkość każdej klatki. Animacja trwa, aż siła sprężyny osiągnie równowagę.

Jeśli na przykład przeciągniesz ikonę aplikacji po ekranie, a później zwolnisz ją, unosząc palec, ikona zostanie przywrócona do pierwotnego miejsca przez niewidzialną, ale znajomą siłę.

Rysunek 1 przedstawia podobny efekt wiosny. Znak plusa (+) w środku okręgu wskazuje siłę wywieraną w wyniku gestu dotykowego.

Premiera wiosenna
Rysunek 1. Efekt wiosennej premiery

Tworzenie wiosennej animacji

Ogólne kroki tworzenia wiosennej animacji w aplikacji:

W kolejnych sekcjach znajdziesz szczegółowe omówienie ogólnych etapów tworzenia animacji na wiosnę.

Dodawanie biblioteki pomocy

Aby korzystać z biblioteki pomocy związanej z fizyką, musisz dodać ją do projektu w ten sposób:

  1. Otwórz plik build.gradle modułu aplikacji.
  2. Dodaj bibliotekę pomocy do sekcji dependencies.

    Odlotowe

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

    Aby wyświetlić bieżące wersje tej biblioteki, zapoznaj się z informacjami na temat animacji dynamicznej na stronie wersji.

Tworzenie animacji wiosennej

Klasa SpringAnimation pozwala utworzyć wiosenną animację dla obiektu. Aby utworzyć animację sprężyny, musisz utworzyć instancję klasy SpringAnimation i podać obiekt, właściwość obiektu, którą chcesz animować, oraz opcjonalną końcową pozycję sprężyny, w której ma się spoczywać animacja.

Uwaga: podczas tworzenia animacji sprężyny końcowa pozycja sprężyny jest opcjonalna. Musisz jednak je zdefiniować przed rozpoczęciem animacji.

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

Animacja oparta na sprężynach może animować widoki na ekranie, zmieniając rzeczywiste właściwości obiektów widoku. W systemie dostępne są te widoki:

  • ALPHA: reprezentuje przezroczystość widoku w wersji alfa. Domyślnie wartość wynosi 1 (nieprzezroczysta), a wartość 0 oznacza pełną przezroczystość (niewidoczną).
  • TRANSLATION_X, TRANSLATION_Y i TRANSLATION_Z: te właściwości określają, gdzie znajduje się widok jako wartość delta od lewej, górnej współrzędnej i wysokości, które są ustawiane przez kontener układu.
  • ROTATION, ROTATION_X i ROTATION_Y: te właściwości sterują obrotem w 2D (usługa rotation) i w 3D wokół punktu przestawnego.
  • SCROLL_X i SCROLL_Y: te właściwości wskazują przesunięcie przewijania względem lewej i górnej krawędzi w pikselach. Wskazuje też pozycję pod względem długości przewijania strony.
  • SCALE_X i SCALE_Y: te właściwości sterują skalowaniem 2D widoku wokół jego punktu obrotu.
  • X, Y i Z: to podstawowe właściwości użytkowe opisujące końcową lokalizację widoku w jego kontenerze.

Rejestrowanie detektorów

Klasa DynamicAnimation zawiera 2 detektory: OnAnimationUpdateListener i OnAnimationEndListener. Te detektory wykrywają aktualizacje animacji, np. moment zmiany wartości animacji lub zakończenia animacji.

Detektor OnAnimationUpdateListener

Jeśli chcesz animować wiele widoków w celu utworzenia łańcuchowej animacji, możesz skonfigurować OnAnimationUpdateListener tak, aby odbierał wywołanie zwrotne za każdym razem, gdy nastąpi zmiana właściwości bieżącego widoku. Wywołanie zwrotne powiadamia drugi widok, aby zaktualizować jego pozycję sprężyny na podstawie zmiany dokonanej we właściwości bieżącego widoku. Aby zarejestrować detektor, wykonaj te czynności:

  1. Wywołaj metodę addUpdateListener() i dołącz detektor do animacji.

    Uwaga: zarejestruj detektor aktualizacji przed rozpoczęciem animacji. Pamiętaj jednak, że detektor aktualizacji należy zarejestrować tylko wtedy, gdy zmiana wartości animacji wymaga aktualizacji na poziomie klatki. Detektor aktualizacji uniemożliwia potencjalne uruchomienie animacji w oddzielnym wątku.

  2. Zastąp metodę onAnimationUpdate(), aby powiadamiać obiekt wywołujący o zmianie bieżącego obiektu. Poniższy przykładowy kod ilustruje ogólne użycie 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);
    }
});

Detektor zakończenia animacji

OnAnimationEndListener powiadamia o zakończeniu animacji. Możesz skonfigurować odbiornik tak, aby odbierał wywołanie zwrotne za każdym razem, gdy animacja osiągnie równowagę lub zostanie anulowana. Aby zarejestrować detektor, wykonaj te czynności:

  1. Wywołaj metodę addEndListener() i dołącz detektor do animacji.
  2. Zastąp metodę onAnimationEnd(), aby otrzymywać powiadomienia za każdym razem, gdy animacja osiągnie równowagę lub zostanie anulowana.

Usuń detektory

Aby przestać otrzymywać wywołania zwrotne aktualizacji animacji i zakończenia animacji, wywołaj odpowiednio metody removeUpdateListener() i removeEndListener().

Ustaw wartość początkową animacji

Aby ustawić wartość początkową animacji, wywołaj metodę setStartValue() i przekaż wartość początkową animacji. Jeśli nie ustawisz wartości początkowej, animacja będzie używać bieżącej wartości właściwości obiektu.

Ustaw zakres wartości animacji

Możesz ustawić minimalne i maksymalne wartości animacji, by ograniczyć wartość właściwości do określonego zakresu. Pomaga też kontrolować zakres, jeśli animujesz właściwości o wewnętrznym zakresie, np. alfa (od 0 do 1).

  • Aby ustawić minimalną wartość, wywołaj metodę setMinValue() i przekaż minimalną wartość właściwości.
  • Aby ustawić maksymalną wartość, wywołaj metodę setMaxValue() i przekaż maksymalną wartość właściwości.

Obie metody zwracają animację, dla której ustawiana jest wartość.

Uwaga: jeśli masz ustawioną wartość początkową i zdefiniowany zakres wartości animacji, upewnij się, że wartość początkowa jest w przedziale od minimalnej do maksymalnej.

Ustaw prędkość początkową

Prędkość początkowa określa szybkość, z jaką zmienia się właściwość animacji na początku animacji. Domyślna prędkość początkowa jest ustawiona na 0 pikseli na sekundę. Możesz ustawić szybkość za pomocą gestów dotykowych lub użyć stałej wartości jako szybkości początkowej. Jeśli zdecydujesz się podać stałą wartość, zalecamy określenie wartości w dp na sekundę, a następnie przekonwertowanie jej na piksele na sekundę. Określenie wartości w dp na sekundę pozwala określić prędkość niezależnie od gęstości i formatu. Więcej informacji o konwertowaniu wartości na piksele na sekundę znajdziesz w sekcji Konwertowanie dp na sekundę na piksele na sekundę.

Aby ustawić prędkość, wywołaj metodę setStartVelocity() i przekazuj ją w pikselach na sekundę. Metoda zwraca obiekt siły sprężyny, dla którego ustawiono prędkość.

Uwaga: aby pobrać i obliczyć szybkość gestów dotykowych, użyj metod klasy GestureDetector.OnGestureListener lub 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);

Przeliczanie dp na sekundę na piksele na sekundę

Prędkość sprężyny musi być podana w pikselach na sekundę. Jeśli jako początek prędkości chcesz podać stałą wartość, podaj wartość w dp na sekundę, a następnie przekonwertuj ją na piksele na sekundę. Do konwersji użyj metody applyDimension() z klasy TypedValue. Skorzystaj z tego przykładowego kodu:

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

Ustawianie właściwości sprężyny

Klasa SpringForce określa metody getter i metod ustalających dla każdej z właściwości sprężyny, takich jak współczynnik tłumienia i sztywność. Aby ustawić właściwości sprężyny, musisz pobrać obiekt siły sprężyny lub utworzyć niestandardową siłę sprężyny, dla której możesz ustawić te właściwości. Więcej informacji o tworzeniu niestandardowej siły sprężyny znajdziesz w sekcji Tworzenie niestandardowej siły sprężyny.

Wskazówka: korzystając z metod ustawiania, możesz utworzyć łańcuch metod, ponieważ wszystkie metody ustawiania zwracają obiekt siły sprężyny.

Współczynnik tłumienia

Współczynnik tłumienia opisuje stopniowe zmniejszanie oscylacji sprężyny. Za pomocą współczynnika tłumienia możesz określić, jak szybko zanikają oscylacje od jednego odbijania do drugiego. Istnieją 4 sposoby wilgotności sprężyny:

  • Przeciążenie występuje, gdy współczynnik tłumienia jest większy niż 1. Umożliwia on delikatne powrót obiektu do pozycji spoczynkowej.
  • Tłumienie krytyczne występuje, gdy współczynnik tłumienia jest równy 1. Umożliwia obiektowi powrót do pozycji spoczynkowej w najkrótszym czasie.
  • Niedostateczne tłumienie występuje, gdy współczynnik tłumienia jest mniejszy niż jeden. Pozwala to wielokrotnie na przekroczenie limitu czasu, przekazując pozycję spoczynkową, a następnie stopniowo osiąga ją do pozycji spoczynku.
  • Tłumienie nie jest tłumione, gdy współczynnik tłumienia wynosi 0. Pozwala obiektowi oscylować w nieskończoność.

Aby dodać współczynnik tłumienia do sprężyny, wykonaj te czynności:

  1. Wywołaj metodę getSpring(), aby pobrać sprężynę i dodać współczynnik tłumienia.
  2. Wywołaj metodę setDampingRatio() i przekaż współczynnik tłumienia, który chcesz dodać do sprężyny. Metoda zwraca obiekt siły sprężyny, dla którego ustawiony jest współczynnik tłumienia.

    Uwaga: współczynnik tłumienia musi być liczbą nieujemną. Jeśli ustawisz współczynnik tłumienia na 0, sprężyna nigdy nie osiągnie pozycji spoczynkowej. Inaczej mówiąc, zmienia się w nieskończoność.

W systemie dostępne są te stałe współczynnika tłumienia:

Rysunek 2. Wysokie odbicie

Rysunek 3. Średnie odbicie

Rysunek 4. Niski współczynnik odrzuceń

Rysunek 5. Brak odrzuceń

Domyślny współczynnik tłumienia jest ustawiony na 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);
…

Sztywność

Sztywność określa stałą sprężystość, która służy do pomiaru siły sprężyny. Sztywna sprężyna wywiera większą siłę na dołączany obiekt, gdy sprężyna nie znajduje się w położeniu spoczynku. Aby zwiększyć sztywność sprężyny, wykonaj te czynności:

  1. Wywołaj metodę getSpring(), aby pobrać sprężynę i dodać sztywność.
  2. Wywołaj metodę setStiffness() i przekaż wartość sztywności, którą chcesz dodać do sprężyny. Metoda zwraca obiekt siły sprężyny, dla którego ustawiono sztywność.

    Uwaga: sztywność musi być liczbą dodatnią.

W systemie dostępne są te stałe sztywności:

Rysunek 6. Duża sztywność

Rysunek 7. Średnia sztywność

Rysunek 8. Niska sztywność

Rysunek 9. Bardzo niska sztywność

Domyślna sztywność jest ustawiona na 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);
…

Utwórz niestandardową siłę sprężyny

Zamiast używać domyślnej siły sprężyny, możesz utworzyć niestandardową siłę sprężyny. Niestandardowa siła sprężyny pozwala współużytkować tę samą instancję siły sprężyny w wielu animacjach sprężyn. Po utworzeniu siły sprężyny możesz określić właściwości takie jak współczynnik tłumienia i sztywność.

  1. Utwórz obiekt SpringForce.

    SpringForce force = new SpringForce();

  2. Przypisz właściwości, wywołując je. Możesz też utworzyć łańcuch metod.

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

  3. Wywołaj metodę setSpring(), aby ustawić sprężynę w animacji.

    setSpring(force);

Rozpocznij animację

Tę animację możesz rozpocząć na 2 sposoby: wywołując metodę start() lub metodę animateToFinalPosition(). Obie metody muszą być wywoływane w wątku głównym.

Metoda animateToFinalPosition() wykonuje 2 czynności:

  • Ustawia końcową pozycję sprężyny.
  • Uruchamia animację, jeśli jeszcze się nie rozpoczęła.

Ponieważ metoda ta aktualizuje końcową pozycję sprężyny i w razie potrzeby rozpoczyna animację, możesz ją wywołać w dowolnym momencie, aby zmienić przebieg animacji. Na przykład w przypadku połączonej łańcuchowo animacji wiosennej animacja jednego widoku zależy od innego. W przypadku takiej animacji wygodniej jest użyć metody animateToFinalPosition(). Użycie tej metody w łańcuchowej wiosennej animacji pozwala uniknąć problemów z tym, że animacja, którą chcesz zaktualizować w następnej kolejności, jest obecnie uruchomiona.

Rysunek 10 przedstawia łańcuchową animację sprężyn, w której animacja jednego widoku zależy od innego.

Łańcuch – prezentacja
Rysunek 10. Prezentacja łańcucha sprężyn

Aby użyć metody animateToFinalPosition(), wywołaj metodę animateToFinalPosition() i przekaż informację o spoczynku sprężyny. Możesz też ustawić spoczynkowe położenie sprężyny, wywołując metodę setFinalPosition().

Metoda start() nie ustawia od razu wartości właściwości na wartość początkową. Wartość właściwości zmienia się przy każdym pulsie animacji, co następuje przed przejściem pobierania. W związku z tym zmiany są odzwierciedlane w następnej ramce, tak jakby wartości były ustawiane natychmiast.

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

Anuluj animację

Możesz anulować odtwarzanie animacji lub przejść do końca. Idealna sytuacja, w której musisz anulować lub przejść do końca amiacji, to sytuacja, gdy interakcja użytkownika wymaga natychmiastowego zakończenia animacji. Dzieje się tak głównie wtedy, gdy użytkownik nagle wychodzi z aplikacji lub widok staje się niewidoczny.

Animację możesz zakończyć na 2 sposoby. Metoda cancel() kończy animację z wartością, w której się znajduje. Metoda skipToEnd() pomija animację do ostatniej wartości, a następnie ją kończy.

Przed zakończeniem animacji trzeba najpierw sprawdzić stan sprężyny. Jeśli stan nie jest wyciszony, animacja nie może osiągnąć pozycji spoczynku. Aby sprawdzić stan sprężyny, wywołaj metodę canSkipToEnd(). Jeśli sprężyna jest tłumiona, metoda zwraca true. W przeciwnym razie false.

Gdy poznasz stan sprężyny, możesz zakończyć animację, korzystając z metody skipToEnd() lub cancel(). Metoda cancel() musi być wywoływana tylko w wątku głównym.

Uwaga: zwykle metoda skipToEnd() powoduje wizualne wyświetlenie reklamy.