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 realiza la transición de 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 Rect y la posición objetivo 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 de boundsTransform

Modo de cambio de tamaño

Cuando se realiza una animación 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. Primero, ScaleToBounds mide el diseño secundario con las restricciones de anticipación (o destino). Luego, el diseño estable del elemento secundario se ajusta para que quepa en los límites compartidos. ScaleToBounds se puede considerar como una "escala gráfica" entre los estados.

En cambio, RemeasureToBounds vuelve a medir y a diseñar el diseño secundario de sharedBounds con restricciones fijas animadas basadas en el tamaño objetivo. La nueva medición se activa con el cambio de tamaño de los límites, que podría ocurrir en cada fotograma.

Para los elementos Text componibles, se recomienda ScaleToBounds, ya que evita el nuevo diseño y el reflujo del texto en diferentes líneas. RemeasureToBounds se recomienda para límites con diferentes relaciones de aspecto y si deseas una continuidad fluida entre los dos elementos compartidos.

En los siguientes ejemplos, se puede observar la diferencia entre los dos modos de cambio de tamaño:

ScaleToBounds

RemeasureToBounds

Saltar 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 comportamiento puede ser no deseado cuando se anima contenido, como texto.

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

Sin Modifier.skipToLookahead(): Observa cómo se reajusta 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

Para que los elementos compartidos 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 se escapará de los límites del elemento principal y de sus transformaciones de capa (por ejemplo, alfa y escala).

Se renderizará sobre otros elementos de la IU no compartidos. Una vez que finalice la transición, el elemento se quitará de la superposición y se colocará en 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 principal, puedes establecer clipInOverlayDuringTransition en sharedElement(). De forma predeterminada, para los límites compartidos anidados, clipInOverlayDuringTransition usa la ruta de recorte del sharedBounds() principal.

Para admitir que elementos específicos de la IU, como una barra inferior o un botón de acción flotante, permanezcan siempre en primer plano 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 debe colocarse sobre el elemento compartido hasta que la pantalla deje de ser visible. Agregar el modificador al elemento componible lo mantiene elevado.

Sin Modifier.renderInSharedTransitionScopeOverlay()

Con Modifier.renderInSharedTransitionScopeOverlay()

Es posible que desees que tu elemento componible no compartido también se anime para desaparecer y permanezca sobre los otros elementos componibles antes de la transición. En esos casos, usa renderInSharedTransitionScopeOverlay().animateEnterExit() para animar el elemento componible a medida que 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 realiza la transición.

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

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

De forma predeterminada, sharedBounds() y sharedElement() no notifican al contenedor principal sobre ningún cambio de tamaño a medida que se realiza la transición del diseño.

Para propagar los cambios de tamaño al contenedor principal a medida que realiza la transición, cambia el parámetro placeHolderSize a PlaceHolderSize.animatedSize. Si lo haces, el elemento crecerá o se encogerá. Todos los demás elementos del diseño responden al cambio.

PlaceholderSize.contentSize (predeterminado)

PlaceholderSize.animatedSize

(Observa cómo los demás elementos de la lista se mueven hacia abajo en respuesta al crecimiento de un elemento).