Animaciones escalonadas
Cómo escribir una animación escalonada (staggered) en Flutter.
Las animaciones escalonadas son un concepto sencillo: los cambios visuales ocurren como una serie de operaciones, en lugar de suceder todos a la vez. La animación puede ser puramente secuencial, con un cambio ocurriendo después del siguiente, o puede superponerse parcial o completamente. También puede tener pausas, donde no ocurran cambios.
Esta guía muestra cómo construir una animación escalonada en Flutter.
El siguiente video demuestra la animación realizada por basic_staggered_animation:
En el video, ves la siguiente animación de un solo widget, que comienza como un cuadrado azul con borde y esquinas ligeramente redondeadas. El cuadrado pasa por cambios en el siguiente orden:
- Aparece gradualmente (fade in)
- Se ensancha
- Se hace más alto mientras se mueve hacia arriba
- Se transforma en un círculo con borde
- Cambia de color a naranja
Después de ejecutarse hacia adelante, la animación se ejecuta en reversa.
Estructura básica de una animación escalonada
#El siguiente diagrama muestra los Intervals utilizados en el ejemplo
basic_staggered_animation.
Podrías notar las siguientes características:
- La opacidad cambia durante el primer 10% de la línea de tiempo.
- Ocurre una pequeña brecha entre el cambio en la opacidad y el cambio en el ancho.
- Nada se anima durante el último 25% de la línea de tiempo.
- Incrementar el padding hace que el widget parezca elevarse hacia arriba.
- Aumentar el radio del borde (border radius) a 0.5, transforma el cuadrado con esquinas redondeadas en un círculo.
- Los cambios de padding y altura ocurren durante el mismo intervalo exacto, pero no tienen por qué hacerlo.
Para configurar la animación:
- Crea un
AnimationControllerque administre todas lasAnimations. -
Create a
Tweenfor each property being animated.- El
Tweendefine un rango de valores. - El método
animatedelTweenrequiere el controladorpadre, y produce unaAnimationpara esa propiedad.
- El
- Especifica el intervalo en la propiedad
curvede laAnimation.
Cuando cambia el valor de la animación controladora, cambia el valor de la nueva animación, lo que activa la actualización de la UI.
El siguiente código crea un tween para la propiedad width.
Construye una CurvedAnimation,
especificando una curva suavizada (eased curve). Consulta Curves
para ver
otras curvas de animación predefinidas disponibles.
width = Tween<double>(
begin: 50.0,
end: 150.0,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.125,
0.250,
curve: Curves.ease,
),
),
),
Los valores begin y end no tienen que ser doubles.
El siguiente código construye el tween para la propiedad borderRadius
(que controla la redondez de las esquinas del cuadrado),
usando BorderRadius.circular().
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.375,
0.500,
curve: Curves.ease,
),
),
),
Animación escalonada completa
#Como todos los widgets interactivos, la animación completa consiste en un par de widgets: un widget stateless y uno stateful.
El widget stateless especifica los Tweens,
define los objetos Animation, y proporciona una función build()
responsable de construir la porción animada del árbol de widgets.
El widget stateful crea el controlador, reproduce la animación, y construye la porción no animada del árbol de widgets. La animación comienza cuando se detecta un toque en cualquier lugar de la pantalla.
Código completo para el main.dart de basic_staggered_animation
Widget Stateless: StaggerAnimation
#En el widget stateless, StaggerAnimation,
la función build() instancia un
AnimatedBuilder—un widget de propósito general para construir
animaciones. El AnimatedBuilder
construye un widget y lo configura usando los valores actuales de los Tweens.
El ejemplo crea una función llamada _buildAnimation() (que realiza
las actualizaciones reales de la UI), y la asigna a su propiedad builder.
AnimatedBuilder escucha las notificaciones del controlador de animación,
marcando el árbol de widgets como dirty a medida que cambian los valores.
Para cada tick de la animación, los valores se actualizan,
dando lugar a una llamada a _buildAnimation().
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({super.key, required this.controller}) :
// Each animation defined here transforms its value during the subset
// of the controller's duration defined by the animation's interval.
// For example the opacity animation transforms its value during
// the first 10% of the controller's duration.
opacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.100,
curve: Curves.ease,
),
),
),
// ... Other tween definitions ...
);
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> width;
final Animation<double> height;
final Animation<EdgeInsets> padding;
final Animation<BorderRadius?> borderRadius;
final Animation<Color?> color;
// This function is called each time the controller "ticks" a new frame.
// When it runs, all of the animation's values will have been
// updated to reflect the controller's current value.
Widget _buildAnimation(BuildContext context, Widget? child) {
return Container(
padding: padding.value,
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
border: Border.all(
color: Colors.indigo[300]!,
width: 3,
),
borderRadius: borderRadius.value,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: controller,
);
}
}
Widget Stateful: StaggerDemo
#El widget stateful, StaggerDemo, crea el AnimationController
(el que los gobierna a todos), especificando una duración de 2000 ms. Reproduce
la animación y construye la porción no animada del árbol de widgets.
La animación comienza cuando se detecta un toque en la pantalla.
La animación se ejecuta hacia adelante y luego hacia atrás.
class StaggerDemo extends StatefulWidget {
@override
State<StaggerDemo> createState() => _StaggerDemoState();
}
class _StaggerDemoState extends State<StaggerDemo>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
// ...Boilerplate...
Future<void> _playAnimation() async {
try {
await _controller.forward().orCancel;
await _controller.reverse().orCancel;
} on TickerCanceled {
// The animation got canceled, probably because it was disposed of.
}
}
@override
Widget build(BuildContext context) {
timeDilation = 10.0; // 1.0 is normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Staggered Animation'),
),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: Center(
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.1),
border: Border.all(
color: Colors.black.withValues(alpha: 0.5),
),
),
child: StaggerAnimation(controller:_controller.view),
),
),
),
);
}
}
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.