import 'dart:math'; import 'package:flutter/material.dart'; import 'package:particle_background/simple_animations_package.dart'; void main() => runApp(ParticleApp()); class ParticleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: ParticleBackgroundPage(), ), ); } } class ParticleBackgroundPage extends StatelessWidget { @override Widget build(BuildContext context) { return Stack( children: [ Positioned.fill(child: AnimatedBackground()), Positioned.fill(child: Particles(30)), Positioned.fill(child: CenteredText()), ], ); } } class Particles extends StatefulWidget { final int numberOfParticles; Particles(this.numberOfParticles); @override _ParticlesState createState() => _ParticlesState(); } class _ParticlesState extends State { final Random random = Random(); final List particles = []; @override void initState() { List.generate(widget.numberOfParticles, (index) { particles.add(ParticleModel(random)); }); super.initState(); } @override Widget build(BuildContext context) { return Rendering( startTime: Duration(seconds: 30), onTick: _simulateParticles, builder: (context, time) { return CustomPaint( painter: ParticlePainter(particles, time), ); }, ); } _simulateParticles(Duration time) { particles.forEach((particle) => particle.maintainRestart(time)); } } class ParticleModel { Animatable tween; double size; AnimationProgress animationProgress; Random random; ParticleModel(this.random) { restart(); } restart({Duration time = Duration.zero}) { final startPosition = Offset(-0.2 + 1.4 * random.nextDouble(), 1.2); final endPosition = Offset(-0.2 + 1.4 * random.nextDouble(), -0.2); final duration = Duration(milliseconds: 3000 + random.nextInt(6000)); tween = MultiTrackTween([ Track("x").add( duration, Tween(begin: startPosition.dx, end: endPosition.dx), curve: Curves.easeInOutSine), Track("y").add( duration, Tween(begin: startPosition.dy, end: endPosition.dy), curve: Curves.easeIn), ]); animationProgress = AnimationProgress(duration: duration, startTime: time); size = 0.2 + random.nextDouble() * 0.4; } maintainRestart(Duration time) { if (animationProgress.progress(time) == 1.0) { restart(time: time); } } } class ParticlePainter extends CustomPainter { List particles; Duration time; ParticlePainter(this.particles, this.time); @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.white.withAlpha(50); particles.forEach((particle) { var progress = particle.animationProgress.progress(time); final animation = particle.tween.transform(progress); final position = Offset(animation["x"] * size.width, animation["y"] * size.height); canvas.drawCircle(position, size.width * 0.2 * particle.size, paint); }); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class AnimatedBackground extends StatelessWidget { @override Widget build(BuildContext context) { final tween = MultiTrackTween([ Track("color1").add(Duration(seconds: 3), ColorTween(begin: Color(0xff8a113a), end: Colors.lightBlue.shade900)), Track("color2").add(Duration(seconds: 3), ColorTween(begin: Color(0xff440216), end: Colors.blue.shade600)) ]); return ControlledAnimation( playback: Playback.MIRROR, tween: tween, duration: tween.duration, builder: (context, animation) { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [animation["color1"], animation["color2"]])), ); }, ); } } class CenteredText extends StatelessWidget { const CenteredText({ Key key, }) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Text( "Welcome to Flutter for web", style: TextStyle(color: Colors.white, fontWeight: FontWeight.w200), textScaleFactor: 4, ), ); } }