Übergang zu gemeinsam genutzten Elementen anpassen

Es gibt einige Parameter, mit denen Sie anpassen können, wie die Animation für den Übergang des gemeinsamen Elements abläuft.

Animationsspezifikation

Wenn Sie die für die Größen- und Positionsbewegung verwendete Animationsspezifikation ändern möchten, können Sie in Modifier.sharedElement() einen anderen boundsTransform-Parameter angeben. So werden die ursprüngliche Rect-Position und die Ziel-Rect-Position angegeben.

Wenn Sie beispielsweise möchten, dass sich der Text im vorherigen Beispiel in einem Bogen bewegt, geben Sie für den Parameter boundsTransform eine keyframes-Spezifikation an:

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
    )
)

Sie können einen beliebigen AnimationSpec verwenden. In diesem Beispiel wird eine keyframes-Spezifikation verwendet.

Abbildung 1. Beispiel für verschiedene boundsTransform-Parameter

Modus zum Ändern der Größe

Wenn Sie eine Animation zwischen zwei gemeinsam genutzten Grenzen erstellen, können Sie den Parameter resizeMode auf RemeasureToBounds oder ScaleToBounds festlegen. Mit diesem Parameter wird festgelegt, wie die Übergänge des freigegebenen Elements zwischen den beiden Status erfolgen. ScaleToBounds first misst das untergeordnete Layout mit den Lookahead- oder Zielbeschränkungen. Das stabile Layout des Kindes wird dann so skaliert, dass es in die gemeinsamen Grenzen passt. ScaleToBounds kann als „grafische Skala“ zwischen den Status betrachtet werden.

Im Gegensatz dazu werden bei RemeasureToBounds das untergeordnete Layout von sharedBounds mit animierten festen Einschränkungen basierend auf der Zielgröße neu gemessen und neu angeordnet. Die erneute Messung wird durch die Änderung der Begrenzungsgröße ausgelöst, was potenziell in jedem Frame geschehen kann.

Für Text-Composables wird ScaleToBounds empfohlen, da dadurch ein erneutes Layout und Umfließen von Text in verschiedene Zeilen vermieden wird. RemeasureToBounds wird für Grenzen mit unterschiedlichen Seitenverhältnissen empfohlen und wenn Sie einen fließenden Übergang zwischen den beiden gemeinsamen Elementen wünschen.

Der Unterschied zwischen den beiden Größenanpassungsmodi ist in den folgenden Beispielen zu sehen:

ScaleToBounds

RemeasureToBounds

Zum endgültigen Layout springen

Standardmäßig wird die Layoutgröße beim Übergang zwischen zwei Layouts zwischen dem Start- und dem Endstatus animiert. Das kann unerwünscht sein, wenn Sie Inhalte wie Text animieren.

Im folgenden Beispiel wird der Beschreibungstext „Lorem Ipsum“ auf zwei verschiedene Arten auf dem Bildschirm angezeigt. Im ersten Beispiel wird der Text umgebrochen, wenn der Container größer wird. Im zweiten Beispiel wird der Text nicht umgebrochen, wenn er länger wird. Durch das Hinzufügen von Modifier.skipToLookaheadSize() wird verhindert, dass sich das Layout ändert, wenn die Anzeige größer wird.

Kein Modifier.skipToLookahead() – der „Lorem Ipsum“-Text wird umgebrochen.

Modifier.skipToLookahead() – Der Text „Lorem Ipsum“ behält seinen Endzustand am Anfang der Animation bei.

Clips und Overlays

Damit freigegebene Elemente zwischen verschiedenen Composables geteilt werden können, wird das Rendern des Composables in eine Layer-Überlagerung verschoben, wenn der Übergang zum entsprechenden Element im Ziel beginnt. Dadurch wird die Ebene außerhalb der Grenzen des übergeordneten Elements und seiner Ebenentransformationen (z. B. Alpha und Skalierung) gerendert.

Es wird über anderen nicht freigegebenen UI-Elementen gerendert. Nach Abschluss der Übergangsphase wird das Element aus dem Overlay in einen eigenen DrawScope verschoben.

Wenn Sie ein gemeinsames Element auf eine Form zuschneiden möchten, verwenden Sie die Standardfunktion Modifier.clip(). Platziere es nach dem 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
)

Wenn Sie sicherstellen müssen, dass ein freigegebenes Element nie außerhalb eines übergeordneten Containers gerendert wird, können Sie clipInOverlayDuringTransition für sharedElement() festlegen. Standardmäßig wird für verschachtelte freigegebene Grenzen in clipInOverlayDuringTransition der Beschneidungspfad des übergeordneten sharedBounds() verwendet.

Wenn bestimmte UI-Elemente wie eine untere Leiste oder eine Floating Action Button während einer Shared Element-Übergangsanimation immer oben bleiben sollen, verwenden Sie Modifier.renderInSharedTransitionScopeOverlay(). Standardmäßig wird mit diesem Modifikator der Inhalt im Overlay während der aktiven gemeinsamen Übergangszeit beibehalten.

In Jetsnack muss das BottomAppBar beispielsweise über dem freigegebenen Element platziert werden, bis der Bildschirm nicht mehr sichtbar ist. Wenn Sie den Modifier dem Composable hinzufügen, bleibt er erhöht.

Ohne Modifier.renderInSharedTransitionScopeOverlay()

Mit Modifier.renderInSharedTransitionScopeOverlay()

Möglicherweise soll das nicht freigegebene Composable nicht nur animiert werden, sondern auch vor dem Übergang über den anderen Composables bleiben. Verwenden Sie in solchen Fällen renderInSharedTransitionScopeOverlay().animateEnterExit(), um die Composable-Funktion während der Übergangsanimation des gemeinsamen Elements zu animieren:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

Abbildung 2. Die untere App-Leiste wird ein- und ausgeblendet, während die Animation übergeht.

In seltenen Fällen möchten Sie vielleicht nicht, dass Ihr gemeinsames Element in einem Overlay gerendert wird. In diesem Fall können Sie renderInOverlayDuringTransition für sharedElement() auf „false“ setzen.

Benachrichtigen von untergeordneten Layouts über Änderungen an der Größe des gemeinsam genutzten Elements

Standardmäßig werden sharedBounds() und sharedElement() nicht über Größenänderungen des übergeordneten Containers während des Layoutübergangs informiert.

Wenn Größenänderungen während der Übergangsphase auf den übergeordneten Container übertragen werden sollen, ändern Sie den Parameter placeHolderSize in PlaceHolderSize.animatedSize. Dadurch wird das Element vergrößert oder verkleinert. Alle anderen Elemente im Layout reagieren auf die Änderung.

PlaceholderSize.contentSize (Standard)

PlaceholderSize.animatedSize

Beachten Sie, wie die anderen Elemente in der Liste nach unten verschoben werden, wenn ein Element größer wird.