Asincronía en Flutter
Introducción a la Asincronía en Programación En el mundo de la programación, uno de los mayores …
leer másLas animaciones son un elemento clave en el desarrollo de aplicaciones modernas. No solo hacen que una interfaz sea más atractiva visualmente, sino que también mejoran la experiencia del usuario al proporcionar transiciones fluidas y efectos intuitivos. Siempre y cuándo estén bien diseñadas y no interfieran con la usabilidad de la aplicación.
A lo largo de la serie de Flutter hemos explorado diferentes conceptos, desde el lenguaje Dart, la creación de interfaces de usuario, los widgets más comunes, hasta asincronía. En esta ocasión veremos cómo crear animaciones para dotar a nuestras aplicaciones de un aspecto más dinámico y atractivo.
Flutter, gracias a su motor gráfico altamente optimizado, facilita la implementación de animaciones sin afectar el rendimiento de la aplicación. En este artículo, aprenderemos a utilizar tres tipos básicos de animaciones en Flutter, con ejemplos prácticos y fáciles de implementar:
AnimationController
.Después, desarrollaremos una aplicación que nos permitirá mostrar cada uno de estos tipos de animaciones en una sola app, a la vez que analizamos cada uno de los casos más habituales y sus posibles usos.
Flutter posee un sistema de animaciones potente y flexible que se adapta a diferentes necesidades. Para empezar, es fundamental conocer las tres animaciones más básicas que nos permitirán dar vida a nuestra UI de manera sencilla y efectiva.
A continuación, exploraremos cómo funcionan, en qué se diferencian y cuándo es recomendable usarlas.
Las animaciones implícitas son las más sencillas de implementar en Flutter. Se utilizan cuando queremos que ciertos cambios en el estado de un widget se animen automáticamente sin necesidad de gestionar manualmente el tiempo o la interpolación.
AnimationController
, ya que la animación es gestionada internamente.AnimatedContainer
– Anima propiedades como tamaño, color y bordes.AnimatedOpacity
– Cambia la transparencia de un widget.AnimatedAlign
– Modifica la alineación de un widget con suavidad.AnimatedSwitcher
– Anima la transición entre widgets cuando se reemplazan.Las animaciones explícitas ofrecen un mayor control sobre el tiempo, la velocidad y el comportamiento de la animación. Son útiles cuando queremos definir transiciones más precisas o cuando las animaciones dependen de interacciones más complejas.
AnimationController
para gestionar el tiempo de la animación.Tween
y curvas de animación con CurvedAnimation
.AnimatedBuilder
para optimizar el rendimiento al reconstruir solo las partes necesarias del
widget.Las animaciones Hero permiten crear transiciones suaves entre pantallas cuando un elemento mantiene su apariencia visual en ambas vistas. Son ideales para mejorar la experiencia de navegación en una app.
Hero
, que asigna una etiqueta compartida (tag
) a un widget en ambas pantallas.Vemos que en nuestro ejemplo práctico hay un pequeño error con el tamaño del texto al hacer la animación de Hero, sería recomendable investigar el porqué y arreglarlo, probablemente se deba a cómo se está manejando el cambio de tamaño del widget al hacer la transición.
En la siguiente parte del artículo, crearemos una aplicación en Flutter que muestre cada una de estas animaciones con ejemplos prácticos.
Esta aplicación en Flutter nos permite explorar los tres tipos de animaciones mencionados anteriormente:
AnimationController
.A continuación, explicaremos el propósito de cada pantalla y los widgets utilizados.
La pantalla principal actúa como un menú para acceder a las diferentes animaciones. Contiene:
Card
) que sirven como botones para navegar a las pantallas de cada animación.Scaffold
: Estructura base de la pantalla.Column
: Organización de los elementos en una lista vertical.Card
: Diseño de botones con una apariencia atractiva.InkWell
: Permite detectar clics en cada tarjeta.Navigator
: Navegación a las pantallas de animaciones.
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animation Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Explore Different Types of Animations',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 30),
_buildAnimationButton(
context,
'Implicit Animations',
'Automatically animate size, color, opacity, and alignment.',
Icons.animation,
ImplicitAnimationsScreen(),
),
SizedBox(height: 20),
_buildAnimationButton(
context,
'Explicit Animations',
'Manually control scaling animations with start/reverse buttons.',
Icons.touch_app,
ExplicitAnimationsScreen(),
),
SizedBox(height: 20),
_buildAnimationButton(
context,
'Hero Animation',
'Tap the widget to see a smooth transition between screens.',
Icons.flight_takeoff,
HeroAnimationScreen(),
),
],
),
),
);
}
Widget _buildAnimationButton(BuildContext context, String title, String description, IconData icon, Widget screen) {
return Card(
elevation: 4,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => screen),
);
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Icon(icon, size: 40, color: Colors.blue),
SizedBox(height: 10),
Text(
title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
Text(
description,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
Esta pantalla muestra cómo usar animaciones implícitas en Flutter. Los cambios de estado en los widgets activan automáticamente las transiciones de tamaño, color y alineación.
Incluye tres ejemplos de animaciones:
AnimatedContainer
– Cambia de tamaño y color al presionar un botón.AnimatedOpacity
– Ajusta la transparencia de un widget.AnimatedAlign
– Modifica la alineación de un widget dentro de su contenedor.Cada animación se activa al presionar un botón, cambiando el estado de la pantalla.
AnimatedContainer
: Para transiciones suaves en propiedades visuales.AnimatedOpacity
: Para efectos de desvanecimiento.AnimatedAlign
: Para animar el posicionamiento de un widget.FloatingActionButton
: Para regresar a la pantalla principal.
class _ImplicitAnimationsScreenState extends State<ImplicitAnimationsScreen> {
bool _expanded = false;
bool _visible = true;
Alignment _alignment = Alignment.topLeft;
void _toggleExpanded() {
setState(() {
_expanded = !_expanded;
});
}
void _toggleVisibility() {
setState(() {
_visible = !_visible;
});
}
void _changeAlignment() {
setState(() {
_alignment = _alignment == Alignment.topLeft
? Alignment.bottomRight
: Alignment.topLeft;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Implicit Animations'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. AnimatedContainer',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to toggle size and color.'),
SizedBox(height: 10),
Center(
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
child: Center(
child: Text(
_expanded ? 'Expanded' : 'Small',
style: TextStyle(color: Colors.white),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleExpanded,
child: Text('Toggle Container'),
),
SizedBox(height: 30),
Text(
'2. AnimatedOpacity',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to toggle visibility.'),
SizedBox(height: 10),
Center(
child: AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(seconds: 1),
child: Container(
width: 100,
height: 100,
color: Colors.green,
child: Center(
child: Text(
_visible ? 'Visible' : 'Invisible',
style: TextStyle(color: Colors.white),
),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleVisibility,
child: Text('Toggle Opacity'),
),
SizedBox(height: 30),
Text(
'3. AnimatedAlign',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to change alignment.'),
SizedBox(height: 10),
Container(
height: 200,
width: double.infinity,
color: Colors.grey[200],
child: AnimatedAlign(
alignment: _alignment,
duration: Duration(seconds: 1),
child: Container(
width: 100,
height: 100,
color: Colors.orange,
child: Center(
child: Text(
_alignment == Alignment.topLeft
? 'Top Left'
: 'Bottom Right',
style: TextStyle(color: Colors.white),
),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _changeAlignment,
child: Text('Change Alignment'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
En esta vemos el uso de animaciones explícitas, donde el desarrollador controla la animación manualmente utilizando un
AnimationController
.
Incluye un ejemplo donde un Container
cambia de tamaño con un efecto de suavizado.
AnimationController
: Controla la duración y el estado de la animación.Tween<double>
: Define un rango de valores para interpolar la animación.CurvedAnimation
: Aplica una curva de aceleración y desaceleración a la animación.AnimatedBuilder
: Optimiza el rendimiento evitando reconstrucciones innecesarias.FloatingActionButton
: Botón flotante clásico, en este caso, para regresar a la pantalla principal.
class _ExplicitAnimationsScreenState extends State<ExplicitAnimationsScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _isAnimating = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
_animation = Tween<double>(begin: 100, end: 200).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}
void _startAnimation() {
_controller.forward();
setState(() {
_isAnimating = true;
});
}
void _reverseAnimation() {
_controller.reverse();
setState(() {
_isAnimating = false;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Explicit Animations'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. Scaling Animation',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Use the buttons to start or reverse the animation.'),
SizedBox(height: 20),
Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.purple,
child: Center(
child: Text(
_isAnimating ? 'Expanding' : 'Shrinking',
style: TextStyle(color: Colors.white),
),
),
);
},
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _startAnimation,
child: Text('Start Animation'),
),
SizedBox(width: 20),
ElevatedButton(
onPressed: _reverseAnimation,
child: Text('Reverse Animation'),
),
],
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
Para terminar, programamos una animación Hero para una transición fluida entre pantallas.
Hero
: Define el widget que se animará entre pantallas.Navigator
: Gestiona la navegación entre pantallas.GestureDetector
: Detecta el toque del usuario para activar la animación.
class HeroAnimationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Hero Animation'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. Hero Transition',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the widget to see a smooth transition to the next screen.'),
SizedBox(height: 20),
Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HeroDetailScreen()),
);
},
child: Hero(
tag: 'heroTag',
child: Container(
width: 100,
height: 100,
color: Colors.teal,
child: Center(
child: Text(
'Tap Me!',
style: TextStyle(color: Colors.white),
),
),
),
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
Cómo decíamos anteriormente, existe un pequeño error en la animación de Hero, el texto no se ajusta correctamente al cambiar de tamaño, sería un buen punto de partida para seguir investigando y mejorando nuestros conocimientos sobre este tipo de animaciones.
Así cómo hemos comentado las ventajas de utilizar animaciones en nuestras aplicaciones, también es importante tener en cuenta que un uso excesivo de animaciones puede resultar en una experiencia de usuario confusa o desagradable. Es fundamental equilibrar la cantidad y la intensidad de las animaciones para garantizar una experiencia de usuario agradable y efectiva.
Hemos tratado los conceptos básicos de animaciones en Flutter y creado una aplicación de ejemplo que muestra cada uno de estos tipos de animaciones en acción. Ahora, puedes aplicar estos conocimientos a tus propios proyectos y dar vida a tus interfaces de usuario con animaciones atractivas y efectivas. Espero que te haya resultado útil y sobre todo desearte un muy Happy Coding!
Quizá te puedan interesar
Introducción a la Asincronía en Programación En el mundo de la programación, uno de los mayores …
leer másFlutter es un framework de desarrollo de aplicaciones móviles creado por Google que ha ganado …
leer másIntroducción En los capítulos anteriores de nuestra serie sobre Flutter, hemos establecido una …
leer másDe concepto a realidad