Снимка на Рей Хенеси на Unsplash

Как да създадете анимации във Flutter с Redux?

Добре дошъл обратно!

Днес ще измислим как да създаваме анимации във Flutter, когато използваме Redux архитектура. Ако не знаете какво е Redux, съветвам ви да прочетете публикацията ми с урока за Redux - Flutter + Redux - Как да направите приложение за списък за пазаруване

Ако сте запознати с Redux, вероятно знаете, че това може да улесни живота ви с архитектурата на приложението ви много. Той е чудесен инструмент за разделяне на вашата презентация и бизнес логика. Но какво да кажем за комбинацията от Flutter, Redux и анимации?

Хайде да пазаруваме!

В тази статия ще използваме пакет flutter_redux.

Да приемем прост случай: имаме приложение за пазаруване с количка, което изглежда така:

Приложение за списък за пазаруване

Можем да добавяме и премахваме продукти от списъка.

Ако искате да видите останалата част от кода (действия, редуктори и т.н.), можете да проверите пълното хранилище тук: pszklarska / flutter_redux_animations_app

Това, което искаме да постигнем, е да покажем текущата стойност на количката в лентата в долната част на екрана. За да усложним нещата малко, ще го покажем с анимацията така:

Приложение за списък за пазаруване (с анимация!)

Както можете да видите, когато добавяме някои продукти в празната количка, бихме искали да покажем долната лента с слайд в анимация. И когато всички продукти бъдат премахнати, бихме искали да изчезнат.

Как можем да го направим?

Първо - нека проверим стойността на количката ни

Ще започнем първо с добавяне на обикновена лента в долната част на екрана, показваща текущата стойност на количката. Разгледайте кода за тази лента:

import 'пакет: flutter / material.dart';

const CART_BAR_HEIGHT = 48.0;

клас CartValueBar удължава StatelessWidget {
  крайна двойна количкаValue;

  const CartValueBar ({Ключ за ключ, this.cartValue})
      : супер (ключ: ключ);

  @override
  Изграждане на приспособления (контекст на BuildContext) {
    връщане количкаValue! = 0
        ? Контейнер(
            височина: CART_BAR_HEIGHT,
            цвят: цветове.оранжево,
            дете: Ред (
              деца: <Джаджа> [
                Padding (
                  подплънки: const EdgeInsets.all (16.0),
                  дете: текст (
                    „Стойност на кошницата $ {_ getCartValue ()} zł“,
                  ),
                ),
              ],
            ),
          )
        : Контейнер();
  }

  String _getCartValue () => cartValue.toStringAsFixed (2);
}
Вижте пълния изходен код за този файл в GitHub тук.

В този код имаме свойство cartValue. В зависимост от това свойство, или показваме долната лента на количката със стойност на количката, или един контейнер. На този етап екранът ни изглежда така:

Приложение за списък за пазаруване (без анимация)

Както можете да видите, в момента нямаме фантазия (всъщност нямаме никаква анимация). За да работи, трябва да направим няколко промени в нашата джаджа.

Покажете ми хубава анимация!

Първият е да преобразувате нашия CartValueBar в StatefulWidget:

клас CartValueBar разширява StatefulWidget {
  крайна двойна количкаValue;

  const CartValueBar ({Key key, this.cartValue}): супер (ключ: ключ);

  @override
  _CartValueBarState createState () => _CartValueBarState ();
}
клас _CartValueBarState разширява състояние  {

  @override
  Изграждане на приспособления (контекст на BuildContext) {
    // TODO тук ще вмъкнем CartValueBar код
    връщане контейнер ();
  }

}

Трябва да направим приспособление щадящо, защото в по-късните стъпки на внедряване на анимация ще ни трябва нещо, наречено SingleTickerProviderStateMixin. Този микс предотвратява анимациите на Flutter да консумират ресурси, когато вече не са необходими (напр. Вече не се виждат).

Можете да прочетете повече за въвеждането на анимации като цяло в ръководството за Flutter Animations.

Сега, когато нашата джаджа е състоятелна, можем да добавим SingleTickerProviderStateMixin, Animation и AnimationController към CartValueBar:

клас _CartValueBarState разширява състояние 
    с SingleTickerProviderStateMixin {
  AnimationController _controller;
  Анимация <двойно> _ анимация;

  @override
  void initState () {
    super.initState ();

    _controller = AnimationController (
      vsync: това,
      продължителност: продължителност (милисекунди: 300),
    ) .. addListener (() {
      setState (() {});
    });

    _animation = Tween <двойник> (
      започнете: CART_BAR_HEIGHT,
      край: 0,
    ) .Animate (
      CurvedAnimation (
        родител: _контролер,
        крива: Curves.easeOut,
        reverseCurve: Curves.easeIn,
      ),
    );
  }

  ...

  @override
  void dispose () {
    super.dispose ();
    _controller.dispose ();
  }
}
Вижте пълния изходен код за този файл в GitHub тук.

В началото първо имаме AnimationController, който по принцип отговаря за изпълнението и контрола на анимацията - стартиране, спиране, пауза и т.н.

Следващият е обект Animation, който декларира по-конкретна информация за анимацията:

  • какви са анимационните стойности? В нашия случай започвайки от височината на лентата и завършвайки на нула, тъй като това ще бъде нашето изместване. Така че лентата ще бъде първо под екрана, когато отместването е равно на височината на лентата, и в долната част на екрана, когато отместването е нула
Забележка: Положението на екрана започва в левия горен ъгъл и се увеличава с преминаването към десния долен ъгъл. Така че, ако искаме да скрием долната лента, трябва да намалим стойността на позицията.
  • какъв е анимационният интерполатор? В нашия случай ще използваме CurvedAnimation с Curves.easeOut и Curves.easeIn интерполатори, така че анимацията ще започне бързо и завършва бавно, когато се показва лента и обратното, когато се скрие

За да стартирате анимация, просто ще се обадим напред () на контролера. И накрая, трябва да извикаме dispose () на AnimationController, когато вече не е необходимо. Ако искаме да стартираме анимация в обратна посока, ще използваме методът reverse ().

Сега, когато нашата анимация е инициализирана, трябва да зададем анимационните стойности на компенсирането на лентата, така че когато анимацията ще премине от височина на лента до 0, нашата лента ще се преведе от тези стойности. Ще използваме Widget Transform, за да го направим:

клас _CartValueBarState разширява състояние 
    с SingleTickerProviderStateMixin {

  ...

  @override
  Изграждане на приспособления (контекст на BuildContext) {
    return widget.cartValue! = 0
        ? Изравнете (
            подравняване: Alignment.bottomCenter,
            дете: Transform.translate (// променено)
              офсет: Отместване (0, _animation.value), // променено
              дете: контейнер (
                цвят: цветове.оранжево,
                дете: Ред (
                  деца: <Джаджа> [
                    Padding (
                      подплънки: const EdgeInsets.all (16.0),
                      дете: текст (
                        „Стойност на кошницата $ {_ getCartValue ()} zł“,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          )
        : Контейнер();
  }

  ...
}
Вижте пълния изходен код за този файл в GitHub тук.

Добре, така че сега имаме нашия набор за анимация, стойностите са присвоени на компенсация на превода, но какво липсва? Действителна анимация работи.

Откъде да започнем анимацията?

За да решим на кое място в кода трябва да поставим начална точка на анимацията, трябва да знаем едно важно нещо за Redux - всеки път, когато магазинът излъчва някакво събитие за промяна, нашите джунджурии се възстановяват. Така че всъщност с всяка актуализация ние знаем само за текущите стойности (в нашия случай - текущата стойност на количката). Не е достатъчно да направим анимация, защото за да я направим правилно, трябва да знаем и за предишното състояние:

  • искаме да покажем лентата на количката, когато стойността на предишната количка беше нула
  • искаме да скрием лентата на количката, когато текущата стойност на количката е нула

Не можем да поставим анимацията да започне вътре в метода initState (), тъй като той се нарича само когато е създадено състояние на джаджи. След промените в количкатаValue, нашата джаджа се актуализира, така че трябва да потърсим актуализация на обратно повикване. За щастие за нас, ние го имаме! Методът, който търсим, се нарича: didUpdateWidget (). Можем да прочетем в документацията:

Ако родителската джаджа се възстанови и поиска това местоположение в дървото да се актуализира, за да се покаже нова джаджа със същия runtimeType и Widget.key, рамката ще актуализира свойството на джаджата на този обект на състояние, за да се позове на новата джаджа и след това да извика този метод с предишната джаджа като аргумент.
Отменете този метод, за да отговорите, когато джаджата се промени (например, за да стартирате неявни анимации).

Страхотен! Можем да го използваме за нашата цел. В този метод ще получим състояние на старата Widget, така че можем да реагираме според предишните стойности. Нека да разгледаме кода след това:

@override
void didUpdateWidget (CartValueBar oldWidget) {
  super.didUpdateWidget (oldWidget);

  ако (oldWidget.cartValue == 0) {
    _controller.forward ();
  }

  ако (widget.cartValue == 0) {
    _controller.reverse ();
  }
}
Вижте пълния изходен код за този файл в GitHub тук.

Тук проверяваме за две условия. Първата ситуация е, когато стойността на количката ни беше преди това 0, това означава, че трябва да покажем нашата лента за колички (така че се обадете напред () на анимационен контролер).

Втората ситуация е, когато новата стойност на количката е 0, така че трябва да скрием лентата на количката (повикване обратно (), за да играем анимация в обратна посока).

Нека проверим как работи!

Голямата анимация е почти там ...

Страхотно, сега имаме анимация! Но може би сте забелязали, че има малък проблем при скриване на кола. Това се случва, защото текстът се променя на „0,00“ точно преди да излезе от екрана. Можем да поправим това с просто if:

Padding (
  подложка: const EdgeInsets.symmetric (хоризонтална: 16.0),
  дете: текст (
    widget.cartValue! = 0
        ? 'Стойност на количката $ {_ getCartValue ()} zł'
        : '',
  ),
),

Сега отново стартирайте нашата анимация:

Краен ефект!

Ура! Това е ефектът, който искахме! Имаме готина анимация в нашето приложение сега

Готино, краят е! Хм .. е?

Всъщност бихме могли да спрем до тук и да завършим статията. Постигнахме това, което искахме. Но ей, ние сме разработчици на Flutter, знаем, че във Flutter всичко е джаджа, така че… може би вече има джаджа, която може да направи цялата тази работа „didUpdateWidget ()“ за нас? Разбира се, че е.

Нека ви представя Анимиран Контейнер:

Контейнер, който постепенно променя стойностите си за определен период от време. AnimatedContainer автоматично ще анимира между старите и новите стойности на свойствата, когато те се променят, използвайки предоставената крива и продължителност.

AnimatedContainer е, както подсказва името, джаджа Container, която може да бъде анимирана. Ако искате да прочетете повече за него (или да видите страхотни анимации, които могат да се правят с него!), Можете да проверите тази страхотна статия от Pooja Bhaumik: Flutter Animated Series: Анимирани контейнери. Фактът, който е най-интересният за нас е, че AnimatedContainer използва метода didUpdateWidget под капака, така че върши цялата работа за нас. Да пробваме!

По принцип ще премахнем целия код, отговорен за контрола на анимациите и вместо това ще поставим AnimatedContainer:

import 'пакет: flutter / material.dart';
const CART_BAR_HEIGHT = 48.0;
клас CartValueBar удължава StatelessWidget {
  крайна двойна количкаValue;

  const CartValueBar ({Ключ за ключ, this.cartValue})
      : супер (ключ: ключ);

  @override
  Изграждане на приспособления (контекст на BuildContext) {
    върнете AnimatedContainer (// променен
      височина: CART_BAR_HEIGHT,
      продължителност: Продължителност (милисекунди: 300), // променена
      transform: Matrix4.translationValues ​​(// променена
          0, cartValue! = 0? 0: CART_BAR_HEIGHT, 0), // променена
      цвят: цветове.оранжево,
      дете: Ред (
        деца: <Джаджа> [
          Padding (
            подплънки: const EdgeInsets.symmetric (
                хоризонтална: 16.0),
            дете: текст (
              cartValue! = 0
                  ? 'Стойност на количката $ {_ getCartValue ()} zł'
                  : '',
            ),
          ),
        ],
      ),
    );
  }

  String _getCartValue () => cartValue.toStringAsFixed (2);
}
Вижте пълния изходен код за този файл в GitHub тук.

Както можете да видите, трябваше да добавим два параметъра към новосъздадения ни AnimatedContainer - продължителност на стойността на анимацията и трансформацията с превод. Ако стойността на количката е различна от нулата, стойността на превода е нула (контейнерът е в долната част на екрана), ако стойността на количката е по-голяма от нула, стойността на превода е равна на височината на лентата (контейнерът е под екрана). Всеки път, когато тази джаджа се актуализира (чрез излъчване на ново събитие в магазина от Redux), AnimatedContainer променя стойности на трансформация с хубава анимация!

Това е краят!

Добре, това е истинският край на тази статия.

В заключение, сега знаем как да създаваме прости анимации, които могат да бъдат полезни, когато имате Redux архитектура. Също така знаем повече за AnimatedContainer. Сега е ваш ред да превърнете това знание в супер-фантастични анимации с Flutter!

Ако искате да проверите кода за пълното приложение, можете да го намерите тук:

Това е всичко! Благодаря, че прочетете статията ми