Personaliza la transición de elementos compartidos

Para personalizar la forma en que se ejecuta la animación de transición de elementos compartidos, hay algunos parámetros que se pueden usar para cambiar la forma en que se transfieren los elementos compartidos.

Especificación de animación

Para cambiar la especificación de animación que se usa para el movimiento de tamaño y posición, puedes especificar un parámetro boundsTransform diferente en Modifier.sharedElement(). Esto proporciona la posición inicial de Rect y la posición de destino de Rect.

Por ejemplo, para que el texto del ejemplo anterior se mueva con un movimiento de arco, especifica el parámetro boundsTransform para usar una especificación 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
    )
)

Puedes usar cualquier AnimationSpec. En este ejemplo, se usa una especificación keyframes.

Figura 1: Ejemplo que muestra diferentes parámetros boundsTransform

Modo de cambio de tamaño

Cuando animas entre dos límites compartidos, puedes establecer el parámetro resizeMode en RemeasureToBounds o ScaleToBounds. Este parámetro determina cómo el elemento compartido realiza la transición entre los dos estados. ScaleToBounds primero mide el diseño secundario con las restricciones de visualización anticipada (o objetivo). Luego, el diseño estable del elemento secundario se ajusta para que se ajuste a los límites compartidos. Se puede considerar que ScaleToBounds es una "escala gráfica" entre los estados.

Mientras que RemeasureToBounds vuelve a medir y a diseñar el diseño secundario de sharedBounds con restricciones fijas animadas según el tamaño objetivo. La re-medición se activa por el cambio de tamaño de los límites, que podría ser cada fotograma.

Para los elementos componibles Text, se recomienda ScaleToBounds, ya que evitará el rediseño y el reflujo de texto en diferentes líneas. Para límites que tienen diferentes relaciones de aspecto y si deseas una continuidad fluida entre los dos elementos compartidos, se recomienda RemeasureToBounds.

La diferencia entre los dos modos de cambio de tamaño se puede ver en los siguientes ejemplos:

ScaleToBounds

RemeasureToBounds

Omitir al diseño final

De forma predeterminada, cuando se realiza una transición entre dos diseños, el tamaño del diseño se anima entre su estado inicial y final. Este puede ser un comportamiento no deseado cuando se anima contenido, como texto.

En el siguiente ejemplo, se ilustra el texto de descripción "Lorem Ipsum" que ingresa a la pantalla de dos maneras diferentes. En el primer ejemplo, el texto se vuelve a ajustar a medida que entra a medida que el contenedor aumenta de tamaño. En el segundo ejemplo, el texto no se vuelve a ajustar a medida que crece. Agregar Modifier.skipToLookaheadSize() evita el reflujo a medida que crece.

Sin Modifier.skipToLookahead(): Observa cómo se vuelve a fluir el texto "Lorem Ipsum".

Modifier.skipToLookahead(): Observa que el texto "Lorem Ipsum" mantiene su estado final al comienzo de la animación.

Clips y superposiciones

Un concepto importante cuando se crean elementos compartidos en Compose es que, para que se compartan entre diferentes elementos componibles, la renderización del elemento componible se eleva a una superposición de capas cuando se inicia la transición a su coincidencia en el destino. El efecto de esto es que escapará de los límites del elemento superior y de sus transformaciones de capas (por ejemplo, la alfa y la escala).

Se renderizará sobre otros elementos de la IU no compartidos. Una vez que finalice la transición, el elemento se soltará de la superposición a su propio DrawScope.

Para recortar un elemento compartido en una forma, usa la función Modifier.clip() estándar. Colócalo después de 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 necesitas asegurarte de que un elemento compartido nunca se renderice fuera de un contenedor superior, puedes configurar clipInOverlayDuringTransition en sharedElement(). De forma predeterminada, para los límites compartidos anidados, clipInOverlayDuringTransition usa la ruta de acceso del clip del sharedBounds() superior.

Para admitir que los elementos específicos de la IU, como una barra inferior o un botón de acción flotante, siempre estén en la parte superior durante una transición de elementos compartidos, usa Modifier.renderInSharedTransitionScopeOverlay(). De forma predeterminada, este modificador mantiene el contenido en la superposición durante el tiempo en que la transición compartida está activa.

Por ejemplo, en Jetsnack, el BottomAppBar se debe colocar sobre el elemento compartido hasta que la pantalla no sea visible. Agregar el modificador al elemento componible lo mantiene elevado.

Sin Modifier.renderInSharedTransitionScopeOverlay()

Con Modifier.renderInSharedTransitionScopeOverlay()

A veces, es posible que desees que el elemento componible no compartido se analice y permanezca sobre los otros elementos componibles antes de la transición. En esos casos, usa renderInSharedTransitionScopeOverlay().animateEnterExit() para animar el elemento componible mientras se ejecuta la transición de elementos compartidos:

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

Figura 2: La barra inferior de la app se desliza hacia adentro y hacia afuera a medida que la animación hace la transición

En el caso poco frecuente de que quieras que tu elemento compartido no se renderice en una superposición, puedes establecer el renderInOverlayDuringTransition en sharedElement() como falso.

Notifica a los diseños hermanos sobre los cambios en el tamaño del elemento compartido

De forma predeterminada, sharedBounds() y sharedElement() no notifican al contenedor superior ningún cambio de tamaño a medida que el diseño realiza transiciones.

Para propagar los cambios de tamaño al contenedor superior a medida que realiza la transición, cambia el parámetro placeHolderSize a PlaceHolderSize.animatedSize. De esta manera, el elemento se agranda o se reduce. Todos los demás elementos del diseño responden al cambio.

PlaceholderSize.contentSize (predeterminado)

PlaceholderSize.animatedSize

(observa cómo los otros elementos de la lista se mueven hacia abajo en respuesta al crecimiento de un elemento)