Flutter可执行屏幕动画的AnimateView

18 篇文章 0 订阅

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

2.可自定义动画。

3.内置平移动画。

演示:

 代码:

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
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许天成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值