Flutter - Анимация

Анимация - сложная процедура в любом мобильном приложении. Несмотря на свою сложность, Animation выводит пользовательский опыт на новый уровень и обеспечивает богатое взаимодействие с пользователем. Благодаря своему разнообразию анимация становится неотъемлемой частью современного мобильного приложения. Фреймворк Flutter признает важность анимации и обеспечивает простую и интуитивно понятную структуру для разработки всех типов анимации.

Введение

Анимация - это процесс показа серии изображений / картинок в определенном порядке в течение определенной продолжительности для создания иллюзии движения. Наиболее важные аспекты анимации следующие:

  • У анимации есть два различных значения: начальное значение и конечное значение. Анимация начинается с начального значения, проходит через серию промежуточных значений и, наконец, заканчивается на конечных значениях. Например, для анимации исчезновения виджета начальным значением будет полная непрозрачность, а конечным значением будет нулевая непрозрачность.

  • Промежуточные значения могут быть линейными или нелинейными (кривая) по своей природе, и их можно настраивать. Поймите, что анимация работает так, как настроена. Каждая конфигурация обеспечивает различное ощущение анимации. Например, затухание виджета будет линейным по своей природе, тогда как отскок мяча будет нелинейным по своей природе.

  • Продолжительность процесса анимации влияет на скорость (медленность или скорость) анимации.

  • Возможность управления процессом анимации, например запуском анимации, остановкой анимации, повторением анимации заданное количество раз, обращением процесса анимации и т. Д.

  • Во Flutter система анимации не делает реальной анимации. Вместо этого он предоставляет только значения, необходимые для каждого кадра для рендеринга изображений.

Классы на основе анимации

Система анимации Flutter основана на объектах Animation. Основные классы анимации и их использование следующие:

Анимация

Создает интерполированные значения между двумя числами в течение определенного периода времени. Наиболее распространенные классы анимации -

  • Animation<double> - интерполировать значения между двумя десятичными числами

  • Animation<Color> - интерполировать цвета между двумя цветами

  • Animation<Size> - интерполировать размеры между двумя размерами

  • AnimationController- Специальный объект Animation для управления самой анимацией. Он генерирует новые значения всякий раз, когда приложение готово к новому кадру. Он поддерживает линейную анимацию, а значение начинается от 0,0 до 1,0.

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

Здесь контроллер управляет анимацией, а параметр продолжительности управляет продолжительностью процесса анимации. vsync - это специальная опция, используемая для оптимизации ресурсов, используемых в анимации.

ИзогнутыйАнимация

Подобен AnimationController, но поддерживает нелинейную анимацию. CurvedAnimation можно использовать вместе с объектом Animation, как показано ниже -

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

Подросток <T>

Производный от Animatable <T> и используемый для генерации чисел между любыми двумя числами, кроме 0 и 1. Его можно использовать вместе с объектом Animation, используя метод animate и передавая фактический объект Animation.

AnimationController controller = AnimationController( 
   duration: const Duration(milliseconds: 1000), 
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);
  • Tween также можно использовать вместе с CurvedAnimation, как показано ниже -

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);

Здесь контроллер - это фактический контроллер анимации. Кривая обеспечивает тип нелинейности, а customTween предоставляет настраиваемый диапазон от 0 до 255.

Рабочий процесс анимации флаттера

Рабочий процесс анимации выглядит следующим образом -

  • Определите и запустите контроллер анимации в initState StatefulWidget.

AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = Tween<double>(begin: 0, end: 300).animate(controller); 
controller.forward();
  • Добавьте прослушиватель на основе анимации, addListener, чтобы изменить состояние виджета.

animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() { 
      // The state that has changed here is the animation object’s value. 
   }); 
});
  • Чтобы пропустить этот процесс, можно использовать встроенные виджеты, AnimatedWidget и AnimatedBuilder. Оба виджета принимают объект Animation и получают текущие значения, необходимые для анимации.

  • Получите значения анимации в процессе сборки виджета, а затем примените их для ширины, высоты или любого соответствующего свойства вместо исходного значения.

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

Рабочее приложение

Давайте напишем простое приложение на основе анимации, чтобы понять концепцию анимации во фреймворке Flutter.

  • Создайте новое приложение Flutter в студии Android, product_animation_app.

  • Скопируйте папку ресурсов из product_nav_app в product_animation_app и добавьте ресурсы в файл 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
  • Удалите код запуска по умолчанию (main.dart).

  • Добавьте импорт и базовую основную функцию.

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp());
  • Создайте виджет MyApp, производный от StatefulWidgtet.

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
}
  • Создайте виджет _MyAppState и реализуйте initState и удалите в дополнение к методу сборки по умолчанию.

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();
   }
}

Вот,

  • В методе initState мы создали объект контроллера анимации (контроллер), объект анимации (анимацию) и запустили анимацию с помощью controller.forward.

  • В методе dispose мы удалили объект контроллера анимации (контроллер).

  • В методе сборки отправьте анимацию в виджет MyHomePage через конструктор. Теперь виджет MyHomePage может использовать объект анимации для анимации своего содержимого.

  • Теперь добавьте виджет 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()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      ); 
   }
}
  • Создайте новый виджет MyAnimatedWidget для простой анимации затухания с использованием непрозрачности.

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), 
   ); 
}
  • Здесь мы использовали AniatedBuilder для создания нашей анимации. AnimatedBuilder - это виджет, который создает свой контент, одновременно выполняя анимацию. Он принимает объект анимации для получения текущего значения анимации. Мы использовали значение анимации animation.value, чтобы установить непрозрачность дочернего виджета. Фактически, виджет будет анимировать дочерний виджет, используя концепцию непрозрачности.

  • Наконец, создайте виджет MyHomePage и используйте объект анимации для анимации любого его содержимого.

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"
               ),
            ],
         )
      );
   }
}

Здесь мы использовали FadeAnimation и MyAnimationWidget для анимации первых двух элементов в списке. FadeAnimation - это встроенный класс анимации, который мы использовали для анимации его дочернего элемента с использованием концепции непрозрачности.

  • Полный код выглядит следующим образом -

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
      ), 
   ); 
}
  • Скомпилируйте и запустите приложение, чтобы увидеть результаты. Начальная и окончательная версии приложения следующие: