Aby dostosować sposób działania animacji przejścia elementu udostępnionego, możesz użyć kilku parametrów, które pozwalają zmienić sposób przejścia elementów udostępnionych.
Specyfikacja animacji
Aby zmienić specyfikację animacji używaną do ruchu rozmiaru i pozycji, możesz określić inny parametr boundsTransform w Modifier.sharedElement().
Podaje początkową pozycję Rect i docelową pozycję Rect.
Jeśli na przykład chcesz, aby tekst w poprzednim przykładzie poruszał się po łuku, określ parametr boundsTransform, aby użyć specyfikacji keyframes:
val textBoundsTransform = BoundsTransform { initialBounds, targetBounds -> keyframes { durationMillis = boundsAnimationDurationMillis initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing targetBounds at boundsAnimationDurationMillis } } Text( "Cupcake", fontSize = 28.sp, modifier = Modifier.sharedBounds( rememberSharedContentState(key = "title"), animatedVisibilityScope = animatedVisibilityScope, boundsTransform = textBoundsTransform ) )
Możesz użyć dowolnego AnimationSpec. W tym przykładzie użyto specyfikacji keyframes.
boundsTransformTryb zmiany rozmiaru
Podczas animowania między 2 udostępnionymi granicami możesz ustawić parametr resizeMode na RemeasureToBounds lub ScaleToBounds. Ten parametr określa, jak element wspólny przechodzi między tymi 2 stanami. ScaleToBounds najpierw mierzy układ podrzędny z ograniczeniami podglądu (lub docelowymi). Następnie stabilny układ elementu podrzędnego jest skalowany, aby zmieścił się w udostępnionych granicach.
ScaleToBounds można traktować jako „skalę graficzną” między stanami.
Z kolei RemeasureToBounds ponownie mierzy i układa układ podrzędny sharedBounds z animowanymi stałymi ograniczeniami na podstawie rozmiaru docelowego. Ponowny pomiar jest wywoływany przez zmianę rozmiaru granic, która może wystąpić w każdej klatce.
W przypadku funkcji kompozycyjnych Text zalecamy używanie ScaleToBounds, ponieważ pozwala to uniknąć ponownego układania i przenoszenia tekstu do innych wierszy. RemeasureToBounds jest zalecany w przypadku granic o różnych formatach obrazu oraz gdy chcesz uzyskać płynną ciągłość między 2 udostępnionymi elementami.
Różnicę między tymi 2 trybami zmiany rozmiaru widać w poniższych przykładach:
|
|
|---|---|
Dynamiczne włączanie i wyłączanie elementów udostępnianych
Domyślnie funkcje sharedElement() i sharedBounds() są skonfigurowane tak, aby animować zmiany układu, gdy w stanie docelowym zostanie znaleziony pasujący klucz. Możesz jednak wyłączyć tę animację dynamicznie na podstawie określonych warunków, takich jak kierunek nawigacji lub bieżący stan interfejsu.
Aby określić, czy przejście elementu wspólnego ma nastąpić, możesz dostosować SharedContentConfig przekazywany do rememberSharedContentState(). Właściwość isEnabled określa, czy udostępniony element jest aktywny.
Poniższy przykład pokazuje, jak zdefiniować konfigurację, która włącza udostępnione przejście tylko podczas nawigacji między określonymi ekranami (np. tylko z A do B), a wyłącza je w przypadku innych ekranów.
SharedTransitionLayout { val transition = updateTransition(currentState) transition.AnimatedContent { targetState -> // Create the configuration that depends on state changing. fun animationConfig() : SharedTransitionScope.SharedContentConfig { return object : SharedTransitionScope.SharedContentConfig { override val SharedTransitionScope.SharedContentState.isEnabled: Boolean // For this example, we only enable the transition in one direction // from A -> B and not the other way around. get() = transition.currentState == "A" && transition.targetState == "B" } } when (targetState) { "A" -> Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } "B" -> { Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } } } } }
Domyślnie, jeśli udostępniony element zostanie wyłączony podczas trwającej animacji, nadal dokończy bieżącą animację, aby zapobiec przypadkowemu usunięciu animacji w trakcie jej trwania. Jeśli musisz usunąć element podczas animacji, możesz zastąpić shouldKeepEnabledForOngoingAnimation w interfejsie SharedContentConfig, aby zwracać wartość false.
Przejdź do ostatecznego układu
Domyślnie podczas przechodzenia między 2 układami rozmiar układu jest animowany między stanem początkowym a końcowym. Może to być niepożądane zachowanie podczas animowania treści takich jak tekst.
W przykładzie poniżej tekst opisu „Lorem Ipsum” pojawia się na ekranie na 2 różne sposoby. W pierwszym przykładzie tekst jest ponownie formatowany podczas wprowadzania, gdy kontener się powiększa. W drugim przykładzie tekst nie jest zawijany, gdy się powiększa. Dodanie Modifier.skipToLookaheadSize() zapobiega ponownemu przepływowi, gdy element się powiększa.
Nie |
|
|---|---|
Klipy i nakładki
Aby elementy udostępnione mogły być udostępniane między różnymi elementami kompozycyjnymi, renderowanie elementu kompozycyjnego jest podnoszone do nakładki warstwy, gdy rozpoczyna się przejście do jego odpowiednika w miejscu docelowym. Dzięki temu wyjdzie poza granice elementu nadrzędnego i jego przekształcenia warstwy (np. alfa i skalę).
Będzie on renderowany nad innymi nieudostępnionymi elementami interfejsu. Po zakończeniu przejścia element zostanie przeniesiony z nakładki do własnego elementu DrawScope.
Aby przyciąć udostępniony element do kształtu, użyj standardowej funkcji Modifier.clip(). Umieść go po sharedElement():
Image( painter = painterResource(id = R.drawable.cupcake), contentDescription = "Cupcake", modifier = Modifier .size(100.dp) .sharedElement( rememberSharedContentState(key = "image"), animatedVisibilityScope = this@AnimatedContent ) .clip(RoundedCornerShape(16.dp)), contentScale = ContentScale.Crop )
Jeśli chcesz mieć pewność, że udostępniony element nigdy nie będzie renderowany poza kontenerem nadrzędnym, możesz ustawić clipInOverlayDuringTransition na sharedElement(). Domyślnie w przypadku zagnieżdżonych granic udostępnionych clipInOverlayDuringTransition używa ścieżki przycinania z nadrzędnego elementu sharedBounds().
Aby podczas przejścia elementu udostępnionego określone elementy interfejsu, takie jak dolny pasek lub pływający przycisk polecenia, zawsze znajdowały się na górze, użyj Modifier.renderInSharedTransitionScopeOverlay(). Domyślnie ten modyfikator utrzymuje zawartość w nakładce, gdy aktywne jest udostępnione przejście.
Na przykład w aplikacji Jetsnack element BottomAppBar musi być umieszczony na elemencie udostępnionym, dopóki ekran nie będzie widoczny. Dodanie modyfikatora do funkcji kompozycyjnej
powoduje, że pozostaje ona podniesiona.
Bez: |
Z: |
|---|---|
Możesz chcieć, aby nieudostępniany element kompozycyjny był animowany, a także pozostawał na innych elementach kompozycyjnych przed przejściem. W takich przypadkach użyj renderInSharedTransitionScopeOverlay().animateEnterExit(), aby animować element kompozycyjny podczas przejścia elementu udostępnionego:
JetsnackBottomBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f, ) .animateEnterExit( enter = fadeIn() + slideInVertically { it }, exit = fadeOut() + slideOutVertically { it } ) )
W rzadkich przypadkach, gdy nie chcesz, aby udostępniony element był renderowany w nakładce, możesz ustawić renderInOverlayDuringTransition w sharedElement() na wartość false.
Powiadamianie układów równorzędnych o zmianach rozmiaru udostępnionego elementu
Domyślnie sharedBounds() i sharedElement() nie powiadamiają kontenera nadrzędnego o zmianach rozmiaru podczas przejść układu.
Aby zmiany rozmiaru były propagowane do kontenera nadrzędnego podczas przejścia, zmień parametr placeholderSize na PlaceholderSize.AnimatedSize. Spowoduje to powiększenie lub zmniejszenie elementu. Wszystkie pozostałe elementy układu dostosowują się do zmiany.
|
(Zwróć uwagę, jak inne elementy na liście przesuwają się w dół w odpowiedzi na powiększenie jednego elementu). |
|---|---|