Dostosuj przejście elementów wspólnych

Aby dostosować sposób działania animacji przejścia elementu udostępnionego, możesz użyć kilku parametrów, które pozwolą Ci zmienić sposób przejścia elementów udostępnionych.

Specyfikacja animacji

Aby zmienić specyfikację animacji używaną do przesuwania rozmiaru i pozycji, możesz określić inny parametr boundsTransform w Modifier.sharedElement(). Określa on początkową pozycję Rect i docelową pozycję Rect.

Aby na przykład tekst w poprzednim przykładzie poruszał się po łuku ruchu, określ parametr boundsTransform, aby używać 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ć dowolnej AnimationSpec. Ten przykład używa specyfikacji keyframes.

Rysunek 1. Przykład pokazujący różne parametry boundsTransform

Tryb zmiany rozmiaru

Podczas animowania między 2 wspólnymi granicami możesz ustawić parametr resizeMode na RemeasureToBounds lub ScaleToBounds. Ten parametr określa, jak element udostępniony przechodzi między 2 stanami. ScaleToBounds najpierw mierzy układ podrzędny z ograniczeniami lookahead (lub docelowymi). Następnie stabilny układ podrzędny jest skalowany tak, aby pasował do wspólnych granic. ScaleToBounds można traktować jako „skalę graficzną” między stanami.

Z kolei RemeasureToBounds ponownie mierzy i ponownie układa układ podrzędny sharedBounds z animowanymi stałymi ograniczeniami na podstawie rozmiaru docelowego. Ponowne pomiary są wywoływane przez zmianę rozmiaru granic, która może występować w każdej klatce.

W przypadku elementów kompozycyjnych Text zalecamy używanie ScaleToBounds, ponieważ pozwala to uniknąć ponownego układania i przepływu tekstu do innych wierszy. RemeasureToBounds zalecamy w przypadku granic o różnych proporcjach oraz jeśli chcesz uzyskać płynną ciągłość między 2 elementami udostępnionymi.

Różnicę między 2 trybami zmiany rozmiaru można zobaczyć w przykładach poniżej:

ScaleToBounds

RemeasureToBounds

Dynamiczne włączanie i wyłączanie elementów udostępnionych

Domyślnie 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 ma wystąpić przejście elementu udostępnionego, możesz dostosować SharedContentConfig przekazywany do rememberSharedContentState(). Właściwość isEnabled określa, czy element udostępniony jest aktywny.

Poniższy przykład pokazuje, jak zdefiniować konfigurację, która włącza przejście udostępnione tylko podczas nawigacji między określonymi ekranami (np. tylko z ekranu A na ekran B), a wyłącza je w innych przypadkach.

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 element udostępniony zostanie wyłączony podczas trwającej animacji, animacja zostanie dokończona, aby zapobiec przypadkowemu usunięciu animacji w trakcie. Jeśli musisz usunąć element podczas trwania animacji, możesz zastąpić shouldKeepEnabledForOngoingAnimation w interfejsie SharedContentConfig, aby zwracać wartość false.

Przejdź do układu końcowego

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.

Poniższy przykład pokazuje, jak tekst opisu „Lorem Ipsum” pojawia się na ekranie na 2 różne sposoby. W pierwszym przykładzie tekst jest ponownie układany, gdy kontener zwiększa swój rozmiar. W drugim przykładzie tekst nie jest ponownie układany, gdy kontener zwiększa swój rozmiar. Dodanie Modifier.skipToLookaheadSize() zapobiega ponownemu układaniu tekstu podczas zwiększania rozmiaru.

Brak Modifier.skipToLookaheadSize() – zwróć uwagę na ponowne układanie tekstu „Lorem Ipsum”

Modifier.skipToLookaheadSize() – zwróć uwagę, że tekst „Lorem Ipsum” zachowuje swój stan końcowy na początku animacji

Przycinanie i nakładki

Aby elementy udostępnione mogły być udostępniane między różnymi elementami kompozycyjnymi, renderowanie elementu kompozycyjnego jest przenoszone do nakładki warstwy, gdy rozpoczyna się przejście do jego odpowiednika w miejscu docelowym. Powoduje to, że element kompozycyjny wykracza poza granice elementu nadrzędnego i jego transformacje warstwy (np. alfa i skala).

Będzie renderowany na wierzchu innych nieudostępnionych elementów interfejsu. Po zakończeniu przejścia element zostanie usunięty z nakładki do własnego DrawScope.

Aby przyciąć element udostępniony do kształtu, użyj standardowej funkcji Modifier.clip(). Umieść ją 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 musisz mieć pewność, że element udostępniony nigdy nie będzie renderowany poza kontenerem nadrzędnym, możesz ustawić clipInOverlayDuringTransition w sharedElement(). Domyślnie w przypadku zagnieżdżonych granic udostępnionych clipInOverlayDuringTransition używa ścieżki przycinania z elementu nadrzędnego sharedBounds().

Aby podczas przejścia elementu udostępnionego zachować na wierzchu określone elementy interfejsu, takie jak pasek u dołu ekranu lub pływający przycisk działania, zawsze na wierzchu, użyj Modifier.renderInSharedTransitionScopeOverlay(). Domyślnie ten modyfikator utrzymuje treść w nakładce, gdy przejście udostępnione jest aktywne.

Na przykład w Jetsnack BottomAppBar musi być umieszczony na wierzchu elementu udostępnionego, dopóki ekran nie będzie widoczny. Dodanie modyfikatora do elementu kompozycyjnego powoduje, że pozostaje on na wierzchu.

Bez Modifier.renderInSharedTransitionScopeOverlay()

Z Modifier.renderInSharedTransitionScopeOverlay()

Możesz chcieć, aby element kompozycyjny nieudostępniony również był animowany i pozostawał na wierzchu innych elementów 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
            }
        )
)

Rysunek 2. Pasek aplikacji u dołu ekranu wysuwa się i chowa podczas przejścia animacji.

W rzadkich przypadkach, gdy nie chcesz, aby element udostępniony był renderowany w nakładce, możesz ustawić renderInOverlayDuringTransition w sharedElement() na false.

Powiadamianie układów równorzędnych o zmianach rozmiaru elementu udostępnionego

Domyślnie sharedBounds() i sharedElement() nie powiadamiają kontenera nadrzędnego o żadnych zmianach rozmiaru podczas przejścia układu.

Aby propagować zmiany rozmiaru do kontenera nadrzędnego podczas przejścia, zmień parametr placeholderSize na PlaceholderSize.AnimatedSize. Spowoduje to, że element będzie się powiększał lub zmniejszał. Wszystkie inne elementy w układzie reagują na tę zmianę.

PlaceholderSize.ContentSize (domyślnie)

PlaceholderSize.AnimatedSize

(Zwróć uwagę, jak inne elementy na liście przesuwają się w dół w odpowiedzi na powiększenie jednego elementu)