Anima un solo valor con animate*AsState
Las funciones animate*AsState
son las APIs de animación más simples en Compose para animar un solo valor. Solo debes proporcionar el valor objetivo (o valor final), y la API comienza la animación desde el valor actual hasta el especificado.
A continuación, se muestra un ejemplo de animación de alfa con esta API. Con solo unir el valor objetivo en animateFloatAsState
, el valor alfa ahora es un valor de animación entre los valores proporcionados (1f
o 0.5f
en este caso).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Ten en cuenta que no necesitas crear una instancia de ninguna clase de animación ni procesar la interrupción. De forma interna, se creará un objeto de animación (es decir, una instancia de Animatable
) y se lo recordará en el sitio que realiza la llamada, con el primer valor objetivo como valor inicial. A partir de ese momento, cada vez que proporciones un valor objetivo diferente a este elemento componible, se iniciará automáticamente una animación con ese valor. Si ya hay una animación en curso, esta comienza desde su valor (y velocidad) actual, y se anima al valor objetivo. Durante la animación, este elemento componible se vuelve a componer y muestra un valor actualizado de la animación en cada fotograma.
Desde el primer momento, Compose brinda funciones animate*AsState
para Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
y IntSize
. Puedes agregar compatibilidad con otros tipos de datos si proporcionas un TwoWayConverter
a animateValueAsState
que tome un tipo genérico.
Puedes personalizar las especificaciones de la animación si proporcionas un AnimationSpec
.
Consulta AnimationSpec para obtener más información.
Cómo animar varias propiedades simultáneamente con una transición
Transition
administra una o más animaciones como elementos secundarios, y las ejecuta de forma simultánea entre varios estados.
Los estados pueden ser de cualquier tipo de datos. En muchos casos, puedes usar un tipo enum
personalizado para garantizar la seguridad del tipo, como en este ejemplo:
enum class BoxState { Collapsed, Expanded }
updateTransition
crea y recuerda una instancia de Transition
, y actualiza su estado.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Luego, puedes usar una de las funciones de extensión animate*
para definir una animación secundaria en esta transición. Especifica los valores objetivo para cada uno de los estados.
Estas funciones animate*
muestran un valor de animación que se actualiza con cada fotograma durante la animación cuando el estado de transición se actualiza con updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
De manera opcional, puedes pasar un parámetro transitionSpec
a fin de especificar un AnimationSpec
diferente para cada una de las combinaciones de cambios de estado de transición. Consulta AnimationSpec para obtener más información.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
Una vez que haya una transición en el estado objetivo, Transition.currentState
será la misma que en Transition.targetState
. Esto se puede usar como indicador para comprobar si finalizó la transición.
En ocasiones, queremos tener un estado inicial diferente del primer estado objetivo. Podemos usar updateTransition
con MutableTransitionState
para lograrlo. Por ejemplo, nos permite iniciar la animación en cuanto el código entra en conflicto.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
En el caso de una transición más compleja que involucra varias funciones que admiten composición, puedes usar createChildTransition
para crear una transición secundaria. Esta técnica es útil para separar los problemas entre varios subcomponentes en un elemento complejo que admite composición. La transición superior tendrá en cuenta todos los valores de animación en las transiciones secundarias.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Usa la transición con AnimatedVisibility
y AnimatedContent
AnimatedVisibility
y AnimatedContent
están disponibles como funciones de extensión de Transition
. targetState
para Transition.AnimatedVisibility
y Transition.AnimatedContent
se deriva de Transition
y activa la transición de entrada y salida en los casos necesarios cuando cambia el targetState
de Transition
. Estas funciones de extensión permiten que todas las animaciones de entrada, salida y sizeTransform que, de lo contrario, serían internas para AnimatedVisibility
o AnimatedContent
se eleven a Transition
.
Con estas funciones de extensión, el cambio de estado de AnimatedVisibility
o AnimatedContent
se puede observar desde el exterior. En lugar de un parámetro booleano visible
, esta versión de AnimatedVisibility
toma una lambda que convierte el estado objetivo de la transición superior en un valor booleano.
Consulta AnimatedVisibility y AnimatedContent para obtener más detalles.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Encapsula una transición y hazla reutilizable
Para casos de uso simples, definir las animaciones de transición en el mismo elemento componible que tu IU es una opción perfectamente válida. Sin embargo, si trabajas en un componente complejo con una serie de valores animados, es posible que quieras separar la implementación de la animación de la IU del elemento componible.
Para hacerlo, crea una clase que contenga todos los valores de animación y una función "update" que muestre una instancia de esa clase. La implementación de la transición se puede extraer en la nueva función separada. Este patrón es útil cuando hay una necesidad de centralizar la lógica de animación o hacer que las animaciones complejas se puedan volver a usar.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Crea una animación que se repita infinitamente con rememberInfiniteTransition
InfiniteTransition
contiene una o más animaciones secundarias, como Transition
, pero las animaciones comienzan a ejecutarse apenas entran en la composición y no se detienen, a menos que se las quite. Puedes crear una instancia de InfiniteTransition
con rememberInfiniteTransition
. Se pueden agregar animaciones secundarias con animateColor
, animatedFloat
o animatedValue
. También debes especificar un infiniteRepeatable para indicar las especificaciones de la animación.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
APIs de animación de bajo nivel
Todas las API de Animation de alto nivel mencionadas en la sección anterior se compilan sobre la base de las API de Animation de bajo nivel.
Las funciones animate*AsState
son las API más simples que procesan un cambio de valor instantáneo como un valor de animación. Cuenta con el respaldo de Animatable
, que es una API basada en corrutinas para animar un valor único. updateTransition
crea un objeto de transición que puede administrar múltiples valores de animación y ejecutarlos según un cambio de estado. rememberInfiniteTransition
es similar, pero crea una transición infinita que puede administrar varias animaciones que se mantienen en ejecución indefinidamente. Todas estas API son componibles, excepto Animatable
, lo que significa que se pueden crear estas animaciones fuera de la composición.
Todas estas API se basan en la API de Animation
más básica. Si bien la mayoría de las apps no interactuarán directamente con Animation
, algunas de las capacidades de personalización de Animation
están disponibles a través de API de nivel superior. Consulta Cómo personalizar animaciones para obtener más información sobre AnimationVector
y AnimationSpec
.
Animatable
: Animación de un solo valor basada en corrutinas
Animatable
es un contenedor de valor que puede animar el valor a medida que se modifica a través de animateTo
. Esta es la API que crea una copia de seguridad de la implementación de animate*AsState
.
Garantiza una continuación coherente y una exclusividad mutua, lo que significa que el cambio de valor es siempre continuo, y cualquier animación en curso será cancelada.
Muchas funciones de Animatable
, como animateTo
, se proporcionan como funciones de suspensión. Por lo tanto, deben unirse a un alcance de corrutinas apropiado. Por ejemplo, puedes usar el elemento componible LaunchedEffect
para crear un alcance únicamente para la duración del par clave-valor especificado.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
En el ejemplo anterior, creamos y recordamos una instancia de Animatable
con el valor inicial de Color.Gray
. Según el valor de la marca booleana ok
, el color se anima a Color.Green
o Color.Red
. Cualquier cambio posterior al valor booleano inicia la animación en el otro color. Si hay una animación en curso cuando cambia el valor, la animación se cancela, y la animación nueva comienza desde el valor de instantánea actual con la velocidad actual.
Esta es la implementación de animación que crea una copia de seguridad de la API de animate*AsState
mencionada en la sección anterior. En comparación con animate*AsState
, el uso directo de Animatable
brinda un control más preciso sobre varios aspectos. En primer lugar, Animatable
puede tener un valor inicial diferente del primer valor objetivo.
Por ejemplo, el código de ejemplo anterior muestra un cuadro gris al principio, que comienza inmediatamente a animarse en verde o rojo. En segundo lugar, Animatable
proporciona más operaciones sobre el valor del contenido, es decir, snapTo
y animateDecay
. snapTo
establece el valor actual en el valor objetivo de inmediato. Esto es útil cuando la animación en sí no es la única fuente de confianza y debe sincronizarse con otros estados, como eventos táctiles. animateDecay
inicia una animación que se ralentiza a partir de la velocidad determinada. Esto es útil para implementar comportamientos de deslizamiento. Consulta Gestos y animación para obtener más información.
Desde el primer momento, Animatable
admite Float
y Color
, pero cualquier tipo de datos puede usarse si se proporciona un TwoWayConverter
. Consulta AnimationVector para obtener más información.
Puedes personalizar las especificaciones de la animación si proporcionas un AnimationSpec
.
Consulta AnimationSpec para obtener más información.
Animation
: Animación controlada de forma manual
Animation
es la API de Animation de nivel más bajo disponible. Muchas de las animaciones que vimos hasta ahora se basan en Animation. Hay dos subtipos de Animation
: TargetBasedAnimation
y DecayAnimation
.
Solo se debe usar Animation
para controlar manualmente la hora de la animación.
Animation
no tiene estado y no tiene ningún concepto de ciclo de vida. Funciona como motor de cálculo de animación que usan las API de nivel superior.
TargetBasedAnimation
Otras API abarcan la mayoría de los casos de uso, pero utilizar directamente TargetBasedAnimation
te permite controlar por tu cuenta el tiempo de reproducción de la animación. En el siguiente ejemplo, el tiempo de reproducción del objeto TargetAnimation
se controla de forma manual en función de la latencia de fotogramas que proporciona withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
A diferencia de TargetBasedAnimation
, DecayAnimation
no requiere que se proporcione un targetValue
. En su lugar, calcula su targetValue
en función de las condiciones de inicio, establecidas por initialVelocity
y initialValue
, y el elemento DecayAnimationSpec
proporcionado.
Las animaciones de disminución suelen usarse después de un gesto de deslizamiento para ralentizar los elementos hasta que se detengan. La velocidad de animación comienza en el valor establecido por initialVelocityVector
y se ralentiza con el tiempo.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Cómo personalizar animaciones {:#customize-animations}
- Animaciones en Compose
- Modificadores de animación y elementos componibles