-
-
Save cfkloss/74f2ada19a568134f24f48d23e808fef to your computer and use it in GitHub Desktop.
Implementation of the ring of circles in Flutter. Initial inspiration: https://twitter.com/InfinityLoopGIF/status/1101584983259533312. Kotlin implementation: https://gist.github.com/alexjlockwood/e3ff7b9a05dd91ff0955b90950bf7ee5
This file contains 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
import 'package:flutter/material.dart'; | |
import 'package:ring_of_circles/src/widget.dart'; | |
/// Just the app. Nothing to see here, except the code for changing | |
/// the number of circles (`n`). | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
/// Number of circles. | |
int n = 16; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text('Flutter Demo Home Page'), | |
), | |
body: Center( | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
// Here's how to use the widget. Just rebuild this when `n` changes. | |
// The widget animates itself. | |
child: RingOfCircles(n), | |
), | |
), | |
floatingActionButton: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
FloatingActionButton( | |
onPressed: () => setState(() => n += 1), | |
tooltip: 'Increment', | |
child: Icon(Icons.add), | |
), | |
SizedBox(height: 12), | |
FloatingActionButton( | |
onPressed: () => setState(() => n -= 1), | |
tooltip: 'Decrement', | |
child: Icon(Icons.remove), | |
), | |
], | |
), | |
); | |
} | |
} |
This file contains 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
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
/// A custom painter that renders a ring of [n] circles at time [t]. | |
class RingOfCirclesPainter extends CustomPainter { | |
final int n; | |
final double t; | |
RingOfCirclesPainter(this.n, this.t); | |
double _ringRadius; | |
double _waveRadius; | |
double _ballRadius; | |
double _gap; | |
@override | |
void paint(Canvas canvas, Size size) { | |
_ringRadius = min(size.width, size.height) * 0.35; | |
_waveRadius = min(size.width, size.height) * 0.10; | |
_ballRadius = _waveRadius / 4; | |
_gap = _ballRadius / 2; | |
canvas.save(); | |
canvas.translate(size.width / 2, size.height / 2); | |
for (var i = 0; i < n; i++) { | |
drawCircle(canvas, i, false); | |
} | |
var paint = Paint() | |
..style = PaintingStyle.stroke | |
..color = Colors.white | |
..strokeWidth = (_ballRadius + _gap * 2); | |
canvas.drawCircle(Offset.zero, _ringRadius, paint); | |
paint | |
..style = PaintingStyle.stroke | |
..color = Colors.black | |
..strokeWidth = _ballRadius; | |
canvas.drawCircle(Offset.zero, _ringRadius, paint); | |
for (var i = 0; i < n; i++) { | |
drawCircle(canvas, i, true); | |
} | |
canvas.restore(); | |
} | |
@override | |
bool shouldRepaint(RingOfCirclesPainter oldDelegate) => | |
n != oldDelegate.n || t != oldDelegate.t; | |
void drawCircle(Canvas canvas, int i, bool above) { | |
var angle0 = (i / n - t) % 1 * (2 * pi); | |
var angle1 = angle0 - t * (2 * pi); | |
if (cos(angle1) < 0 == above) { | |
return; | |
} | |
canvas.save(); | |
canvas.rotate(angle0); | |
canvas.translate((_ringRadius + sin(angle1) * _waveRadius), 0); | |
var paint = Paint() | |
..style = PaintingStyle.stroke | |
..color = Colors.white | |
..strokeWidth = (_gap * 2); | |
canvas.drawCircle(Offset.zero, _ballRadius, paint); | |
paint | |
..style = PaintingStyle.fill | |
..color = Colors.black; | |
canvas.drawCircle(Offset.zero, _ballRadius, paint); | |
canvas.restore(); | |
} | |
} |
This file contains 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
import 'package:flutter/material.dart'; | |
import 'package:ring_of_circles/src/painter.dart'; | |
/// An animated ring of circles that fills the available space. | |
class RingOfCircles extends StatefulWidget { | |
/// The number of circles to show. | |
final int n; | |
/// The duration of a single loop. Defaults to 5 seconds. | |
final Duration loopLength; | |
RingOfCircles( | |
this.n, { | |
Key key, | |
this.loopLength = const Duration(seconds: 5), | |
}) : super(key: key); | |
@override | |
_RingOfCirclesState createState() => _RingOfCirclesState(); | |
} | |
class _RingOfCirclesState extends State<RingOfCircles> | |
with SingleTickerProviderStateMixin { | |
/// The source of the animation. This controller goes repeatedly from `0` | |
/// to `1`. | |
AnimationController controller; | |
@override | |
Widget build(BuildContext context) { | |
return AnimatedBuilder( | |
animation: controller, | |
builder: (context, child) => CustomPaint( | |
painter: RingOfCirclesPainter(widget.n, controller.value), | |
child: child, | |
), | |
child: Center(child: Text('${widget.n}', style: TextStyle(fontSize: 32))), | |
); | |
} | |
@override | |
void didUpdateWidget(RingOfCircles oldWidget) { | |
if (widget.loopLength != oldWidget.loopLength) { | |
controller.duration = widget.loopLength; | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
void dispose() { | |
controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
controller = AnimationController(duration: widget.loopLength, vsync: this); | |
controller.repeat(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment