import 'package:flutter/material.dart'; /// 물리 기반 스프링 애니메이션을 적용하는 위젯 class SpringAnimationWidget extends StatefulWidget { final Widget child; final Duration delay; final SpringDescription spring; final Offset? initialOffset; final double? initialScale; final double? initialRotation; const SpringAnimationWidget({ super.key, required this.child, this.delay = Duration.zero, this.spring = const SpringDescription( mass: 1, stiffness: 100, damping: 10, ), this.initialOffset, this.initialScale, this.initialRotation, }); @override State createState() => _SpringAnimationWidgetState(); } class _SpringAnimationWidgetState extends State with TickerProviderStateMixin { late AnimationController _controller; late Animation _offsetAnimation; late Animation _scaleAnimation; late Animation _rotationAnimation; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); // 오프셋 애니메이션 _offsetAnimation = Tween( begin: widget.initialOffset ?? const Offset(0, 50), end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, curve: Curves.elasticOut, )); // 스케일 애니메이션 _scaleAnimation = Tween( begin: widget.initialScale ?? 0.5, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.elasticOut, )); // 회전 애니메이션 _rotationAnimation = Tween( begin: widget.initialRotation ?? 0.0, end: 0.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.elasticOut, )); // 지연 후 애니메이션 시작 Future.delayed(widget.delay, () { if (mounted) { _controller.forward(); } }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: _offsetAnimation.value, child: Transform.scale( scale: _scaleAnimation.value, child: Transform.rotate( angle: _rotationAnimation.value, child: child, ), ), ); }, child: widget.child, ); } } /// 바운스 효과가 있는 버튼 class BouncyButton extends StatefulWidget { final Widget child; final VoidCallback? onPressed; final EdgeInsetsGeometry? padding; final BoxDecoration? decoration; const BouncyButton({ super.key, required this.child, this.onPressed, this.padding, this.decoration, }); @override State createState() => _BouncyButtonState(); } class _BouncyButtonState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _scaleAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.95, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); } @override void dispose() { _controller.dispose(); super.dispose(); } void _handleTapDown(TapDownDetails details) { _controller.forward(); } void _handleTapUp(TapUpDetails details) { _controller.reverse(); widget.onPressed?.call(); } void _handleTapCancel() { _controller.reverse(); } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: _handleTapDown, onTapUp: _handleTapUp, onTapCancel: _handleTapCancel, child: AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( padding: widget.padding, decoration: widget.decoration, child: widget.child, ), ); }, ), ); } } /// 중력 효과 애니메이션 class GravityAnimation extends StatefulWidget { final Widget child; final double gravity; final double bounceFactor; final double initialVelocity; const GravityAnimation({ super.key, required this.child, this.gravity = 9.8, this.bounceFactor = 0.8, this.initialVelocity = 0, }); @override State createState() => _GravityAnimationState(); } class _GravityAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; double _position = 0; double _velocity = 0; final double _floor = 300; @override void initState() { super.initState(); _velocity = widget.initialVelocity; _controller = AnimationController( vsync: this, duration: const Duration(seconds: 10), )..addListener(_updatePhysics); _controller.repeat(); } void _updatePhysics() { setState(() { // 속도 업데이트 (중력 적용) _velocity += widget.gravity * 0.016; // 60fps 가정 // 위치 업데이트 _position += _velocity; // 바닥 충돌 감지 if (_position >= _floor) { _position = _floor; _velocity = -_velocity * widget.bounceFactor; // 너무 작은 바운스는 멈춤 if (_velocity.abs() < 1) { _velocity = 0; } } }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Transform.translate( offset: Offset(0, _position), child: widget.child, ); } } /// 물결 효과 애니메이션 class RippleAnimation extends StatefulWidget { final Widget child; final Color rippleColor; final Duration duration; const RippleAnimation({ super.key, required this.child, this.rippleColor = Colors.blue, this.duration = const Duration(milliseconds: 600), }); @override State createState() => _RippleAnimationState(); } class _RippleAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _animation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, )); } @override void dispose() { _controller.dispose(); super.dispose(); } void _handleTap() { _controller.forward(from: 0.0); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Stack( alignment: Alignment.center, children: [ AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: 100 + 200 * _animation.value, height: 100 + 200 * _animation.value, decoration: BoxDecoration( shape: BoxShape.circle, color: widget.rippleColor.withValues( alpha: (1 - _animation.value) * 0.3, ), ), ); }, ), widget.child, ], ), ); } }