Flutter动画组合插件combined_animation的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter动画组合插件combined_animation的使用

combined_animation 是一个用于创建复杂且优雅的进入和离开动画组合的Flutter插件。它允许开发者轻松地将对齐、透明度、变换(如旋转、缩放等)、大小等动画效果应用到小部件上,从而实现丰富的视觉体验。

Features

  • ✅ 对齐动画
  • ✅ 透明度动画
  • ✅ 变换动画
  • ✅ 大小动画
  • ✅ 进入动画配置
  • ✅ 离开动画配置
  • ✅ 消失时的大小动画

preview

Getting Started

在开始之前,请确保你已经在项目的pubspec.yaml文件中添加了combined_animation依赖:

flutter pub add combined_animation

然后,在你的Dart代码中导入这个包:

import 'package:combined_animation/combined_animation.dart';

Usage

下面是一个简单的例子,展示了如何使用CombinedAnimation来创建带有动画效果的小部件:

示例Demo

我们将创建一个包含多个动画项的应用程序,每个项都可以通过点击按钮进行删除,并伴有相应的动画效果。此外,我们还会演示如何通过状态和控制器来控制动画。

main.dart

// ignore_for_file: avoid_print

import 'dart:math' as math;

import 'package:combined_animation/combined_animation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Animation Demo Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final widgets = <Widget>[];
  final configs = <AnimationConfig>[];
  final keys = <ValueKey<int>>[];
  final configList = [
    AnimationConfig.slideInFrom(const Alignment(0, -2)),
    AnimationConfig.slideInFrom(const Alignment(0, 2)),
    AnimationConfig.slideInFrom(const Alignment(-2, 0)),
    AnimationConfig.slideInFrom(const Alignment(2, 0)),
    AnimationConfig.slideAndFadeInFrom(const Alignment(0, -2)),
    AnimationConfig.slideAndFadeInFrom(const Alignment(0, 2)),
    AnimationConfig.slideAndFadeInFrom(const Alignment(-2, 0)),
    AnimationConfig.slideAndFadeInFrom(const Alignment(2, 0)),
    AnimationConfig.zoomIn,
    AnimationConfig.fadeIn,
    AnimationConfig.fadeAndZoomIn,
    AnimationConfig.vFlipIn,
    AnimationConfig.hFlipIn,
    AnimationConfig.zoomIn.copyWith(curve: Curves.bounceOut),
    AnimationConfig.fadeAndZoomIn.copyWith(curve: Curves.bounceOut),
    AnimationConfig.vFlipIn.copyWith(curve: Curves.bounceOut),
    AnimationConfig.hFlipIn.copyWith(curve: Curves.bounceOut),
    AnimationConfig.fadeIn.copyWith(
      startTransform: Matrix4.identity()..rotateZ(math.pi / 2),
      endTransform: Matrix4.identity(),
      curve: Curves.bounceOut,
    )
  ];

  final controller = ScrollController();

  /// generate a new animation item
  void _incrementCounter() {
    setState(() {
      final index = widgets.length;
      widgets.add(Container(
        width: 100.0,
        height: 40.0,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Colors.primaries[index % 18],
              Colors.primaries[(index + 1) % 18],
              Colors.primaries[(index + 2) % 18],
            ],
          ),
          borderRadius: const BorderRadius.all(Radius.circular(10)),
        ),
      ));
      configs.add(configList[index % configList.length]);
      keys.add(ValueKey(index == 0 ? 0 : keys.last.value + 1));
    });
    WidgetsBinding.instance.scheduleFrameCallback((timeStamp) {
      controller.animateTo(
        controller.position.maxScrollExtent,
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeIn,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SafeArea(
        child: Column(
          children: [
            const StateDemoWidget(),
            const ControlDemoWidget(),
            Expanded(
              child: ListView.builder(
                controller: controller,
                itemCount: widgets.length,
                padding: const EdgeInsets.only(bottom: 80),
                findChildIndexCallback: (key) {
                  final index = keys.indexOf(key as ValueKey<int>);
                  return index > -1 ? index : null;
                },
                itemBuilder: (context, index) {
                  return AnimateItem(
                    key: keys[index],
                    animate: configs[index],
                    onDismiss: () {
                      setState(() {
                        widgets.removeAt(index);
                        configs.removeAt(index);
                        keys.removeAt(index);
                      });
                    },
                    child: widgets[index],
                  );
                },
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'AddItem',
        child: const Icon(Icons.add),
      ),
    );
  }
}

/// Generate an Anitate Widget control by button
class AnimateItem extends StatefulWidget {
  const AnimateItem({
    Key? key,
    required this.child,
    required this.animate,
    this.onDismiss,
    this.height = 60.0,
  }) : super(key: key);

  final Widget child;
  final VoidCallback? onDismiss;
  final double height;
  final AnimationConfig animate;

  @override
  State<AnimateItem> createState() => _AnimateItemState();
}

class _AnimateItemState extends State<AnimateItem> {
  bool isDissmissing = false;
  bool isLeave = false;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        ConstrainedBox(
          constraints: isDissmissing
              ? BoxConstraints.loose(Size.infinite)
              : BoxConstraints.tight(Size.fromHeight(widget.height)),
          child: Center(
            child: CombinedAnimation(
              state: isLeave ? AnimationType.end : AnimationType.start,
              onLeaved: () {
                setState(() {
                  isDissmissing = true;
                });
              },
              onDismiss: widget.onDismiss,
              config: widget.animate,
              child: widget.child,
            ),
          ),
        ),
        if (!isLeave)
          Positioned(
            right: 8,
            top: 0,
            bottom: 0,
            child: Center(
              child: OutlinedButton.icon(
                style: OutlinedButton.styleFrom(
                  shape: const StadiumBorder(),
                ),
                label: const Text('delete'),
                onPressed: () {
                  if (isLeave) return;
                  setState(() {
                    isLeave = true;
                  });
                },
                icon: const Icon(Icons.remove),
              ),
            ),
          ),
      ],
    );
  }
}

class StateDemoWidget extends StatefulWidget {
  const StateDemoWidget({Key? key}) : super(key: key);

  @override
  State<StateDemoWidget> createState() => _StateDemoWidgetState();
}

class _StateDemoWidgetState extends State<StateDemoWidget> {
  AnimationType? state = AnimationType.start;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Container(
          alignment: Alignment.center,
          height: 60,
          child: CombinedAnimation(
            state: state,
            config: AnimationConfig.vFlipIn,
            child: Container(
              width: 100.0,
              height: 40.0,
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.primaries[0][200]!,
                    Colors.primaries[1][200]!,
                    Colors.primaries[2][200]!,
                  ],
                ),
                borderRadius: const BorderRadius.all(Radius.circular(10)),
              ),
            ),
          ),
        ),
        const Positioned(
          left: 8,
          top: 0,
          bottom: 0,
          child: Align(
            alignment: Alignment.centerLeft,
            child: Text(
              'Control by state',
            ),
          ),
        ),
        Positioned(
          right: 8,
          top: 0,
          bottom: 0,
          child: Center(
            child: OutlinedButton.icon(
              style: OutlinedButton.styleFrom(
                shape: const StadiumBorder(),
              ),
              label: Text(
                state == null
                    ? 'Show'
                    : state == AnimationType.start
                        ? 'Hide'
                        : 'Reset',
              ),
              onPressed: () {
                final cState = (state?.index ?? -1) + 1;

                setState(() {
                  state = cState > 1 ? null : AnimationType.values[cState];
                });
              },
              icon: const Icon(Icons.animation),
            ),
          ),
        ),
      ],
    );
  }
}

/// control animation by controller
class ControlDemoWidget extends StatefulWidget {
  const ControlDemoWidget({Key? key}) : super(key: key);

  @override
  State<ControlDemoWidget> createState() => _ControlDemoWidgetState();
}

class _ControlDemoWidgetState extends State<ControlDemoWidget> {
  late CombinedAnimationController caController =
      CombinedAnimationController()
        ..addListener(() {
          print(
              '${caController.state} ${caController.isEntered} ${caController.isLeaved}');
          setState(() {});
        });

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Container(
          alignment: Alignment.center,
          height: 60,
          child: CombinedAnimation(
            config: AnimationConfig.vFlipIn,
            controller: caController,
            isControlled: true,
            child: Container(
              width: 100.0,
              height: 40.0,
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.primaries[9][200]!,
                    Colors.primaries[10][200]!,
                    Colors.primaries[11][200]!,
                  ],
                ),
                borderRadius: const BorderRadius.all(Radius.circular(10)),
              ),
            ),
          ),
        ),
        const Positioned(
          left: 8,
          top: 0,
          bottom: 0,
          child: Align(
            alignment: Alignment.centerLeft,
            child: Text(
              'Control by controller',
            ),
          ),
        ),
        Positioned(
          right: 8,
          top: 0,
          bottom: 0,
          child: Center(
            child: OutlinedButton.icon(
              style: OutlinedButton.styleFrom(
                shape: const StadiumBorder(),
              ),
              label: Text((caController.isLeaved)
                  ? 'Reset'
                  : (caController.isEntered)
                      ? 'Hide'
                      : 'Show'),
              onPressed: () {
                print(caController.isEntered);
                if (caController.isEntered) {
                  caController.leave();
                } else if (caController.isLeaved) {
                  caController.init();
                  setState(() {});
                } else {
                  caController.enter();
                }
              },
              icon: const Icon(Icons.animation),
            ),
          ),
        ),
      ],
    );
  }
}

在这个示例中,我们创建了一个可以动态添加和删除带动画效果的小部件的应用程序。每个小部件都有自己的动画配置,并且可以通过点击“delete”按钮触发消失动画。同时,我们还提供了两个演示区域,分别展示了如何通过状态和控制器来控制动画。


更多关于Flutter动画组合插件combined_animation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动画组合插件combined_animation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,combined_animation 并不是一个官方的插件或标准库中的组件。不过,基于你的需求,我们可以实现一个自定义的动画组合示例,通过组合多个动画来实现复杂的动画效果。

通常,我们会使用 AnimationControllerTween 以及多个 Animation 来实现组合动画。下面是一个简单的示例,展示了如何组合多个动画(例如,缩放和旋转)来实现一个复杂的动画效果。

首先,确保你的 Flutter 环境已经设置好,并且你已经在 pubspec.yaml 文件中添加了必要的依赖(虽然这个示例不需要额外的依赖,但一般开发过程中会用到一些)。

dependencies:
  flutter:
    sdk: flutter

接下来,我们编写一个包含组合动画的 Flutter 应用。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Combined Animation Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CombinedAnimationScreen(),
    );
  }
}

class CombinedAnimationScreen extends StatefulWidget {
  @override
  _CombinedAnimationScreenState createState() => _CombinedAnimationScreenState();
}

class _CombinedAnimationScreenState extends State<CombinedAnimationScreen> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _rotateAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true); // 无限循环动画,反向播放

    _scaleAnimation = Tween<double>(begin: 1.0, end: 1.5).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.0, 0.5, curve: Curves.easeInOut),
      ),
    );

    _rotateAnimation = Tween<double>(begin: 0.0, end: 2 * 3.141592653589793).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.0, 1.0, curve: Curves.linear),
      ),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Combined Animation Example'),
      ),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          child: Transform.scale(
            scale: 1.0,
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              child: Center(
                child: Text(
                  'Flutter',
                  style: TextStyle(color: Colors.white, fontSize: 24),
                ),
              ),
            ),
          ),
          builder: (context, child) {
            return Transform.rotate(
              angle: _rotateAnimation.value,
              child: Transform.scale(
                scale: _scaleAnimation.value,
                child: child,
              ),
            );
          },
        ),
      ),
    );
  }
}

代码解析

  1. 引入必要的包:我们引入了 flutter/material.dart 包。

  2. 创建应用入口MyApp 是一个 StatelessWidget,它创建了 MaterialApp 实例。

  3. 创建主屏幕CombinedAnimationScreen 是一个 StatefulWidget,用于包含和管理动画状态。

  4. 初始化动画控制器和动画

    • AnimationController 用于控制动画的时长和循环。
    • Tween 用于定义动画的起始值和结束值。
    • CurvedAnimationInterval 用于定义动画的曲线和播放时间区间。
  5. 组合动画

    • 使用 AnimatedBuilder 来构建动画。
    • 将缩放动画和旋转动画组合在一起。
  6. 释放资源:在 dispose 方法中释放 AnimationController

这个示例展示了如何通过组合多个动画来实现复杂的动画效果。在实际开发中,你可以根据需要调整动画的曲线、时长和区间,以及组合更多的动画效果。

回到顶部