Personnaliser la transition d'élément partagé

Pour personnaliser l'exécution de l'animation de transition d'élément partagé, vous pouvez utiliser quelques paramètres pour modifier la façon dont les éléments partagés effectuent la transition.

Spécification d'animation

Pour modifier la spécification d'animation utilisée pour le mouvement de la taille et de la position, vous pouvez spécifier un paramètre boundsTransform différent sur Modifier.sharedElement(). Cela fournit la position Rect initiale et la position Rect cible.

Par exemple, pour que le texte de l'exemple précédent se déplace en arc de cercle, spécifiez le paramètre boundsTransform pour utiliser une spécification 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
    )
)

Vous pouvez utiliser n'importe quel AnimationSpec. Cet exemple utilise une spécification keyframes.

Figure 1. Exemple montrant différents paramètres boundsTransform

Mode Redimensionner

Lorsque vous animez entre deux limites partagées, vous pouvez définir le paramètre resizeMode sur RemeasureToBounds ou ScaleToBounds. Ce paramètre détermine la façon dont l'élément partagé passe d'un état à l'autre. ScaleToBounds mesure d'abord la mise en page enfant avec les contraintes de prévisualisation (ou cibles). La mise en page stable de l'enfant est ensuite mise à l'échelle pour s'adapter aux limites partagées. ScaleToBounds peut être considéré comme une "échelle graphique" entre les états.

En revanche, RemeasureToBounds remesure et remet en page la mise en page enfant de sharedBounds avec des contraintes fixes animées basées sur la taille cible. La nouvelle mesure est déclenchée par le changement de taille des limites, ce qui peut potentiellement se produire à chaque frame.

Pour les composables Text, ScaleToBounds est recommandé, car il évite la réorganisation et le réagencement du texte sur différentes lignes. RemeasureToBounds est recommandé pour les limites qui ont des proportions différentes et si vous souhaitez une continuité fluide entre les deux éléments partagés.

La différence entre les deux modes de redimensionnement est visible dans les exemples suivants :

ScaleToBounds

RemeasureToBounds

Passer à la mise en page finale

Par défaut, lors de la transition entre deux mises en page, la taille de la mise en page est animée entre son état de début et son état final. Ce comportement peut être indésirable lors de l'animation de contenu tel que du texte.

L'exemple suivant illustre le texte de description "Lorem Ipsum" entrant dans l'écran de deux manières différentes. Dans le premier exemple, le texte est redistribué à mesure qu'il entre dans le conteneur et que celui-ci grandit. Dans le deuxième exemple, le texte ne se réorganise pas à mesure qu'il s'allonge. L'ajout de Modifier.skipToLookaheadSize() empêche le réagencement du contenu à mesure qu'il se développe.

Non Modifier.skipToLookahead() : remarquez le réagencement du texte "Lorem Ipsum"

Modifier.skipToLookahead() : notez que le texte "Lorem Ipsum" conserve son état final au début de l'animation.

Extraits et superpositions

Pour que les éléments partagés le soient entre différents composables, le rendu du composable est élevé dans une superposition de calques lorsque la transition est lancée vers sa correspondance dans la destination. L'effet de cette propriété est que l'élément échappe aux limites du parent et à ses transformations de calque (par exemple, l'alpha et l'échelle).

Il s'affichera au-dessus des autres éléments d'interface utilisateur non partagés. Une fois la transition terminée, l'élément est supprimé de la superposition et placé dans son propre DrawScope.

Pour découper un élément partagé selon une forme, utilisez la fonction standard Modifier.clip(). Placez-le après le 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
)

Si vous devez vous assurer qu'un élément partagé ne s'affiche jamais en dehors d'un conteneur parent, vous pouvez définir clipInOverlayDuringTransition sur sharedElement(). Par défaut, pour les limites partagées imbriquées, clipInOverlayDuringTransition utilise le chemin de découpage du sharedBounds() parent.

Pour que certains éléments d'UI, comme une barre inférieure ou un bouton d'action flottant, restent toujours au premier plan lors d'une transition d'élément partagé, utilisez Modifier.renderInSharedTransitionScopeOverlay(). Par défaut, ce modificateur conserve le contenu dans le calque pendant la durée d'activation de la transition partagée.

Par exemple, dans Jetsnack, le BottomAppBar doit être placé au-dessus de l'élément partagé jusqu'à ce que l'écran ne soit plus visible. L'ajout du modificateur au composable le maintient en hauteur.

Sans Modifier.renderInSharedTransitionScopeOverlay()

Avec Modifier.renderInSharedTransitionScopeOverlay()

Vous pouvez faire en sorte que votre composable non partagé s'anime et reste au-dessus des autres composables avant la transition. Dans ce cas, utilisez renderInSharedTransitionScopeOverlay().animateEnterExit() pour animer le composable lors de l'exécution de la transition d'élément partagé :

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

Figure 2. La barre d'application inférieure glisse vers l'intérieur et l'extérieur lors de la transition de l'animation.

Dans le cas rare où vous ne souhaitez pas que votre élément partagé s'affiche dans une superposition, vous pouvez définir renderInOverlayDuringTransition sur sharedElement() sur "false".

Notifier les mises en page frères des modifications apportées à la taille des éléments partagés

Par défaut, sharedBounds() et sharedElement() n'informent pas le conteneur parent des changements de taille lors des transitions de mise en page.

Pour propager les changements de taille au conteneur parent lors de la transition, remplacez le paramètre placeHolderSize par PlaceHolderSize.animatedSize. L'élément grandit ou rétrécit. Tous les autres éléments de la mise en page répondent à la modification.

PlaceholderSize.contentSize (par défaut)

PlaceholderSize.animatedSize

(Notez comment les autres éléments de la liste se déplacent vers le bas lorsque l'un d'eux s'agrandit.)