You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter如何实现带扩散渐隐涟漪效果的圆形按钮?

Flutter 按钮涟漪扩散动画实现方案

目标涟漪效果参考动图

你当前的静态实现已经完成了基础布局,只需要补全动画逻辑即可实现白色圆形放大+透明度渐隐的涟漪效果,具体实现如下:

核心实现逻辑

  • 用AnimationController控制动画总时长、循环播放状态
  • 分别定义缩放、透明度两个补间动画,控制涟漪的尺寸变化和透明度过渡
  • 改造自定义画笔CirclePainter,支持传入动画参数动态绘制不同状态的圆形
  • 用AnimatedBuilder监听动画值变化,驱动画布实时重绘

完整代码实现

1. 封装涟漪动画组件

class RippleEffect extends StatefulWidget {
  const RippleEffect({super.key, required this.buttonDiameter});
  // 传入中心GO按钮的直径,保证涟漪和按钮起始尺寸对齐
  final double buttonDiameter;

  @override
  State<RippleEffect> createState() => _RippleEffectState();
}

class _RippleEffectState extends State<RippleEffect> with SingleTickerProviderStateMixin {
  late AnimationController _rippleController;
  late Animation<double> _scaleAnim;
  late Animation<double> _opacityAnim;

  @override
  void initState() {
    super.initState();
    // 动画总时长1.5秒,可按需调整
    _rippleController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1500),
    )..repeat(); // 设置为循环播放

    // 涟漪从按钮等大放大到2.5倍,可按需调整放大倍率
    _scaleAnim = Tween<double>(begin: 1, end: 2.5).animate(
      CurvedAnimation(parent: _rippleController, curve: Curves.easeOut),
    );

    // 涟漪从完全不透明过渡到完全透明
    _opacityAnim = Tween<double>(begin: 1, end: 0).animate(
      CurvedAnimation(parent: _rippleController, curve: Curves.easeOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _rippleController,
      builder: (context, child) {
        return CustomPaint(
          size: Size(widget.buttonDiameter, widget.buttonDiameter),
          painter: CirclePainter(
            scale: _scaleAnim.value,
            opacity: _opacityAnim.value,
          ),
        );
      },
    );
  }
}

2. 改造自定义圆形画笔

class CirclePainter extends CustomPainter {
  CirclePainter({required this.scale, required this.opacity});
  final double scale;
  final double opacity;

  final _basePaint = Paint()
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;

  @override
  void paint(Canvas canvas, Size size) {
    // 动态设置透明度
    _basePaint.color = Colors.white.withOpacity(opacity);
    // 以中心为原点绘制缩放后的正圆,保证和按钮始终对齐
    final center = Offset(size.width / 2, size.height / 2);
    final currentRadius = (size.width / 2) * scale;
    canvas.drawCircle(center, currentRadius, _basePaint);
  }

  @override
  bool shouldRepaint(covariant CirclePainter oldDelegate) {
    // 动画值变化时触发重绘
    return oldDelegate.scale != scale || oldDelegate.opacity != opacity;
  }
}

3. 替换原有Stack布局代码

注意把涟漪层和按钮层的位置参数对齐,避免错位:

Stack(
  alignment: Alignment.center,
  children: [
    // 涟漪层:GO按钮加完内边距直径约为70,可根据实际显示效果微调
    Positioned(
      bottom: 15,
      child: RippleEffect(buttonDiameter: 70),
    ),
    // 原有GO按钮层
    Positioned(
      bottom: 15,
      child: InkWell(
        onTap: () {},
        child: Material(
          type: MaterialType.circle,
          color: Color(0xFF246DE9),
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Text(
              'GO',
              style: TextStyle(
                fontSize: 25,
                color: Colors.white,
              ),
            ),
          ),
        ),
      ),
    ),
  ],
)

当前实现静态效果截图

效果可调参数

  • 修改动画控制器的duration值,可以调整涟漪扩散的速度
  • 修改缩放动画的end值,可以调整涟漪最终扩散的范围
  • 修改画笔的strokeWidth值,可以调整涟漪描边的粗细
  • 如果需要多层叠加涟漪,只需要在Stack里多加几个RippleEffect组件,给对应动画设置不同的启动延迟即可。

内容的提问来源于stack exchange,提问作者Ng Lok Chun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:06:42