Skip to content

Instantly share code, notes, and snippets.

@urusaich
Last active September 9, 2025 12:24
Show Gist options
  • Select an option

  • Save urusaich/956c29d3c97b8e5418fadbaecf26aa94 to your computer and use it in GitHub Desktop.

Select an option

Save urusaich/956c29d3c97b8e5418fadbaecf26aa94 to your computer and use it in GitHub Desktop.
ColorTween and AnimatedColor widget
import 'dart:async';
import 'dart:ui';
import 'package:flutter/widgets.dart';
class MyColorTween extends Tween<Color> {
MyColorTween({required Color super.begin, required Color super.end});
MyColorTween.fromArguments(Color begin, Color end)
: this(begin: begin, end: end);
@override
Color get begin => super.begin!;
@override
Color get end => super.end!;
@override
Color lerp(double t) => lerpColors(begin, end, t);
static Color lerpColors(Color a, Color b, double t) {
final w1 = (1 - t) * a.a;
final w2 = t * b.a;
final n = w1 + w2;
final w = n > 0.000001 ? w2 / n : 0.5;
return Color.from(
alpha: lerpDouble(a.a, b.a, t)!,
red: lerpDouble(a.r, b.r, w)!,
green: lerpDouble(a.g, b.g, w)!,
blue: lerpDouble(a.b, b.b, w)!,
);
}
}
class MyAnimatedColor extends StatelessWidget {
const MyAnimatedColor({
super.key,
required this.duration,
required this.color,
required this.builder,
this.child,
});
final Duration duration;
final Color color;
final ValueWidgetBuilder<Color> builder;
final Widget? child;
@override
Widget build(BuildContext context) => MyAnimatedValue<Color>(
duration: duration,
value: color,
builder: builder,
tweenBuilder: MyColorTween.fromArguments,
child: child,
);
}
class MyAnimatedValue<T> extends StatefulWidget {
const MyAnimatedValue({
super.key,
required this.duration,
required this.value,
required this.builder,
this.tweenBuilder,
this.child,
});
final Duration duration;
final T value;
final ValueWidgetBuilder<T> builder;
final Widget? child;
final Tween<T> Function(T begin, T end)? tweenBuilder;
@override
State<MyAnimatedValue<T>> createState() => _MyAnimatedValueState<T>();
}
class _MyAnimatedValueState<T> extends State<MyAnimatedValue<T>>
with SingleTickerProviderStateMixin {
late final _controller = AnimationController(
duration: widget.duration,
vsync: this,
);
late Tween<T> _tween;
T get _currentValue => _tween.evaluate(_controller);
@override
void initState() {
super.initState();
if (widget.tweenBuilder case final tweenBuilder?) {
_tween = tweenBuilder(widget.value, widget.value);
} else {
_tween = Tween<T>(begin: widget.value, end: widget.value);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant MyAnimatedValue<T> oldWidget) {
super.didUpdateWidget(oldWidget);
_controller.duration = widget.duration;
if (widget.value != oldWidget.value) {
_tween.begin = _currentValue;
_tween.end = widget.value;
unawaited(_controller.forward(from: _controller.lowerBound));
}
}
@override
Widget build(BuildContext context) => AnimatedBuilder(
animation: _controller,
builder: (context, child) => widget.builder(context, _currentValue, child),
child: widget.child,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment