Omówienie animacji właściwości

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

System animacji właściwości to stabilna platforma, która pozwala animować niemal wszystko. Możesz zdefiniować animację, która zmienia z czasem dowolną właściwość obiektu niezależnie od tego, czy jest wyświetlana na ekranie czy nie. Animacja właściwości zmienia wartość właściwości (pole w obiekcie) przez określony czas. Aby coś animować, określ właściwość obiektu, którą chcesz animować, np. położenie obiektu na ekranie, czas trwania animacji i wartości, pomiędzy którymi chcesz animować obiekt.

System animacji właściwości umożliwia definiowanie tych właściwości animacji:

  • Czas trwania: możesz określić czas trwania animacji. Domyślna długość to 300 ms.
  • Interpolacja czasowa: możesz określić sposób obliczania wartości właściwości jako funkcji bieżącego czasu trwania animacji.
  • Liczba powtórzeń i zachowanie: możesz określić, czy animacja ma być powtarzana na końcu czasu trwania, oraz ile razy ma być powtarzana. Możesz też określić, czy animacja ma być odtwarzana wstecz. Włączenie tej opcji powoduje odtwarzanie animacji do przodu, a potem do tyłu, aż zostanie osiągnięta liczba powtórzeń.
  • Zestawy animatorów: możesz grupować animacje w zbiory logiczne, które odtwarzają się razem lub po kolei lub po określonych opóźnieniach.
  • Opóźnienie odświeżania klatki: możesz określić, jak często mają być odświeżane klatki animacji. Domyślnie ustawione jest odświeżanie co 10 ms, ale szybkość, z jaką aplikacja może odświeżać klatki, zależy od ogólnego obciążenia systemu oraz od szybkości, z jaką system może obsługiwać bazowy licznik czasu.

Aby zobaczyć pełny przykład animacji właściwości, zapoznaj się z klasą ChangeColor w przykładzie z CustomMove na GitHubie.

Jak działa animacja właściwości

Zobaczmy, jak działa animacja, korzystając z prostego przykładu. Rysunek 1 przedstawia hipotetyczny obiekt animowany za pomocą właściwości x, która reprezentuje jego poziome położenie na ekranie. Czas trwania animacji jest ustawiony na 40 ms, a odległość – 40 pikseli. Co 10 ms, co jest domyślną częstotliwością odświeżania klatek, obiekt przesuwa się w poziomie o 10 pikseli. Po upływie 40 ms animacja zatrzymuje się, a obiekt kończy się w pozycji poziomej 40. To przykład animacji z interpolacją liniową, co oznacza, że obiekt porusza się ze stałą prędkością.

Rysunek 1. Przykład animacji liniowej

Możesz także określić animacje z interpolacją nieliniową. Rysunek 2 ilustruje hipotetyczny obiekt, który przyspiesza na początku animacji i spowalnia na końcu. Obiekt przesuwa się o 40 pikseli w czasie 40 ms, ale nieliniowo. Na początku animacja przyspiesza do połowy, a następnie zwalnia od połowy do końca. Jak pokazano na Rysunku 2, odległość pokonana na początku i na końcu animacji jest mniejsza niż w środku.

Rysunek 2. Przykład animacji nieliniowej

Przyjrzyjmy się, jak ważne komponenty systemu animacji właściwości obliczają animacje podobne do przedstawionych powyżej. Rysunek 3 przedstawia wzajemne współdziałanie klas głównych.

Rysunek 3. Metoda obliczania animacji

Obiekt ValueAnimator śledzi czas trwania animacji, np. czas jej trwania i aktualną wartość właściwości, którą tworzy.

W elemencie ValueAnimator zawarty jest element TimeInterpolator, który określa interpolację animacji, oraz element TypeEvaluator, który określa sposób obliczania wartości animowanej właściwości. Na przykład na Rysunku 2 użyto wartości TimeInterpolator jako AccelerateDecelerateInterpolator, a TypeEvaluatorIntEvaluator.

Aby rozpocząć animację, utwórz obiekt ValueAnimator, nadaj mu wartości początkowe i końcowe właściwości, którą chcesz animować, oraz czas trwania animacji. Gdy wywołasz start(), rozpocznie się animacja. W trakcie całej animacji funkcja ValueAnimator oblicza upływ czasu od 0 do 1 na podstawie czasu trwania animacji i czasu, który upłynął. Ułamek upływu to procent czasu zakończenia animacji. 0 oznacza 0%, a 1 oznacza 100%. Na przykład na Rysunku 1 ułamek upływu czasu w przypadku t = 10 ms wyniesie 0, 25, ponieważ łączny czas trwania wynosi t = 40 ms.

Gdy funkcja ValueAnimator oblicza ułamek, wywołuje obecnie ustawioną wartość TimeInterpolator, aby obliczyć ułamek interpolowany. Ułamek interpolowany mapuje upływ czasu na nowy ułamek z uwzględnieniem ustawionej interpolacji czasowej. Na przykład na Rysunku 2, ponieważ animacja przyspiesza, ułamek interpolowany, czyli około 0, 15, jest mniejszy niż ułamek, który upłynął (0, 25) w czasie t = 10 ms. Na Rysunku 1 ułamek interpolowany jest zawsze taki sam jak ułamek, który upłynął.

Podczas obliczania ułamka interpolowanego ValueAnimator wywołuje odpowiednią wartość TypeEvaluator, aby obliczyć wartość animowanej właściwości na podstawie ułamka interpolowanego, wartości początkowej i końcowej wartości animacji. Na przykład na Rysunku 2 ułamek interpolowany wynosił 0, 15 w czasie t = 10 ms, więc wartość dla właściwości w tym czasie będzie wynosić 0, 15 × (40 – 0), czyli 6.

Czym różni się animacja właściwości od animacji widoku

System animacji widoku umożliwia animowanie tylko obiektów View. Jeśli chcesz animować obiekty inne niż View, musisz wdrożyć własny kod. Ogranicza on także tym, że naraża on tylko niektóre aspekty obiektu View do animowania, takie jak na przykład skalowanie i obrót widoku, ale nie na przykład kolor tła.

Inną wadą systemu animacji widoku jest to, że modyfikuje się on tylko w miejscu, w którym został narysowany widok, a nie sam widok. Jeśli na przykład animowany przycisk przesuwa się po ekranie, przycisk jest rysowany poprawnie, ale rzeczywista lokalizacja, w której można go kliknąć, nie zmienia się, więc musisz zaimplementować własną logikę.

Dzięki systemowi animacji właściwości te ograniczenia są całkowicie usuwane, możesz animować dowolną właściwość dowolnych obiektów (View i innych obiektów), a sam obiekt jest w rzeczywistości modyfikowany. System animacji właściwości jest też bardziej wydajny w sposobie przeprowadzania animacji. Ogólnie rzecz biorąc, przypisujesz animatorów do właściwości, które chcesz animować, takich jak kolor, położenie lub rozmiar, i możesz definiować takie aspekty animacji, jak interpolacja i synchronizacja wielu animatorów.

Konfiguracja systemu wyświetlania animacji zajmuje jednak mniej czasu i wymaga mniej kodu. Jeśli animacje widoku danych spełniają wszystkie Twoje oczekiwania lub jeśli Twój kod działa już zgodnie z oczekiwaniami, nie musisz korzystać z systemu animacji właściwości. Wykorzystanie obu systemów animacji może też być przydatne w różnych sytuacjach, jeśli tylko zaistnieje taka potrzeba.

Omówienie interfejsu API

Większość interfejsów API systemu animacji właściwości znajdziesz w tym pliku: android.animation. System animacji widoków definiuje wiele interpolatorów w android.view.animation, więc możesz ich też używać w systemie animacji właściwości. W tabelach poniżej opisujemy główne komponenty systemu animacji właściwości.

Klasa Animator określa podstawową strukturę do tworzenia animacji. Zwykle nie używasz tej klasy bezpośrednio, ponieważ zapewnia ona tylko nieznaczne funkcje, które trzeba rozszerzyć, aby w pełni obsługiwać wartości animowane. Te podklasy są rozszerzone o Animator:

Tabela 1. Animatorzy

Kategoria Opis
ValueAnimator Główny mechanizm czasowy animacji właściwości, który oblicza również wartości właściwości do animacji. Obejmuje wszystkie główne funkcje, które pozwalają obliczać wartości animacji, szczegółowe dane o czasie trwania każdej animacji, informacje o tym, czy animacja się powtarza, detektory odbierające zdarzenia aktualizacji oraz możliwość ustawiania niestandardowych typów do oceny. Właściwości animowania składają się z 2 elementów: obliczania wartości animowanych i ustawiania ich dla animowanego obiektu i właściwości. ValueAnimator nie wykonuje drugiego utworu, dlatego musisz nasłuchiwać aktualizacji wartości obliczonych przez ValueAnimator i zmodyfikować obiekty, które chcesz animować zgodnie z własną logiką. Więcej informacji znajdziesz w sekcji dotyczącej animowania za pomocą ValueAnimator.
ObjectAnimator Podklasa klasy ValueAnimator, która pozwala ustawić animowany obiekt docelowy i właściwość obiektu. Ta klasa odpowiednio aktualizuje właściwość podczas obliczania nowej wartości animacji. Najlepiej używać ObjectAnimator, ponieważ ułatwia to animowanie wartości w obiektach docelowych. Czasami warto jednak używać metody ValueAnimator bezpośrednio, ponieważ ObjectAnimator ma więcej ograniczeń, na przykład wymaganie obecności określonych metod akcesora w obiekcie docelowym.
AnimatorSet Udostępnia mechanizm grupowania animacji, tak aby uruchamiały się względem siebie. Możesz ustawić animacje do odtwarzania razem, sekwencyjnego lub po określonym opóźnieniu. Więcej informacji znajdziesz w sekcji na temat tworzenia układu wielu animacji za pomocą zestawów animacji.

Oceniający informują system animacji właściwości, jak obliczać wartości danej właściwości. Biorą pod uwagę dane o czasie dostarczane przez klasę Animator, wartość początkową i końcową animacji, a następnie na podstawie tych danych obliczają animowane wartości właściwości. System animacji właściwości udostępnia następujące weryfikatory:

Tabela 2. Weryfikatorzy

Klasa/interfejs Opis
IntEvaluator Domyślny wskaźnik do obliczania wartości właściwości int.
FloatEvaluator Domyślny wskaźnik do obliczania wartości właściwości float.
ArgbEvaluator Domyślny narzędzie do obliczania wartości właściwości koloru, które są przedstawiane jako wartości szesnastkowe.
TypeEvaluator Interfejs, który umożliwia utworzenie własnego narzędzia do oceny. Jeśli animujesz właściwość obiektu, która nie jest właściwością int, float ani kolorem, musisz zaimplementować interfejs TypeEvaluator, aby określić sposób obliczania animowanych wartości właściwości obiektu. Możesz też określić niestandardowy TypeEvaluator dla wartości int, float i kolorów, jeśli chcesz, aby były przetwarzane inaczej niż domyślne. Więcej informacji o tworzeniu niestandardowego oceniającego znajdziesz w sekcji poświęconej korzystaniu z narzędzia TypeEvaluator.

Interpolator czasu definiuje sposób obliczania konkretnych wartości w animacji jako funkcja czasu. Na przykład możesz określić animacje, które mają się odtwarzać liniowo w obrębie całej animacji, co oznacza, że porusza się ona równomiernie w trakcie jej trwania, albo określić, by wykorzystywała czas nieliniowy, np. przyspieszać na początku i spowalniać na końcu. Tabela 3 opisuje interpolatory zawarte w komponencie android.view.animation. Jeśli żaden z dostępnych interpolatorów nie odpowiada Twoim potrzebom, zaimplementuj interfejs TimeInterpolator i utwórz własny. Więcej informacji o tworzeniu interpolatora niestandardowego znajdziesz w sekcji Korzystanie z interpolatorów.

Tabela 3. Interpolatory

Klasa/interfejs Opis
AccelerateDecelerateInterpolator Interpolator, którego tempo zmian powoli się rozpoczyna i kończy, a przyspiesza w środku.
AccelerateInterpolator Interpolator, którego tempo zmian zaczyna się powoli, a następnie przyspiesza.
AnticipateInterpolator Interpolator, którego zmiana rozpoczyna się wstecz, a potem przesuwa do przodu.
AnticipateOvershootInterpolator Interpolator, którego zmiana rozpoczyna się wstecz, przesuwa do przodu i przekracza wartość docelową, a następnie wraca do ostatecznej wartości.
BounceInterpolator Interpolator, którego zmiana zostaje odbita na końcu.
CycleInterpolator Interpolator, którego animacja powtarza się przez określoną liczbę cykli.
DecelerateInterpolator Interpolator, którego szybkość zmian zaczyna się szybko, a następnie zwalnia.
LinearInterpolator Interpolator, którego szybkość zmian jest stała.
OvershootInterpolator Interpolator, którego zmiana zostaje odwrócona do przodu i przekracza ostatnią wartość, a następnie zwraca wartość.
TimeInterpolator Interfejs umożliwiający wdrożenie własnego interpolatora.

Animacja za pomocą ValueAnimator

Klasa ValueAnimator umożliwia animowanie wartości określonego typu przez czas trwania animacji przez określenie zbioru wartości int, float lub kolorów, które mają być animowane. Otrzymasz urządzenie ValueAnimator, wywołując jedną z metod fabrycznych: ofInt(), ofFloat() lub ofObject(). Na przykład:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

W tym kodzie ValueAnimator zaczyna obliczać wartości animacji (od 0 do 100) dla okresu 1000 ms, gdy działa metoda start().

Możesz też określić niestandardowy typ animacji, wykonując te czynności:

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

W tym kodzie ValueAnimator zaczyna obliczać wartości animacji od startPropertyValue do endPropertyValue, korzystając z logiki dostarczonej przez MyTypeEvaluator, przez okres 1000 ms, gdy działa metoda start().

Aby użyć wartości animacji, dodaj AnimatorUpdateListener do obiektu ValueAnimator, jak pokazano w tym kodzie:

Kotlin

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

W metodzie onAnimationUpdate() możesz uzyskać dostęp do zaktualizowanej wartości animacji i użyć jej we właściwości jednego z widoków. Więcej informacji o detektorach znajdziesz w sekcji o detektorach animacji.

Tworzenie animacji z użyciem narzędzia ObjectAnimator

ObjectAnimator jest podklasą klasy ValueAnimator (omówioną w poprzedniej sekcji) i łączy mechanizm pomiaru czasu i obliczanie wartości ValueAnimator z możliwością animowania właściwości nazwanej obiektu docelowego. Bardzo ułatwia to animowanie obiektów, ponieważ nie trzeba już implementować ValueAnimator.AnimatorUpdateListener, ponieważ animowana właściwość aktualizuje się automatycznie.

Tworzenie wystąpienia obiektu ObjectAnimator jest podobne do ValueAnimator, ale musisz też określić obiekt, nazwę właściwości tego obiektu (w postaci ciągu znaków) oraz wartości, które mają być animowane między:

Kotlin

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

Java

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

Aby właściwości się aktualizowały ObjectAnimator, musisz wykonać te czynności:

  • Animowana właściwość obiektu musi mieć funkcję seter (wielbłąd) w postaci set<PropertyName>(). Ponieważ właściwość ObjectAnimator automatycznie aktualizuje właściwość podczas animacji, musi mieć dostęp do niej za pomocą tej metody ustawiania. Jeśli nazwa właściwości to np. foo, potrzebujesz metody setFoo(). Jeśli ta metoda ustawiająca nie istnieje, masz 3 możliwości:
    • Dodaj do klasy metodę ustawiającą, jeśli masz do tego uprawnienia.
    • Użyj klasy otoki, którą masz uprawnienia do zmieniania, która powinna otrzymywać wartość za pomocą prawidłowej metody ustawiającej i przekazywać ją do pierwotnego obiektu.
    • Użyj w zamian zasady ValueAnimator.
  • Jeśli określisz tylko jedną wartość parametru values... w jednej z metod fabrycznych ObjectAnimator, zostanie ona uznana za wartość końcową animacji. Dlatego właściwość obiektu, którą animujesz, musi mieć funkcję pobierania, która służy do uzyskania wartości początkowej animacji. Funkcja pobierająca musi mieć postać get<PropertyName>(). Jeśli np. nazwa właściwości to foo, potrzebujesz metody getFoo().
  • Metody pobierające (w razie potrzeby) i ustawiające dla animowanej właściwości muszą działać na tym samym typie co wartości początkowa i końcowa, które podajesz w polu ObjectAnimator. Jeśli na przykład tworzysz taką ObjectAnimator, musisz mieć targetObject.setPropName(float) i targetObject.getPropName():
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
    
  • W zależności od animowanych właściwości lub obiektów może być konieczne wywołanie w widoku metody invalidate(), aby wymusić ponowne rysowanie ekranu ze zaktualizowanymi animowanymi wartościami. Możesz to zrobić w wywołaniu zwrotnym onAnimationUpdate(). Na przykład animowanie właściwości koloru obiektu rysowalnego powoduje zaktualizowanie ekranu tylko wtedy, gdy ten obiekt jest ponownie rysowany. Wszystkie ustawienia właściwości w widoku danych, takie jak setAlpha() i setTranslationX(), prawidłowo unieważniają widok, więc nie musisz unieważniać widoku podczas wywoływania tych metod z nowymi wartościami. Więcej informacji o detektorach znajdziesz w sekcji o detektorach animacji.

Utwórz choreografię wielu animacji za pomocą AnimatorSet

Często chcesz odtworzyć animację zależną od momentu rozpoczęcia lub zakończenia innej animacji. System Android umożliwia łączenie animacji w element AnimatorSet, dzięki czemu możesz określić, czy animacje mają być uruchamiane jednocześnie, sekwencyjnie czy z określonym opóźnieniem. Obiekty AnimatorSet możesz też zagnieżdżać wewnątrz siebie.

Ten fragment kodu odtwarza obiekty Animator w taki sposób:

  1. Odtwarza: bounceAnim.
  2. Wyświetla jednocześnie squashAnim1, squashAnim2, stretchAnim1 i stretchAnim2.
  3. Odtwarza: bounceBackAnim.
  4. Odtwarza: fadeAnim.

Kotlin

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

Detektory animacji

W trakcie trwania animacji możesz nasłuchiwać ważnych zdarzeń, korzystając z opisanych poniżej detektorów.

  • Animator.AnimatorListener
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate() – wywoływane w każdej klatce animacji. Odsłuchaj to zdarzenie, aby użyć obliczonych wartości wygenerowanych przez funkcję ValueAnimator podczas animacji. Aby użyć tej wartości, wyślij zapytanie do obiektu ValueAnimator przekazanego do zdarzenia, aby uzyskać bieżącą animowaną wartość za pomocą metody getAnimatedValue(). Jeśli używasz ValueAnimator, musisz wdrożyć ten detektor.

      W zależności od animowanych właściwości lub obiektów może być konieczne wywołanie w widoku invalidate() wywołania invalidate(), aby wymusić ponowne rysowanie tego obszaru na ekranie z nowymi animowanymi wartościami. Na przykład animowanie właściwości koloru obiektu rysowalnego powoduje zaktualizowanie ekranu tylko wtedy, gdy ten obiekt jest ponownie rysowany. Wszystkie ustawienia właściwości w widoku danych, np. setAlpha() i setTranslationX(), prawidłowo unieważniają widok, więc nie musisz unieważniać widoku podczas wywoływania tych metod z nowymi wartościami.

Zamiast implementować interfejs Animator.AnimatorListener, możesz rozszerzyć klasę AnimatorListenerAdapter, jeśli nie chcesz implementować wszystkich metod interfejsu Animator.AnimatorListener. Klasa AnimatorListenerAdapter udostępnia puste implementacje metod, które możesz zastąpić.

Na przykład ten fragment kodu tworzy AnimatorListenerAdapter tylko dla wywołania zwrotnego onAnimationEnd():

Kotlin

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

Animuj zmiany układu obiektów ViewGroup

System animacji właściwości umożliwia animowanie zmian w obiektach ViewGroup, a także zapewnia prosty sposób animowania samych obiektów View.

Zmiany układu w grupie widoków możesz animować za pomocą klasy LayoutTransition. Wyświetlenia w grupie widoków mogą przechodzić przez animację pojawiania się i znikania po dodaniu do niej lub jej usunięciu albo po wywołaniu metody setVisibility() widoku danych za pomocą VISIBLE, INVISIBLE lub GONE. Pozostałe widoki w grupie wyświetleń mogą się pojawić na nowych pozycjach po dodaniu lub usunięciu widoków. Te animacje możesz zdefiniować w obiekcie LayoutTransition, wywołując setAnimator() i przekazując obiekt Animator z jedną z tych stałych LayoutTransition:

  • APPEARING – flaga wskazująca animację wyświetlaną na elementach widocznych w kontenerze.
  • CHANGE_APPEARING – flaga wskazująca animację uruchamianą na elementach, które zmieniają się w wyniku pojawienia się w kontenerze nowego elementu.
  • DISAPPEARING – flaga wskazująca animację uruchamianą na elementach, które znikają z kontenera.
  • CHANGE_DISAPPEARING – flaga wskazująca animację uruchamianą na elementach, które zmieniają się w wyniku znikania elementu z kontenera.

Możesz definiować własne animacje niestandardowe dla tych 4 typów zdarzeń, aby dostosować wygląd przejść układu, lub po prostu wskazać systemowi animacji, by używał animacji domyślnych.

Aby ustawić atrybut android:animateLayoutchanges na true dla grupy widoków danych, wykonaj te czynności:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

Ustawienie tego atrybutu na wartość Prawda automatycznie powoduje animowanie widoków danych, które zostały dodane do grupy widoków danych lub z niej usunięte, a także pozostałe widoki w tej grupie.

Animacja zmian stanu widoku za pomocą StateListAnimator

Klasa StateListAnimator umożliwia definiowanie animatorów uruchamianych po zmianie stanu widoku. Ten obiekt działa jako otoka obiektu Animator, wywołując tę animację po każdej zmianie określonego stanu widoku (np. „naciśnięcie” lub „zaznaczenie”).

Element StateListAnimator może być zdefiniowany w zasobie XML z elementem głównym <selector> i podrzędnymi elementami <item>. Każdy z nich określa inny stan widoku zdefiniowany przez klasę StateListAnimator. Każdy element <item> zawiera definicję zestawu animacji właściwości.

Na przykład ten plik tworzy animator listy stanów, który po naciśnięciu zmienia skalę X i Y widoku:

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>

Aby dołączyć do widoku animatora listy stanów, dodaj atrybut android:stateListAnimator w ten sposób:

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

Teraz w przypadku zmiany stanu przycisku używane są animacje zdefiniowane w elemencie animate_scale.xml.

Aby zamiast tego przypisać do widoku w kodzie animatora listy stanów, użyj metody AnimatorInflater.loadStateListAnimator() i przypisz animatora do widoku za pomocą metody View.setStateListAnimator().

Zamiast animować właściwości widoku, możesz też odtworzyć rysowalną animację między zmianami stanu za pomocą funkcji AnimatedStateListDrawable. Niektóre widżety systemowe w Androidzie 5.0 domyślnie używają tych animacji. Poniższy przykład pokazuje, jak zdefiniować AnimatedStateListDrawable jako zasób 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>

Użyj narzędzia do oceny typów

Jeśli chcesz animować typ, który jest nieznany w systemie Android, możesz utworzyć własny ocenianie, implementując interfejs TypeEvaluator. Typy znane w systemie Android to int, float lub kolor. Są one obsługiwane przez weryfikatorów typów IntEvaluator, FloatEvaluator i ArgbEvaluator.

W interfejsie TypeEvaluator można wdrożyć tylko jedną metodę – evaluate(). Dzięki temu używane animatorzy mogą zwrócić odpowiednią wartość właściwości animacji w bieżącym punkcie animacji. Klasa FloatEvaluator pokazuje, jak to zrobić:

Kotlin

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

Uwaga: gdy jest uruchamiany ValueAnimator (lub ObjectAnimator), oblicza on bieżący ułamek animacji (wartość od 0 do 1), a następnie oblicza jego wersję interpolowaną w zależności od używanego interpolatora. Ułamek interpolowany to wartość, którą TypeEvaluator otrzymuje dzięki parametrowi fraction. Dlatego przy obliczaniu animowanych wartości nie musisz uwzględniać interpolatora.

Użyj interpolatorów

Interpolator określa sposób obliczania konkretnych wartości w animacji jako funkcji czasu. Na przykład możesz ustawić animacje, które będą przebiegały liniowo w całej animacji, czyli będzie ona przesuwała się równomiernie przez cały czas, albo możesz wskazać animacje do korzystania z czasu nieliniowego – np. użyć przyspieszenia lub zwolnienia na początku lub na końcu animacji.

Interpolatory w systemie animacji otrzymują od animacji ułamek reprezentujący czas trwania animacji. Interpolatory modyfikują ten ułamek tak, aby zbiegł się z typem animacji, który ma przedstawiać. System Android udostępnia w elemencie android.view.animation package zestaw typowych interpolatorów. Jeśli żaden z nich nie odpowiada Twoim potrzebom, możesz wdrożyć interfejs TimeInterpolator i utworzyć własny.

Poniżej przedstawiamy przykład porównanie ułamków interpolowanych przez domyślny interpolator AccelerateDecelerateInterpolator i LinearInterpolator. LinearInterpolator nie ma wpływu na ułamek, który upłynął. AccelerateDecelerateInterpolator przyspiesza do animacji i zwalnia z niej. Logika tych interpolatorów definiuje się w następujących metodach:

Przyspieszenie DecelerateInterpolator

Kotlin

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

Interpolator liniowy

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

@Override
public float getInterpolation(float input) {
    return input;
}

W tabeli poniżej znajdziesz przybliżone wartości obliczone przez te interpolatory dla animacji trwającej 1000 ms:

upłynęło: ms Upływ ułamka/ułamek interpolowany (liniowy) Ułamek zinterpolowany (przyspieszenie/obniżenie poziomu)
0 0 0
200 0,2 0,1
400 0,4 0,345
600 0,6 0,8
800 0,8 0,9
1000 1 1

Jak widać w tabeli, LinearInterpolator zmienia wartości z tą samą szybkością, czyli 0,2 na każde 200 ms. AccelerateDecelerateInterpolator zmienia wartości szybciej niż LinearInterpolator między 200 ms a 600 ms i wolniej od 600 ms do 1000 ms.

Określanie klatek kluczowych

Obiekt Keyframe składa się z pary czas/wartość, który umożliwia zdefiniowanie określonego stanu w konkretnym momencie animacji. Każda klatka kluczowa może też mieć własny interpolator kontrolujący zachowanie animacji w odstępie między czasem poprzedniej klatki kluczowej a czasem tej klatki kluczowej.

Aby utworzyć instancję obiektu Keyframe, musisz użyć jednej z metod fabrycznych: ofInt(), ofFloat() lub ofObject(), aby uzyskać odpowiedni typ Keyframe. Następnie wywołaj metodę fabryczną ofKeyframe(), aby uzyskać obiekt PropertyValuesHolder. Gdy już będziesz mieć obiekt, możesz uzyskać animator, przesyłając do niego obiekt PropertyValuesHolder i obiekt do animacji. Ten fragment kodu pokazuje, jak to zrobić:

Kotlin

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

Animacja widoków

System animacji właściwości umożliwia uproszczoną animację obiektów View i ma kilka zalet w porównaniu z systemem animacji widoku. System animacji widoku przekształcił obiekty widoku, zmieniając sposób ich rysowania. Ta czynność została przeprowadzona w kontenerze każdego widoku danych, ponieważ widok ten nie miał żadnych właściwości do manipulowania. W rezultacie widok był animowany, ale nie spowodował zmian w samym obiekcie widoku. W efekcie obiekt nadal istniał w pierwotnej lokalizacji, mimo że został narysowany w innej lokalizacji na ekranie. Aby wyeliminować tę wadę, w Androidzie 3.0 dodano nowe właściwości oraz odpowiadające im metody getter i seter.

System animacji właściwości może animować widoki na ekranie, zmieniając ich rzeczywiste właściwości. Widoki automatycznie wywołują metodę invalidate(), aby odświeżyć ekran po każdej zmianie jego właściwości. Nowe właściwości klasy View ułatwiające animacje właściwości to:

  • translationX i translationY: te właściwości określają, gdzie znajduje się widok, w zależności od jego współrzędnych od lewej i od góry, które są ustawiane przez kontener układu.
  • rotation, rotationX i rotationY: te właściwości określają obrót w 2D (właściwość rotation) i 3D wokół punktu obrotu.
  • scaleX i scaleY: te właściwości sterują skalowaniem 2D widoku wokół jego punktu obrotu.
  • pivotX i pivotY: te właściwości określają lokalizację punktu obrotu, wokół którego odbywają się przekształcenia obrotu i skalowania. Domyślnie punkt przestawny znajduje się na środku obiektu.
  • x i y: to proste właściwości narzędziowe, które opisują końcową lokalizację widoku w jego kontenerze, stanowiącą sumę wartości lewo i góry oraz wartości translacjiX i przesunięciaY.
  • alpha: reprezentuje przezroczystość widoku alfa w widoku alfa. Domyślna wartość to 1 (nieprzezroczyste), a 0 oznacza pełną przezroczystość (niewidoczną).

Aby animować właściwość obiektu widoku, np. jej kolor lub wartość obrotu, musisz tylko utworzyć animatora właściwości i określić właściwość widoku, którą chcesz animować. Na przykład:

Kotlin

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

Java

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

Więcej informacji o tworzeniu animatorów znajdziesz w sekcjach poświęconych animowaniu za pomocą usług ValueAnimator i ObjectAnimator.

Tworzenie animacji za pomocą ViewPropertyAnimator

Obiekt ViewPropertyAnimator pozwala w prosty sposób animować kilka właściwości elementu View równolegle za pomocą pojedynczego obiektu Animator. Zachowuje się podobnie do elementu ObjectAnimator, bo zmienia rzeczywiste wartości jego właściwości, ale jest bardziej wydajny, jeśli animuje wiele właściwości naraz. Poza tym kod ViewPropertyAnimator jest znacznie bardziej zwięzły i czytelny. Fragmenty kodu poniżej pokazują różnice w używaniu kilku obiektów ObjectAnimator, 1 obiektu ObjectAnimator i ViewPropertyAnimator podczas jednoczesnej animowania właściwości x i y widoku.

Wiele obiektów ObjectAnimator

Kotlin

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

One ObjectAnimator

Kotlin

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

Kotlin

myView.animate().x(50f).y(100f)

Java

myView.animate().x(50f).y(100f);

Więcej informacji na temat właściwości ViewPropertyAnimator znajdziesz w odpowiednim poście na blogu dla deweloperów aplikacji na Androida.

Deklarowanie animacji w pliku XML

System animacji właściwości umożliwia deklarowanie animacji właściwości za pomocą kodu XML, zamiast robić to automatycznie. Dzięki zdefiniowaniu animacji w języku XML możesz ich łatwo używać ponownie w wielu działaniach i łatwiej edytować sekwencję animacji.

Aby odróżnić pliki animacji korzystające z nowych interfejsów API animacji właściwości od tych, które korzystają ze starszej platformy animacji widoku danych (od Androida 3.1), zapisz pliki XML animacji właściwości w katalogu res/animator/.

Poniższe klasy animacji właściwości obsługują deklarację XML z tymi tagami XML:

Atrybuty, których możesz używać w deklaracji XML, znajdziesz w sekcji Zasoby animacji. W przykładzie poniżej dwa zestawy animacji obiektów są odtwarzane sekwencyjnie, a pierwszy zagnieżdżony zestaw odtwarza razem 2 animacje obiektów:

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

Aby uruchomić tę animację, musisz uzupełnić zasoby XML w swoim kodzie do obiektu AnimatorSet, a następnie ustawić obiekty docelowe dla wszystkich animacji przed rozpoczęciem zestawu animacji. Wywołanie setTarget() ustawia jako wygodę jeden obiekt docelowy dla wszystkich elementów podrzędnych obiektu AnimatorSet. Poniższy kod pokazuje, jak to zrobić:

Kotlin

(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 możesz też zadeklarować w pliku XML, tak jak w tym przykładzie:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

Aby użyć poprzedniej wartości ValueAnimator w kodzie, musisz powiększyć obiekt, dodać atrybut AnimatorUpdateListener, uzyskać zaktualizowaną wartość animacji i użyć jej we właściwości jednego z widoków danych, jak w tym kodzie:

Kotlin

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

Informacje o składni XML służącej do definiowania animacji właściwości znajdziesz w sekcji Zasoby animacji .

Potencjalny wpływ na wydajność interfejsu

Animatory aktualizujące interfejs użytkownika wykonują dodatkową pracę związaną z renderowaniem w przypadku każdej klatki, w której wyświetla się animacja. Z tego powodu korzystanie z animacji zużywających dużo zasobów może niekorzystnie wpłynąć na działanie aplikacji.

Na etap animacji w potoku renderowania dodawany jest czas potrzebny do animowania interfejsu użytkownika. Aby sprawdzić, czy Twoje animacje wpływają na wydajność aplikacji, włącz Renderowanie GPU w profilu i monitoruj etap animacji. Więcej informacji znajdziesz w artykule Przewodnik po renderowaniu GPU.