Skip to content

Commit

Permalink
Extract animationController from animated icons (#98)
Browse files Browse the repository at this point in the history
Fixes #83
  • Loading branch information
Jupi007 committed Dec 1, 2022
1 parent fa6034d commit 3915a1b
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 73 deletions.
74 changes: 38 additions & 36 deletions lib/src/animated_icons/yaru_animated_no_network_icon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class YaruAnimatedNoNetworkIcon extends StatefulWidget {
super.key,
this.size = 24.0,
this.color,
this.onCompleted,
this.progress,
});

/// Determines the icon canvas size
Expand All @@ -24,43 +24,46 @@ class YaruAnimatedNoNetworkIcon extends StatefulWidget {
/// If null, defaults to colorScheme.onSurface
final Color? color;

/// Callback called once animation completeds
final Function? onCompleted;
/// The animation progress for the animated icon.
/// The value is clamped to be between 0 and 1.
/// If null, a defaut animation controller will be created, which will run only once.
final Animation<double>? progress;

@override
State<YaruAnimatedNoNetworkIcon> createState() =>
_YaruAnimatedNoNetworkIconState();
}

class _YaruAnimatedNoNetworkIconState extends State<YaruAnimatedNoNetworkIcon>
with TickerProviderStateMixin {
with SingleTickerProviderStateMixin {
late final Animation<double> _animation;
late final AnimationController _controller;

Animation<double> get progress => widget.progress ?? _animation;

@override
void initState() {
super.initState();

_controller = AnimationController(
duration: const Duration(milliseconds: _kAnimationDuration),
vsync: this,
);
_animation = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: _kAnimationCurve))
.animate(_controller);

_controller.addStatusListener((status) {
if (status == AnimationStatus.completed && widget.onCompleted != null) {
widget.onCompleted!();
}
});

_controller.forward();
if (widget.progress == null) {
_controller = AnimationController(
duration: const Duration(milliseconds: _kAnimationDuration),
vsync: this,
);
_animation = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: _kAnimationCurve))
.animate(_controller);

_controller.forward();
}
}

@override
void dispose() {
_controller.dispose();
if (widget.progress == null) {
_controller.dispose();
}

super.dispose();
}

Expand All @@ -70,13 +73,13 @@ class _YaruAnimatedNoNetworkIconState extends State<YaruAnimatedNoNetworkIcon>
child: SizedBox.square(
dimension: widget.size,
child: AnimatedBuilder(
animation: _animation,
animation: progress,
builder: (context, child) {
return CustomPaint(
painter: _YaruAnimatedNoNetworkIconPainter(
widget.size,
widget.color ?? Theme.of(context).colorScheme.onSurface,
_animation.value,
progress.value,
),
);
},
Expand All @@ -91,8 +94,8 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {
_YaruAnimatedNoNetworkIconPainter(
this.size,
this.color,
this.animationPosition,
) : assert(animationPosition >= 0.0 && animationPosition <= 1.0) {
this.progress,
) : assert(progress >= 0.0 && progress <= 1.0) {
wave1Metric = _getWave1Path().computeMetrics().single;
wave2Metric = _getWave2Path().computeMetrics().single;
wave3Metric = _getWave3Path().computeMetrics().single;
Expand All @@ -102,7 +105,7 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {

final double size;
final Color color;
final double animationPosition;
final double progress;

late final PathMetric wave1Metric;
late final PathMetric wave2Metric;
Expand Down Expand Up @@ -245,10 +248,10 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {
final end1 = Offset(size * 0.8358561, size * 0.7058105);
final end2 = Offset(size * 0.8063965, size * 0.7352702);

final localAnimationPosition = _computeLocalAnimationPosition(.4, .6);
final localProgress = _computeLocalProgress(.4, .6);

final drawEnd1 = Offset.lerp(start1, end1, localAnimationPosition)!;
final drawEnd2 = Offset.lerp(start2, end2, localAnimationPosition)!;
final drawEnd1 = Offset.lerp(start1, end1, localProgress)!;
final drawEnd2 = Offset.lerp(start2, end2, localProgress)!;

final stripe2 = Path();
stripe2.moveTo(start1.dx, start1.dy);
Expand All @@ -268,7 +271,7 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {
) {
final drawPath = metric.extractPath(
0,
metric.length * _computeLocalAnimationPosition(start, duration),
metric.length * _computeLocalProgress(start, duration),
);

canvas.drawPath(drawPath, _getStrokePaint());
Expand All @@ -277,7 +280,7 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {
void _drawDot(Canvas canvas) {
canvas.drawCircle(
Offset(size * 0.5, size * 0.7916667),
(size * 0.08333333) * _computeLocalAnimationPosition(0, .1),
(size * 0.08333333) * _computeLocalProgress(0, .1),
_getFillPaint(),
);
}
Expand All @@ -297,21 +300,20 @@ class _YaruAnimatedNoNetworkIconPainter extends CustomPainter {
..blendMode = BlendMode.src;
}

double _computeLocalAnimationPosition(double start, double duration) {
double _computeLocalProgress(double start, double duration) {
assert(start >= 0.0 && start <= 1.0);
assert(duration >= 0.0 && duration <= 1.0);
assert(start + duration <= 1.0);

final localAnimationPosition = animationPosition >= start
? (animationPosition - start) * (1.0 / duration)
: 0.0;
final localProgress =
progress >= start ? (progress - start) * (1.0 / duration) : 0.0;

return localAnimationPosition < 1.0 ? localAnimationPosition : 1.0;
return localProgress < 1.0 ? localProgress : 1.0;
}

@override
bool shouldRepaint(_YaruAnimatedNoNetworkIconPainter oldDelegate) {
return oldDelegate.animationPosition != animationPosition ||
return oldDelegate.progress != progress ||
oldDelegate.size != size ||
oldDelegate.color != color;
}
Expand Down
76 changes: 39 additions & 37 deletions lib/src/animated_icons/yaru_animated_ok_icon.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';

const yaruAnimatedOkIconAnimationCurve = Curves.easeInCubic;
const yaruAnimatedOkIconAnimationDuration = 500;
const _kTargetCanvasSize = 24.0;
const _kTargetIconSize = 20.0;
const _kAnimationCurve = Curves.easeInCubic;
const _kAnimationDuration = 500;

/// An animated Yaru ok icon, similar to the original one
class YaruAnimatedOkIcon extends StatefulWidget {
Expand All @@ -13,7 +13,7 @@ class YaruAnimatedOkIcon extends StatefulWidget {
this.size = 24.0,
this.filled = false,
this.color,
this.onCompleted,
this.progress,
});

/// Determines the icon canvas size
Expand All @@ -29,8 +29,10 @@ class YaruAnimatedOkIcon extends StatefulWidget {
/// If null, defaults to colorScheme.onSurface
final Color? color;

/// Callback called once animation completed
final Function? onCompleted;
/// The animation progress for the animated icon.
/// The value is clamped to be between 0 and 1.
/// If null, a defaut animation controller will be created, which will run only once.
final Animation<double>? progress;

@override
State<YaruAnimatedOkIcon> createState() => _YaruAnimatedOkIconState();
Expand All @@ -41,30 +43,32 @@ class _YaruAnimatedOkIconState extends State<YaruAnimatedOkIcon>
late final Animation<double> _animation;
late final AnimationController _controller;

Animation<double> get progress => widget.progress ?? _animation;

@override
void initState() {
super.initState();

_controller = AnimationController(
duration: const Duration(milliseconds: _kAnimationDuration),
vsync: this,
);
_animation = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: _kAnimationCurve))
.animate(_controller);

_controller.addStatusListener((status) {
if (status == AnimationStatus.completed && widget.onCompleted != null) {
widget.onCompleted!();
}
});
if (widget.progress == null) {
_controller = AnimationController(
duration:
const Duration(milliseconds: yaruAnimatedOkIconAnimationDuration),
vsync: this,
);
_animation = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: yaruAnimatedOkIconAnimationCurve))
.animate(_controller);

_controller.forward();
_controller.forward();
}
}

@override
void dispose() {
_controller.dispose();
if (widget.progress == null) {
_controller.dispose();
}

super.dispose();
}

Expand All @@ -74,14 +78,14 @@ class _YaruAnimatedOkIconState extends State<YaruAnimatedOkIcon>
child: SizedBox.square(
dimension: widget.size,
child: AnimatedBuilder(
animation: _animation,
animation: progress,
builder: (context, child) {
return CustomPaint(
painter: _YaruAnimatedOkIconPainter(
widget.size,
widget.filled,
widget.color ?? Theme.of(context).colorScheme.onSurface,
_animation.value,
progress.value,
),
);
},
Expand All @@ -96,17 +100,17 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
this.size,
this.filled,
this.color,
this.animationPosition,
) : assert(animationPosition >= 0.0 && animationPosition <= 1.0);
this.progress,
) : assert(progress >= 0.0 && progress <= 1.0);

final double size;
final bool filled;
final Color color;
final double animationPosition;
final double progress;

@override
void paint(Canvas canvas, Size size) {
if (filled && animationPosition >= 0.5) {
if (filled && progress >= 0.5) {
final clipRect = Rect.fromCenter(
center: Offset.zero,
width: this.size * 2,
Expand Down Expand Up @@ -152,8 +156,8 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
? Offset(size * 0.939, size * 0.314)
: Offset(size * 0.892, size * 0.360);

if (animationPosition < 0.5) {
final pathT = animationPosition * 2.0;
if (progress < 0.5) {
final pathT = progress * 2.0;
final drawMid1 = Offset.lerp(start1, mid1, pathT)!;
final drawMid2 = Offset.lerp(start2, mid2, pathT)!;

Expand All @@ -163,7 +167,7 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
checkmark.lineTo(start2.dx, start2.dy);
checkmark.close();
} else {
final pathT = (animationPosition - 0.5) * 2.0;
final pathT = (progress - 0.5) * 2.0;
final drawEnd1 = Offset.lerp(mid1, end1, pathT)!;
final drawEnd2 = Offset.lerp(mid2, end2, pathT)!;

Expand All @@ -183,10 +187,9 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
final finalCircleRadius =
(size / 2 - 1) * _kTargetIconSize / _kTargetCanvasSize;
// From 1.0 to 0.75 to 1.0
final circleRadius = animationPosition < 0.5
? finalCircleRadius - finalCircleRadius * 0.25 * animationPosition
: finalCircleRadius * 0.75 +
finalCircleRadius * 0.25 * animationPosition;
final circleRadius = progress < 0.5
? finalCircleRadius - finalCircleRadius * 0.25 * progress
: finalCircleRadius * 0.75 + finalCircleRadius * 0.25 * progress;

return Path()
..addOval(
Expand All @@ -202,9 +205,8 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
final finalCircleRadius =
(size / 2 - 1) * _kTargetIconSize / _kTargetCanvasSize;
// From 1.0 to 0.75 to 1.0
final circleRadius = animationPosition < 0.5
? 0.0
: (animationPosition - 0.5) * 2 * finalCircleRadius;
final circleRadius =
progress < 0.5 ? 0.0 : (progress - 0.5) * 2 * finalCircleRadius;

return Path()
..addOval(
Expand Down Expand Up @@ -236,7 +238,7 @@ class _YaruAnimatedOkIconPainter extends CustomPainter {
bool shouldRepaint(
_YaruAnimatedOkIconPainter oldDelegate,
) {
return oldDelegate.animationPosition != animationPosition ||
return oldDelegate.progress != progress ||
oldDelegate.size != size ||
oldDelegate.filled != filled ||
oldDelegate.color != color;
Expand Down

0 comments on commit 3915a1b

Please sign in to comment.