Mimo, że MotionLayout
nie został jeszcze wydany w stabilnej wersji (obecnie beta4), to wielu programistów zaczęło już z nim eksperymentować. Sam należę do tej grupy i muszę przyznać, że możliwości tego narzędzia są imponujące.
W tym wpisie zostaną przedstawione podstawowe zagadnienia związane z MotionLayout
pozwalające na swobodne korzystanie z tego narzędzia. Poniżej znajdziesz przykłady animacji jakie będą tworzone w ramach tego wpisu.
Jeśli ten temat Cię zaciekawi, to zachęcam do przejrzenia serii Introduction to MotionLayout na Medium, gdzie znajdziesz bardziej szczegółowe przykłady.
Property Animator
Zanim zaczniesz korzystać z MotionLayout
spróbuj użyć PropertyAnimator
. Google CodeLabs w pierwszej kolejności zachęca do nauki animatorów pojedynczych właściwości elementów widoku, z których później można budować bardziej skomplikowane przejścia. Takie podejście gwarantuje łatwiejszą pracę z MotionLayout
ze względu na sporą analogię. W nim także określamy konkretnie jakie właściwości mają być zmieniane oraz w jaki sposób ma się to odbywać.
Powyższe przekształcenie można uzyskać wywołując jedną metodę.
Zalety i wady MotionLayout
Łatwy sposób uzyskania efektu z powyższego przykładu daje złudną pokusę, że wszystkie inne rzeczy będą takie proste. Co w przypadku kiedy chcemy animować kartę przeciągając palcem po innym elemencie lub w zależności od warunków zmieniać dane przejście? Wizja wielu animatorów i listenerów nie jest już taka obiecująca.
Wypunktujmy więc plusy tego rozwiązania:
- Reagowanie na przeciąganie i kliknięcia.
- Konkretna scena zamknięta w xml’u.
- Podgląd początku i końca (stan początkowy, przejściowy i końcowy jest odwzorowywany w edytorze widoku).
- Dynamiczna zmiana scen z poziomu kodu.
- Łatwe dodawanie interpolatorów.
- Dodawanie stanów pomiędzy początkiem i końcem (
KeyFrameSet
).
Oczywiście liczba przydatnych opcji oraz atrybutów z czasem będzie rosła. Przypominam, że MotionLayout jest wciąż w wersji testowej.
Oczywiście nie brakuje też minusów:
- Konieczność utrzymania minimum dwóch plików xml.
- Każdy błąd w scenie nie odświeża poprawnie głównego widoku. Brak jeszcze dobrego powiadomienia programisty co jest nie tak.
- Naprzemienne używanie przedrostka
android
imotion
do różnych atrybutów jest mylące. - Każdy komponent musi posiadać identyfikator.
- Wymuszanie budowy widoku w
ConstraintLayout
.
Konfiguracja i opis komponentów
Aby zacząć korzystać z MotionLayout
trzeba zaimportować ConstraintLayout
w wersji 2.0.0. Dodatkowo dla walorów estetycznych można dodać bibliotekę MaterialComponents
.
Animację budujemy z czterech podstawowych komponentów:
MotionScene
– Główny layout dla danej sceny. W nim definiujemy wszystkie potrzebne elementy.ConstraintSet
– Określa na jakie komponenty będzie oddziaływać MotionLayout. Aby doszło do przejścia potrzebny jest minimum układ początkowy i końcowy.Constraint
– Określa parametry komponentów np.alpha
,margin
itp.Transition
– Opisuje zachowanie przejścia np.duration
,interpolation
i czy wyzwalaczem jest kliknięcie czy przeciągnięcie. Na ten moment nie najlepiej działa korzystanie z wieluTransition
.
Ten układ zapisujemy pod nazwą naszej sceny do folderu res/xml/ z rozszerzeniem .xml.
Aby sprawnie podglądać zachowanie sceny w naszym układzie warto znać parametry:
app:applyMotionScene="boolean"
– czy scena ma być zaaplikowana.app:currentState=”reference”
– pokazuje w layoucie wcześniej zdefiniowany stan np. początek lub koniec.app:layoutDescription=”reference”
– definiuje stworzoną scenę.app:motionProgress=”float”
– stan sceny w danym punkcie. Przyjmuje wartości od 0 do 1.app:showPaths=”boolean”
– pokazuje ścieżki ruchu w celach testowych.
Przejście FloatingActionButton w CardView
Layout został przygotowany do zaprezentowania animacji przejścia do pseudo BottonSheetDialog
po kliknięciu w FloatingActionButton
. Tak na prawdę rozwijanym z dołu elementem będzie MaterialCardView
.
Scena prezentuje się następująco:
W clickAction
została ustawiona wartość transitionToEnd
, co gwarantuje tylko rozwinięcie, a sama karta nie zwinie się przy ponownym kliknięciu. Podstawowe parametry jak elevation
, alpha
czy visibility
(z tym trzeba uważać, bywa przydatna flaga visibilityMode
) mogą być określone bezpośrednio w sekcji Constraint
. Dla specyficznych atrybutów widoku korzystamy z CustomAttribute
i dostępnych parametrów. Na koniec warto zauważyć jeszcze, że Constraint
dla widoku poprzedzamy słówkiem motion
, a nie app
.
Efekt po tych działaniach został zaprezentowany poniżej.
Nasłuchiwanie animacji z poziomu kodu
Jeżeli na bieżąco uważnie śledzisz kod, to czegoś powinno Ci tutaj brakować. Brakuje EditText
, który powinien znajdować się na górze rozwiniętej karty. To dobry moment, żeby podkreślić coś ważnego:
MotionLayout nie działa dla zagnieżdżonych widoków!
Oznacza to, że aby animować pozostałe komponenty musimy dowiedzieć się w jakim stanie znajduje się nasza scena. Z pomocą przychodzi MotionLayout.TransitionListener
.
Na potrzeby nasłuchiwania zmiany progresu, interfejs MotionLayout.TransitionListener
został opakowany w sposób zaprezentowany powyżej. W MainActivity wystarczy tylko dodać taki fragment kodu:
Tą zmianą osiągnęliśmy efekt pojawiającego się pola tekstowego i chowającej się strzałki.
Reagowanie na przesunięcie listy
Czym byłoby dynamiczne dodawanie elementów bez umieszczania ich w liście? W naszym przykładzie powinniśmy ukryć dużą kartę kiedy użytkownik zaczyna przesuwać listę. Jak już wcześniej wspomniałem, na razie niezbyt dobrze MotionLayout
radzi sobie z wieloma typami tranzycji. Jest to dobry pretekst aby ręcznie sprawdzić przewijanie listy.
Jak widać animowanie do początku to po prostu transitionToStart()
. Dodatkowo czyścimy wtedy pole tekstowe. Dodajmy jeszcze animację rozwijania karty wywoływaną kliknięciem na element. Jest to równie prosta operacja.
A tak będzie się prezentował nasz ekran po wprowadzonych zmianach:
Za mało animacji… Dodajmy Lottie!
Lottie to bardzo popularna biblioteka od Airbnb do renderowania animacji After Effects. Dodajmy ją do naszego projektu.
Do głównego widoku dodam własny Toolbar
zawierający kontrolkę LottieAnimationView
.
Dodatkowo z oficjalnej strony została pobrana i zaimportowana animacja ołówka.
Teraz wystarczy połączyć te wszystkie części w całość, powiadomić Lottie o progresie i Voilla!
Dynamiczne podmienianie sceny
W jednym z pierwszych akapitów zostało wyszczególnione, że możemy dynamicznie zmieniać scenę. Daje to ogromne możliwości zmiany układów, animowania tych samych widoków na wiele sposobów. Na potrzeby demonstracji do toolbara został dodany prosty switch, który podmieni dużą kartę na końcu sceny na małą. Teraz trzeba stworzyć wariacje. Ja skopiowałem poprzednią scenę i zmieniłem tylko niektóre parametry w stanie końcowym. Początek zostaje bez zmian.
Poprzednią scenę nazwałem expandable_bottom_sheet_big.xml
, a nową expandable_bottom_sheet_small.xml
. W kodzie tylko trzeba obsłużyć przeładowanie sceny na kliknięcie switcha.
Na koniec tylko dodam, że warto po czasie zacząć używać flagi deriveConstraintsFrom="id"
, bo dzięki niej możemy kopiować powtarzający się ConstraintSet
.
Podsumowanie
MotionLayout
już w obecnej wersji wydaje się być potężnym narzędziem i dawno nie miałem takiej frajdy z animowania widoków w Androidzie. Aktualnie Google zaleca, żeby używać go tylko do animowania widoku reagującego na akcje użytkownika. Zgadzam się z tym podejściem bo, MotionLayout
potrafi być momentami uciążliwy przy przebudowie lub rozbudowie widoku. Występują też pewne zawiłości związane z widocznością pojedynczych elementów, którą chcemy zmieniać z poziomu kodu, a jest ona już zdefiniowana w scenie.
Źródła
Link do repozytorium:
https://github.com/tmaxxdd/MotionLayoutAnimatedBottomSheet
Bardzo fajny artykuł ciekawy layout