© Khmer Angkor Academy - sophearithput168

Animations

Animations ក្នុង Flutter

Animation ធ្វើឱ្យ UI រស់រវើក និងធ្វើឱ្យ user experience កាន់តែល្អ។ Flutter មាន animation APIs ដែលមានភាពទូលំទូលាយ និងមានប្រសិទ្ធភាពខ្ពស់។

📚 Animation Theory

Animation Pipeline:

Flutter Animation Flow

1️⃣ TICKER (Animation Timer)
   - Fires every frame (~60 FPS)
   - Provides elapsed time
   ↓
2️⃣ ANIMATION CONTROLLER
   - Controls animation (start, stop, reverse)
   - Manages duration & state
   ↓
3️⃣ TWEEN (Interpolation)
   - Defines start & end values
   - Calculates intermediate values
   ↓
4️⃣ ANIMATED BUILDER/WIDGET
   - Rebuilds UI with new values
   - Displays smooth transition
   ↓
5️⃣ RENDERED FRAME
   - User sees smooth animation

Animation Types in Flutter:

Type Complexity Use Case Example
Implicit Animations ⭐ Easy Simple property changes AnimatedContainer, AnimatedOpacity
Explicit Animations ⭐⭐ Medium Custom control needed AnimationController, Tween
Hero Animations ⭐ Easy Shared element transitions Hero widget
Physics Animations ⭐⭐⭐ Advanced Natural motion SpringSimulation

Common Animation Curves:

Curve Behavior Best For
linear Constant speed Loading indicators
easeIn Slow start, fast end Elements leaving screen
easeOut Fast start, slow end Elements entering screen
easeInOut Slow start & end Most UI animations
bounceIn/Out Bouncing effect Playful interactions
elasticIn/Out Elastic spring effect Attention-grabbing

🎬 Implicit Animations (ងាយបំផុត)

Implicit Animations គឺ automatic animations ដែល Flutter គ្រប់គ្រងឱ្យ។ គ្រាន់តែប្តូរ property, Flutter នឹង animate ដោយស្វ័យប្រវត្តិ។

1. AnimatedContainer

class AnimatedBox extends StatefulWidget {
  @override
  _AnimatedBoxState createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox> {
  bool isExpanded = false;
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          isExpanded = !isExpanded;
        });
      },
      child: AnimatedContainer(
        duration: Duration(seconds: 1),
        width: isExpanded ? 200 : 100,
        height: isExpanded ? 200 : 100,
        color: isExpanded ? Colors.blue : Colors.red,
        curve: Curves.easeInOut,
      ),
    );
  }
}

2. Other Implicit Animations

// AnimatedOpacity - Fade in/out
AnimatedOpacity(
  opacity: isVisible ? 1.0 : 0.0,
  duration: Duration(milliseconds: 500),
  child: Text('Hello'),
)

// AnimatedPositioned - Move widget
AnimatedPositioned(
  duration: Duration(seconds: 1),
  top: isTop ? 50 : 200,
  left: isLeft ? 50 : 200,
  child: Container(width: 100, height: 100, color: Colors.blue),
)

// AnimatedDefaultTextStyle - Text style
AnimatedDefaultTextStyle(
  duration: Duration(milliseconds: 300),
  style: TextStyle(
    fontSize: isLarge ? 40 : 20,
    color: isRed ? Colors.red : Colors.blue,
  ),
  child: Text('Animated Text'),
)

// AnimatedCrossFade - Switch between two widgets
AnimatedCrossFade(
  duration: Duration(milliseconds: 300),
  firstChild: Icon(Icons.favorite_border, size: 100),
  secondChild: Icon(Icons.favorite, size: 100, color: Colors.red),
  crossFadeState: isFavorite 
    ? CrossFadeState.showSecond 
    : CrossFadeState.showFirst,
)

⚡ Explicit Animations (Full Control)

Explicit Animations ប្រើ AnimationController ដើម្បី control animation manually។

class ExplicitAnimationExample extends StatefulWidget {
  @override
  _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    
    // Create controller
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,  // Prevents offscreen animations
    );
    
    // Create tween animation
    _animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeInOut,
      ),
    );
    
    // Start animation
    _controller.forward();
  }
  
  @override
  void dispose() {
    _controller.dispose();  // Important: clean up!
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Container(
          width: _animation.value,
          height: _animation.value,
          color: Colors.blue,
        );
      },
    );
  }
}

Animation Controller Methods:

// Control methods
_controller.forward();           // Play animation forward
_controller.reverse();           // Play animation backward
_controller.repeat();            // Loop animation
_controller.reset();             // Reset to start
_controller.stop();              // Stop animation

// Animation with callbacks
_controller.forward().then((_) {
  print('Animation completed!');
});

// Listen to animation
_controller.addListener(() {
  print('Value: ' + _controller.value.toString());
});

_controller.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    _controller.reverse();  // Reverse when completed
  }
});

🎯 Multiple Animations (Staggered)

class StaggeredAnimation extends StatefulWidget {
  @override
  _StaggeredAnimationState createState() => _StaggeredAnimationState();
}

class _StaggeredAnimationState extends State<StaggeredAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _opacity;
  late Animation<double> _width;
  late Animation<double> _height;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // Stagger animations with intervals
    _opacity = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.0, 0.5, curve: Curves.easeIn),
      ),
    );
    
    _width = Tween<double>(begin: 50, end: 200).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.25, 0.75, curve: Curves.easeOut),
      ),
    );
    
    _height = Tween<double>(begin: 50, end: 200).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.5, 1.0, curve: Curves.easeInOut),
      ),
    );
    
    _controller.forward();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Opacity(
          opacity: _opacity.value,
          child: Container(
            width: _width.value,
            height: _height.value,
            color: Colors.purple,
          ),
        );
      },
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

🦸 Hero Animations (Shared Element Transitions)

Hero animations ធ្វើឱ្យ widget "ហោះ" ពីមួយ screen ទៅ screen ផ្សេង។

// First Screen
class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => DetailScreen()),
          );
        },
        child: Hero(
          tag: 'imageHero',  // Must match!
          child: Image.network('https://example.com/image.jpg'),
        ),
      ),
    );
  }
}

// Second Screen
class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Hero(
          tag: 'imageHero',  // Same tag!
          child: Image.network('https://example.com/image.jpg'),
        ),
      ),
    );
  }
}

💫 Page Transitions

// Custom page transition
class FadePageRoute<T> extends PageRoute<T> {
  final Widget child;
  
  FadePageRoute({required this.child});
  
  @override
  Color? get barrierColor => null;
  
  @override
  String? get barrierLabel => null;
  
  @override
  Duration get transitionDuration => Duration(milliseconds: 500);
  
  @override
  bool get maintainState => true;
  
  @override
  Widget buildPage(
    BuildContext context,
    Animation animation,
    Animation secondaryAnimation,
  ) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }
}

// Usage
Navigator.push(
  context,
  FadePageRoute(child: NextPage()),
);

🎨 Transform & Matrix Animations

// Rotation animation
Transform.rotate(
  angle: _animation.value * 2 * 3.14159,  // Radians
  child: Icon(Icons.refresh, size: 100),
)

// Scale animation
Transform.scale(
  scale: _animation.value,
  child: Container(width: 100, height: 100, color: Colors.green),
)

// Translation animation
Transform.translate(
  offset: Offset(_animation.value, 0),
  child: Text('Moving Text'),
)

⚡ Performance Tips

// ❌ BAD: Rebuilds entire widget tree
AnimatedBuilder(
  animation: _animation,
  builder: (context, child) {
    return ExpensiveWidget(  // Rebuilds every frame!
      value: _animation.value,
      child: VeryExpensiveChild(),
    );
  },
)

// ✅ GOOD: Use child parameter
AnimatedBuilder(
  animation: _animation,
  child: VeryExpensiveChild(),  // Built once!
  builder: (context, child) {
    return ExpensiveWidget(
      value: _animation.value,
      child: child,  // Reuses same child
    );
  },
)

💡 Best Practices:

  • ✅ Use implicit animations for simple cases (easier)
  • ✅ Use explicit animations when you need full control
  • ✅ Always dispose() AnimationControllers to prevent memory leaks
  • ✅ Use const widgets inside AnimatedBuilder child parameter
  • ✅ Keep animations subtle (200-500ms usually best)
  • ✅ Test animations on real devices (not just emulator)
  • ✅ Use Transform instead of changing layout (better performance)

⚠️ Performance Warnings:

  • ❌ Too many simultaneous animations (causes jank)
  • ❌ Animating expensive widgets without optimization
  • ❌ Forgetting to dispose controllers (memory leak)
  • ❌ Using animations in ListView items (performance issue)
  • ⚠️ Aim for 60 FPS (16ms per frame)

💡 ជំនួយ: AnimatedContainer ងាយប្រើសម្រាប់ animation ធម្មតា។ AnimationController សម្រាប់ animation ស្មុគស្មាញ។ Hero widget សម្រាប់ shared element transitions។