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 控制动画。这些技术可以扩展到更多复杂的动画场景,为你的应用增添视觉效果。

相关推荐
problc6 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
lqj_本人14 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人18 小时前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔1 天前
Flutter启动流程(2)
flutter
hello world smile1 天前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人1 天前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai1 天前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人1 天前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos
lqj_本人1 天前
Flutter&鸿蒙next 中的 setState 使用场景与最佳实践
flutter·华为·harmonyos
hello world smile1 天前
Flutter常用命令整理
android·flutter·移动开发·android studio·安卓