Created with <3 with dartpad.dev.
Last active
November 7, 2023 15:30
-
-
Save evanca/aeca0ed2a52e75b79215076bbbe5d458 to your computer and use it in GitHub Desktop.
SnowflakeCanvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Designed as a submission for Flutteristas Code Challenge 2023, the SnowflakeCanvas | |
/// is a simple Flutter app that creates a visual simulation of snowflakes | |
/// falling at different speeds. It uses a `CustomPainter` and a `Koch curve` | |
/// to draw snowflakes on a canvas, with each snowflake having a unique size and | |
/// falling speed. The snowflakes enter the screen smoothly from the top and reset | |
/// their position once they fall off the bottom, creating an endless snowfall effect. | |
/// This app uses basic animation techniques with `setState` and is designed to work | |
/// in environments with limited animation capabilities such as DartPad. | |
import 'dart:async'; | |
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(MaterialApp(home: SnowflakeCanvas())); | |
} | |
class SnowflakeCanvas extends StatefulWidget { | |
@override | |
SnowflakeCanvasState createState() => SnowflakeCanvasState(); | |
} | |
class SnowflakeCanvasState extends State<SnowflakeCanvas> { | |
final Random random = Random(); | |
List<Snowflake> snowflakes = []; | |
@override | |
void initState() { | |
super.initState(); | |
// Create 16 snowflakes at random positions and with random speeds | |
for (int i = 0; i < 16; i++) { | |
var centerX = random.nextDouble(); | |
var centerY = random.nextDouble(); | |
var scaleFactor = 0.1 + random.nextDouble() * 0.2; | |
var speed = 0.005 + random.nextDouble() * 0.015; // Falling speed | |
snowflakes.add(Snowflake(centerX, centerY, scaleFactor, speed)); | |
} | |
// Start the animation | |
Timer.periodic(const Duration(milliseconds: 50), (timer) { | |
_animateSnowflakes(); | |
}); | |
} | |
void _animateSnowflakes() { | |
setState(() { | |
// Update each snowflake position to animate them falling down | |
for (var snowflake in snowflakes) { | |
snowflake.centerY += snowflake.speed; // Use the snowflake's speed | |
if (snowflake.centerY > 1.0 + snowflake.scaleFactor) { | |
// They reset after completely disappearing at the bottom | |
snowflake.centerY = -0.05 - | |
snowflake.scaleFactor; // They start above the top of the screen | |
// Randomize the X position when snowflake re-enters | |
snowflake.centerX = random.nextDouble(); | |
} | |
} | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: CustomPaint( | |
painter: SnowflakePainter(snowflakes, MediaQuery.of(context).size), | |
size: Size(MediaQuery.of(context).size.width, | |
MediaQuery.of(context).size.height), | |
), | |
); | |
} | |
} | |
class Snowflake { | |
double centerX; | |
double centerY; | |
double scaleFactor; | |
double speed; | |
Snowflake(this.centerX, this.centerY, this.scaleFactor, this.speed); | |
} | |
class SnowflakePainter extends CustomPainter { | |
List<Snowflake> snowflakes; | |
Size screenSize; | |
SnowflakePainter(this.snowflakes, this.screenSize); | |
@override | |
void paint(Canvas canvas, Size size) { | |
// Fill the canvas with Flutter brand color | |
Paint backgroundPaint = Paint()..color = const Color(0xFF027DFD); | |
canvas.drawRect( | |
Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint); | |
final snowflakePaint = Paint() | |
..color = Colors.white | |
..strokeWidth = 2.0; | |
for (var snowflake in snowflakes) { | |
var sideLength = snowflake.scaleFactor * screenSize.width; | |
var p1 = Offset(snowflake.centerX * screenSize.width - sideLength / 2, | |
snowflake.centerY * screenSize.height); | |
var p2 = Offset(snowflake.centerX * screenSize.width + sideLength / 2, | |
snowflake.centerY * screenSize.height); | |
var p3 = Offset(snowflake.centerX * screenSize.width, | |
snowflake.centerY * screenSize.height + sideLength * sqrt(3) / 2); | |
drawKochCurve(canvas, p1, p2, 4, snowflakePaint); | |
drawKochCurve(canvas, p2, p3, 4, snowflakePaint); | |
drawKochCurve(canvas, p3, p1, 4, snowflakePaint); | |
} | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return true; // Always repaint for a new frame in the animation | |
} | |
void drawKochCurve( | |
Canvas canvas, Offset p1, Offset p2, int depth, Paint paint) { | |
if (depth == 0) { | |
canvas.drawLine(p1, p2, paint); | |
} else { | |
var dx = p2.dx - p1.dx; | |
var dy = p2.dy - p1.dy; | |
// Calculate 1/3 distances | |
var p3 = Offset(p1.dx + dx / 3, p1.dy + dy / 3); | |
var p5 = Offset(p1.dx + dx * 2 / 3, p1.dy + dy * 2 / 3); | |
// Calculate the tip of the equilateral triangle | |
var dist = sqrt(pow(dx, 2) + pow(dy, 2)) / 3; | |
var angle = atan2(dy, dx) - pi / 3; | |
var p4 = Offset(p3.dx + cos(angle) * dist, p3.dy + sin(angle) * dist); | |
// Recursively draw the lines with the same paint | |
drawKochCurve(canvas, p1, p3, depth - 1, paint); | |
drawKochCurve(canvas, p3, p4, depth - 1, paint); | |
drawKochCurve(canvas, p4, p5, depth - 1, paint); | |
drawKochCurve(canvas, p5, p2, depth - 1, paint); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment