Animar aplicaciones con Flutter
Publicado: 2022-03-10Las aplicaciones para cualquier plataforma son elogiadas cuando son intuitivas, atractivas y brindan comentarios agradables a las interacciones del usuario. La animación es una de las maneras de hacer precisamente eso.
Flutter, un marco multiplataforma, ha madurado en los últimos dos años para incluir soporte web y de escritorio. Se ha ganado la reputación de que las aplicaciones desarrolladas con él son fluidas y atractivas. Con su rico soporte de animación, forma declarativa de escribir UI, "Recarga en caliente" y otras características, ahora es un marco multiplataforma completo.
Si estás comenzando con Flutter y quieres aprender una forma poco convencional de agregar animaciones, estás en el lugar correcto: exploraremos el ámbito de la animación y los widgets de movimiento, una forma implícita de agregar animaciones.
Flutter se basa en el concepto de widgets. Cada componente visual de una aplicación es un widget; piense en ellos como vistas en Android. Flutter proporciona soporte de animación utilizando una clase de animación, un objeto "AnimationController" para la administración y "Tween" para interpolar el rango de datos. Estos tres componentes trabajan juntos para proporcionar una animación fluida. Dado que esto requiere la creación y gestión manual de la animación, se conoce como una forma explícita de animar.
Ahora déjame presentarte los widgets de animación y movimiento. Flutter proporciona numerosos widgets que son inherentemente compatibles con la animación. No es necesario crear un objeto de animación ni ningún controlador, ya que esta categoría de widgets maneja toda la animación. Simplemente elija el widget apropiado para la animación requerida y pase los valores de propiedades del widget para animar. Esta técnica es una forma implícita de animar.
El cuadro anterior establece aproximadamente la jerarquía de animación en Flutter, cómo se admiten tanto la animación explícita como la implícita.
Algunos de los widgets animados cubiertos en este artículo son:
-
AnimatedOpacity
-
AnimatedCrossFade
-
AnimatedAlign
-
AnimatedPadding
-
AnimatedSize
-
AnimatedPositioned
.
Flutter no solo proporciona widgets animados predefinidos, sino también un widget genérico llamado AnimatedWidget
, que se puede usar para crear widgets animados implícitos personalizados. Como es evidente por el nombre, estos widgets pertenecen a la categoría de widgets animados y de movimiento, por lo que tienen algunas propiedades comunes que nos permiten hacer animaciones mucho más fluidas y de mejor apariencia.
Permítanme explicar estas propiedades comunes ahora, ya que se utilizarán más adelante en todos los ejemplos.
-
duration
La duración sobre la que animar los parámetros. -
reverseDuration
La duración de la animación inversa. -
curve
La curva que se aplicará al animar los parámetros. Los valores interpolados se pueden tomar de una distribución lineal o, si se especifica y cuando se especifica, se pueden tomar de una curva.
Comencemos el viaje creando una aplicación simple que llamaremos "Citado". Mostrará una cotización aleatoria cada vez que se inicie la aplicación. Dos cosas a tener en cuenta: primero, todas estas cotizaciones estarán codificadas en la aplicación; y segundo, no se guardarán datos de usuario.
Nota : Todos los archivos de estos ejemplos se pueden encontrar en GitHub.
Empezando
Flutter debe estar instalado y necesitará cierta familiaridad con el flujo básico antes de continuar. Un buen lugar para comenzar es "Uso de Flutter de Google para un desarrollo móvil verdaderamente multiplataforma".
Cree un nuevo proyecto de Flutter en Android Studio.
Esto abrirá un nuevo asistente de proyecto, donde puede configurar los conceptos básicos del proyecto.
En la pantalla de selección del tipo de proyecto, hay varios tipos de proyectos de Flutter, cada uno de los cuales se adapta a un escenario específico. Para este tutorial, elija la aplicación Flutter y presione Siguiente .
Ahora debe ingresar información específica del proyecto: el nombre y la ruta del proyecto, el dominio de la empresa, etc. Echa un vistazo a la imagen de abajo.
Agregue el nombre del proyecto, la ruta del SDK de Flutter, la ubicación del proyecto y una descripción del proyecto opcional. Presiona Siguiente .
Cada aplicación (ya sea Android o iOS) requiere un nombre de paquete único. Por lo general, utiliza el reverso del dominio de su sitio web; por ejemplo, com.google o com.yahoo. Presiona Finalizar para generar una aplicación de Flutter que funcione.
Una vez que se genera el proyecto, debería ver la pantalla que se muestra arriba. Abra el archivo main.dart (resaltado en la captura de pantalla). Este es el archivo principal de la aplicación. El proyecto de muestra está completo en sí mismo y se puede ejecutar directamente en un emulador o en un dispositivo físico sin ninguna modificación.
Reemplace el contenido del archivo main.dart con el siguiente fragmento de código:
import 'package:animated_widgets/FirstPage.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); } }
Este código limpia el archivo main.dart simplemente agregando información simple relevante para crear una nueva aplicación. La clase MyApp devuelve un objeto: un widget de MaterialApp
, que proporciona la estructura básica para crear aplicaciones que se ajusten a Material Design. Para que el código esté más estructurado, cree dos nuevos archivos dart dentro de la carpeta lib : FirstPage.dart y Quotes.dart .
FirstPage.dart contendrá todo el código responsable de todos los elementos visuales (widgets) necesarios para nuestra aplicación citada. Toda la animación se maneja en este archivo.
Nota : Más adelante en el artículo, todos los fragmentos de código para cada widget animado se agregan a este archivo como elementos secundarios del widget Scaffold. Para obtener más información, este ejemplo en GitHub podría ser útil.
Comience agregando el siguiente código a FirstPage.dart . Este es el código parcial donde se agregarán otras cosas más adelante.
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
El archivo Quotes.dart contiene una lista de todas las citas codificadas. Un punto a tener en cuenta aquí es que la lista es un objeto estático. Esto significa que se puede usar en otros lugares sin crear un nuevo objeto de la clase Quotes. Esto se elige por diseño, ya que la lista anterior actúa simplemente como una utilidad.
Agregue el siguiente código a este archivo:
class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }
El esqueleto del proyecto ya está listo, así que desarrollemos Quoted un poco más.
AnimatedOpacity
Para darle un toque personal a la aplicación, sería bueno saber el nombre del usuario, así que solicitémoslo y mostremos un botón siguiente. Hasta que el usuario ingrese su nombre, este botón está oculto y aparecerá con gracia cuando se le dé un nombre. Necesitamos algún tipo de animación de visibilidad para el botón, pero ¿hay un widget para eso? Sí hay.
Introduzca AnimatedOpacity
. Este widget se basa en el widget Opacity al agregar soporte de animación implícito. ¿Cómo lo usamos? Recuerde nuestro escenario: necesitamos mostrar un botón siguiente con visibilidad animada. Envolvemos el widget de botón dentro del widget AnimatedOpacity
, ingresamos algunos valores adecuados y agregamos una condición para activar la animación, y Flutter puede manejar el resto.
_getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }
El widget AnimatedOpacity
tiene dos propiedades obligatorias:
-
opacity
Un valor de 1 significa completamente visible; 0 (cero) significa oculto. Mientras anima, Flutter interpola valores entre estos dos extremos. Puede ver cómo se coloca una condición para cambiar la visibilidad, lo que activa la animación. -
child
El widget secundario que tendrá su visibilidad animada.
Ahora debería comprender lo realmente simple que es agregar una animación de visibilidad con el widget implícito. Y todos esos widgets siguen las mismas pautas y son fáciles de usar. Pasemos al siguiente.
AnimatedCrossFade
Tenemos el nombre del usuario, pero el widget todavía está esperando la entrada. En el paso anterior, a medida que el usuario ingresa su nombre, mostramos el siguiente botón. Ahora, cuando el usuario presiona el botón, quiero dejar de aceptar la entrada y mostrar el nombre ingresado. Hay muchas formas de hacerlo, por supuesto, pero tal vez podamos ocultar el widget de entrada y mostrar un widget de texto no editable. Probémoslo usando el widget AnimatedCrossFade
.
Este widget requiere dos hijos, ya que el widget se fusiona entre ellos en función de alguna condición. Una cosa importante a tener en cuenta al usar este widget es que ambos elementos secundarios deben tener el mismo ancho. Si la altura es diferente, el widget más alto se recorta desde la parte inferior. En este escenario, se utilizarán dos widgets como elementos secundarios: entrada y etiqueta.
_getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); }
Este widget requiere un conjunto diferente de parámetros obligatorios:
-
crossFadeState
Este estado determina qué niño mostrar. -
firstChild
Especifica el primer hijo de este widget. -
secondChild
Especifica el segundo hijo.
AnimatedAlign
En este punto, la etiqueta del nombre se coloca en el centro de la pantalla. Se verá mucho mejor en la parte superior, ya que necesitamos el centro de la pantalla para mostrar las comillas. En pocas palabras, la alineación del widget de etiqueta de nombre debe cambiarse del centro hacia arriba. ¿Y no sería bueno animar este cambio de alineación junto con la animación de fundido cruzado anterior? Vamos a hacerlo.
Como siempre, se pueden utilizar varias técnicas para lograr esto. Dado que el widget de etiqueta de nombre ya está alineado en el centro, animar su alineación sería mucho más simple que manipular los valores superior e izquierdo del widget. El widget AnimatedAlign
es perfecto para este trabajo.
Para iniciar esta animación, se requiere un disparador. El único propósito de este widget es animar el cambio de alineación, por lo que solo tiene algunas propiedades: agregar un elemento secundario, establecer su alineación, desencadenar el cambio de alineación y eso es todo.
_getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }
Tiene sólo dos propiedades obligatorias:
- niño:
El niño cuya alineación será modificada. - alineación:
Valor de alineación requerido.
Este widget es realmente simple pero los resultados son elegantes. Además, vimos con qué facilidad podemos usar dos widgets animados diferentes para crear una animación más compleja. Esta es la belleza de los widgets animados.
AnimatedPadding
Ahora tenemos el nombre del usuario en la parte superior, suavemente animado sin mucho esfuerzo, utilizando diferentes tipos de widgets animados. Agreguemos un saludo, "Hola", antes del nombre. Agregar un widget de texto con el valor "Hola" en la parte superior hará que se superponga al widget de texto de saludo, luciendo como la imagen a continuación.
¿Qué pasaría si el widget de texto del nombre tuviera algo de relleno a la izquierda? Aumentar el relleno izquierdo definitivamente funcionará, pero espera: ¿podemos aumentar el relleno con alguna animación? Sí, y eso es lo que hace AnimatedPadding
. Para hacer que todo esto se vea mucho mejor, hagamos que el widget de texto de saludos se desvanezca y el relleno del widget de texto de nombre aumente al mismo tiempo.
_getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }
Dado que la animación anterior debe ocurrir solo después de que se complete la alineación animada anterior, debemos retrasar la activación de esta animación. Desviándonos brevemente del tema, este es un buen momento para hablar sobre un mecanismo popular para agregar retraso. Flutter proporciona varias técnicas de este tipo, pero el constructor Future.delayed es uno de los enfoques más simples, limpios y legibles. Por ejemplo, para ejecutar un fragmento de código después de 1 segundo:
Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });
Dado que la duración del retraso ya se conoce (calculada a partir de duraciones de animación anteriores), la animación se puede activar después de este intervalo.
// Showing “Hi” after 1 second - greetings visibility trigger. _showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); }); } // Increasing the padding for name label widget after 1 second - increase padding trigger. _increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); }); }
Este widget tiene solo dos propiedades obligatorias:
-
child
El niño dentro de este widget, al que se aplicará el relleno. -
padding
La cantidad de espacio a agregar.
AnimatedSize
Hoy en día, cualquier aplicación que tenga algún tipo de animación incluirá acercar o alejar los componentes visuales para captar la atención del usuario (comúnmente llamada animación de escala). ¿Por qué no usar la misma técnica aquí? Podemos mostrar al usuario una cita motivacional que se amplía desde el centro de la pantalla. Permítanme presentarles el widget AnimatedSize
, que activa los efectos de acercar y alejar, controlados cambiando el tamaño de su elemento secundario.
Este widget es un poco diferente de los demás cuando se trata de los parámetros requeridos. Necesitamos lo que Flutter llama un "Ticker". Flutter tiene un método para informar a los objetos cada vez que se activa un nuevo evento de cuadro. Se puede considerar como algo que envía una señal que dice: “¡Hazlo ahora! … ¡Hazlo ahora! … ¡Hazlo ahora! …”
El widget AnimatedSize
requiere una propiedad, vsync , que acepta un proveedor de teletipos. La forma más fácil de obtener un proveedor de teletipos es agregar un Mixin a la clase. Hay dos implementaciones básicas de proveedores de teletipos: SingleTickerProviderStateMixin
, que proporciona un solo teletipo; y TickerProviderStateMixin
, que proporciona varios.
La implementación predeterminada de un Ticker se usa para marcar los fotogramas de una animación. En este caso, se emplea este último. Más sobre mixins.
// Helper method to create quotes card widget. _getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), ); } // Helper method to create animated size widget and set its properties. _getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), ); } // Helper method to create the quotes container widget with different sizes. _getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), ); } // Trigger used to show the quote card widget. _showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); }); }
Propiedades obligatorias para este widget:
-
vsync
El proveedor de teletipos necesario para coordinar la animación y los cambios de fotogramas.< -
child
El niño cuyo tamaño cambia será animado.
La animación de acercar y alejar ahora se puede controlar fácilmente.
AnimatedPositioned
¡Genial! Las citas se amplían desde el centro para captar la atención del usuario. ¿Qué pasa si se desliza hacia arriba desde la parte inferior mientras se acerca? Vamos a intentarlo. Este movimiento implica jugar con la posición del widget de cotización y animar los cambios en las propiedades de posición. AnimatedPositioned
es el candidato perfecto.
Este widget cambia automáticamente la posición del niño durante un tiempo determinado cada vez que cambia la posición especificada. Un punto a tener en cuenta: solo funciona si su widget principal es una "Pila". Este widget es bastante simple y fácil de usar. Vamos a ver.
// Helper method to create the animated positioned widget. // With position changes based on “showQuoteCard” flag. _getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, ); }
Este widget solo tiene una propiedad obligatoria:
-
child
El widget cuya posición se cambiará.
Si no se espera que el tamaño del niño cambie junto con su posición, una alternativa más eficaz a este widget sería SlideTransition
.
Aquí está nuestra animación completa:
Conclusión
Las animaciones son una parte integral de la experiencia del usuario. Las aplicaciones estáticas o las aplicaciones con animación janky no solo reducen la retención de usuarios, sino también la reputación de un desarrollador para ofrecer resultados.
Hoy en día, las aplicaciones más populares tienen algún tipo de animación sutil para deleitar a los usuarios. Los comentarios animados a las solicitudes de los usuarios también pueden involucrarlos para explorar más. Flutter ofrece una gran cantidad de funciones para el desarrollo multiplataforma, incluido un rico soporte para animaciones fluidas y receptivas.
Flutter tiene un excelente soporte de complementos que nos permite usar animaciones de otros desarrolladores. Ahora que ha madurado a la versión 1.9, con tanto amor por parte de la comunidad, Flutter seguramente mejorará en el futuro. ¡Diría que ahora es un buen momento para aprender Flutter!
Más recursos
- "Widgets de animación y movimiento", Flutter Docs
- “Introducción a las animaciones”, Flutter Docs
- Laboratorios de código de Flutter
Nota del editor : Muchas gracias a Ahmad Awais por su ayuda en la revisión de este artículo.