Skip to content

Instantly share code, notes, and snippets.

@mkiisoft
Forked from filiph/main.dart
Created March 4, 2019 19:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mkiisoft/564d7893ecb57c4f2e4a0a977c9f5bde to your computer and use it in GitHub Desktop.
Save mkiisoft/564d7893ecb57c4f2e4a0a977c9f5bde 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
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),
),
],
),
);
}
}
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();
}
}
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