Toy project to learn Flutter/Dart.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.4 KiB

import 'dart:async';
import 'package:flutter/material.dart';
import 'utils.dart';
// number of particles
const int particleNumber = 6;
// status of a particle
enum AnimStatus {
running,
finished,
}
/// particle leading to an animation between start and end
class Particle {
/// begin position of particle
// particle will return at this begin position when finished
// if the particle replaces an other, last position of previous particle will be used instead
Alignment start;
/// end position of particle
Alignment end;
/// widget that will appear inside particle
Widget child;
/// custom interpolation curve
Curve curve;
/// duration of animation (return animation has duration equal to zero)
Duration duration;
/// status of animation
// running animation will play particle parameters, finished animation will return to start position
AnimStatus status;
/// constructor requires a child, all other parameters have default values
Particle({
this.start = Alignment.center,
this.end = Alignment.center,
required this.child,
this.curve = Curves.easeIn,
this.duration = const Duration(milliseconds: 0),
this.status = AnimStatus.finished,
});
}
// particle generator for this status
Particle animParticle(Status status) {
return Particle(
start: Alignment(rng.nextDouble()*2.0-1.0, 1.25),
end: Alignment(rng.nextDouble()*3.0-1.5, -1-rng.nextDouble()*5),
child: Text(animEmoji(status), textScaleFactor: 5),
duration: const Duration(milliseconds: 1800),
status: AnimStatus.running,
);
}
/// replace inactive particle or add new one to the list
int replaceInactiveOrAddNew(List<Particle> particles, Particle newParticle) {
int index = 0; // keep track of index
for(Particle particle in particles) {
if(particle.status == AnimStatus.finished) {
particles[index] = newParticle; // replace it
return index; // return it's index
}
index++;
}
// no particle was finished
particles.add(newParticle);
return particles.length-1; // return last index
}
/// emoji rain widget
class EmojiRainWidget extends StatefulWidget {
const EmojiRainWidget({Key? key, required this.stream}) : super(key: key);
final Stream stream;
@override
State<EmojiRainWidget> createState() => _EmojiRainState();
}
/// emoji rain state
class _EmojiRainState extends State<EmojiRainWidget> {
List<Particle> particles = []; // empty list that will store particles
@override void initState() {
super.initState();
widget.stream.listen((particle) {
// when receiving a particle, add it to particle list and trigger redraw
setState(() {
replaceInactiveOrAddNew(particles, particle);
});
});
}
@override
Widget build(BuildContext context) {
return Stack( // animation stack,
fit: StackFit.loose,
children: particles.where((p) => p.status == AnimStatus.running).map<Widget>( (p) =>
TweenAnimationBuilder(
key: ObjectKey(p), // let identify particle uniquely
tween: Tween<Alignment>(begin: p.start, end: p.end),
duration: p.duration,
curve: p.curve,
builder: (_, Alignment align, Widget? child) {
return Container(alignment: align, child: child);
},
child: p.child,
onEnd: () { setState(() { p.status = AnimStatus.finished; }); },
)
).toList(),
);
}
}