Skip to content

Instantly share code, notes, and snippets.

@evanca
Last active November 7, 2023 15:30
Show Gist options
  • Save evanca/aeca0ed2a52e75b79215076bbbe5d458 to your computer and use it in GitHub Desktop.
Save evanca/aeca0ed2a52e75b79215076bbbe5d458 to your computer and use it in GitHub Desktop.
SnowflakeCanvas
/// 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