Animuj zmiany układu za pomocą przejścia

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

Platforma przejścia w Androidzie umożliwia animowanie wszystkich rodzajów ruchu w interfejsie, zapewniając układ początkowy i końcowy. Możesz wybrać odpowiedni typ animacji – np. rozjaśnić lub znikać widoki albo zmienić rozmiar widoku. Struktura przejścia określa sposób animowania z układu początkowego do końcowego.

Strategia przenoszenia obejmuje te funkcje:

  • Animacje na poziomie grupy: stosuj efekty animacji do wszystkich widoków w hierarchii widoków.
  • Wbudowane animacje: używaj wstępnie zdefiniowanych animacji, aby używać typowych efektów, takich jak zanikanie lub ruch.
  • Obsługa plików zasobów: umożliwia wczytywanie hierarchii widoków i wbudowanych animacji z plików zasobów układu.
  • Wywołania zwrotne cyklu życia: otrzymują wywołania zwrotne, które zapewniają kontrolę nad animacjami i procesem zmiany hierarchii.

Przykładowy kod animowany pomiędzy zmianami układu znajdziesz w sekcji BasicTransition.

Podstawowy proces animowania między dwoma układami wygląda tak:

  1. Utwórz obiekt Scene dla układu początkowego i końcowego. Jednak scena w układzie początkowym jest często określana automatycznie na podstawie bieżącego układu.
  2. Aby określić typ animacji, utwórz obiekt Transition.
  3. Wywołaj metodę TransitionManager.go(), a system uruchomi animację, by zamienić układ.

Diagram na rys. 1 przedstawia zależność między układami, scenami, przejściem i końcową animacją.

Rysunek 1. Podstawowa ilustracja przedstawiająca sposób tworzenia animacji przez platformę przejścia.

Tworzenie sceny

Sceny przechowują stan hierarchii widoków, w tym wszystkie widoki i wartości właściwości. Platforma przejść może uruchamiać animacje między sceną początkową i końcową.

Sceny możesz tworzyć z pliku zasobów układu lub z grupy widoków w kodzie. Scena początkowa przejścia jest często określana automatycznie na podstawie bieżącego interfejsu użytkownika.

Scena może też definiować własne działania wykonywane po wprowadzeniu zmiany. Ta funkcja jest przydatna do czyszczenia ustawień widoku po przejściu do sceny.

Tworzenie sceny z zasobu układu

Instancję Scene możesz utworzyć bezpośrednio z pliku zasobów układu. Zastosuj tę metodę, gdy hierarchia widoków w pliku jest w większości statyczna. Otrzymana scena przedstawia stan hierarchii widoków w momencie utworzenia instancji Scene. Jeśli zmienisz hierarchię widoków, odtwórz scenę. Platforma tworzy scenę na podstawie całej hierarchii widoków w pliku. Nie można utworzyć sceny z części pliku układu.

Aby utworzyć instancję Scene z pliku zasobów układu, pobierz katalog główny sceny z układu jako ViewGroup. Następnie wywołaj funkcję Scene.getSceneForLayout() z poziomem głównym sceny i identyfikatorem zasobu pliku układu, który zawiera hierarchię widoków danych sceny.

Określ układy scen

Fragmenty kodu w dalszej części tej sekcji pokazują, jak utworzyć 2 różne sceny z tym samym głównym elementem. Fragmenty kodu pokazują też, że można wczytać wiele niepowiązanych ze sobą obiektów Scene bez sugerowania, że są ze sobą powiązane.

Przykład składa się z tych definicji układu:

  • Główny układ aktywności z etykietą tekstową i elementem podrzędnym FrameLayout.
  • ConstraintLayout pierwszej sceny z 2 polami tekstowymi.
  • ConstraintLayout dla drugiej sceny z tymi samymi 2 polami tekstowymi w różnej kolejności.

W przykładzie cała animacja odbywa się w układzie podrzędnym głównego układu aktywności. Etykieta tekstowa w układzie głównym pozostanie statyczna.

Główny układ aktywności jest zdefiniowany jako:

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/master_layout">
    <TextView
        android:id="@+id/title"
        ...
        android:text="Title"/>
    <FrameLayout
        android:id="@+id/scene_root">
        <include layout="@layout/a_scene" />
    </FrameLayout>
</LinearLayout>

Ta definicja układu zawiera pole tekstowe i podrzędny element FrameLayout jako poziom główny sceny. Układ pierwszej sceny jest zawarty w głównym pliku układu. Dzięki temu aplikacja może wyświetlić go jako część początkowego interfejsu użytkownika oraz wczytać go w scenie, ponieważ platforma może wczytać w scenie tylko cały plik układu.

Układ pierwszej sceny jest zdefiniowany jako:

res/layout/a_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

Układ drugiej sceny zawiera te same 2 pola tekstowe – z identycznymi identyfikatorami – rozmieszczone w innej kolejności. Jest ona definiowana w następujący sposób:

res/layout/another_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

Wygeneruj sceny z układów

Po utworzeniu definicji 2 układów ograniczeń możesz uzyskać scenę dla każdego z nich. Pozwala to przełączać się między 2 konfiguracjami interfejsu. Aby uzyskać scenę, potrzebujesz odwołania do jej katalogu głównego i identyfikatora zasobu układu.

Ten fragment kodu pokazuje, jak uzyskać odwołanie do katalogu głównego sceny i utworzyć 2 obiekty Scene z plików układu:

Kotlin

val sceneRoot: ViewGroup = findViewById(R.id.scene_root)
val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this)
val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)

Java

Scene aScene;
Scene anotherScene;

// Create the scene root for the scenes in this app.
sceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes.
aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this);
anotherScene =
    Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);

W aplikacji są teraz 2 obiekty Scene podzielone na podstawie hierarchii widoków. Obie sceny korzystają z poziomu głównego poziomu sceny zdefiniowanego przez element FrameLayout w elemencie res/layout/activity_main.xml.

Tworzenie sceny w kodzie

Możesz też utworzyć w kodzie instancję Scene z obiektu ViewGroup. Skorzystaj z tej metody, gdy modyfikujesz hierarchie widoków bezpośrednio w kodzie lub generujesz je dynamicznie.

Aby utworzyć scenę na podstawie hierarchii widoków w kodzie, użyj konstruktora Scene(sceneRoot, viewHierarchy). Wywołanie tego konstruktora jest równoważne wywołaniu funkcji Scene.getSceneForLayout(), gdy plik szablonu został już powiększony.

Poniższy fragment kodu pokazuje, jak utworzyć wystąpienie Scene z głównego elementu sceny oraz z hierarchii widoków sceny w kodzie:

Kotlin

val sceneRoot = someLayoutElement as ViewGroup
val viewHierarchy = someOtherLayoutElement as ViewGroup
val scene: Scene = Scene(sceneRoot, viewHierarchy)

Java

Scene mScene;

// Obtain the scene root element.
sceneRoot = (ViewGroup) someLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered.
viewHierarchy = (ViewGroup) someOtherLayoutElement;

// Create a scene.
mScene = new Scene(sceneRoot, mViewHierarchy);

Utwórz działania na scenach

Platforma umożliwia definiowanie niestandardowych działań sceny, które system uruchamia, gdy wchodzisz do sceny lub ją opuszczasz. W wielu przypadkach definiowanie niestandardowych scenariuszy jest niepotrzebne, ponieważ platforma automatycznie animuje zmianę między scenami.

Akcje związane ze sceną przydają się w tych przypadkach:

  • Aby animować widoki danych, które należą do innej hierarchii, Widoki sceny początkowej i końcowej można animować za pomocą działań sceny wyjściowej i wejściowej.
  • Aby animować widoki, których platforma przejść nie może animować automatycznie, takie jak obiekty ListView. Więcej informacji znajdziesz w sekcji o ograniczeniach.

Aby określić niestandardowe działania sceny, zdefiniuj je jako obiekty Runnable i przekaż je do funkcji Scene.setExitAction() lub Scene.setEnterAction(). Platforma wywołuje funkcję setExitAction() w scenie początkowej przed uruchomieniem animacji przejścia oraz funkcję setEnterAction() na scenie końcowej po uruchomieniu animacji przejścia.

Zastosuj przejście

Platforma przejść reprezentuje styl animacji między scenami z obiektem Transition. Możesz utworzyć instancję Transition przy użyciu wbudowanych podklas, takich jak AutoTransition i Fade, albo zdefiniować własne przejście. Następnie możesz włączyć animację między scenami, przesyłając końcowy URL Scene i Transition do TransitionManager.go().

Cykl życia przejścia jest podobny do cyklu życia aktywności i reprezentuje stany przejścia, które platforma monitoruje między rozpoczęciem a zakończeniem animacji. W ważnych stanach cyklu życia platforma wywołuje funkcje wywołań zwrotnych, które możesz wdrożyć, aby dostosować interfejs użytkownika na różnych etapach przejścia.

Tworzenie przejścia

W poprzedniej sekcji pokazujemy, jak tworzyć sceny odzwierciedlające stan różnych hierarchii widoków. Gdy zdefiniujesz scenę początkową i końcową, którą chcesz przechodzić, utwórz obiekt Transition definiujący animację. Platforma umożliwia określenie wbudowanego przejścia w pliku zasobów i zamieszczanie go w kodzie lub utworzenie wystąpienia wbudowanego przejścia bezpośrednio w kodzie.

Tabela 1. Typy wbudowanych przejść.

Kategoria Oznacz Efekt
AutoTransition <autoTransition/> Przejście domyślne. W tej kolejności zanika, przesuwa i zmienia rozmiar oraz zanika w widoku.
ChangeBounds <changeBounds/> Przenosi widoki i zmienia ich rozmiar.
ChangeClipBounds <changeClipBounds/> Rejestruje View.getClipBounds() przed zmianą sceny i po niej oraz animuje te zmiany w trakcie przejścia.
ChangeImageTransform <changeImageTransform/> Rejestruje macierz elementu ImageView przed zmianą sceny i po niej oraz animuje ją w trakcie przejścia.
ChangeScroll <changeScroll/> Przechwytuje właściwości przewijania celów przed zmianą sceny i po niej oraz animuje wszelkie zmiany.
ChangeTransform <changeTransform/> Rejestruje skalę i obrót widoków przed zmianą sceny i po niej oraz animuje te zmiany w trakcie przejścia.
Explode <explode/> Ścieżki zmieniają widoczność widoków docelowych w sceny początkowej i końcowej oraz przesuwa widoki do lub z krawędzi sceny.
Fade <fade/> fade_in – zmniejsza się w widoku.
fade_out – wycisza widoki.
fade_in_out (domyślnie) wykonuje fade_out, a po nim fade_in.
Slide <slide/> Ścieżki zmieniają widoczność docelowych widoków w sceny początkowej i końcowej oraz przesuwają widoki do lub z jednej z krawędzi sceny.

Tworzenie instancji przejściowej z pliku zasobów

Ta metoda pozwala zmodyfikować definicję przejścia bez zmiany kodu aktywności. Ta metoda jest też przydatna do oddzielania złożonych definicji przejść od kodu aplikacji, jak pokazano w sekcji poświęconej określaniu wielu przejść.

Aby określić wbudowane przejście w pliku zasobów, wykonaj te czynności:

  • Dodaj do projektu katalog res/transition/.
  • Utwórz nowy plik XML zasobów w tym katalogu.
  • Dodaj węzeł XML dla jednego z wbudowanych przejść.

Na przykład ten plik zasobów określa przejście Fade:

res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

Ten fragment kodu pokazuje, jak powiększyć instancję Transition w aktywności z pliku zasobów:

Kotlin

var fadeTransition: Transition =
    TransitionInflater.from(this)
                      .inflateTransition(R.transition.fade_transition)

Java

Transition fadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

Tworzenie instancji przejścia w kodzie

Ta metoda przydaje się do dynamicznego tworzenia obiektów przejść w przypadku modyfikowania interfejsu w kodzie i tworzenia prostych wbudowanych instancji przejściowych z niewielką lub zerową liczbą parametrów.

Aby utworzyć instancję wbudowanego przejścia, wywołaj jeden z publicznych konstruktorów w podklasach klasy Transition. Na przykład ten fragment kodu tworzy wystąpienie przejścia Fade:

Kotlin

var fadeTransition: Transition = Fade()

Java

Transition fadeTransition = new Fade();

Zastosuj przejście

Zazwyczaj przechodzi się między różnymi hierarchiami widoków danych w odpowiedzi na zdarzenie, np. działanie użytkownika. Weźmy np. aplikację do wyszukiwania: gdy użytkownik wpisze wyszukiwane hasło i kliknie przycisk wyszukiwania, aplikacja zmieni się w scenę reprezentującą układ wyników, a jednocześnie zastosuje przejście, które znika i zanika w wynikach wyszukiwania.

Aby wprowadzić zmianę sceny podczas stosowania przejścia w odpowiedzi na zdarzenie w aktywności, wywołaj funkcję klasy TransitionManager.go() ze sceną końcową i instancją przejścia do animacji, jak pokazano w tym fragmencie:

Kotlin

TransitionManager.go(endingScene, fadeTransition)

Java

TransitionManager.go(endingScene, fadeTransition);

Platforma zmienia hierarchię widoków w głównej części sceny w hierarchii widoków ze sceny końcowej podczas uruchamiania animacji określonej przez instancję przejścia. Scena początkowa to scena końcowa od ostatniego przejścia. Jeśli nie ma poprzedniego przejścia, scena początkowa jest określana automatycznie na podstawie bieżącego stanu interfejsu.

Jeśli nie określisz instancji przejścia, menedżer przenoszenia może zastosować automatyczne przenoszenie, które w większości przypadków będzie korzystne. Więcej informacji znajdziesz w dokumentacji interfejsu API klasy TransitionManager.

Wybierz konkretne widoki docelowe

Platforma domyślnie stosuje przejścia do wszystkich widoków w scenie początkowej i końcowej. W niektórych przypadkach animację warto zastosować tylko do podzbioru widoków w scenie. Schemat pozwala wybrać widoki do animacji. Na przykład platforma nie obsługuje animowania zmian obiektów ListView, więc nie próbuj ich animować podczas przejścia.

Każdy widok animowanego przejścia jest nazywany celem. Możesz wybrać tylko cele należące do hierarchii widoków powiązanej ze sceną.

Aby usunąć co najmniej 1 widok z listy celów, przed rozpoczęciem przenoszenia wywołaj metodę removeTarget(). Aby dodać do listy celów tylko określone widoki, wywołaj funkcję addTarget(). Więcej informacji znajdziesz w dokumentacji interfejsu API klasy Transition.

Określ wiele przeniesień

Aby uzyskać jak największy wpływ animacji, dopasuj ją do typu zmian, które zachodzą między scenami. Jeśli np. usuwasz niektóre widoki i dodajesz inne między scenami, ściemnienie lub rozjaśnienie animacji będzie sygnalizacją, że niektóre widoki nie są już dostępne. Jeśli przenosisz widoki do innych punktów na ekranie, lepiej animować ruch, aby użytkownicy zauważyli nową lokalizację widoków.

Nie musisz wybierać tylko jednej animacji, ponieważ platforma przejść umożliwia łączenie efektów w ramach zestawu przejść, który zawiera grupę pojedynczych wbudowanych lub niestandardowych przejść.

Aby zdefiniować zestaw przejść dla zbioru przejść w pliku XML, utwórz plik zasobu w katalogu res/transitions/ i wymień przejścia w elemencie TransitionSet. Ten fragment kodu pokazuje na przykład, jak określić zbiór przejść, który działa tak samo jak klasa AutoTransition:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

Aby zwiększyć wartość przejścia w kodzie do obiektu TransitionSet, wywołaj w aktywności funkcję TransitionInflater.from(). Klasa TransitionSet pochodzi z klasy Transition, więc możesz jej używać z menedżerem przenoszenia tak samo jak każde inne wystąpienie Transition.

Stosowanie przejścia bez scen

Zmiana hierarchii widoków nie jest jedynym sposobem modyfikacji interfejsu użytkownika. Możesz też wprowadzać zmiany, dodając, modyfikując i usuwając widoki podrzędne w bieżącej hierarchii.

Możesz np. zaimplementować interakcję z wyszukiwarką z jednym układem. Zacznij od układu z polem do wpisywania zapytania i ikoną wyszukiwania. Aby zmienić interfejs i wyświetlać wyniki wyszukiwania, usuń przycisk wyszukiwania, gdy użytkownik go kliknie, wywołując funkcję ViewGroup.removeView() i dodaj wyniki wyszukiwania, wywołując funkcję ViewGroup.addView().

Z tej metody możesz skorzystać, jeśli alternatywą jest zastosowanie 2 prawie identycznych hierarchii. Zamiast tworzyć i utrzymywać 2 oddzielne pliki układu z uwzględnieniem niewielkiej różnicy w interfejsie, możesz utworzyć jeden plik układu zawierający hierarchię widoków, którą zmodyfikujesz w kodzie.

Jeśli wprowadzisz w ten sposób zmiany w bieżącej hierarchii widoków, nie musisz tworzyć sceny. Zamiast tego możesz utworzyć i zastosować przejście między dwoma stanami w hierarchii widoków, korzystając z przejścia opóźnionego. Ta funkcja platformy przejść rozpoczyna się od bieżącego stanu hierarchii widoków, rejestruje zmiany wprowadzone w jego widokach i stosuje przejście, które animuje te zmiany, gdy system ponownie prześle interfejs użytkownika.

Aby utworzyć opóźnione przejście w obrębie hierarchii jednego widoku, wykonaj te czynności:

  1. Po wystąpieniu zdarzenia, które wywołuje przejście, wywołaj funkcję TransitionManager.beginDelayedTransition(), udostępniając widok nadrzędny wszystkich widoków, które chcesz zmienić, oraz sposób, z którego chcesz korzystać. Platforma przechowuje bieżący stan widoków podrzędnych i ich wartości właściwości.
  2. Wprowadź zmiany w widokach podrzędnych zgodnie ze swoimi potrzebami. Platforma rejestruje zmiany wprowadzane w widokach podrzędnych i ich właściwościach.
  3. Kiedy system ponownie rysuje interfejs zgodnie z wprowadzonymi zmianami, platforma animuje zmiany między stanem początkowym a nowym.

Poniższy przykład pokazuje, jak animować dodanie widoku tekstu do hierarchii widoku z użyciem opóźnionego przejścia. Pierwszy fragment zawiera plik definicji układu:

res/layout/activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <EditText
        android:id="@+id/inputText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>

Następny fragment zawiera kod animujący dodanie widoku tekstu:

MainActivity

Kotlin

setContentView(R.layout.activity_main)
val labelText = TextView(this).apply {
    text = "Label"
    id = R.id.text
}
val rootView: ViewGroup = findViewById(R.id.mainLayout)
val mFade: Fade = Fade(Fade.IN)
TransitionManager.beginDelayedTransition(rootView, mFade)
rootView.addView(labelText)

Java

private TextView labelText;
private Fade mFade;
private ViewGroup rootView;
...
// Load the layout.
setContentView(R.layout.activity_main);
...
// Create a new TextView and set some View properties.
labelText = new TextView(this);
labelText.setText("Label");
labelText.setId(R.id.text);

// Get the root view and create a transition.
rootView = (ViewGroup) findViewById(R.id.mainLayout);
mFade = new Fade(Fade.IN);

// Start recording changes to the view hierarchy.
TransitionManager.beginDelayedTransition(rootView, mFade);

// Add the new TextView to the view hierarchy.
rootView.addView(labelText);

// When the system redraws the screen to show this update,
// the framework animates the addition as a fade in.

Zdefiniuj wywołania zwrotne cyklu życia przejścia

Cykl życia przejścia jest podobny do cyklu życia aktywności. Przedstawia stany przejścia, które platforma monitoruje w okresie między wywołaniem funkcji TransitionManager.go() a zakończeniem animacji. W ważnych stanach cyklu życia platforma wywołuje wywołania zwrotne zdefiniowane przez interfejs TransitionListener.

Wywołania zwrotne cyklu życia przejścia przydają się na przykład do kopiowania wartości właściwości widoku z początkowej hierarchii widoku danych do hierarchii widoku końcowego podczas zmiany sceny. Nie możesz po prostu skopiować wartości z widoku początkowego do widoku w hierarchii widoków końcowych, ponieważ hierarchia widoku końcowego nie zostanie zawyżona do czasu zakończenia przejścia. Zamiast tego musisz zapisać wartość w zmiennej, a potem skopiować ją do końcowej hierarchii widoków, gdy platforma zakończy przenoszenie. Aby otrzymać powiadomienie o zakończeniu przenoszenia, zaimplementuj w swojej aktywności funkcję TransitionListener.onTransitionEnd().

Więcej informacji znajdziesz w dokumentacji interfejsu API klasy TransitionListener.

Ograniczenia

W tej sekcji wymienione są niektóre znane ograniczenia procesu przenoszenia:

  • Animacje zastosowane do elementu SurfaceView mogą wyświetlać się nieprawidłowo. Instancje (SurfaceView) są aktualizowane z wątku bez interfejsu użytkownika, więc aktualizacje mogą nie być zsynchronizowane z animacjami innych widoków.
  • Niektóre typy przejść mogą nie dawać pożądanego efektu animacji po zastosowaniu do elementu TextureView.
  • Klasy, które rozszerzają zakres AdapterView, np. ListView, zarządzają widokami podrzędnymi w sposób niezgodny z zasadami przechodzenia. Jeśli spróbujesz animować widok, który wykorzystuje atrybut AdapterView, wyświetlacz urządzenia może przestać odpowiadać.
  • Jeśli spróbujesz zmienić rozmiar obiektu TextView za pomocą animacji, tekst wyskoczy w nowe miejsce, zanim obiekt zostanie całkowicie zmieniony. Aby uniknąć tego problemu, nie animuj zmiany rozmiaru widoków, które zawierają tekst.