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
}
相关推荐
大龄秃头程序员12 小时前
Riverpod 深度解析:从响应式本质到最小可运行闭环
flutter
曹小强wxx13 小时前
让 AI 写代码不“翻车”:用 Claude Code + Codex 给 Flutter 项目搭一套工程规范
flutter·ai编程·claude
西西学代码16 小时前
像素和颜色之间的转换(Flutter)
前端·javascript·flutter
曹小强wxx16 小时前
Flutter 局域网设备发现:UDP 广播 + mDNS 双路合并,以及靠谱的在线判定与断线重连
物联网·网络协议·flutter
好的佩奇18 小时前
Dart 之库与可见性
dart
FungLeo19 小时前
成为全栈·Flutter App 篇·移动端错误与限流:429、重试和写请求边界
flutter·限流·dart·dio·错误处理·成为全栈
恋猫de小郭19 小时前
Kotlin Toolchain 0.13,JetBrains 推荐 KMP 都切换到到全新工具链
android·前端·flutter
m0_7381858219 小时前
Flutter 鸿蒙化实战:rive 适配 OpenHarmony,Rive 动画
flutter·华为·harmonyos·鸿蒙
Geek_Vison1 天前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo1 天前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接