1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-N4.md 17 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Простые советы по анимациям в Flutter

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

Пример анимации

Анимационный эффект

На самом деле создание подобного анимационного эффекта в Flutter довольно просто, даже без необходимости создания собственного компонента. Для этого можно использовать встроенные контроллеры.

Для начала нам потребуются AnimatedPositioned и AnimatedContainer:

  • AnimatedPositioned используется для создания анимации перемещения внутри Stack
  • AnimatedContainer используется для создания анимации изменения размера

Затем мы определяем класс PositionItem, который объединяет AnimatedPositioned и AnimatedContainer. Мы также используем класс PositionedItemData для управления положением и размерами этих элементов.

class PositionItem extends StatelessWidget {
  final PositionedItemData data;
  final Widget child;

  const PositionItem(this.data, {required this.child});

  @override
  Widget build(BuildContext context) {
    return new AnimatedPositioned(
      duration: Duration(seconds: 1),
      curve: Curves.fastOutSlowIn,
      child: new AnimatedContainer(
        duration: Duration(seconds: 1),
        curve: Curves.fastOutSlowIn,
        width: data.width,
        height: data.height,
        child: child,
      ),
      left: data.left,
      top: data.top,
    );
  }
}

class PositionedItemData {
  final double left;
  final double top;
  final double width;
  final double height;

  PositionedItemData({
    required this.left,
    required this.top,
    required this.width,
    required this.height,
  });
}
```После этого достаточно поместить `PositionItem` внутрь `Stack`, а затем использовать `LayoutBuilder`, чтобы получить размер родительского контейнера и корректировать положение и размер `PositionItem` через `PositionedItemData`. Таким образом, можно легко воспроизвести начальный анимационный эффект.

```dart
child: LayoutBuilder(
  builder: (_, constraints) {
    var first = getIndexPosition(currentIndex % 3, constraints.biggest);
    var second = getIndexPosition((currentIndex + 1) % 3, constraints.biggest);
    var third = getIndexPosition((currentIndex + 2) % 3, constraints.biggest);
    return Stack(
      fit: StackFit.expand,
      children: [
        PositionItem(first,
          child: InkWell(
            onTap: () {
              print("красный");
            },
            child: Container(color: Colors.redAccent),
          )
        ),
        PositionItem(second,
          child: InkWell(
            onTap: () {
              print("зелёный");
            },
            child: Container(color: Colors.greenAccent),
          )
        ),
        PositionItem(third,
          child: InkWell(
            onTap: () {
              print("жёлтый");
            },
            child: Container(color: Colors.yellowAccent),
          )
        ),
      ],
    );
  },
),

Как показано ниже, достаточно каждый раз менять соответствующий индекс, чтобы изменить размер и положение соответствующего элемента, что приведёт к запуску анимации AnimatedPositioned и AnimatedContainer, создающей эффект, аналогичный начальной анимации.

Вычисление размера Эффект

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

Изменение любого параметра в AnimatedPositioned и AnimatedContainer позволяет им запустить анимацию, так как их параметры полностью совпадают с параметрами Positioned и Container. Поэтому они могут использоваться взаимозаменяемо, при этом требуется лишь простое конфигурирование дополнительных параметров типа duration.

Продвинутый уровень

А теперь вопрос, как AnimatedPositioned и AnimatedContainer запускают анимацию? Здесь мы рассмотрим абстрактный родительский класс ImplicitlyAnimatedWidget.

Почти все компоненты, начинающиеся со слова "Animated", наследуются от него. Так как он используется для анимации, то ImplicitlyAnimatedWidget обязательно является StatefulWidget. Соответственно, основная логика реализуется в ImplicitlyAnimatedWidgetState, которую мы будем использовать далее.

Сначала давайте вспомним, какие компоненты обычно используются в Flutter для создания анимации:- AnimationController: используется для управления запуском и остановкой анимации

  • TickerProvider: используется для создания параметра vsync для AnimationController, чаще всего это SingleTickerProviderStateMixin
  • Animation: используется для обработки значений анимации, например, часто используемый CurvedAnimation
  • Объект, принимающий анимацию: например, FadeTransitionКратко говоря, анимация в Flutter начинается с Ticker. Когда мы добавляем with TickerProviderStateMixin в State, это означает, что наш компонент способен выполнять анимацию. Каждый раз, когда Flutter рисует кадр, объект Ticker синхронизируется вызовом метода _tick в контроллере анимации AnimationController, после чего выполняется метод notifyListeners. Это приводит к изменению значения анимации Animation, что в свою очередь запускает метод setState состояния или метод markNeedsPaint объекта отрисовки, чтобы обновить интерфейс.

Например, рассмотрим следующий код:

class _AnimatedOpacityState extends State<AnimatedOpacity>
    with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  )..repeat(reverse: true);
  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.easeIn,
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: FadeTransition(
        opacity: _animation,
        child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()),
      ),
    );
  }
}

Для простой анимации требуется немало кода, который часто повторяется. Поэтому официальное API предоставляет шаблон ImplicitlyAnimatedWidget.

Если мы используем ImplicitlyAnimatedWidgetState, нам нужно реализовать только два метода — forEachTween и didUpdateTweens. Мы больше не должны беспокоиться о создании AnimationController и CurvedAnimation.

Пример использования ImplicitlyAnimatedWidgetState:```dart class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState { Tween? _opacity; late Animation _opacityAnimation;

@override void forEachTween(TweenVisitor visitor) { _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween(begin: value as double)) as Tween?; }

@override void didUpdateTweens() { _opacityAnimation = animation.drive(_opacity!); }

@override Widget build(BuildContext context) { return FadeTransition( opacity: _opacityAnimation, alwaysIncludeSemantics: widget.alwaysIncludeSemantics, child: widget.child, ); } }


Как же `ImplicitlyAnimatedWidgetState` позволяет менять значение `opacity` и запускать анимацию? Ключевой момент заключается в реализации метода `forEachTween`: когда обновляется значение `opacity`, вызывается метод `forEachTween`. Внутри этого метода через `_shouldAnimateTween` проверяется, было ли значение изменено. Если целевое значение действительно изменилось, выполняется метод `AnimationController.forward` базового класса для запуска анимации. ![image-20220611170418125](http://img.cdn.guoshuyu.cn/20220619_N4/image4.png)

> В этом разделе добавлено следующее содержание: внутренне в `FadeTransition` добавляется совместимость с `_opacityAnimation`. Когда `AnimationController` начинает выполнение анимации, это приводит к вызову слушателя `_opacityAnimation`, который затем запускает метод `markNeedsPaint`. **Как показано на нижеприведённой图为所指示**, `markNeedsPaint` в конечном итоге вызывает перерисовку объекта `RenderObject`.

![image-20220611173533772](http://img.cdn.guoshuyu.cn/20220619_N4/image5.png)
Итак, мы узнали, что: **путём наследования от `ImplicitlyAnimatedWidget` и `ImplicitlyAnimatedWidgetState` можно более удобно реализовать некоторые эффекты анимации; многие стандартные анимационные эффекты в Flutter создаются именно таким образом**.> Кроме того, в шаблоне `ImplicitlyAnimatedWidget` помимо `ImplicitlyAnimatedWidgetState`, официальная документация также предоставляет другой подкласс `AnimatedWidgetBaseState`.

На самом деле большинство часто используемых нами анимационных компонентов в Flutter созданы путём использования шаблона `ImplicitlyAnimatedWidget`, как показано на нижеприведённом рисунке:

| `ImplicitlyAnimatedWidgetState`                               | `AnimatedWidgetBaseState`                                     |
| ------------------------------------------------------------- | ------------------------------------------------------------ |
| ![](http://img.cdn.guoshuyu.cn/20220619_N4/image6.png)        | ![](http://img.cdn.guoshuyu.cn/20220619_N4/image7.png)       |

Отличия между этими двумя состояниями можно понять следующим образом:

- В `ImplicitlyAnimatedWidgetState` используется для работы с различными контроллерами типа `*Transition`, такими как использование `FadeTransition` в `AnimatedOpacity` и `ScaleTransition` в `AnimatedScale`. **Потому что `ImplicitlyAnimatedWidgetState` не использует `setState`, а вместо этого обновляет интерфейс через вызов метода `markNeedsPaint` у объекта `RenderObject`.**

- **`AnimatedWidgetBaseState` добавляет автоматическое слежение за `setState` на основе `ImplicitlyAnimatedWidgetState`**, поэтому можно создавать более гибкие анимации, такие как `AnimatedPositioned` и `AnimatedContainer`, которые мы использовали ранее.

  ![](http://img.cdn.guoshuyu.cn/20220619_N4/image8.png)На самом деле `AnimatedContainer` является очень характерным примером такой реализации. Если вы посмотрите его исходный код, то заметите, что его реализация довольно проста, **необходимо просто реализовать соответствующие `Tween` для каждого параметра в методе `forEachTween`**.![](http://img.cdn.guoshuyu.cn/20220619_N4/image9.png)Например, изменения, которые мы внесли ранее, такие как `width` и `height`, фактически меняют `BoxConstraints` контейнера, поэтому соответствующее решение — это `BoxConstraintsTween`. **А `BoxConstraintsTween` наследует `Tween`, главным образом реализуя метод `lerp` этого класса.**

![](http://img.cdn.guoshuyu.cn/20220619_N4/image10.png)

В Flutter метод `lerp` используется для реализации интерполяции: например, во время анимации между двумя значениями `BoxConstraint` происходит линейная интерполяция, где t представляет собой значение времени анимации. Например:

> Вычисление промежуточных размеров при переходе от 100x100 до 200x200.

Как показано ниже с помощью наследования от `AnimatedWidgetBaseState` и использования метода `lerp` класса `ColorTween`, можно быстро создать следующий эффект плавной смены цвета текста.

| Код                                                                                                                   | Эффект                                                                                                               |
| ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20220619_N4/image11.png)                                                               | ![](http://img.cdn.guoshuyu.cn/20220619_N4/image12.gif)                                                             |

# Подведение итогов

В заключение стоит отметить, что в данной статье были рассмотрены:- Использование `AnimatedPositioned` и `AnimatedContainer` для быстрого создания эффекта смены анимации;
- Введение в `ImplicitlyAnimatedWidget` и описание того, как использовать `ImplicitlyAnimatedWidgetState` / `AnimatedWidgetBaseState` для упрощения процесса создания анимаций и быстрой реализации пользовательских анимаций.Имеете ли вы ещё какие-либо советы по использованию анимации в Flutter?

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master