Personnaliser la transition d'élément partagé

Pour personnaliser le mode d'exécution de l'animation de transition d'éléments partagés, vous pouvez modifier la façon dont les éléments partagés s'exécutent à l'aide de certains paramètres.

Spécification de l'animation

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

Par exemple, pour que le texte de l'exemple précédent se déplace avec un arc, spécifiez le paramètre boundsTransform afin d'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 illustrant différents paramètres boundsTransform

Mode Redimensionnement

Lors d'une animation entre deux limites partagées, vous pouvez définir le paramètre resizeMode sur RemeasureToBounds ou ScaleToBounds. Ce paramètre détermine la manière dont l'élément partagé passe d'un état à un autre. ScaleToBounds mesure d'abord la mise en page enfant avec les contraintes d'aperçu (ou cible). La mise en page stable de l'enfant est ensuite ajustée pour tenir dans les limites partagées. ScaleToBounds peut être considéré comme une "échelle graphique" entre les états.

Tandis que RemeasureToBounds mesure à nouveau et remet en page la mise en page enfant de sharedBounds avec des contraintes fixes animées en fonction de la taille cible. La nouvelle mesure est déclenchée par le changement de taille des limites, qui peut potentiellement concerner chaque image.

Pour les composables Text, ScaleToBounds est recommandé, car il évite la remise en page et l'ajustement du texte sur différentes lignes. Pour les limites qui présentent des proportions différentes, et si vous souhaitez assurer une continuité fluide entre les deux éléments partagés, nous vous recommandons d'utiliser RemeasureToBounds.

La différence entre les deux modes de redimensionnement est illustrée 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 s'anime 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" qui s'affiche à l'écran de deux manières différentes. Dans le premier exemple, le texte s'ajuste à mesure qu'il entre à mesure que la taille du conteneur augmente. Dans le deuxième exemple, le texte n'est pas ajusté à mesure qu'il s'agrandit. L'ajout de Modifier.skipToLookaheadSize() empêche l'ajustement de la mise en page à mesure qu'il augmente.

Pas de Modifier.skipToLookahead(). Notez que le texte "Lorem Ipsum" est remanié.

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

Clips et superpositions

Lorsque vous créez des éléments partagés dans Compose, il est important de noter que, pour qu'ils puissent être partagés entre différents composables, le rendu du composable est surélevé dans une superposition de couche lorsque la transition commence à correspondre à la destination. Cela permet d'échapper aux limites du parent et aux transformations de la couche (par exemple, la valeur alpha et l'échelle).

Il s'affiche 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 vers son propre DrawScope.

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

Pour 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, clipInOverlayDuringTransition utilise le chemin de découpe du sharedBounds() parent pour les limites partagées imbriquées.

Pour permettre de conserver des éléments d'interface utilisateur spécifiques, tels qu'une barre inférieure ou un bouton d'action flottant, toujours en haut lors d'une transition d'éléments partagés, utilisez Modifier.renderInSharedTransitionScopeOverlay(). Par défaut, ce modificateur conserve le contenu de la superposition pendant que la transition partagée est active.

Par exemple, dans Jetsnack, 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 permet de le maintenir élevé.

Sans Modifier.renderInSharedTransitionScopeOverlay()

Avec Modifier.renderInSharedTransitionScopeOverlay()

Parfois, vous pouvez souhaiter que votre composable non partagé s'anime et qu'il reste au-dessus des autres composables avant la transition. Dans ce cas, utilisez renderInSharedTransitionScopeOverlay().animateEnterExit() pour animer le composable lorsque la transition d'éléments partagés s'exécute:

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

Figure 2 : Barre d'application inférieure glissante vers l'intérieur et l'extérieur lors des transitions de l'animation

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

Informer les mises en page sœurs des modifications de la taille de l'élément partagé

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 dans le conteneur parent lors de sa transition, définissez le paramètre placeHolderSize sur PlaceHolderSize.animatedSize. Cela entraîne l'augmentation ou la réduction de l'élément. Tous les autres éléments de la mise en page répondent à cette modification.

PlaceholderSize.contentSize (par défaut)

PlaceholderSize.animatedSize

(Vous remarquerez que les autres éléments de la liste descendent en réponse à la croissance de l'élément.)