Flutter 进阶:实现雪花下落动画效果

实现 Flutter 的雪花下落动画效果

在这篇文章中,我们将探讨如何使用 Flutter 创建一个逼真的雪花下落动画。通过这个示例,你将学会如何使用自定义绘制、动画控制器以及状态管理来实现复杂的动画效果。

效果展示视频地址:https://live.csdn.net/v/417736

资源文件下载地址:https://download.csdn.net/download/yang_6799/89641781

1. 项目初始化

首先,确保你已经安装了 Flutter 开发环境并创建了一个新的 Flutter 项目。如果还没有,可以参考 Flutter 官方文档 进行环境配置和项目创建。

复制代码
flutter create snow_fall_animation
cd snow_fall_animation

2. 创建 Snowflake 类

Snowflake 类代表一个雪花,包括其位置、速度、角度和旋转速度等属性。以下是 Snowflake 类的定义:

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

class Snowflake {
  double x, y, speed, angle, angularSpeed;

  Snowflake(this.x, this.y, this.speed, this.angle, this.angularSpeed);

  void updatePosition(double screenHeight) {
    y += speed;
    angle += angularSpeed; // 更新旋转角度
    if (y > screenHeight) {
      y = -10; // 重置雪花位置到屏幕顶部之外,使其重新开始下落
      x = Random().nextDouble() * screenHeight; // 随机重置x坐标
      angle = 0; // 重置角度
    }
  }
}

解释:

  • Snowflake 类有五个属性:x 和 y 表示位置;speed 表示下落速度;angle 和 angularSpeed 分别表示当前角度和旋转速度。
  • 构造函数用于初始化这些属性。
  • updatePosition 方法根据速度更新雪花的位置,当雪花超出屏幕底部时,它会重置位置和角度,使其再次从顶部开始下落。

3. 创建 SnowflakePainter 类

SnowflakePainter 是一个自定义画家,用于绘制雪花。它会遍历所有雪花并调用 drawSnowflake 方法来绘制每一个雪花。

复制代码
class SnowflakePainter extends CustomPainter {
  final List<Snowflake> snowflakes;
  final Paint snowPaint = Paint()..color = Colors.white; // 定义白色画笔

  // 构造函数接受一个雪花列表
  SnowflakePainter(this.snowflakes);

  @override
  void paint(Canvas canvas, Size size) {
    for (var snowflake in snowflakes) {
      canvas.save(); // 保存当前画布状态
      canvas.translate(snowflake.x, snowflake.y); // 平移画布到雪花位置
      canvas.rotate(snowflake.angle); // 应用旋转
      drawSnowflake(canvas, 0, 0, size.width / 40); // 调整雪花的大小和位置
      canvas.restore(); // 恢复画布状态
    }
  }

  void drawSnowflake(Canvas canvas, double x, double y, double size) {
    final path = Path();
    const int numBranches = 6; // 六个主要分支
    final double branchLength = size;
    final double angleStep = pi / 3; // 每个分支间隔60度

    for (int i = 0; i < numBranches; i++) {
      double angle = i * angleStep;
      
      // 绘制主分支
      drawBranch(path, x, y, branchLength, angle);
      
      // 对称地绘制相反方向的分支
      drawBranch(path, x, y, branchLength, angle + pi);
    }

    // 使用画笔绘制路径
    canvas.drawPath(
      path,
      Paint()
        ..color = Colors.white
        ..strokeWidth = 2
        ..style = PaintingStyle.stroke
    );
  }

  void drawBranch(Path path, double startX, double startY, double length, double angle) {
    if (length < 2) return; // 基础情况:当分支太小时停止递归

    double endX = startX + length * cos(angle); // 计算分支终点的x坐标
    double endY = startY + length * sin(angle); // 计算分支终点的y坐标

    path.moveTo(startX, startY); // 移动画笔到起点
    path.lineTo(endX, endY); // 画出主干线

    double subBranchLength = length * 0.5; // 小分支长度为主分支的一半
    double offsetAngle = pi / 6; // 小分支偏移角度为30度

    // 递归地在两侧画出小分支
    drawBranch(path, endX, endY, subBranchLength, angle - offsetAngle);
    drawBranch(path, endX, endY, subBranchLength, angle + offsetAngle);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true; // 每次绘制都需要重新绘制
  }
}

解释:

  • SnowflakePainter 类继承自 CustomPainter,负责绘制雪花。
  • paint 方法通过保存当前画布状态、平移到雪花位置、旋转画布,然后调用 drawSnowflake 来绘制雪花,最后恢复画布状态。
  • drawSnowflake 方法通过递归方式绘制由多个分支组成的雪花形状,每个雪花有六个主要分支,每个主分支上有两个小分支。
  • drawBranch 方法递归地绘制雪花的主分支和小分支,直到分支长度小于特定值。

4. 创建 SnowFallAnimation 小部件

接下来,我们创建 SnowFallAnimation 小部件,它包含一个动画控制器来更新雪花的位置,并调用 CustomPaint 来绘制雪花。

复制代码
class SnowFallAnimation extends StatefulWidget {
  @override
  _SnowFallAnimationState createState() => _SnowFallAnimationState();
}

class _SnowFallAnimationState extends State<SnowFallAnimation>
    with SingleTickerProviderStateMixin {
  AnimationController? _controller;
  List<Snowflake> snowflakes = [];
  static const int numberOfSnowflakes = 200; // 增加雪花的数量

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this, // 同步动画
      duration: Duration(seconds: 5),
    )..repeat(); // 循环播放动画

    // 每帧更新雪花位置
    _controller!.addListener(() {
      setState(() {
        for (var snowflake in snowflakes) {
          snowflake.updatePosition(MediaQuery.of(context).size.height);
        }
      });
    });

    print("Animation Controller Initialized");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (snowflakes.isEmpty) { // 初始化雪花列表
      final screenWidth = MediaQuery.of(context).size.width;
      for (int i = 0; i < numberOfSnowflakes; i++) {
        double startX = Random().nextDouble() * screenWidth; // 随机生成雪花的x坐标
        double startY = -Random().nextDouble() * 1000; // 随机生成雪花的初始y坐标
        double fallSpeed = Random().nextDouble() * 2 + 1; // 随机生成下落速度
        double angle = Random().nextDouble() * 2 * pi; // 随机生成初始角度
        double angularSpeed = (Random().nextDouble() - 0.5) * 0.02; // 随机生成旋转速度
        snowflakes.add(Snowflake(startX, startY, fallSpeed, angle, angularSpeed));
      }
      print("Snowflakes initialized: ${snowflakes.length}");
    }
  }

  @override
  void dispose() {
    _controller?.dispose(); // 销毁动画控制器
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue[100], // 设置背景颜色为蓝色,模拟天空效果
      body: CustomPaint(
        painter: SnowflakePainter(snowflakes), // 使用自定义画家绘制雪花
        child: Container(), // 空容器作为画布子组件
      ),
    );
  }
}

解释:

  • _SnowFallAnimationState 类实现了 SingleTickerProviderStateMixin,以便提供 vsync 给动画控制器。
  • 在 initState 方法中,我们初始化动画控制器并设置其重复播放。在每帧触发的监听器中,调用 setState 方法使界面重新绘制,从而更新每个雪花的位置。
  • 在 didChangeDependencies 方法中,我们确保雪花列表仅初始化一次。通过调用 MediaQuery.of(context).size.width 获取屏幕宽度,随后利用循环初始化指定数量的雪花(numberOfSnowflakes)。
  • 每个雪花的位置、下落速度、初始角度和旋转速度都是随机生成的,以确保动画效果更加自然。
  • 在 dispose 方法中,释放动画控制器,以避免资源泄漏。
  • build 方法返回一个 Scaffold,其背景颜色设置为淡蓝色(模拟天空)。CustomPaint 用于绘制雪花图案,其 painter 参数设置为自定义的 SnowflakePainter。

5. 主文件 main.dart

最后,在 main.dart 文件中,创建一个简单的应用程序入口点,将 SnowFallAnimation 小部件作为主页显示。

复制代码
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: SnowFallAnimation(),
    );
  }
}

解释:

  • main.dart 文件引入了 snow_fall_animation.dart 文件,该文件包含我们先前定义的 SnowFallAnimation 小部件。
  • MyApp 类是一个无状态小部件,定义了应用程序的基本结构。
  • build 方法返回一个 MaterialApp,其中 home 属性设置为 SnowFallAnimation,即我们的雪花动画效果。

结论

通过以上步骤,你成功创建了一个逼真的 Flutter 雪花下落动画。我们探索了如何使用 CustomPainter 自定义绘制复杂形状,以及如何使用 AnimationController 控制动画。这些技术可以扩展到更多复杂的动画场景,为你的应用增添视觉效果。

相关推荐
用户091 小时前
Flutter插件与包的本质差异
android·flutter·面试
浅蓝色2 小时前
flutter平台判断,这次应该没问题了。支持鸿蒙,插件已发布
flutter·harmonyos
怀君2 小时前
Flutter——打印之PdfPreview功能详细教程
flutter
唔6614 小时前
flutter实现web端实现效果
前端·flutter
sunly_18 小时前
Flutter:启动动画Lottie
flutter
柿蒂1 天前
聊聊SliverPersistentHeader优先消费滑动的设计
android·flutter
fishofeyes1 天前
Riverpod使用
flutter
消失的旧时光-19432 天前
iFlutter --> Flutter 开发者 的 IntelliJ IDEA / Android Studio 插件
flutter·android studio·intellij-idea
马拉萨的春天2 天前
block的样式有哪些?如果copy的话分别会有啥样式
flutter·性能优化·ios的block
Rattenking2 天前
【CSS】---- 图形函数详解
笔记·学习·flutter