Animuj ruch za pomocą fizyki sprężyny

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak używać animacji w Compose.

Ruch oparty na fizyce jest napędzany siłą. Siła naprężenia to jedna z sił, które wpływają na interaktywność i ruch. Siła naprężenia ma te właściwości: tłumienie i sztywność. W animacji opartej na sprężynie wartość i prędkość są obliczane na podstawie siły sprężyny, która jest stosowana w każdej klatce.

Jeśli chcesz, aby animacje w aplikacji zwalniały tylko w jednym kierunku, rozważ użycie animacji przesunięcia opartej na tarciu.

Cykl życia animacji sprężynowej

W animacji opartej na sprężynie klasa SpringForce umożliwia dostosowanie sztywności sprężyny, współczynnika tłumienia i pozycji końcowej. Gdy tylko animacja się rozpocznie, siła sprężyny będzie aktualizować wartość animacji i prędkość w każdej klatce. Animacja trwa, dopóki siła sprężyny nie osiągnie stanu równowagi.

Jeśli na przykład przeciągniesz ikonę aplikacji po ekranie, a potem ją puścisz, podnosząc palec, ikona wróci na swoje pierwotne miejsce pod wpływem niewidocznej, ale znanej siły.

Rysunek 1 przedstawia podobny efekt sprężyny. Znak plusa (+) na środku okręgu wskazuje siłę nacisku wywieranego przez gest dotykowy.

Wiosenna wersja
Rysunek 1. Efekt wiosennej premiery

Tworzenie animacji sprężynowej

Ogólne kroki tworzenia animacji sprężynowej w aplikacji są następujące:

W kolejnych sekcjach szczegółowo omówimy ogólne etapy tworzenia animacji sprężynowej.

Dodawanie biblioteki pomocy

Aby używać biblioteki pomocy opartej na fizyce, 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.

    Dynamiczny

            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 o bibliotece Dynamic Animation na stronie wersji.

Tworzenie animacji sprężynowej

Klasa SpringAnimation umożliwia tworzenie animacji sprężynowej dla obiektu. Aby utworzyć animację sprężynową, 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 animacja ma się zatrzymać.

Uwaga: podczas tworzenia animacji sprężyny końcowa pozycja sprężyny jest opcjonalna. Musi być jednak zdefiniowana 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ężynie może animować widoki na ekranie, zmieniając rzeczywiste właściwości w obiektach widoku. W systemie dostępne są te widoki:

  • ALPHA: Represents the alpha transparency on the view. Domyślnie wartość wynosi 1 (nieprzezroczysty), a wartość 0 oznacza pełną przezroczystość (niewidoczny).
  • TRANSLATION_X, TRANSLATION_YTRANSLATION_Z: te właściwości określają położenie widoku jako odchylenie od jego współrzędnych po lewej stronie, u góry i wysokości, które są ustawiane przez kontener układu.
  • ROTATION, ROTATION_XROTATION_Y: te właściwości kontrolują obrót w 2D (właściwość rotation) i 3D wokół punktu obrotu.
  • SCROLL_XSCROLL_Y: te właściwości wskazują przesunięcie przewijania lewej i górnej krawędzi źródła w pikselach. Wskazuje też pozycję w odniesieniu do tego, jak bardzo strona jest przewinięta.
  • SCALE_XSCALE_Y: te właściwości kontrolują skalowanie 2D widoku wokół jego punktu obrotu.
  • X, YZ: są to podstawowe właściwości narzędziowe opisujące ostateczną lokalizację widoku w jego kontenerze.

Rejestrowanie detektorów

Klasa DynamicAnimation udostępnia 2 słuchaczy: OnAnimationUpdateListenerOnAnimationEndListener. Te odbiorniki nasłuchują aktualizacji animacji, np. gdy zmienia się wartość animacji lub gdy animacja się kończy.

OnAnimationUpdateListener

Jeśli chcesz animować wiele widoków, aby utworzyć animację łańcuchową, możesz skonfigurować OnAnimationUpdateListener tak, aby otrzymywać wywołanie zwrotne za każdym razem, gdy zmieni się właściwość bieżącego widoku. Wywołanie zwrotne powiadamia inny widok o konieczności zaktualizowania pozycji sprężyny na podstawie zmiany wprowadzonej we właściwości bieżącego widoku. Aby zarejestrować odbiorcę, wykonaj te czynności:

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

    Uwaga: musisz zarejestrować odbiorcę aktualizacji, zanim rozpocznie się animacja. Jednak odbiornik aktualizacji należy zarejestrować tylko wtedy, gdy potrzebujesz aktualizacji wartości animacji w każdej klatce. Listener aktualizacji zapobiega potencjalnemu uruchomieniu animacji w osobnym wątku.

  2. Zastąp metodę onAnimationUpdate(), aby powiadomić wywołującego o zmianie w bieżącym obiekcie. 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);
    }
});

OnAnimationEndListener

OnAnimationEndListener powiadamia o zakończeniu animacji. Możesz skonfigurować odbiornik tak, aby otrzymywał wywołanie zwrotne, gdy animacja osiągnie stan równowagi lub zostanie anulowana. Aby zarejestrować odbiornik, wykonaj te czynności:

  1. Wywołaj metodę addEndListener() i dołącz słuchacza do animacji.
  2. Zastąp metodę onAnimationEnd(), aby otrzymywać powiadomienia, gdy animacja osiągnie stan równowagi lub zostanie anulowana.

Usuwanie detektorów

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

Ustawianie wartości początkowej 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 użyje bieżącej wartości właściwości obiektu jako wartości początkowej.

Ustawianie zakresu wartości animacji

Jeśli chcesz ograniczyć wartość właściwości do określonego zakresu, możesz ustawić minimalną i maksymalną wartość animacji. Pomaga też kontrolować zakres w przypadku animowania właściwości, które mają zakres wewnętrzny, np. przezroczystość (od 0 do 1).

  • Aby ustawić wartość minimalną, wywołaj metodę setMinValue() i przekaż minimalną wartość właściwości.
  • Aby ustawić wartość maksymalną, 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 ustawisz wartość początkową i określisz zakres wartości animacji, upewnij się, że wartość początkowa mieści się w zakresie wartości minimalnej i maksymalnej.

Ustaw prędkość początkową

Prędkość początkowa określa szybkość, z jaką właściwość animacji zmienia się na początku animacji. Domyślna prędkość początkowa to zero pikseli na sekundę. Prędkość możesz ustawić za pomocą prędkości gestów dotykowych lub użyć stałej wartości jako prędkości początkowej. Jeśli zdecydujesz się podać stałą wartość, zalecamy określenie jej w jednostkach dp na sekundę, a następnie przekonwertowanie jej na piksele na sekundę. Określenie wartości w pikselach niezależnych od gęstości na sekundę pozwala na niezależność prędkości od gęstości i formatów. Więcej informacji o przeliczaniu wartości na piksele na sekundę znajdziesz w sekcji Przeliczanie dp na sekundę na piksele na sekundę.

Aby ustawić prędkość, wywołaj metodę setStartVelocity() i przekaż prędkość w pikselach na sekundę. Metoda zwraca obiekt siły sprężystości, w którym ustawiono prędkość.

Uwaga: do pobierania i obliczania prędkości gestów dotykowych używaj 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);

Przekształcanie dp na sekundę na piksele na sekundę

Prędkość sprężyny musi być podana w pikselach na sekundę. Jeśli zdecydujesz się podać stałą wartość jako początek prędkości, podaj ją w dp na sekundę, a następnie przekonwertuj na piksele na sekundę. W przypadku konwersji użyj metody applyDimension() z klasy TypedValue. Zapoznaj się z poniższym przykładowym kodem:

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 definiuje metody pobierania i ustawiania każdej właściwości sprężyny, np. współczynnika tłumienia i sztywności. Aby ustawić właściwości sprężyny, musisz pobrać obiekt siły sprężystości lub utworzyć niestandardową siłę sprężystości, w której możesz ustawić właściwości. Więcej informacji o tworzeniu niestandardowej siły sprężystości znajdziesz w sekcji Tworzenie niestandardowej siły sprężystości.

Wskazówka: podczas korzystania z metod ustawiających możesz utworzyć łańcuch metod, ponieważ wszystkie metody ustawiające zwracają obiekt siły sprężystej.

Współczynnik tłumienia

Współczynnik tłumienia opisuje stopniowe zmniejszanie się oscylacji sprężyny. Za pomocą współczynnika tłumienia możesz określić, jak szybko drgania zanikają z jednego odbicia do drugiego. Sprężynę można wytłumić na 4 sposoby:

  • Nadmierne tłumienie występuje, gdy współczynnik tłumienia jest większy niż 1. Pozwala to na delikatne powrócenie obiektu do pozycji spoczynkowej.
  • Tłumienie krytyczne występuje, gdy współczynnik tłumienia jest równy 1. Pozwala to obiektowi wrócić do pozycji spoczynkowej w najkrótszym czasie.
  • Niedotłumienie występuje, gdy współczynnik tłumienia jest mniejszy niż 1. Pozwala on obiektowi wielokrotnie przekraczać pozycję spoczynkową, a następnie stopniowo ją osiągać.
  • Nietłumione występuje, gdy współczynnik tłumienia jest równy zero. Pozwala to obiektowi oscylować w nieskończoność.

Aby dodać do sprężyny współczynnik tłumienia, 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ężystości, w którym ustawiono współczynnik tłumienia.

    Uwaga: współczynnik tłumienia musi być liczbą nieujemną. Jeśli ustawisz współczynnik tłumienia na zero, sprężyna nigdy nie osiągnie pozycji spoczynkowej. Innymi słowy, oscyluje w nieskończoność.

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

Rysunek 2. Wysoki współczynnik odrzuceń

Ilustracja 3. Średnie odbicie

Rysunek 4. Niski współczynnik odrzuceń

Rysunek 5. Brak odbicia

Domyślny współczynnik tłumienia to 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ści, która mierzy siłę sprężyny. Sztywna sprężyna wywiera większą siłę na przymocowany do niej obiekt, gdy nie znajduje się w pozycji spoczynkowej. Aby dodać sztywność sprężyny, wykonaj te czynności:

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

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

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

Rysunek 6. Wysoka sztywność

Rysunek 7. Średnia sztywność

Rysunek 8. Niska sztywność

Rysunek 9. Bardzo mała sztywność

Domyślna sztywność to 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);

Tworzenie niestandardowej siły sprężystości

Możesz utworzyć niestandardową siłę sprężyny zamiast domyślnej. Niestandardowa siła sprężyny umożliwia udostępnianie tego samego wystąpienia siły sprężyny w wielu animacjach sprężyny. Po utworzeniu siły sprężystości możesz ustawić 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 odpowiednie metody. Możesz też utworzyć łańcuch metod.

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

  3. Aby ustawić sprężynę animacji, wywołaj metodę setSpring().

    setSpring(force);

Uruchom animację

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

animateToFinalPosition() wykonuje 2 zadania:

  • Ustawia końcową pozycję sprężyny.
  • Rozpoczyna animację, jeśli nie została jeszcze uruchomiona.

Metoda ta aktualizuje ostateczną pozycję sprężyny i w razie potrzeby rozpoczyna animację, więc możesz ją wywołać w dowolnym momencie, aby zmienić przebieg animacji. Na przykład w animacji sprężynowej połączonej w łańcuch animacja jednego widoku zależy od innego widoku. W przypadku takiej animacji wygodniej jest użyć metody animateToFinalPosition(). Jeśli użyjesz tej metody w animacji sprężynowej z połączonymi elementami, nie musisz się martwić, czy animacja, którą chcesz zaktualizować, jest obecnie uruchomiona.

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

Wersja demonstracyjna połączonych sprężyn
Rysunek 10. Prezentacja połączonych sprężyn

Aby użyć metody animateToFinalPosition(), wywołaj metodę animateToFinalPosition() i przekaż pozycję spoczynkową sprężyny. Możesz też ustawić pozycję spoczynkową 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 impulsie animacji, który występuje przed etapem rysowania. Dzięki temu zmiany są odzwierciedlane w kolejnej klatce, tak jakby wartości były ustawiane od razu.

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

Anulowanie animacji

Możesz anulować animację lub przejść do jej końca. Idealna sytuacja, w której musisz anulować animację lub przejść do jej końca, to taka, gdy interakcja użytkownika wymaga natychmiastowego zakończenia animacji. Dzieje się to najczęściej, gdy użytkownik nagle zamknie aplikację lub widok stanie się niewidoczny.

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

Zanim zakończysz animację, sprawdź stan sprężyny. Jeśli stan jest nietłumiony, animacja nigdy nie osiągnie pozycji spoczynkowej. 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ę za pomocą metody skipToEnd() lub cancel(). Metodę cancel() należy wywoływać tylko w głównym wątku.

Uwaga: metoda skipToEnd() powoduje zwykle skok wizualny.