Flutter可执行屏幕动画的AnimateView

1.让动画使用起来就像使用widget。

2.可自定义动画。

3.内置平移动画。

演示:

代码:

Dart 复制代码
import 'dart:math';
import 'package:flutter/cupertino.dart';

class AnimateView extends StatefulWidget {
  ///子Widget
  final Widget child;

  ///动画自定义
  final IAnimate? animate;

  ///是否需要每次刷新时都执行动画
  final bool isNeedFlashEveryTime;

  const AnimateView({
    super.key,
    required this.child,
    this.animate,
    this.isNeedFlashEveryTime = false,
  });

  @override
  State<StatefulWidget> createState() => _AnimateViewState();
}

class _AnimateViewState extends State<AnimateView>
    with TickerProviderStateMixin {
  late IAnimate animate;
  late AnimationController controller;
  late Animation animation;

  @override
  void initState() {
    super.initState();
    animate = widget.animate ??
        TranslationAnimate(
            angle: TranslationAnimateDirection.rightToLeft.angle);
    animate.init();
    controller = animate.getAnimationController(this);
    animation = animate.getAnimation(controller, this);
    //启动动画(正向执行)
    controller.forward();
  }

  @override
  void didUpdateWidget(covariant AnimateView oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isNeedFlashEveryTime) {
      animate = widget.animate ??
          TranslationAnimate(
              angle: TranslationAnimateDirection.rightToLeft.angle);
      animate.init();
      controller = animate.getAnimationController(this);
      animation = animate.getAnimation(controller, this);
      //启动动画(正向执行)
      controller.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return animate.animate(context, widget.child, animation, controller);
  }
}

///动画抽象类。
///实现该类,定制自己的动画。
abstract class IAnimate {
  ///初始化
  void init();

  ///获取AnimationController
  AnimationController getAnimationController(TickerProvider provider);

  ///获取Animation
  Animation getAnimation(
      AnimationController controller, State<StatefulWidget> state);

  ///定制自己的动画,每一个item都会调用到animate,
  ///[widget] 执行动画之后的widget
  ///[index] 列表的item的index
  Widget animate(
    BuildContext context,
    Widget widget,
    Animation animation,
    AnimationController controller,
  );
}

///平移动画
class TranslationAnimate extends IAnimate {
  ///动画执行的总长度
  static double gap = 1000.0;

  ///动画执行角度
  final int angle;

  ///动画执行时长,毫秒(ms)
  final int duration;

  ///进入动画还是出去动画
  final TranslationAnimateType type;

  ///界面的宽,动画需要根据你需要操作的界面宽进行计算,不传代表屏幕宽
  final double? width;

  ///界面的高,动画需要根据你需要操作的界面高进行计算,不传代表屏幕高
  final double? height;

  TranslationAnimate({
    this.angle = 0,
    this.duration = 500,
    this.type = TranslationAnimateType.translateIn,
    this.width,
    this.height,
  });

  @override
  Widget animate(BuildContext context, Widget widget, Animation animation,
      AnimationController controller) {
    final size = MediaQuery.of(context).size;
    double width = this.width ?? size.width;
    double height = this.height ?? size.height;
    double left = 0;
    double top = 0;

    ///范围0.0->1000.0
    double animateValue = animation.value;
    int positiveAngle = angle;
    if (angle < 0) {
      int tempAngle = angle % 360;
      positiveAngle = 360 - tempAngle;
    }
    positiveAngle = positiveAngle % 360;

    ///范围0->1
    double rate = animateValue / gap;
    if (type == TranslationAnimateType.translateIn) {
      rate = rate - 1;
    }
    if (positiveAngle >= 0 && positiveAngle <= 45 ||
        positiveAngle >= 135 && positiveAngle <= 225 ||
        positiveAngle > 315 && positiveAngle <= 360) {
      ///移出距离以宽度为准
      left = rate * width;
      if (positiveAngle > 90 && positiveAngle < 270) {
        left = -left;
      }
      double tanValue = tan(positiveAngle * pi / 180);
      top = rate * width * tanValue;
    } else if (positiveAngle == 90) {
      top = rate * height;
      left = 0;
    } else if (positiveAngle == 270) {
      top = -rate * height;
      left = 0;
    } else {
      ///移出距离以高度为准
      top = rate * height;
      if (positiveAngle > 180 && positiveAngle < 360) {
        top = -top;
      }
      double tanValue = tan(positiveAngle * pi / 180);
      if (tanValue == 0) {
        left = 0;
      } else {
        left = rate * height / tanValue;
      }
    }

    //print("angle=$positiveAngle");
    //print("left=$left");
    //print("top=$top");

    return Container(
      transform: Matrix4.translationValues(left, top, 0),
      child: widget,
    );
  }

  @override
  Animation getAnimation(
      AnimationController controller, State<StatefulWidget> state) {
    return Tween(begin: 0.0, end: gap).animate(controller)
      ..addListener(() {
        if (state.mounted) {
          state.setState(() {});
        }
      });
  }

  @override
  AnimationController getAnimationController(TickerProvider provider) {
    return AnimationController(
        duration: Duration(milliseconds: duration), vsync: provider);
  }

  @override
  void init() {}
}

///平移动画执行方向枚举
enum TranslationAnimateDirection {
  ///从下往上
  bottomToTop(90),

  ///从上往下
  topToBottom(270),

  ///从左往右
  leftToRight(0),

  ///从右往左
  rightToLeft(180);

  ///动画执行方向度数
  final int angle;

  const TranslationAnimateDirection(this.angle);
}

///位移动画类型
enum TranslationAnimateType {
  ///出去动画
  translateOut,

  ///进入动画
  translateIn
}
相关推荐
一头小火烧9 小时前
flutter打包签名问题
flutter
sunly_9 小时前
Flutter:异步多线程结合
flutter
AiFlutter9 小时前
Flutter网络通信-封装Dio
flutter
B.-9 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克9 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
sunly_21 小时前
Flutter:父组件,向子组件传值,子组件向二级页面传值
flutter
爱学习的绿叶1 天前
flutter TabBarView 动态添加删除页面
flutter
趴菜小玩家1 天前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle
jhonjson2 天前
Flutter开发之flutter_local_notifications
flutter·macos·cocoa
iFlyCai2 天前
23种设计模式的Flutter实现第一篇创建型模式(一)
flutter·设计模式·dart