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