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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:52:53