Flutter - Animação

A animação é um procedimento complexo em qualquer aplicativo móvel. Apesar de sua complexidade, o Animation aprimora a experiência do usuário a um novo nível e fornece uma interação rica do usuário. Devido à sua riqueza, a animação torna-se parte integrante da aplicação móvel moderna. A estrutura Flutter reconhece a importância da animação e fornece uma estrutura simples e intuitiva para desenvolver todos os tipos de animações.

Introdução

Animação é um processo de mostrar uma série de imagens / imagens em uma ordem particular dentro de uma duração específica para dar uma ilusão de movimento. Os aspectos mais importantes da animação são os seguintes -

  • A animação tem dois valores distintos: valor inicial e valor final. A animação começa no valor inicial e passa por uma série de valores intermediários e, finalmente, termina nos valores finais. Por exemplo, para animar um widget para desaparecer, o valor inicial será a opacidade total e o valor final será a opacidade zero.

  • Os valores intermediários podem ser lineares ou não lineares (curva) por natureza e podem ser configurados. Entenda que a animação funciona como está configurada. Cada configuração fornece uma sensação diferente para a animação. Por exemplo, o esmaecimento de um widget será de natureza linear, enquanto o salto de uma bola será de natureza não linear.

  • A duração do processo de animação afeta a velocidade (lentidão ou rapidez) da animação.

  • A capacidade de controlar o processo de animação, como iniciar a animação, interromper a animação, repetir a animação para definir o número de vezes, reverter o processo de animação, etc.,

  • No Flutter, o sistema de animação não faz nenhuma animação real. Em vez disso, ele fornece apenas os valores necessários em cada quadro para renderizar as imagens.

Aulas baseadas em animação

O sistema de animação Flutter é baseado em objetos de animação. As principais classes de animação e seu uso são as seguintes -

Animação

Gera valores interpolados entre dois números ao longo de uma certa duração. As aulas de animação mais comuns são -

  • Animation<double> - interpolar valores entre dois números decimais

  • Animation<Color> - interpolar cores entre duas cores

  • Animation<Size> - interpolar tamanhos entre dois tamanhos

  • AnimationController- Objeto de animação especial para controlar a própria animação. Ele gera novos valores sempre que o aplicativo está pronto para um novo quadro. Suporta animação de base linear e o valor começa de 0,0 a 1,0

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);

Aqui, o controlador controla a animação e a opção de duração controla a duração do processo de animação. vsync é uma opção especial usada para otimizar o recurso usado na animação.

Animação Curvada

Semelhante ao AnimationController, mas suporta animação não linear. CurvedAnimation pode ser usado junto com o objeto Animation como abaixo -

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)

Tween <T>

Derivado de Animatable <T> e usado para gerar números entre quaisquer dois números diferentes de 0 e 1. Ele pode ser usado junto com o objeto Animation usando o método animate e passando o objeto Animation real.

AnimationController controller = AnimationController( 
   duration: const Duration(milliseconds: 1000), 
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);
  • Tween também pode ser usado junto com CurvedAnimation como abaixo -

AnimationController controller = AnimationController(
   duration: const Duration(milliseconds: 500), vsync: this); 
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut); 
Animation<int> customTween = IntTween(begin: 0, end: 255).animate(curve);

Aqui, o controlador é o controlador de animação real. curve fornece o tipo de não linearidade e customTween fornece uma faixa personalizada de 0 a 255.

Fluxo de trabalho da Animação Flutter

O fluxo de trabalho da animação é o seguinte -

  • Defina e inicie o controlador de animação no initState do StatefulWidget.

AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = Tween<double>(begin: 0, end: 300).animate(controller); 
controller.forward();
  • Adicione ouvinte baseado em animação, addListener para alterar o estado do widget.

animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() { 
      // The state that has changed here is the animation object’s value. 
   }); 
});
  • Widgets embutidos, AnimatedWidget e AnimatedBuilder podem ser usados ​​para pular esse processo. Ambos os widgets aceitam o objeto Animation e obtêm os valores atuais necessários para a animação.

  • Obtenha os valores da animação durante o processo de construção do widget e, em seguida, aplique-os para largura, altura ou qualquer propriedade relevante em vez do valor original.

child: Container( 
   height: animation.value, 
   width: animation.value, 
   child: <Widget>, 
)

Aplicação de Trabalho

Vamos escrever um aplicativo baseado em animação simples para entender o conceito de animação no framework Flutter.

  • Crie um novo aplicativo Flutter no Android Studio, product_animation_app.

  • Copie a pasta de ativos de product_nav_app para product_animation_app e inclua ativos dentro do arquivo pubspec.yaml.

flutter: 
   assets: 
   - assets/appimages/floppy.png 
   - assets/appimages/iphone.png 
   - assets/appimages/laptop.png 
   - assets/appimages/pendrive.png 
   - assets/appimages/pixel.png 
   - assets/appimages/tablet.png
  • Remova o código de inicialização padrão (main.dart).

  • Adicionar importação e função principal básica.

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp());
  • Crie o widget MyApp derivado de StatefulWidgtet.

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
}
  • Crie o widget _MyAppState e implemente initState e descarte além do método de construção padrão.

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { 
   Animation<double> animation; 
   AnimationController controller; 
   @override void initState() {
      super.initState(); 
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this
      ); 
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller); 
      controller.forward(); 
   } 
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose();
   }
}

Aqui,

  • No método initState, criamos um objeto controlador de animação (controlador), um objeto de animação (animação) e iniciamos a animação usando controller.forward.

  • No método dispose, descartamos o objeto controlador de animação (controlador).

  • No método de construção, envie a animação para o widget MyHomePage por meio do construtor. Agora, o widget MyHomePage pode usar o objeto de animação para animar seu conteúdo.

  • Agora, adicione o widget ProductBox

class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image})
      : super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card( 
            child: Row( 
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.name, style: 
                                 TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.description), 
                                 Text("Price: " + this.price.toString()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      ); 
   }
}
  • Crie um novo widget, MyAnimatedWidget para fazer uma animação de fade simples usando opacidade.

class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
      
   final Widget child; 
   final Animation<double> animation; 
   
   Widget build(BuildContext context) => Center( 
   child: AnimatedBuilder(
      animation: animation, 
      builder: (context, child) => Container( 
         child: Opacity(opacity: animation.value, child: child), 
      ), 
      child: child), 
   ); 
}
  • Aqui, usamos o AniatedBuilder para fazer nossa animação. AnimatedBuilder é um widget que constrói seu conteúdo enquanto faz a animação ao mesmo tempo. Ele aceita um objeto de animação para obter o valor da animação atual. Usamos o valor da animação, animation.value, para definir a opacidade do widget filho. Na verdade, o widget irá animar o widget filho usando o conceito de opacidade.

  • Por fim, crie o widget MyHomePage e use o objeto de animação para animar qualquer um de seu conteúdo.

class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title, this.animation}) : super(key: key); 
   
   final String title; 
   final Animation<double> 
   animation; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")),body: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0), 
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone", 
                     description: "iPhone is the stylist phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), opacity: animation
               ), 
               MyAnimatedWidget(child: ProductBox(
                  name: "Pixel", 
                  description: "Pixel is the most featureful phone ever", 
                  price: 800, 
                  image: "pixel.png"
               ), animation: animation), 
               ProductBox(
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet", 
                  description: "Tablet is the most useful device ever for meeting", 
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}

Aqui, usamos FadeAnimation e MyAnimationWidget para animar os dois primeiros itens da lista. FadeAnimation é uma classe de animação integrada, que usamos para animar seu filho usando o conceito de opacidade.

  • O código completo é o seguinte -

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp()); 

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
} 
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
   Animation<double> animation; 
   AnimationController controller; 
   
   @override 
   void initState() {
      super.initState(); 
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this); 
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller); 
      controller.forward(); 
   } 
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp( 
         title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,) 
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose(); 
   } 
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title, this.animation}): super(key: key);
   final String title; 
   final Animation<double> animation; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")), 
         body: ListView(
            shrinkWrap: true, 
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0), 
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone", 
                     description: "iPhone is the stylist phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), 
                  opacity: animation
               ), 
               MyAnimatedWidget(
                  child: ProductBox( 
                     name: "Pixel", 
                     description: "Pixel is the most featureful phone ever", 
                     price: 800, 
                     image: "pixel.png"
                  ), 
                  animation: animation
               ), 
               ProductBox( 
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ), 
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ), 
            ], 
         )
      ); 
   } 
} 
class ProductBox extends StatelessWidget { 
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded(
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ), 
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ), 
                           ], 
                        )
                     )
                  ) 
               ]
            )
         )
      ); 
   } 
}
class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
   final Widget child; 
   final Animation<double> animation; 
 
   Widget build(BuildContext context) => Center( 
      child: AnimatedBuilder(
         animation: animation, 
         builder: (context, child) => Container( 
            child: Opacity(opacity: animation.value, child: child), 
         ), 
         child: child
      ), 
   ); 
}
  • Compile e execute o aplicativo para ver os resultados. A versão inicial e final do aplicativo é a seguinte -