Framework przejść Androida umożliwia animowanie wszelkiego rodzaju ruchu w interfejsie, udostępniając układy początkowy i końcowy. Możesz wybrać typ animacji, np. zanikanie widoków lub zmianę ich rozmiaru, a framework przejść określi, jak animować przejście z układu początkowego do układu końcowego.
Framework przejść obejmuje te funkcje:
- Animacje na poziomie grupy: stosuj efekty animacji do wszystkich widoków w hierarchii widoków.
- Wbudowane animacje: używaj predefiniowanych animacji do typowych efektów, takich jak zanikanie lub ruch.
- Obsługa plików zasobów: wczytuj hierarchie widoków i wbudowane animacje z plików zasobów układu.
- Wywołania zwrotne cyklu życia: otrzymuj wywołania zwrotne, które umożliwiają kontrolowanie animacji i procesu zmiany hierarchii.
Podstawowy proces animowania przejścia między 2 układami wygląda tak:
- Utwórz obiekt
Scenedla układu początkowego i końcowego. Scena układu początkowego jest jednak często określana automatycznie na podstawie bieżącego układu. - Utwórz
Transitionobiekt, aby określić typ animacji. - Wywołaj
TransitionManager.go(), a system uruchomi animację, aby zamienić układy.
Schemat na rysunku 1 przedstawia relacje między układami, scenami, przejściem i animacją końcową.
Rysunek 1. Podstawowa ilustracja sposobu, w jaki framework przejść tworzy animację.
Tworzenie sceny
Sceny przechowują stan hierarchii widoków, w tym wszystkie widoki i ich wartości właściwości. Framework przejść może uruchamiać animacje między sceną początkową a końcową.
Sceny możesz tworzyć na podstawie pliku zasobu układu lub grupy widoków w kodzie. Scena początkowa przejścia jest jednak często określana automatycznie na podstawie bieżącego interfejsu.
Scena może też definiować własne działania, które są wykonywane po zmianie sceny. Ta funkcja jest przydatna do czyszczenia ustawień widoku po przejściu do sceny.
Tworzenie sceny na podstawie zasobu układu
Instancję Scene możesz utworzyć bezpośrednio na podstawie pliku zasobu layoutu. Użyj tej techniki, gdy hierarchia widoków w pliku jest w większości statyczna.
Wynikowa scena reprezentuje stan hierarchii widoków w momencie utworzenia instancji Scene. Jeśli zmienisz hierarchię widoków, utwórz scenę ponownie. Framework tworzy scenę na podstawie całej hierarchii widoków w pliku. Nie możesz utworzyć sceny na podstawie części pliku układu.
Aby utworzyć instancję Scene na podstawie pliku zasobu layoutu, pobierz główny widok sceny z układu jako ViewGroup. Następnie wywołaj funkcję
Scene.getSceneForLayout()
z głównym widokiem sceny i identyfikatorem zasobu pliku układu, który
zawiera hierarchię widoków dla sceny.
Definiowanie układów dla scen
Fragmenty kodu w pozostałej części tej sekcji pokazują, jak utworzyć 2 różne sceny z tym samym elementem głównym sceny. Fragmenty pokazują też, że możesz wczytać wiele niezwiązanych ze sobą obiektów Scene bez sugerowania, że są one ze sobą powiązane.
Przykład składa się z tych definicji układu:
- Główny układ działania z etykietą tekstową i elementem podrzędnym
FrameLayout. - A
ConstraintLayoutdla pierwszej sceny z 2 polami tekstowymi. ConstraintLayoutdla drugiej sceny z tymi samymi 2 polami tekstowymi w innej kolejności.
Przykład jest tak zaprojektowany, że cała animacja odbywa się w układzie podrzędnym głównego układu działania. Etykieta tekstowa w układzie głównym pozostaje statyczna.
Główny układ działania jest zdefiniowany w ten sposób:
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 element podrzędny FrameLayout dla głównego widoku sceny. Układ pierwszej sceny jest zawarty w głównym pliku układu.
Dzięki temu aplikacja może wyświetlać go jako część początkowego interfejsu, a także wczytywać go do sceny, ponieważ framework może wczytać do sceny tylko cały plik układu.
Układ pierwszej sceny jest zdefiniowany w ten sposób:
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" > <TextView android:id="@+id/text_view1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 1" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> <TextView android:id="@+id/text_view2" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 2" app:layout_constraintTop_toBottomOf="@id/text_view1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Układ drugiej sceny zawiera te same 2 pola tekstowe z tymi samymi identyfikatorami, ale w innej kolejności. Jest on zdefiniowany w ten 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" > <TextView android:id="@+id/text_view2" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 2" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <TextView android:id="@+id/text_view1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 1" app:layout_constraintTop_toBottomOf="@id/text_view2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
Generowanie scen na podstawie układów
Po utworzeniu definicji 2 układów ograniczeń możesz uzyskać scenę dla każdego z nich. Umożliwi Ci to przechodzenie między 2 konfiguracjami interfejsu. Aby uzyskać scenę, potrzebujesz odwołania do głównego widoku sceny i identyfikatora zasobu układu.
Ten fragment kodu pokazuje, jak uzyskać odwołanie do głównego widoku sceny i utworzyć 2 obiekty Scene na podstawie 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 znajdują się teraz 2 obiekty Scene oparte na hierarchiach widoków. Obie sceny używają głównego widoku sceny zdefiniowanego przez element FrameLayout w pliku res/layout/activity_main.xml.
Tworzenie sceny w kodzie
Instancję Scene możesz też utworzyć w kodzie na podstawie obiektu ViewGroup. Użyj tej techniki, gdy modyfikujesz hierarchie widoków bezpośrednio w kodzie lub gdy generujesz je dynamicznie.
Aby utworzyć scenę na podstawie hierarchii widoków w kodzie, użyj
Scene(sceneRoot, viewHierarchy)
konstruktora. Wywołanie tego konstruktora jest równoznaczne z wywołaniem funkcji
Scene.getSceneForLayout()
po rozwinięciu pliku układu.
Ten fragment kodu pokazuje, jak utworzyć instancję Scene na podstawie elementu głównego widoku sceny i hierarchii widoków dla 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);
Tworzenie działań sceny
Framework umożliwia definiowanie niestandardowych działań sceny, które system wykonuje podczas wchodzenia do sceny lub wychodzenia z niej. W wielu przypadkach definiowanie niestandardowych działań sceny jest zbędne, ponieważ framework automatycznie animuje zmianę między scenami.
Działania sceny są przydatne w tych przypadkach:
- Animowanie widoków, które nie znajdują się w tej samej hierarchii. Możesz animować widoki dla scen początkowej i końcowej za pomocą działań sceny wyjścia i wejścia.
- Animowanie widoków, których framework przejść nie może animować automatycznie,
np. obiektów
ListView. Więcej informacji znajdziesz w sekcji Ograniczenia.
Aby udostępnić niestandardowe działania sceny, zdefiniuj je jako
Runnable obiekty i przekaż je do
Scene.setExitAction()
lub Scene.setEnterAction()
funkcji. Framework wywołuje funkcję setExitAction() w scenie początkowej przed uruchomieniem animacji przejścia oraz funkcję setEnterAction() w scenie końcowej po uruchomieniu animacji przejścia.
Stosowanie przejścia
Framework przejść reprezentuje styl animacji między scenami za pomocą obiektu Transition. Możesz utworzyć instancję Transition za pomocą wbudowanych
podklas, takich jak
AutoTransition i
Fade, lub
zdefiniować własne przejście.
Następnie możesz uruchomić
animację między scenami, przekazując końcową Scene
i Transition do
TransitionManager.go().
Cykl życia przejścia jest podobny do cyklu życia działania i reprezentuje stany przejścia, które framework monitoruje między rozpoczęciem a zakończeniem animacji. W ważnych stanach cyklu życia framework wywołuje funkcje wywołania zwrotnego, które możesz zaimplementować, aby dostosować interfejs użytkownika w różnych fazach przejścia.
Tworzenie przejścia
W poprzedniej sekcji pokazaliśmy, jak tworzyć sceny reprezentujące stan różnych hierarchii widoków. Gdy zdefiniujesz sceny początkową i końcową, między którymi chcesz się przełączać, utwórz obiekt Transition, który definiuje animację.
Framework umożliwia określenie wbudowanego przejścia w pliku zasobu i rozwinięcie go w kodzie lub utworzenie instancji wbudowanego przejścia bezpośrednio w kodzie.
Tabela 1. Wbudowane typy przejść.
| Zajęcia | Tag | Efekt |
|---|---|---|
AutoTransition |
<autoTransition/> |
Domyślne przejście. Zanika, przesuwa i zmienia rozmiar widoków, a następnie je rozjaśnia. |
ChangeBounds |
<changeBounds/> |
Przesuwa i zmienia rozmiar widoków. |
ChangeClipBounds |
<changeClipBounds/> |
Rejestruje View.getClipBounds() przed i po zmianie sceny
oraz animuje te zmiany podczas przejścia. |
ChangeImageTransform |
<changeImageTransform/> |
Rejestruje macierz ImageView przed i po zmianie sceny
oraz animuje ją podczas przejścia. |
ChangeScroll |
<changeScroll/> |
Rejestruje właściwości przewijania celów przed i po zmianie sceny oraz animuje wszelkie zmiany. |
ChangeTransform |
<changeTransform/> |
Rejestruje skalę i rotację widoków przed i po zmianie sceny oraz animuje te zmiany podczas przejścia. |
Explode |
<explode/> |
Śledzi zmiany widoczności widoków docelowych w scenach początkowej i końcowej oraz przesuwa widoki do lub z krawędzi sceny. |
Fade |
<fade/> |
fade_in rozjaśnia widoki.fade_out zanika widoki.fade_in_out (domyślnie) wykonuje fade_out, a następnie
fade_in.
|
Slide |
<slide/> |
Śledzi zmiany widoczności widoków docelowych w scenach początkowej i końcowej oraz przesuwa widoki do lub z jednej z krawędzi sceny. |
Tworzenie instancji przejścia na podstawie pliku zasobu
Ta technika umożliwia modyfikowanie definicji przejścia bez zmiany kodu działania. Ta technika jest też przydatna do oddzielania złożonych definicji przejść od kodu aplikacji, jak pokazano w sekcji Określanie wielu przejść.
Aby określić wbudowane przejście w pliku zasobu:
- Dodaj do projektu katalog
res/transition/. - Utwórz w tym katalogu nowy plik zasobu XML.
- Dodaj węzeł XML dla jednego z wbudowanych przejść.
Na przykład ten plik zasobu 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 rozwinąć instancję Transition w działaniu na podstawie pliku zasobu:
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 technika jest przydatna do dynamicznego tworzenia obiektów przejścia, jeśli modyfikujesz interfejs w kodzie, oraz do tworzenia prostych wbudowanych instancji przejścia z kilkoma parametrami lub bez nich.
Aby utworzyć instancję wbudowanego przejścia, wywołaj jeden z publicznych konstruktorów w podklasach klasy Transition. Na przykład ten fragment kodu tworzy instancję przejścia Fade:
Kotlin
var fadeTransition: Transition = Fade()
Java
Transition fadeTransition = new Fade();
Stosowanie przejścia
Zazwyczaj stosujesz przejście, aby przełączać się między różnymi hierarchiami widoków w odpowiedzi na zdarzenie, np. działanie użytkownika. Rozważmy na przykład aplikację do wyszukiwania: gdy użytkownik wpisze wyszukiwane hasło i kliknie przycisk wyszukiwania, aplikacja przełączy się na scenę reprezentującą układ wyników, stosując przejście, które zanika przycisk wyszukiwania i rozjaśnia wyniki wyszukiwania.
Aby zmienić scenę podczas stosowania przejścia w odpowiedzi na zdarzenie w działaniu, wywołaj funkcję klasy TransitionManager.go() z końcową sceną i instancją przejścia, która ma być używana do animacji, jak pokazano w tym fragmencie:
Kotlin
TransitionManager.go(endingScene, fadeTransition)
Java
TransitionManager.go(endingScene, fadeTransition);
Framework zmienia hierarchię widoków w głównym widoku sceny na hierarchię widoków z końcowej sceny, uruchamiając animację określoną przez instancję przejścia. Scena początkowa to scena końcowa z 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 przejść może zastosować automatyczne przejście, które w większości sytuacji będzie działać w rozsądny sposób. Więcej informacji znajdziesz w dokumentacji API klasy
TransitionManager.
Wybieranie konkretnych widoków docelowych
Domyślnie framework stosuje przejścia do wszystkich widoków w scenach początkowej i końcowej. W niektórych przypadkach możesz chcieć zastosować animację tylko do podzbioru widoków w scenie. Framework umożliwia wybieranie konkretnych widoków, które chcesz animować. Na przykład framework nie obsługuje animowania zmian w obiektach ListView, więc nie próbuj ich animować podczas przejścia.
Każdy widok, który animuje przejście, jest nazywany celem. Możesz wybierać tylko cele, które są częścią hierarchii widoków powiązanej ze sceną.
Aby usunąć co najmniej 1 widok z listy celów, przed rozpoczęciem przejścia wywołaj metodę
removeTarget(). Aby dodać do listy celów tylko określone widoki, wywołaj addTarget()funkcję. Więcej informacji znajdziesz w dokumentacji API klasy
Transition.
Określanie wielu przejść
Aby uzyskać jak najlepszy efekt animacji, dopasuj ją do typu zmian, które zachodzą między scenami. Jeśli na przykład usuwasz niektóre widoki i dodajesz inne między scenami, animacja zanikania lub rozjaśniania wyraźnie wskazuje, że niektóre widoki nie są już dostępne. Jeśli przenosisz widoki w różne miejsca na ekranie, lepiej jest animować ruch, aby użytkownicy zauważyli nowe położenie widoków.
Nie musisz wybierać tylko 1 animacji, ponieważ framework przejść umożliwia łączenie efektów animacji w zestawie przejść, który zawiera grupę poszczególnych wbudowanych lub niestandardowych przejść.
Aby zdefiniować zestaw przejść na podstawie kolekcji przejść w XML, utwórz plik zasobu w katalogu res/transitions/ i wymień przejścia w elemencie TransitionSet. Na przykład ten fragment kodu pokazuje, jak określić zestaw przejść, który ma takie samo działanie 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 rozwinąć zestaw przejść w
TransitionSet obiekt w
kodzie, wywołaj
TransitionInflater.from()
funkcję w działaniu. Klasa TransitionSet rozszerza klasę
Transition, więc możesz jej używać z menedżerem przejść tak jak każdej
innej Transition instancji.
Stosowanie przejścia bez scen
Zmiana hierarchii widoków to nie jedyny sposób modyfikowania interfejsu. Możesz też wprowadzać zmiany, dodając, modyfikując i usuwając widoki podrzędne w bieżącej hierarchii.
Możesz na przykład zaimplementować interakcję wyszukiwania za pomocą jednego układu. Zacznij od układu z polem wprowadzania wyszukiwania i ikoną wyszukiwania. Aby zmienić interfejs tak, aby wyświetlał wyniki, usuń przycisk wyszukiwania
gdy użytkownik go kliknie, wywołując
ViewGroup.removeView()
i dodaj wyniki wyszukiwania, wywołując
ViewGroup.addView().
Możesz użyć tego podejścia, jeśli alternatywą jest użycie 2 prawie identycznych hierarchii. Zamiast tworzyć i utrzymywać 2 oddzielne pliki układu, które różnią się tylko nieznacznie, możesz mieć 1 plik układu zawierający hierarchię widoków, którą modyfikujesz w kodzie.
Jeśli w ten sposób wprowadzisz zmiany w bieżącej hierarchii widoków, nie musisz tworzyć sceny. Zamiast tego możesz utworzyć i zastosować przejście między 2 stanami hierarchii widoków za pomocą opóźnionego przejścia. Ta funkcja frameworka przejść zaczyna się od bieżącego stanu hierarchii widoków, rejestruje zmiany wprowadzane w widokach i stosuje przejście, które animuje zmiany, gdy system ponownie rysuje interfejs.
Aby utworzyć opóźnione przejście w ramach pojedynczej hierarchii widoków:
- Gdy wystąpi zdarzenie, które wywołuje przejście, wywołaj funkcję
TransitionManager.beginDelayedTransition(), podając widok rodzica wszystkich widoków , które chcesz zmienić, oraz przejście, które ma być używane. Framework przechowuje bieżący stan widoków podrzędnych i ich wartości właściwości. - Wprowadź zmiany w widokach podrzędnych zgodnie z wymaganiami przypadku użycia. Framework rejestruje zmiany wprowadzane w widokach podrzędnych i ich właściwościach.
- Gdy system ponownie narysuje interfejs zgodnie z wprowadzonymi zmianami, framework animuje zmiany między stanem pierwotnym a nowym.
Ten przykład pokazuje, jak animować dodanie widoku tekstowego do hierarchii widoków za pomocą opóźnionego przejścia. Pierwszy fragment kodu pokazuje 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 kodu pokazuje, jak animować dodanie widoku tekstowego:
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.
Definiowanie wywołań zwrotnych cyklu życia przejścia
Cykl życia przejścia jest podobny do cyklu życia działania. Reprezentuje on stany przejścia, które framework monitoruje w okresie między wywołaniem funkcji TransitionManager.go() a zakończeniem animacji. W ważnych stanach cyklu życia framework wywołuje wywołania zwrotne zdefiniowane przez interfejs TransitionListener.
Wywołania zwrotne cyklu życia przejścia są przydatne np. do kopiowania wartości właściwości widoku z początkowej hierarchii widoków do końcowej hierarchii widoków podczas zmiany sceny. Nie możesz po prostu skopiować wartości z początkowego widoku do widoku w końcowej hierarchii widoków, ponieważ końcowa hierarchia widoków nie jest rozwijana do momentu zakończenia przejścia. Zamiast tego musisz zapisać wartość w zmiennej, a następnie skopiować ją do końcowej hierarchii widoków, gdy framework zakończy przejście. Aby otrzymywać powiadomienia o zakończeniu przejścia,
zaimplementuj w działaniu funkcję
TransitionListener.onTransitionEnd().
Więcej informacji znajdziesz w dokumentacji API klasy
TransitionListener.
Ograniczenia
W tej sekcji wymieniamy niektóre znane ograniczenia frameworka przejść:
- Animacje stosowane do
SurfaceViewmogą nie wyświetlać się prawidłowo. InstancjeSurfaceViewsą aktualizowane z wątku innego niż wątek interfejsu, więc aktualizacje mogą być niezsynchronizowane z animacjami innych widoków. - Niektóre typy przejść mogą nie dawać oczekiwanego efektu animacji
gdy są stosowane do
TextureView. - Klasy, które rozszerzają
AdapterView, np.ListView, zarządzają swoimi widokami podrzędnymi w sposób niezgodny z platformą przejść. Jeśli spróbujesz animować widok oparty naAdapterView, wyświetlacz urządzenia może przestać odpowiadać. - Jeśli spróbujesz zmienić rozmiar
TextViewza pomocą animacji, tekst przeskoczy do nowej lokalizacji, zanim obiekt zostanie całkowicie zmieniony. Aby uniknąć tego problemu, nie animuj zmiany rozmiaru widoków zawierających tekst.