【Flutter】水波纹扩散动画

1.说明

中心的按钮缩放,按钮底部有水波纹无限扩散的效果。

2.UI效果

水波纹扩散动画

3.源码

dart 复制代码
import 'dart:async';
import 'package:flutter/material.dart';

// 水波纹扩散动画
// AnimationController 用完,需要自行销毁
class WaterRippleDiffusionAnimation extends StatefulWidget {
  const WaterRippleDiffusionAnimation({super.key});

  @override
  State<WaterRippleDiffusionAnimation> createState() =>
      _WaterRippleDiffusionAnimationState();
}

class _WaterRippleDiffusionAnimationState
    extends State<WaterRippleDiffusionAnimation>
    with SingleTickerProviderStateMixin {
  // 中心按钮的动画
  late AnimationController _centerCtrl;
  late Animation<double> _centerScaleAnimation;

  final centerTweenItems = [
    TweenSequenceItem(
        tween: Tween<double>(begin: 1.0, end: 1.1)
            .chain(CurveTween(curve: Curves.easeIn)),
        weight: 40),
    TweenSequenceItem(
        tween: Tween<double>(begin: 1.1, end: 1.0)
            .chain(CurveTween(curve: Curves.easeOut)),
        weight: 40),
    TweenSequenceItem(
        tween: Tween<double>(begin: 1.0, end: 1.0)
            .chain(CurveTween(curve: Curves.easeOut)),
        weight: 20)
  ];

  @override
  void initState() {
    super.initState();

    _centerCtrl = AnimationController(
        vsync: this, duration: Duration(milliseconds: 2200));
    _centerScaleAnimation = _centerCtrl.drive(TweenSequence(centerTweenItems));

    _centerCtrl.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text('水波纹扩散动画'),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            const RippleCircles(
              circleCount: 2,
              circleMilliSecondsInterval: 1000,
              circleAnimationMilliSeconds: 3000,
              tweenBegin: 100,
              tweenEnd: 200,
              curve: Curves.linear,
            ),
            ScaleTransition(
                scale: _centerScaleAnimation,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    gradient: LinearGradient(colors: [
                      const Color(0xFFD92F2F).withOpacity(0.9),
                      const Color(0xFFFF7367).withOpacity(0.9),
                    ], begin: Alignment.bottomCenter, end: Alignment.topCenter),
                    boxShadow: [
                      BoxShadow(
                          color: const Color(0xFFE94031).withOpacity(0.6),
                          blurRadius: 200),
                    ],
                  ),
                )),
          ],
        ),
      ),
    );
  }
}

// 扩散圆形
class RippleCircles extends StatefulWidget {
  final int? circleCount;
  // 动画时间间隔
  final int? circleMilliSecondsInterval;
  // 一个动画的持续时间
  final int? circleAnimationMilliSeconds;
  final double? tweenBegin;
  final double? tweenEnd;
  final Curve? curve;

  const RippleCircles(
      {super.key,
      this.circleCount = 3,
      this.circleMilliSecondsInterval = 1000,
      this.circleAnimationMilliSeconds = 4000,
      this.tweenBegin = 100,
      this.tweenEnd = 300,
      this.curve = Curves.linear});

  @override
  State<RippleCircles> createState() => _RippleCirclesState();
}

class _RippleCirclesState extends State<RippleCircles>
    with TickerProviderStateMixin {
  //动画控件集合
  final List<Widget> _children = [];
  //动画控制器
  final List<AnimationController> _controllers = [];
  //添加动画计时器
  Timer? _addTimer;
  int _count = 0;

  @override
  void initState() {
    super.initState();
    _count = widget.circleCount!;
    _startAnimation();
  }

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

  void _startAnimation() {
    // 每隔1秒添加一个扩散圆形,总共创建_count个
    _addTimer = Timer.periodic(
        Duration(milliseconds: widget.circleMilliSecondsInterval!), (timer) {
      if (_count >= 1) {
        _addAnimation(isInit: true);
      } else {
        _addTimer?.cancel();
      }
    });
  }

  void _addAnimation({bool isInit = true}) {
    // 创建动画控制器
    var controller = AnimationController(
        duration: Duration(milliseconds: widget.circleAnimationMilliSeconds!),
        vsync: this);

    _controllers.add(controller);
    _count--;

    // 创建Tween
    var animation = Tween(begin: widget.tweenBegin, end: widget.tweenEnd)
        .animate(CurvedAnimation(parent: controller, curve: widget.curve!));

    var child = AnimatedBuilder(
        animation: controller,
        builder: (BuildContext context, Widget? child) {
          return Opacity(
            // opacity: 1.0 -
            //     ((animation.value - widget.tweenBegin!) /
            //         (widget.tweenEnd! - widget.tweenBegin!)),
            opacity: 0.65 -
                ((animation.value - widget.tweenBegin!) /
                        (widget.tweenEnd! - widget.tweenBegin!)) *
                    0.65,
            child: Container(
              width: animation.value,
              height: animation.value,
              decoration: const BoxDecoration(
                  color: Color(0xFFFF867B),
                  // border: Border.all(color: const Color(0xFFFF867B), width: 2),
                  shape: BoxShape.circle),
            ),
          );
        });

    _children.add(child);

    controller.repeat();

    if (mounted) {
      setState(() {});
    }
  }

  // 销毁所有动画
  void _disposeControllers() {
    for (var element in _controllers) {
      element.dispose();
    }
    _controllers.clear();
    _children.clear();
    if (_addTimer != null && _addTimer!.isActive) {
      _addTimer?.cancel();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: _children,
    );
  }
}
相关推荐
sunly_2 小时前
Flutter:导航固定背景图,滚动时导航颜色渐变
android·javascript·flutter
恋猫de小郭5 小时前
为什么跨平台框架可以适配鸿蒙,它们的技术原理是什么?
android·前端·flutter
SY.ZHOU5 小时前
详细讲解Flutter GetX的使用
flutter
sunly_5 小时前
Flutter:下拉框选择
flutter
明似水5 小时前
用 Melos 解决 Flutter Monorepo 的依赖冲突:一个真实案例
前端·javascript·flutter
张风捷特烈6 小时前
每日一题 Flutter#5,6 | 两道 Widget 选择题
android·flutter
玖夜Kty16 小时前
国内环境修改 flutter.bat 来设置 flutter 的网络环境
flutter
LinXunFeng18 小时前
Flutter - GetX Helper 助你规范应用 tag
flutter·github·visual studio code
阅文作家助手开发团队_山神1 天前
第五章:Flutter Quill渲染原理深度剖析:Delta到RichText的华丽转身
flutter
未来猫咪花1 天前
# Flutter状态管理对比:view_model vs Riverpod
flutter·ios·android studio