Skip to content

Instantly share code, notes, and snippets.

@m7mdra
Last active May 22, 2025 13:37
Show Gist options
  • Save m7mdra/a838cd6d766621a2eef8860e990c664c to your computer and use it in GitHub Desktop.
Save m7mdra/a838cd6d766621a2eef8860e990c664c to your computer and use it in GitHub Desktop.
Animate every single property of a Container
class AnimatedContainerPlus extends StatefulWidget {
final double? width;
final double? height;
final Color? color;
final Alignment? alignment;
final EdgeInsets? padding;
final EdgeInsets? margin;
final BorderRadius? borderRadius;
final double? rotation; // radians
final double? opacity;
final Duration duration;
final double? skewY;
final List<BoxShadow>? boxShadow;
final double? scale;
final Offset? offset;
final Border? border;
final Gradient? gradient;
final BoxShape? shape;
final double? blurSigma;
final Widget? child;
const AnimatedContainerPlus({
super.key,
this.duration = const Duration(milliseconds: 300),
this.width,
this.height,
this.color,
this.alignment,
this.padding,
this.margin,
this.borderRadius,
this.rotation,
this.opacity,
this.skewY,
this.boxShadow,
this.scale,
this.offset,
this.border,
this.gradient,
this.shape,
this.blurSigma,
this.child,
});
@override
State<AnimatedContainerPlus> createState() => _AnimatedContainerPlusState();
}
class _AnimatedContainerPlusState extends State<AnimatedContainerPlus>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> skewY;
late Animation<double> width;
late Animation<double> height;
late Animation<Color?> color;
late Animation<Alignment> alignment;
late Animation<EdgeInsets> padding;
late Animation<EdgeInsets> margin;
late Animation<BorderRadius?> borderRadius;
late Animation<double> rotation;
late Animation<double> opacity;
late Animation<List<BoxShadow>?> boxShadow;
late Animation<double> scale;
late Animation<Offset> offset;
late Animation<Border?> border;
late Animation<Gradient?> gradient;
late Animation<double> blurSigma;
// Cache current values for continued animation
double? _currentWidth;
double? _currentHeight;
double? _currentSkewY;
Color? _currentColor;
Alignment? _currentAlignment;
EdgeInsets? _currentPadding;
EdgeInsets? _currentMargin;
BorderRadius? _currentBorderRadius;
double? _currentRotation;
double? _currentOpacity;
List<BoxShadow>? _currentBoxShadow;
double? _currentScale;
Offset? _currentOffset;
Border? _currentBorder;
Gradient? _currentGradient;
double? _currentBlurSigma;
BoxShape? _currentShape;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
_initCurrentValues(); // Initialize from widget values
_setupTweens();
_controller.forward(from: 0);
}
void _initCurrentValues() {
_currentWidth = widget.width ?? 0;
_currentHeight = widget.height ?? 0;
_currentSkewY = widget.skewY ?? 0;
_currentColor = widget.color ?? Colors.transparent;
_currentAlignment = widget.alignment ?? Alignment.center;
_currentPadding = widget.padding ?? EdgeInsets.zero;
_currentMargin = widget.margin ?? EdgeInsets.zero;
_currentBorderRadius = widget.borderRadius ?? BorderRadius.zero;
_currentRotation = widget.rotation ?? 0;
_currentOpacity = widget.opacity ?? 1;
_currentBoxShadow = widget.boxShadow ?? [];
_currentScale = widget.scale ?? 1.0;
_currentOffset = widget.offset ?? Offset.zero;
_currentBorder = widget.border;
_currentGradient = widget.gradient;
_currentBlurSigma = widget.blurSigma ?? 0.0;
_currentShape = widget.shape ?? BoxShape.rectangle;
}
void _setupTweens() {
_controller.duration = widget.duration;
width =
Tween<double>(begin: _currentWidth, end: widget.width ?? _currentWidth)
.animate(_controller);
height = Tween<double>(
begin: _currentHeight, end: widget.height ?? _currentHeight)
.animate(_controller);
color = ColorTween(begin: _currentColor, end: widget.color ?? _currentColor)
.animate(_controller);
alignment = AlignmentTween(
begin: _currentAlignment,
end: widget.alignment ?? _currentAlignment)
.animate(_controller);
padding = EdgeInsetsTween(
begin: _currentPadding, end: widget.padding ?? _currentPadding)
.animate(_controller);
margin = EdgeInsetsTween(
begin: _currentMargin, end: widget.margin ?? _currentMargin)
.animate(_controller);
borderRadius = BorderRadiusTween(
begin: _currentBorderRadius,
end: widget.borderRadius ?? _currentBorderRadius)
.animate(_controller);
rotation = Tween<double>(
begin: _currentRotation, end: widget.rotation ?? _currentRotation)
.animate(_controller);
opacity = Tween<double>(
begin: _currentOpacity, end: widget.opacity ?? _currentOpacity)
.animate(_controller);
skewY =
Tween<double>(begin: _currentSkewY, end: widget.skewY ?? _currentSkewY)
.animate(_controller);
// New animations
boxShadow = BoxShadowTween(
begin: _currentBoxShadow ?? [], end: widget.boxShadow ?? [])
.animate(_controller);
scale =
Tween<double>(begin: _currentScale, end: widget.scale ?? _currentScale)
.animate(_controller);
offset = Tween<Offset>(
begin: _currentOffset, end: widget.offset ?? _currentOffset)
.animate(_controller);
border = BorderTween(begin: _currentBorder, end: widget.border)
.animate(_controller);
gradient = GradientTween(begin: _currentGradient, end: widget.gradient)
.animate(_controller);
blurSigma = Tween<double>(
begin: _currentBlurSigma,
end: widget.blurSigma ?? _currentBlurSigma)
.animate(_controller);
_controller.forward(from: 0);
}
@override
void didUpdateWidget(covariant AnimatedContainerPlus oldWidget) {
super.didUpdateWidget(oldWidget);
// Force update for reference types and check other property changes
bool needsUpdate = false;
// Always update reference type properties
if (!_areBoxShadowsEqual(oldWidget.boxShadow, widget.boxShadow)) {
_currentBoxShadow = widget.boxShadow;
needsUpdate = true;
}
if (!_areBordersEqual(oldWidget.border, widget.border)) {
_currentBorder = widget.border;
needsUpdate = true;
}
if (!_areGradientsEqual(oldWidget.gradient, widget.gradient)) {
_currentGradient = widget.gradient;
needsUpdate = true;
}
// Check other property changes
if (oldWidget.duration != widget.duration ||
oldWidget.width != widget.width ||
oldWidget.height != widget.height ||
oldWidget.color != widget.color ||
oldWidget.alignment != widget.alignment ||
oldWidget.padding != widget.padding ||
oldWidget.margin != widget.margin ||
oldWidget.borderRadius != widget.borderRadius ||
oldWidget.rotation != widget.rotation ||
oldWidget.opacity != widget.opacity ||
oldWidget.skewY != widget.skewY ||
oldWidget.scale != widget.scale ||
oldWidget.offset != widget.offset ||
oldWidget.shape != widget.shape ||
oldWidget.blurSigma != widget.blurSigma) {
needsUpdate = true;
}
if (needsUpdate) {
// Store current animation values as new start points
_currentWidth = width.value;
_currentHeight = height.value;
_currentColor = color.value ?? Colors.transparent;
_currentAlignment = alignment.value;
_currentPadding = padding.value;
_currentMargin = margin.value;
_currentBorderRadius = borderRadius.value;
_currentRotation = rotation.value;
_currentOpacity = opacity.value;
_currentSkewY = skewY.value;
_currentBoxShadow = boxShadow.value;
_currentScale = scale.value;
_currentOffset = offset.value;
_currentBorder = border.value;
_currentGradient = gradient.value;
_currentShape = widget.shape ?? BoxShape.rectangle;
_currentBlurSigma = blurSigma.value;
_setupTweens();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
Widget result = child ?? const SizedBox();
// Apply blur effect if needed
if (blurSigma.value > 0) {
result = ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: blurSigma.value,
sigmaY: blurSigma.value,
),
child: result,
),
);
}
// Create a container with explicit dimensions first
final container = Container(
alignment: alignment.value,
padding: padding.value,
margin: margin.value,
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
borderRadius: borderRadius.value,
boxShadow: boxShadow.value,
border: border.value,
gradient: gradient.value,
shape: _currentShape ?? BoxShape.rectangle,
),
child: result,
);
// Apply transforms with explicit sizing to prevent layout issues
return Opacity(
opacity: opacity.value,
child: SizedBox(
width: width.value +
padding.value.horizontal +
margin.value.horizontal,
height:
height.value + padding.value.vertical + margin.value.vertical,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.skewY(skewY.value),
child: Transform.rotate(
angle: rotation.value,
alignment: Alignment.center,
child: Transform.scale(
scale: scale.value,
alignment: Alignment.center,
child: Transform.translate(
offset: offset.value,
child: container,
),
),
),
),
),
);
},
child: widget.child ?? const FlutterLogo(size: 40),
);
}
// Helper methods for comparing reference types
bool _areBoxShadowsEqual(List<BoxShadow>? a, List<BoxShadow>? b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i].color != b[i].color ||
a[i].offset != b[i].offset ||
a[i].blurRadius != b[i].blurRadius ||
a[i].spreadRadius != b[i].spreadRadius) {
return false;
}
}
return true;
}
bool _areBordersEqual(Border? a, Border? b) {
if (a == b) return true;
if (a == null || b == null) return false;
return a.top == b.top &&
a.left == b.left &&
a.right == b.right &&
a.bottom == b.bottom;
}
bool _areGradientsEqual(Gradient? a, Gradient? b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (a.runtimeType != b.runtimeType) return false;
if (a is LinearGradient && b is LinearGradient) {
return a.colors.length == b.colors.length &&
a.begin == b.begin &&
a.end == b.end &&
a.tileMode == b.tileMode;
}
if (a is RadialGradient && b is RadialGradient) {
return a.colors.length == b.colors.length &&
a.center == b.center &&
a.radius == b.radius &&
a.tileMode == b.tileMode;
}
return false;
}
}
// Custom Tweens
class BoxShadowTween extends Tween<List<BoxShadow>?> {
BoxShadowTween({List<BoxShadow>? begin, List<BoxShadow>? end})
: super(begin: begin, end: end);
@override
List<BoxShadow>? lerp(double t) {
return BoxShadow.lerpList(begin, end, t);
}
}
class BorderTween extends Tween<Border?> {
BorderTween({Border? begin, Border? end}) : super(begin: begin, end: end);
@override
Border? lerp(double t) {
return Border.lerp(begin, end, t);
}
}
class GradientTween extends Tween<Gradient?> {
GradientTween({Gradient? begin, Gradient? end})
: super(begin: begin, end: end);
@override
Gradient? lerp(double t) {
return Gradient.lerp(begin, end, t);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment