Skip to content

Instantly share code, notes, and snippets.

@evanca
Created April 3, 2025 06:10
Show Gist options
  • Save evanca/48226cf1cf96f88ab9747bea85b8c8de to your computer and use it in GitHub Desktop.
Save evanca/48226cf1cf96f88ab9747bea85b8c8de to your computer and use it in GitHub Desktop.
Flutter Kaleidoscope

Flutter Kaleidoscope

A Hypnotic Blend of Tech, Nostalgia, and the Playful Dance of Physics

Remember the wonder of looking through a kaleidoscope as a child, watching patterns shift and transform? That same sense of magic exists in today's tech world. We're still those curious kids—only now, we're watching AI reshape our digital landscapes, with fragments of code and data rearranging at every turn of innovation. But unlike childhood toys, we're not just observers; we shape the patterns ourselves.

The magic isn’t in finding permanent solutions (we all know those don’t last in tech) but in bringing our creativity to each new formation. That nostalgia for simple wonder inspired me to create my Flutter kaleidoscope—where emojis become the fragments of an ever-changing digital reflection of how we communicate. Playful, ephemeral, yet meaningful in their brief alignments.

I wanted this piece to feel like looking through a real kaleidoscope—something hypnotic, a momentary escape from the day’s worries, drawing you into a sense of playful wonder.

// Copyright © Ivanna Kacevica https://happycode.studio/
//
// Submission for the Flutteristas 2025 Code Challenge.
//
// Permission is granted to use, modify, and distribute this code
// under the terms of the MIT License that can be found
// at https://opensource.org/licenses/MIT.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home: const KaleidoscopeScreen(),
);
}
}
class KaleidoscopeScreen extends StatefulWidget {
const KaleidoscopeScreen({super.key});
@override
State<KaleidoscopeScreen> createState() => _KaleidoscopeScreenState();
}
class _KaleidoscopeScreenState extends State<KaleidoscopeScreen>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
final List<KaleidoscopeParticle> _particles = [];
final int _numSegments = 8;
double _lastUpdateTime = 0;
final double _kaleidoscopeRotationSpeed = 0.5;
double _prevRotationValue = 0.0;
final random = math.Random();
final double _baseParticleSize = 0.15;
@override
void initState() {
super.initState();
_initializeParticles();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 30),
)..repeat();
_controller.addListener(() {
setState(() {
final now = DateTime.now().millisecondsSinceEpoch / 1000.0;
final deltaTime = _lastUpdateTime == 0 ? 0.016 : now - _lastUpdateTime;
_lastUpdateTime = now;
final currentRotation = _controller.value * 2 * math.pi / 2;
final rotationDelta = _calculateDeltaAngle(
currentRotation,
_prevRotationValue,
);
_prevRotationValue = currentRotation;
_updateParticles(deltaTime, rotationDelta);
});
});
}
double _calculateDeltaAngle(double current, double previous) {
double delta = current - previous;
if (delta > math.pi) delta -= 2 * math.pi;
if (delta < -math.pi) delta += 2 * math.pi;
return delta;
}
void _initializeParticles() {
final random = math.Random();
final baseSize = _baseParticleSize;
Widget createSizedWidget(
Widget Function(double size) widgetBuilder,
double scaleFactor,
) {
return Builder(
builder: (context) {
final size = MediaQuery.of(context).size;
final circleRadius = size.shortestSide * 0.75 / 2;
final widgetSize = circleRadius * baseSize * scaleFactor;
return widgetBuilder(widgetSize);
},
);
}
final particleTypes = [
ParticleProperties(
widgets: [
createSizedWidget(
(size) => Text('💎', style: TextStyle(fontSize: size)),
1.2,
),
createSizedWidget(
(size) => Text('🌟', style: TextStyle(fontSize: size)),
1.2,
),
],
massMultiplier: 2.5,
restitution: 0.85,
frictionCoefficient: 0.02,
momentOfInertiaMultiplier: 1.2,
dragCoefficient: 0.01,
),
ParticleProperties(
widgets: [
createSizedWidget(
(size) => Text('🌸', style: TextStyle(fontSize: size)),
1.0,
),
createSizedWidget(
(size) => Text('🌹', style: TextStyle(fontSize: size)),
1.0,
),
createSizedWidget(
(size) => Text('🦋', style: TextStyle(fontSize: size)),
1.0,
),
],
massMultiplier: 1.0,
restitution: 0.65,
frictionCoefficient: 0.04,
momentOfInertiaMultiplier: 1.0,
dragCoefficient: 0.03,
),
ParticleProperties(
widgets: [
createSizedWidget(
(size) => Icon(
Icons.auto_awesome,
color: Colors.purpleAccent,
size: size,
),
0.9,
),
createSizedWidget(
(size) => FlutterLogo(size: size, style: FlutterLogoStyle.markOnly),
1.2,
),
],
massMultiplier: 0.7,
restitution: 0.5,
frictionCoefficient: 0.05,
momentOfInertiaMultiplier: 0.8,
dragCoefficient: 0.05,
),
ParticleProperties(
widgets: [
createSizedWidget(
(size) => Text('🍭', style: TextStyle(fontSize: size)),
1.1,
),
createSizedWidget(
(size) => Text('👑', style: TextStyle(fontSize: size)),
1.1,
),
createSizedWidget(
(size) => Text('🍀', style: TextStyle(fontSize: size)),
1.1,
),
createSizedWidget(
(size) => Text('🎪', style: TextStyle(fontSize: size)),
1.1,
),
],
massMultiplier: 1.5,
restitution: 0.7,
frictionCoefficient: 0.03,
momentOfInertiaMultiplier: 1.1,
dragCoefficient: 0.02,
),
];
for (final particleType in particleTypes) {
for (final widget in particleType.widgets) {
final scale = 0.5 + random.nextDouble() * 0.7;
final collisionRadius = scale * 20.0;
_particles.add(
KaleidoscopeParticle(
widget: widget,
position: Offset(
0.1 + random.nextDouble() * 0.7,
0.1 + random.nextDouble() * 0.7,
),
velocity: Offset(
(random.nextDouble() - 0.5) * 0.005,
(random.nextDouble() - 0.5) * 0.005,
),
scale: scale,
collisionRadius: collisionRadius,
mass: scale * particleType.massMultiplier,
restitution: particleType.restitution,
frictionCoefficient: particleType.frictionCoefficient,
momentOfInertia:
math.pow(collisionRadius, 2) *
particleType.momentOfInertiaMultiplier,
dragCoefficient: particleType.dragCoefficient,
),
);
_particles.last.angularVelocity = (random.nextDouble() - 0.5) * 0.1;
}
}
}
void _updateParticles(double deltaTime, double rotationDelta) {
final random = math.Random();
for (var particle in _particles) {
final distanceFromCenter = math.sqrt(
math.pow(particle.position.dx - 0.5, 2) +
math.pow(particle.position.dy - 0.5, 2),
);
if (distanceFromCenter > 0.01) {
final directionToCenter = Offset(
(0.5 - particle.position.dx) / distanceFromCenter,
(0.5 - particle.position.dy) / distanceFromCenter,
);
final forceMagnitude =
0.00002 / (distanceFromCenter * distanceFromCenter);
particle.applyForce(
directionToCenter.scale(forceMagnitude, forceMagnitude),
);
}
if (rotationDelta.abs() > 0.0001) {
final centrifugalMagnitude =
rotationDelta *
_kaleidoscopeRotationSpeed *
distanceFromCenter *
0.01 *
particle.mass;
final directionFromCenter = Offset(
(particle.position.dx - 0.5) / distanceFromCenter,
(particle.position.dy - 0.5) / distanceFromCenter,
);
particle.applyForce(
directionFromCenter.scale(centrifugalMagnitude, centrifugalMagnitude),
);
}
if (random.nextDouble() < 0.01) {
particle.applyForce(
Offset(
(random.nextDouble() - 0.5) * 0.00005,
(random.nextDouble() - 0.5) * 0.00005,
),
);
}
final velocity = math.sqrt(
math.pow(particle.velocity.dx, 2) + math.pow(particle.velocity.dy, 2),
);
if (velocity > 0.0001) {
final dragMagnitude = particle.dragCoefficient * velocity * velocity;
final dragForce = Offset(
-particle.velocity.dx / velocity * dragMagnitude,
-particle.velocity.dy / velocity * dragMagnitude,
);
particle.applyForce(dragForce);
}
particle.update(deltaTime * 10);
_handleBoundaryCollision(particle);
}
_handleParticleCollisions();
}
void _handleBoundaryCollision(KaleidoscopeParticle particle) {
final boundaryMin = 0.05;
final boundaryMax = 0.85;
final distanceFromLeft = particle.position.dx - boundaryMin;
final distanceFromRight = boundaryMax - particle.position.dx;
final distanceFromTop = particle.position.dy - boundaryMin;
final distanceFromBottom = boundaryMax - particle.position.dy;
final collisionThreshold = particle.collisionRadius / 500;
if (distanceFromLeft < collisionThreshold && particle.velocity.dx < 0) {
particle.position = Offset(
boundaryMin + collisionThreshold,
particle.position.dy,
);
particle.velocity = Offset(
-particle.velocity.dx * particle.restitution,
particle.velocity.dy * (1.0 - particle.frictionCoefficient),
);
final contactPointOffsetY = random.nextDouble() * 0.4 - 0.2;
final torque = contactPointOffsetY * particle.velocity.dx.abs() * 0.3;
particle.applyTorque(torque);
} else if (distanceFromRight < collisionThreshold &&
particle.velocity.dx > 0) {
particle.position = Offset(
boundaryMax - collisionThreshold,
particle.position.dy,
);
particle.velocity = Offset(
-particle.velocity.dx * particle.restitution,
particle.velocity.dy * (1.0 - particle.frictionCoefficient),
);
final contactPointOffsetY = random.nextDouble() * 0.4 - 0.2;
final torque = -contactPointOffsetY * particle.velocity.dx.abs() * 0.3;
particle.applyTorque(torque);
}
if (distanceFromTop < collisionThreshold && particle.velocity.dy < 0) {
particle.position = Offset(
particle.position.dx,
boundaryMin + collisionThreshold,
);
particle.velocity = Offset(
particle.velocity.dx * (1.0 - particle.frictionCoefficient),
-particle.velocity.dy * particle.restitution,
);
final contactPointOffsetX = random.nextDouble() * 0.4 - 0.2;
final torque = contactPointOffsetX * particle.velocity.dy.abs() * 0.3;
particle.applyTorque(torque);
} else if (distanceFromBottom < collisionThreshold &&
particle.velocity.dy > 0) {
particle.position = Offset(
particle.position.dx,
boundaryMax - collisionThreshold,
);
particle.velocity = Offset(
particle.velocity.dx * (1.0 - particle.frictionCoefficient),
-particle.velocity.dy * particle.restitution,
);
final contactPointOffsetX = random.nextDouble() * 0.4 - 0.2;
final torque = -contactPointOffsetX * particle.velocity.dy.abs() * 0.3;
particle.applyTorque(torque);
}
}
void _handleParticleCollisions() {
for (int i = 0; i < _particles.length; i++) {
final particleA = _particles[i];
for (int j = i + 1; j < _particles.length; j++) {
final particleB = _particles[j];
final dx = particleB.position.dx - particleA.position.dx;
final dy = particleB.position.dy - particleA.position.dy;
final distanceSquared = dx * dx + dy * dy;
final combinedRadius =
(particleA.collisionRadius + particleB.collisionRadius) / 500;
final minDistanceSquared = combinedRadius * combinedRadius;
if (distanceSquared < minDistanceSquared) {
final distance = math.sqrt(distanceSquared);
final nx = dx / distance;
final ny = dy / distance;
final overlap = combinedRadius - distance;
final totalMass = particleA.mass + particleB.mass;
final particleAShare = particleB.mass / totalMass;
final particleBShare = particleA.mass / totalMass;
particleA.position = Offset(
particleA.position.dx - nx * overlap * particleAShare,
particleA.position.dy - ny * overlap * particleAShare,
);
particleB.position = Offset(
particleB.position.dx + nx * overlap * particleBShare,
particleB.position.dy + ny * overlap * particleBShare,
);
final rvx = particleB.velocity.dx - particleA.velocity.dx;
final rvy = particleB.velocity.dy - particleA.velocity.dy;
final velAlongNormal = rvx * nx + rvy * ny;
if (velAlongNormal > 0) continue;
final combinedRestitution =
(particleA.restitution + particleB.restitution) / 2.0;
final j = -(1.0 + combinedRestitution) * velAlongNormal;
final impulseScalar = j / totalMass;
final impulseX = nx * impulseScalar;
final impulseY = ny * impulseScalar;
particleA.velocity = Offset(
particleA.velocity.dx - impulseX * particleB.mass,
particleA.velocity.dy - impulseY * particleB.mass,
);
particleB.velocity = Offset(
particleB.velocity.dx + impulseX * particleA.mass,
particleB.velocity.dy + impulseY * particleA.mass,
);
final offNormalX = -ny;
final offNormalY = nx;
final torqueA =
(offNormalX * rvx + offNormalY * rvy) *
particleB.mass *
0.01 /
particleA.momentOfInertia;
final torqueB =
-(offNormalX * rvx + offNormalY * rvy) *
particleA.mass *
0.01 /
particleB.momentOfInertia;
particleA.applyTorque(torqueA);
particleB.applyTorque(torqueB);
}
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size.shortestSide;
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: OneSecondLoader(
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: double.infinity,
height: double.infinity,
child: CustomPaint(painter: BackgroundPainter()),
),
ClipOval(
child: SizedBox(
width: size * 0.75,
height: size * 0.75,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Colors.lightBlue.shade200.withValues(alpha: 0.3),
shape: BoxShape.circle,
border: Border.all(
color: Colors.white.withValues(alpha: 0.2),
width: 1.5,
),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.lightBlue.shade200.withValues(alpha: 0.5),
Colors.lightBlue.shade200.withValues(alpha: 0.2),
],
),
),
),
),
),
),
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
final children =
_particles.map((particle) => particle.widget).toList();
return SizedBox(
width: size,
height: size,
child: KaleidoscopeWidget(
particles: _particles,
numSegments: _numSegments,
animationValue: _controller.value,
useFrostedGlass: true,
children: children,
),
);
},
),
],
),
),
),
);
}
}
class ParticleProperties {
final List<Widget> widgets;
final double massMultiplier;
final double restitution;
final double frictionCoefficient;
final double momentOfInertiaMultiplier;
final double dragCoefficient;
ParticleProperties({
required this.widgets,
required this.massMultiplier,
required this.restitution,
required this.frictionCoefficient,
required this.momentOfInertiaMultiplier,
required this.dragCoefficient,
});
}
class KaleidoscopeParticle {
Widget widget;
Offset position;
Offset velocity;
Offset acceleration = Offset.zero;
double scale;
double collisionRadius;
double mass;
double momentOfInertia;
double restitution;
double frictionCoefficient;
double dragCoefficient;
double angle = 0;
double angularVelocity = 0;
double angularAcceleration = 0;
KaleidoscopeParticle({
required this.widget,
required this.position,
required this.velocity,
required this.scale,
required this.collisionRadius,
required this.mass,
required this.restitution,
required this.frictionCoefficient,
required this.momentOfInertia,
required this.dragCoefficient,
});
void update(double deltaTime) {
velocity += acceleration.scale(deltaTime, deltaTime);
position += velocity.scale(deltaTime, deltaTime);
angularVelocity += angularAcceleration * deltaTime;
angle += angularVelocity * deltaTime;
acceleration = Offset.zero;
angularAcceleration = 0;
velocity = velocity.scale(
math.pow(1.0 - frictionCoefficient, deltaTime).toDouble(),
math.pow(1.0 - frictionCoefficient, deltaTime).toDouble(),
);
angularVelocity *= math.pow(0.99, deltaTime).toDouble();
}
void applyForce(Offset force) {
acceleration += force.scale(1.0 / mass, 1.0 / mass);
}
void applyTorque(double torque) {
angularAcceleration += torque / momentOfInertia;
}
}
class BackgroundPainter extends CustomPainter {
final List<Color> colorPalette = [
Color(0xFF087F8C),
Color(0xFF7955BF),
Color(0xFFFF8B04),
Color(0xFFFDD836),
Color(0xFFFF2C8A),
];
@override
void paint(Canvas canvas, Size size) {
final random = math.Random(42);
for (int i = 0; i < 40; i++) {
final paint =
Paint()
..color = colorPalette[random.nextInt(colorPalette.length)]
.withValues(alpha: 0.3)
..style = PaintingStyle.fill;
final x = random.nextDouble() * size.width;
final y = random.nextDouble() * size.height;
final radius = 10 + random.nextDouble() * 50;
canvas.drawCircle(Offset(x, y), radius, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class KaleidoscopeWidget extends MultiChildRenderObjectWidget {
final List<KaleidoscopeParticle> particles;
final int numSegments;
final double animationValue;
final bool useFrostedGlass;
const KaleidoscopeWidget({
super.key,
required this.particles,
required this.numSegments,
required this.animationValue,
this.useFrostedGlass = false,
required super.children,
});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderKaleidoscope(
particles: particles,
numSegments: numSegments,
animationValue: animationValue,
useFrostedGlass: useFrostedGlass,
);
}
@override
void updateRenderObject(
BuildContext context,
RenderKaleidoscope renderObject,
) {
renderObject
..particles = particles
..numSegments = numSegments
..animationValue = animationValue
..useFrostedGlass = useFrostedGlass;
}
}
class KaleidoscopeParentData extends ContainerBoxParentData<RenderBox> {
int particleIndex = 0;
}
class RenderKaleidoscope extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, KaleidoscopeParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, KaleidoscopeParentData> {
RenderKaleidoscope({
required List<KaleidoscopeParticle> particles,
required int numSegments,
required double animationValue,
required bool useFrostedGlass,
}) : _particles = particles,
_numSegments = numSegments,
_animationValue = animationValue,
_useFrostedGlass = useFrostedGlass;
// ignore: unused_field
double _radius = 0;
List<KaleidoscopeParticle> get particles => _particles;
set particles(List<KaleidoscopeParticle> value) {
if (_particles == value) return;
_particles = value;
markNeedsLayout();
markNeedsPaint();
}
List<KaleidoscopeParticle> _particles;
int get numSegments => _numSegments;
set numSegments(int value) {
if (_numSegments == value) return;
_numSegments = value;
markNeedsLayout();
markNeedsPaint();
}
int _numSegments;
double get animationValue => _animationValue;
set animationValue(double value) {
if (_animationValue == value) return;
_animationValue = value;
markNeedsPaint();
}
double _animationValue;
bool get useFrostedGlass => _useFrostedGlass;
set useFrostedGlass(bool value) {
if (_useFrostedGlass == value) return;
_useFrostedGlass = value;
markNeedsPaint();
}
bool _useFrostedGlass;
@override
void setupParentData(RenderBox child) {
if (child.parentData is! KaleidoscopeParentData) {
child.parentData = KaleidoscopeParentData();
}
}
@override
void adoptChild(RenderObject child) {
super.adoptChild(child);
int index = 0;
RenderBox? currentChild = firstChild;
while (currentChild != null) {
final KaleidoscopeParentData parentData =
currentChild.parentData! as KaleidoscopeParentData;
if (currentChild == child) {
parentData.particleIndex = index < _particles.length ? index : 0;
break;
}
index++;
currentChild = childAfter(currentChild);
}
}
@override
void performLayout() {
size = constraints.biggest;
final centerX = size.width / 2;
final centerY = size.height / 2;
_radius = math.min(centerX, centerY) * 0.75;
int index = 0;
RenderBox? child = firstChild;
while (child != null && index < _particles.length) {
final KaleidoscopeParentData parentData =
child.parentData! as KaleidoscopeParentData;
parentData.particleIndex = index;
child.layout(
BoxConstraints(maxWidth: double.infinity, maxHeight: double.infinity),
parentUsesSize: true,
);
child = childAfter(child);
index++;
}
}
@override
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
final centerX = size.width / 2;
final centerY = size.height / 2;
final radius = math.min(centerX, centerY) * 0.75;
_radius = radius;
canvas.save();
canvas.translate(centerX + offset.dx, centerY + offset.dy);
canvas.rotate(animationValue * 2 * math.pi / 2);
final segmentAngle = 2 * math.pi / numSegments;
void paintParticleInSegment(
int particleIndex,
int segmentIndex,
bool mirrored,
) {
if (particleIndex >= _particles.length) return;
final particle = _particles[particleIndex];
RenderBox? child = firstChild;
KaleidoscopeParentData? particleParentData;
while (child != null) {
final parentData = child.parentData! as KaleidoscopeParentData;
if (parentData.particleIndex == particleIndex) {
particleParentData = parentData;
break;
}
child = childAfter(child);
}
if (child == null || particleParentData == null) return;
final particleX = particle.position.dx * radius;
final particleY = particle.position.dy * radius;
canvas.save();
canvas.rotate(segmentIndex * segmentAngle);
if (mirrored) {
canvas.scale(1, -1);
}
canvas.translate(particleX, particleY);
canvas.rotate(particle.angle);
context.paintChild(
child,
Offset(-child.size.width / 2, -child.size.height / 2),
);
canvas.restore();
}
for (int segment = 0; segment < numSegments; segment++) {
final segmentPath =
Path()
..moveTo(0, 0)
..lineTo(radius, 0)
..arcTo(
Rect.fromCircle(center: Offset.zero, radius: radius),
0,
segmentAngle,
false,
)
..close();
canvas.save();
canvas.rotate(segment * segmentAngle);
canvas.clipPath(segmentPath);
for (int i = 0; i < _particles.length; i++) {
paintParticleInSegment(i, 0, false);
paintParticleInSegment(i, 0, true);
}
final linePaint =
Paint()
..color = Colors.white.withValues(alpha: 0.3)
..style = PaintingStyle.stroke
..strokeWidth = 1;
canvas.drawPath(segmentPath, linePaint);
canvas.restore();
}
final borderPaint =
Paint()
..color = Colors.white.withValues(alpha: 0.7)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset.zero, radius * 0.99, borderPaint);
canvas.restore();
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
class OneSecondLoader extends StatefulWidget {
final Widget child;
final Duration loadingDuration;
final VoidCallback? onLoadComplete;
final Color overlayColor;
final Widget? loadingIndicator;
const OneSecondLoader({
super.key,
required this.child,
this.loadingDuration = const Duration(seconds: 1),
this.onLoadComplete,
this.overlayColor = Colors.black,
this.loadingIndicator,
});
@override
State<OneSecondLoader> createState() => _OneSecondLoaderState();
}
class _OneSecondLoaderState extends State<OneSecondLoader>
with SingleTickerProviderStateMixin {
bool _showOverlay = true;
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 750),
vsync: this,
);
_animation = TweenSequence<double>([
TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.5), weight: 1),
TweenSequenceItem(tween: Tween<double>(begin: 1.5, end: 0.8), weight: 1),
TweenSequenceItem(tween: Tween<double>(begin: 0.8, end: 1.0), weight: 1),
]).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_animationController.repeat();
_startTimer();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _startTimer() {
Future.delayed(widget.loadingDuration, () {
if (mounted) {
setState(() {
_showOverlay = false;
});
if (widget.onLoadComplete != null) {
widget.onLoadComplete!();
}
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (_showOverlay)
Container(
color: widget.overlayColor,
child: Center(
child:
widget.loadingIndicator ??
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: Icon(
Icons.auto_awesome,
color: Colors.purpleAccent,
size: 48,
),
);
},
),
),
),
],
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment