Last active
May 22, 2025 13:37
-
-
Save m7mdra/a838cd6d766621a2eef8860e990c664c to your computer and use it in GitHub Desktop.
Animate every single property of a Container
This file contains hidden or 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
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