如何在Flutter中实现背景雨滴表情动画(附示例参考)
Flutter 背景雨滴表情动画实现方案
核心思路
用Stack作为底层容器,在其之上生成多个独立的雨滴表情组件,每个组件通过AnimationController控制下落位移、透明度、缩放等动画参数,模拟雨滴从顶部下落并消失的效果;同时随机生成雨滴的初始位置、下落速度和大小,让动画更自然。
实现步骤
1. 编写单个雨滴动画组件
创建RaindropEmoji组件,负责单个雨滴的完整动画逻辑:
import 'dart:math'; import 'package:flutter/material.dart'; class RaindropEmoji extends StatefulWidget { final String emoji; final double screenWidth; final double screenHeight; const RaindropEmoji({ super.key, required this.emoji, required this.screenWidth, required this.screenHeight, }); @override State<RaindropEmoji> createState() => _RaindropEmojiState(); } class _RaindropEmojiState extends State<RaindropEmoji> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _fallAnimation; late Animation<double> _opacityAnimation; late Animation<double> _scaleAnimation; final Random _random = Random(); late double _startX; late double _fallSpeed; late double _size; @override void initState() { super.initState(); // 初始化随机参数 _startX = _random.nextDouble() * widget.screenWidth; _fallSpeed = 1.5 + _random.nextDouble() * 2; _size = 16 + _random.nextDouble() * 12; _controller = AnimationController( vsync: this, duration: Duration(seconds: 3 + _random.nextInt(3)), ); // 下落动画:从屏幕顶部外落到底部外 _fallAnimation = Tween<double>( begin: -_size, end: widget.screenHeight + _size, ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); // 透明度动画:渐入后渐出 _opacityAnimation = Tween<double>( begin: 0, end: 1, ).animate(CurvedAnimation( parent: _controller, curve: const Interval(0.1, 0.9, curve: Curves.easeInOut), )); // 缩放动画:轻微放大后缩小 _scaleAnimation = Tween<double>( begin: 0.8, end: 1.2, ).animate(CurvedAnimation( parent: _controller, curve: const Interval(0.2, 0.8, curve: Curves.easeInOut), )); // 循环播放动画 _controller.repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Positioned( left: _startX, top: _fallAnimation.value, child: Opacity( opacity: _opacityAnimation.value, child: Transform.scale( scale: _scaleAnimation.value, child: Text( widget.emoji, style: TextStyle(fontSize: _size), ), ), ), ); }, ); } }
2. 构建背景动画容器
创建RaindropEmojiBackground组件,用于包裹页面内容并生成雨滴背景:
class RaindropEmojiBackground extends StatefulWidget { final Widget child; final List<String> emojis; const RaindropEmojiBackground({ super.key, required this.child, this.emojis = const ["🌧️", "💧"], }); @override State<RaindropEmojiBackground> createState() => _RaindropEmojiBackgroundState(); } class _RaindropEmojiBackgroundState extends State<RaindropEmojiBackground> { late Size _screenSize; final int _raindropCount = 20; // 雨滴数量可按需调整 @override void didChangeDependencies() { super.didChangeDependencies(); _screenSize = MediaQuery.of(context).size; } @override Widget build(BuildContext context) { return Stack( children: [ // 生成雨滴背景层 ...List.generate(_raindropCount, (index) { final emoji = widget.emojis[index % widget.emojis.length]; return RaindropEmoji( emoji: emoji, screenWidth: _screenSize.width, screenHeight: _screenSize.height, ); }), // 前景内容层 widget.child, ], ); } }
3. 页面使用示例
直接用RaindropEmojiBackground包裹需要添加雨滴背景的页面内容:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: RaindropEmojiBackground( emojis: ["🌧️", "💧", "☔"], child: const Center( child: Text( "雨滴表情背景演示", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ), ), ); } }
可优化方向
- 添加横向摆动动画,模拟风吹效果,让雨滴下落轨迹更真实
- 根据屏幕尺寸动态调整雨滴数量,避免小屏幕过于密集
- 增加动画暂停/恢复逻辑,页面不可见时暂停动画节省资源
- 自定义雨滴初始位置范围,避免过于靠近屏幕边缘
内容的提问来源于stack exchange,提问作者AnilGusaiwal
相关产品推荐
相关产品推荐

