Platforma przejść Androida umożliwia animowanie wszystkich rodzajów ruchu w interfejsie użytkownika przez udostępnienie układów początkowego i końcowego. Możesz wybrać rodzaj animacji, np. płynne przejście z jednego widoku do drugiego lub zmianę rozmiaru widoku. Ramka przejścia określa, jak animacja ma przebiegać od układu początkowego do końcowego.
Ramy przejściowe obejmują 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 do tworzenia efektów takich jak znikanie lub ruch.
- Obsługa plików zasobów: wczytuj hierarchie widoków i wbudowane animacje z plików zasobów układu.
- Zwroty wywołania cyklu życia: otrzymują zwroty wywołania, które zapewniają kontrolę nad animacją i procesem zmiany hierarchii.
Przykładowy kod, który animuje zmiany układu, znajdziesz w artykule Przejście podstawowe.
Podstawowy proces animowania przejścia między dwoma układami:
- Utwórz obiekt
Scene
dla początkowego i końcowego układu. Scena układu początkowego jest jednak często określana automatycznie na podstawie bieżącego układu. - Utwórz obiekt
Transition
, aby określić, jaki rodzaj animacji chcesz uzyskać. - Wywołaj metodę
TransitionManager.go()
, a system uruchomi animację, aby zamienić układy.
Diagram na rysunku 1 przedstawia relację między układami, scenami, przejściami i ostateczną animacją.
Tworzenie sceny
Sceny przechowują stan hierarchii widoku, w tym wszystkie jego widoki i ich wartości właściwości. Framework przejść może uruchamiać animacje między początkową a końcową sceną.
Możesz tworzyć sceny z pliku zasobów układu lub grupy widoków w kodzie. Jednak scena początkowa przejścia często jest 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
Możesz utworzyć instancję Scene
bezpośrednio z pliku zasobu układu. Ta metoda jest przydatna, gdy hierarchia widoków w pliku jest w większości statyczna.
Wynikowa scena odzwierciedla stan hierarchii widoku w momencie utworzenia instancji Scene
. Jeśli zmienisz hierarchię widoku,
odtwórz scenę. Framework tworzy scenę z całej hierarchii widoku w pliku. Nie można utworzyć sceny z części pliku układu.
Aby utworzyć instancję Scene
z pliku zasobu układu, pobierz wierzchołek sceny z układu jako ViewGroup
. Następnie wywołaj funkcję Scene.getSceneForLayout()
, podając jej poziom główny i identyfikator zasobu w pliku układu, który zawiera hierarchię widoków sceny.
Definiowanie układów scen
Fragmenty kodu w pozostałych częściach tej sekcji pokazują, jak utworzyć 2 różne sceny z tym samym elementem głównym sceny. Fragmenty kodu pokazują też, że można wczytywać wiele niepowiązanych obiektów Scene
, nie sugerując, że są one 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
w pierwszej scenie z 2 polami tekstowymi.ConstraintLayout
dla drugiej sceny z tymi samymi 2 polami tekstowymi w innej kolejności.
Przykład jest tak zaprojektowany, aby cała animacja miała miejsce w ramach układu podrzędnego w głównym układzie aktywności. Etykieta tekstowa w głównym układzie pozostaje statyczna.
Główny układ aktywności jest zdefiniowany w ten sposób:
<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 FrameLayout
dla poziomu głównego sceny. Układ pierwszej sceny jest zawarty w pliku głównego układu.
Dzięki temu aplikacja może wyświetlać go jako część początkowego interfejsu użytkownika, a także wczytywać go do sceny, ponieważ framework może wczytywać do sceny tylko cały plik układu.
Układ pierwszej sceny jest zdefiniowany w ten sposób:
<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 tymi samymi identyfikatorami) umieszczone w innej kolejności. Jest ona zdefiniowana w ten sposób:
<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>
Generowanie scen na podstawie układów
Po utworzeniu definicji dla 2 schematów ograniczeń możesz uzyskać scenę dla każdego z nich. Dzięki temu możesz przełączać się między 2 konfiguracjami interfejsu. Aby uzyskać scenę, musisz podać odwołanie do katalogu sceny i identyfikator zasobu układu.
Ten fragment kodu pokazuje, jak uzyskać odwołanie do katalogu 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
oparte na hierarchiach widoków. Obie sceny używają rdzenia sceny zdefiniowanego przez element FrameLayout
w res/layout/activity_main.xml
.
Tworzenie sceny w kodzie
Możesz też utworzyć instancję Scene
w kodzie z obiektu ViewGroup
. Stosuj tę metodę, gdy modyfikujesz hierarchie widoków bezpośrednio w kodzie lub generujesz je dynamicznie.
Aby utworzyć scenę z hierarchii widoku w kodzie, użyj konstruktora Scene(sceneRoot, viewHierarchy)
. Wywołanie tego konstruktora jest równoważne wywołaniu funkcji Scene.getSceneForLayout()
po zainflatowaniu pliku układu.
Ten fragment kodu pokazuje, jak utworzyć instancję Scene
z elementu sceny głównej i hierarchii widoku sceny w kodu:
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ń związanych ze scenami
Platforma pozwala zdefiniować niestandardowe działania wykonywane w ramach sceny, które system wykonuje przy dochodzeniu do sceny lub jej opuszczaniu. W wielu przypadkach definiowanie niestandardowych działań sceny jest niepotrzebne, ponieważ platforma automatycznie animuje przejścia między scenami.
Działania sceny przydają się w tych sytuacjach:
- Aby animować widoki, które nie znajdują się w tej samej hierarchii. Możesz animować widoki sceny początkowej i końcowej, używając działań sceny wyjścia i wejścia.
- Aby animować widoki, których nie można animować automatycznie w ramach ramy przejść, na przykład 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()
. Przed uruchomieniem animacji przejścia framework wywołuje funkcję setExitAction()
w scenie początkowej, a po jej uruchomieniu funkcję setEnterAction()
w scenie końcowej.
Stosowanie przejścia
Ramka przejścia reprezentuje styl animacji między scenami z obiektem Transition
. Możesz utworzyć instancję Transition
za pomocą wbudowanych podklas, takich jak AutoTransition
i Fade
, lub zdefiniować własną animację przejścia.
Później możesz uruchomić animację między scenami, przekazując końcową wartość 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 początkiem a zakończeniem animacji. W ważnych stanach cyklu życia framework wywołuje funkcje wywołania zwrotnego, które możesz zaimplementować, aby dostosowywać interfejs użytkownika w różnych fazach przejścia.
Tworzenie przejścia
W poprzedniej sekcji pokazaliśmy, jak tworzyć sceny, które odzwierciedlają stan różnych hierarchii widoku. Po zdefiniowaniu początkowej i końcowej sceny, między którymi chcesz przełączać się w animacji, utwórz obiekt Transition
, który definiuje animację.
Framework umożliwia określenie wbudowanego przejścia w pliku zasobów i napompowanie go w kodzie lub utworzenie wystąpienia wbudowanego przejścia bezpośrednio w kodzie.
Kategoria | Tag | Efekt |
---|---|---|
AutoTransition |
<autoTransition/> |
Domyślne przeniesienie. widoki znikają, przesuwają się, zmieniają rozmiar i pojawiają się w tej kolejności. |
ChangeBounds |
<changeBounds/> |
Przenosi widoki i zmienia ich rozmiar. |
ChangeClipBounds |
<changeClipBounds/> |
rejestruje View.getClipBounds() przed i po zmianie sceny oraz animuje te zmiany podczas przejścia. |
ChangeImageTransform |
<changeImageTransform/> |
Przechwytuje macierz ImageView przed i po zmianie sceny oraz animuje ją podczas przejścia. |
ChangeScroll |
<changeScroll/> |
Przechwytuje właściwości przewijania obiektów przed i po zmianie sceny oraz animuje wszelkie zmiany. |
ChangeTransform |
<changeTransform/> |
Rejestruje skalę i obrót widoków przed zmianą sceny i po niej oraz animuje te zmiany podczas przejścia. |
Explode |
<explode/> |
Śledzi zmiany widoczności widoków docelowych w scenie początkowej i końcowej oraz przesuwa wyświetlenia do i z krawędzi sceny. |
Fade |
<fade/> |
fade_in powoduje stopniowe pojawianie się widoku.fade_out powoduje zaniknięcie widoku.fade_in_out (domyślnie) wykonuje fade_out , po którym następuje
fade_in .
|
Slide |
<slide/> |
Śledzi zmiany widoczności widoków docelowych w scenie początkowej i końcowej oraz przenosi widoki do lub z jednej z krawędzi sceny. |
Tworzenie instancji przejścia z pliku zasobu
Ta technika pozwala zmienić 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 określania wielu przejść.
Aby określić wbudowane przejście w pliku zasobów:
- 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 zasobów określa przejście Fade
:
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
Ten fragment kodu pokazuje, jak wczytać instancję Transition
w swojej 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 wystąpienia przejścia w kodzie
Ta technika jest przydatna do dynamicznego tworzenia obiektów przejść, jeśli modyfikujesz interfejs użytkownika w kodzie i chcesz utworzyć proste wbudowane przejścia z niewielką liczbą parametrów lub bez nich.
Aby utworzyć instancję wbudowanego przejścia, wywołaj jeden z konstruktorów publicznych 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();
Stosowanie przejścia
Przejście zwykle stosujesz, aby przełączać się między różnymi hierarchiami widoku w odpowiedzi na zdarzenie, np. działanie użytkownika. Weźmy na przykład aplikację do wyszukiwania: gdy użytkownik wpisze wyszukiwane hasło i kliknie przycisk wyszukiwania, aplikacja zmieni scenę na taką, która przedstawia układ wyników, a przy okazji zastosuje przejście, w którym przycisk wyszukiwania zniknie, a wyniki wyszukiwania zaczną się pojawiać.
Aby zmienić scenę podczas stosowania przejścia w odpowiedzi na zdarzenie w Twojej aktywności, wywołaj funkcję klasy TransitionManager.go()
z użyciem końcowej sceny i przypadku przejścia, które mają być używane w animacji, jak pokazano w tym fragmencie kodu:
Kotlin
TransitionManager.go(endingScene, fadeTransition)
Java
TransitionManager.go(endingScene, fadeTransition);
Framework zmienia hierarchię widoku w korzenia sceny za pomocą hierarchii widoku ze sceny końcowej podczas wykonywania animacji określonej przez instancję przejścia. Początkowa scena to końcowa scena z poprzedniego przejścia. Jeśli nie było poprzedniego przejścia, scena początkowa jest określana automatycznie na podstawie bieżącego stanu interfejsu.
Jeśli nie określisz wystąpienia przejścia, menedżer przejścia może zastosować automatyczne przejście, które w większości sytuacji działa prawidłowo. Więcej informacji znajdziesz w dokumentacji interfejsu API dotyczącej klasy TransitionManager
.
Wybieranie konkretnych widoków docelowych
Domyślnie framework stosuje przejścia do wszystkich widoków w scenkach początkowej i końcowej. W niektórych przypadkach możesz chcieć zastosować animację tylko do podzbioru widoków w scenie. Framework umożliwia wybranie konkretnych widoków, które chcesz animować. Na przykład platforma nie obsługuje animowania zmian w obiektach ListView
, więc nie próbuj animować ich podczas przejścia.
Każdy widok, który jest animowany przez przejście, nazywa się docelowym. Możesz wybierać tylko cele, które są częścią hierarchii widoku powiązanej ze sceną.
Aby usunąć jeden lub więcej widoków z listy celów, przed rozpoczęciem przejścia wywołaj metodę removeTarget()
. Aby do listy docelowych widoków dodać tylko określone widoki, wywołaj funkcję addTarget()
. Więcej informacji znajdziesz w dokumentacji interfejsu API klasy Transition
.
Określ wiele przejść
Aby uzyskać jak największy wpływ animacji, dopasuj ją do typu zmian zachodzących między scenami. Jeśli na przykład usuniesz niektóre widoki i dodasz inne między scenami, animacja zanikania lub pojawiania się będzie wyraźnie wskazywać, że niektóre widoki nie są już dostępne. Jeśli przenosisz widoki do różnych punktów na ekranie, lepiej jest je animować, aby użytkownicy zauważyli nową lokalizację.
Nie musisz wybierać tylko jednej animacji, ponieważ framework animacji umożliwia łączenie efektów animacji w zestawie animacji zawierającym grupę poszczególnych wbudowanych lub niestandardowych animacji.
Aby zdefiniować zbiór przejść na podstawie kolekcji przejść w formacie XML, utwórz plik zasobu w katalogu res/transitions/
i wymień w elemencie TransitionSet
listę przejść. 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 wczytać zestaw przejść do obiektu TransitionSet
w kodzie, wywołaj funkcję TransitionInflater.from()
w swojej aktywności. Klasa TransitionSet
rozszerza klasę Transition
, więc możesz jej używać z menedżerem przejścia tak jak z inną instancją Transition
.
Zastosuj przejście bez scen
Zmiana hierarchii widoków to nie jedyny sposób na modyfikowanie interfejsu użytkownika. Możesz też wprowadzać zmiany, dodając, modyfikując i usuwając widoki podrzędne w ramach bieżącej hierarchii.
Możesz na przykład zaimplementować interakcję wyszukiwania z jednym układem. Zacznij od układu, w którym widać pole wyszukiwania i ikonę wyszukiwania. Aby zmienić interfejs użytkownika, aby wyświetlał wyniki, usuń przycisk wyszukiwania, gdy użytkownik go kliknie, wywołując funkcję ViewGroup.removeView()
, i dodaj wyniki wyszukiwania, wywołując funkcję ViewGroup.addView()
.
Możesz użyć tej metody, jeśli alternatywą jest posiadanie 2 hierarchii, które są prawie identyczne. Zamiast tworzyć i utrzymywać 2 osobne pliki układu z niewielką różnicą w interfejsie, możesz mieć jeden plik układu zawierający hierarchię widoków, którą modyfikujesz w kodzie.
Jeśli w ten sposób wprowadzisz zmiany w ramach bieżącej hierarchii widoku, nie musisz tworzyć sceny. Zamiast tego możesz utworzyć i zastosować przejście między 2 stanami w hierarchii widoku za pomocą opóźnionego przejścia. Ta funkcja frameworku przejść zaczyna się od bieżącego stanu hierarchii widoku, rejestruje zmiany wprowadzone w widokach i zastosowanie przejścia, które animuje zmiany, gdy system ponownie wyświetla interfejs użytkownika.
Aby utworzyć opóźniony przejście w hierarchii pojedynczego widoku:
- Gdy nastąpi zdarzenie, które powoduje przejście, wywołaj funkcję
TransitionManager.beginDelayedTransition()
, podając widok nadrzędny wszystkich widoków, które chcesz zmienić, oraz przejście, którego chcesz użyć. Platforma 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. Platforma rejestruje zmiany wprowadzane w widokach podrzędnych i ich właściwościach.
- Gdy system ponownie narysuje interfejs użytkownika zgodnie z Twoimi zmianami, framework wyświetli animację zmian między pierwotnym stanem a nowym.
Z przykładu poniżej dowiesz się, jak animować dodawanie widoku tekstu do hierarchii widoków przy użyciu opóźnionego przejścia. Pierwszy fragment kodu pokazuje plik definicji układu:
<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>
Poniższy fragment kodu pokazuje animację dodawania widoku tekstu:
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 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 framework wywołuje funkcje zwracania wartości zdefiniowane przez interfejs TransitionListener
.
Callbacki cyklu życia przejścia są przydatne np. do kopiowania wartości właściwości widoku z hierarchii widoku początkowego do hierarchii widoku końcowego podczas zmiany sceny. Nie możesz po prostu skopiować wartości z widoku początkowego do widoku w końcowej hierarchii widoku, ponieważ końcowa hierarchia widoku nie zostanie powiększona, dopóki przejście nie zostanie ukończone. Zamiast tego musisz zapisać wartość w zmiennej, a potem skopiować ją do hierarchii widoku końcowego, gdy framework zakończy przejście. Aby otrzymywać powiadomienia o zakończeniu przenoszenia, w swojej aktywności zastosuj funkcję TransitionListener.onTransitionEnd()
.
Więcej informacji znajdziesz w dokumentacji interfejsu API klasy TransitionListener
.
Ograniczenia
W tej sekcji znajdziesz kilka znanych ograniczeń platformy przejść:
- Animacje zastosowane do
SurfaceView
mogą nie wyświetlać się prawidłowo. instancjeSurfaceView
są aktualizowane z wątku innego niż interfejs użytkownika, więc te aktualizacje mogą być niezsynchronizowane z animacjami innych widoków. - Niektóre typy przejść mogą nie dać pożądanego efektu animacji, gdy zostaną zastosowane do
TextureView
. - Klasy, które rozszerzają
AdapterView
, takie jakListView
, zarządzają widokami podklas w sposób niezgodny z ramami przekształceń. Jeśli spróbujesz animować widok na podstawieAdapterView
, 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 rozmiar obiektu zostanie całkowicie zmieniony. Aby uniknąć tego problemu, nie animuj zmiany rozmiaru widoków zawierających tekst.