Я покажу вам 6 прикольных анимационных эффектов, которые можно попробовать в своих приложениях. Добавлять анимацию с Flutter — одно удовольствие. И делать это можно по-разному. Например, можно скачать пакет с dart pub или воспользоваться виджетом AnimatedBuilder, который позволяет настроить каждую деталь анимации.

В этой статье я буду работать с виджетом AnimatedBuilder. Если вы не знакомы с ним, то почитайте документацию. Раз мы решили поработать с AnimatedBuilder, то нам потребуются два дополнительных виджета:

1. AnimationController — задает длительность анимации;

2. Animation — определяет тип и стиль анимации.

Проще говоря, эти виджеты используются для настройки и обработки анимации. Не забудьте протестировать анимацию в виджете изменяемых состояний StatefulWidget. А в определение класса обязательно добавьте SingleTickerProviderStateMixin. Он нужен для управления временем в анимации.

Готовы начать? Поехали.

Базовый макет

Я создал базовый макет, на котором вы можете потренироваться в настройке и переключении анимации. Выглядит он вот так:

Базовый макет

В процессе работы мы будем пользоваться разными изображениями. Вы можете добавить свои изображения или другие виджеты.

Триггером для анимации у нас служит плавающая кнопка снизу. Я буду активировать анимацию через вызов _controller.forward().

Не пугайтесь кода ниже. Пользуйтесь им для настройки контроллера и виджета анимации. 

Теперь я познакомлю вас с разными типами анимации в двух блоках кода — для определения и использования анимации. Все очень просто: сначала выбираете понравившийся тип анимации, а затем копируете и вставляете два блока кода. Первый нужен для запуска контроллера, а второй — для работы с анимацией.

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{

  //*-----определяем Animation и AnimationController-----*
  AnimationController _controller;
  Animation _myAnimation;

  //*-----запускаем Animation и AnimationController-----*
  @override
  void initState() {
    super.initState();

  }

  @override
  void dispose() {
    super.dispose();
    //-disposing the animation controller-
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter Animations"),
      ),

      body: Center(
          child:
          Container(
            width: 250,
            height: 250,
            decoration: BoxDecoration(
                image: new DecorationImage(
                    image: new AssetImage(
                      'assets/images/sample-image.png',
                    )
                )
            ),
          )
      ),

      floatingActionButtonLocation:
      FloatingActionButtonLocation.centerFloat,

      floatingActionButton:
      FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: (){
          //*------включаем анимацию-----*
          _controller.forward();
        },
      ),
    );
  }
}

01. Появление/исчезновение

Fade-эффект

Воспользуйтесь кодом ниже для определения анимации. Лучше всего это делать через метод initState. Теперь он будет запускаться только при отображении StatefulWidget.

AnimationController _controller;
Animation _myAnimation;

void initState() {
  // TODO: implement initState
  super.initState();
  _controller = AnimationController(
    vsync: this,
    duration: Duration(milliseconds: 1200),

  );
  _myAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
  
}

Во Flutter уже есть встроенный виджет под названием FadeTransition. Таким образом, мы можем обернуть наш виджет-контейнер в FadeTransition, а затем настроить динамические значения для непрозрачности.

body: Center(
        child:
        FadeTransition(
          opacity: _myAnimation,
          child: Container(
            width: 100,
            height: 100,
            decoration: BoxDecoration(
                image: new DecorationImage(
                    image: new AssetImage(
                      'assets/images/ghost.png',
                    )
                )
            ),
          ),
        )
    ),

02. Изменение размера/пульсация

Это тоже простая разновидность анимации. Для начала, зададим нужные параметры.

AnimationController _controller;
Animation<Size> _myAnimation;

@override
void initState() {
  // TODO: implement initState
  super.initState();
  _controller = AnimationController(
    vsync: this,
    duration: Duration(milliseconds: 1000),

  );
  _myAnimation = Tween<Size>(
      begin: Size(100, 100),
      end:  Size(120, 120)
  ).animate(
      CurvedAnimation(parent: _controller, curve: Curves.bounceIn)
  );

  _controller.addStatusListener((AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      _controller.repeat();
    }
  });
}

Возможно, сейчас вы заметите что-то новое. Например, мы используем _controller.addStatusListener() каждый раз, когда хотим повторить анимацию. После определения анимации в AnimationController она готова к использованию в виджете-контейнере.

body: Center(
        child:
        AnimatedBuilder(animation: _myAnimation,
            builder: (ctx, ch) =>  Container(
              width: _myAnimation.value.width,
              height: _myAnimation.value.height,

              decoration: BoxDecoration(
                  image: new DecorationImage(
                      image: new AssetImage(
                        'assets/images/heart.png',
                      )
                  )
              ),
            )
        )
    ),

03. Скольжение

Slide-анимация — это еще одна встроенная функция Flutter. Вот, как ее можно определить.

AnimationController _controller;
Animation<Offset> _myAnimation;

@override
void initState() {
  // TODO: implement initState
  super.initState();
  _controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );

  _myAnimation = Tween<Offset>(
    begin: Offset.zero,
    end: const Offset(1.5, 0.0),

  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Curves.elasticIn,
  ));
}

Чтобы воспользоваться анимацией, оберните ее в виджет SlideTransition. Проще простого, не так ли?

body: Center(
      child:
      SlideTransition(
        position: _myAnimation,
        child: const Padding(
          padding: EdgeInsets.all(8.0),
          child: FlutterLogo(size: 150.0),
        ),
      ),
    ),

04. Прыгающая анимация

Еще одна забавная разновидность, которую можно попробовать. Во многом она похожа на анимацию пульсации. Только помните, что в этот раз нам нужно будет изменить отступы контейнера. Давайте определим параметры.

AnimationController _controller;
  Animation<double> _slideAnimation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );

    _slideAnimation = Tween(begin: 200.0, end: 120.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.0, 1.0, curve: Curves.elasticIn),
      ),
    )..addStatusListener((AnimationStatus status) {
      if (status == AnimationStatus.completed) {
        _controller.repeat(reverse: true);
      }
    });
  }

Будьте осторожны — мы играем с огнем. Если я выставлю граничное значение 0.0, то появится ошибка. Для тех, кто уже работал с Flutter, это совсем не новость. Да, перед вами — ошибка переполнения, она же — overflow error. Мы не можем быть уверенными в том, что значения в этих границах будут меняться. Особенно в случае, если эффект анимации (эластичный) выйдет за пределы границ. Запомните, что отступы не могут иметь отрицательного значения. 

Теперь, когда мы настроили все необходимые параметры, самое время воспользоваться анимацией.

body:
      AnimatedBuilder(animation: _slideAnimation,
          builder: (ctx, ch) =>  Container(
            width: 100,
            height: 100,
            margin: EdgeInsets.only(
                top: _slideAnimation.value,
                left: 125
            ),
            decoration: BoxDecoration(
                image: new DecorationImage(
                    image: new AssetImage(
                      'assets/images/bounce-ball.png',
                    )
                )
            ),
          )
      ),

05. 3D-переворот

Flip-анимация — это простой и прикольный эффект, который легко добавить в приложение. (Одна из моих любимых анимаций). Давайте посмотрим, как все реализовать.

AnimationController _controller;
  Animation _myAnimation;
  AnimationStatus _animationStatus = AnimationStatus.dismissed;

  @override
  void initState() {

    super.initState();
    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 1));
    _myAnimation = Tween(end: 1.0, begin: 0.0).animate(_controller)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((status) {
        _animationStatus = status;
      });
  }

Здесь появляется кое-что новенькое — AnimationStatus. Им я пользовался для реализации прямых и обратных функций. Этот дополнительный виджет помогает мне получить текущий статус анимации.

Тут я воспользовался простым контейнером, чтобы показать flip-анимацию в действии.

body: Center(
          child: Transform(
            alignment: FractionalOffset.center,
            transform: Matrix4.identity()
              ..setEntry(3, 2, 0.002)
              ..rotateX(pi*(_myAnimation.value)),
            child: Container(
              color: Colors.blueAccent,
              width: 200,
              height: 200,
              child: Icon(
                Icons.accessibility_new,
                color: Colors.white,
                size: 50,
              ),
            ),
          )
      ),

06. Вращение с падением

Перейдем к более сложным примерам. Здесь я объединил сразу несколько эффектов. Мы называем это последовательностью анимации. Посмотрите, как можно определить анимацию врещения.

AnimationController _controller;

  Animation _rotateAnimation;
  Animation<double> _slideAnimation;
  Animation<double> _opacityAnimation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 3000),
    );

    _rotateAnimation = Tween(end: 0.15, begin: 0.0)
        .animate(
      CurvedAnimation(
          parent: _controller,
          curve: Interval(0.0, 0.5, curve: Curves.bounceInOut),
      ),
    );

    _slideAnimation = Tween(begin: 100.0, end: 600.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
      ),
    );

    _opacityAnimation = Tween(begin: 1.0, end: 0.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
      ),
    );

  }

Обратите внимание, что при определении анимации мы пользуемся 3 отдельными виджетами Animation. И для всех них берется один AnimationController. А еще я передавал разные значения для каждого виджета Interval. Именно так и задается последовательность анимации.

Теперь давайте посмотрим, как работает последовательность анимации:

body:
      AnimatedBuilder(
        animation: _slideAnimation,
        builder: (ctx, ch) => Container(
          width: 200,
          height: 100,
          padding: EdgeInsets.all(0),
          margin: EdgeInsets.only(
            left: 75,
            top: _slideAnimation.value,
          ),
          child: RotationTransition(
            turns: _rotateAnimation,
            child: Center(
              child: Text('Animation', style: TextStyle(
                  fontSize: 40,
                  fontWeight: FontWeight.bold,
                  color: Color.fromRGBO(0, 0, 128, _opacityAnimation.value)
              ),),
            ),
          ),
        ),
      ),

Все правильно! Нам нужен еще один виджет –AnimatedBuilder. Затем мы можем поменять значения контейнера. В этом примере я изменял отступы (для смены положения объекта) и непрозрачность.

Примеры анимации можно посмотреть на Github

Читайте также:


Перевод статьи Sahan Amarsha: Cool Flutter Animations That You Can Try

Предыдущая статьяКак оптимизировать пулл-реквесты и порадовать тех, кто проверяет ваш код
Следующая статьяКак улучшить производительность сайта с помощью мониторинга реальных пользователей