flitter-ui (지금 베타입니다)

Animation Basics

Overview

Flitter provides the same powerful animation system as Flutter. You can easily implement smooth and natural animations by combining AnimationController, Tween, and Curve.

Why are animations important?

Well-designed animations greatly improve user experience:

  • Visual Feedback: Provide immediate response to user actions
  • State Transitions: Smoothly express changes in screens or elements
  • Attention Guidance: Draw user focus to important information or changes
  • Brand Identity: Express app personality through unique animations
  • Cognitive Continuity: Naturally show element movement or transformation

Core Concepts

1. AnimationController

The core class that controls animation playback, stopping, repeating, etc.:

class MyWidgetState extends State<MyWidget> {
  controller!: AnimationController;
 
  initState(context: BuildContext) {
    super.initState(context);
    // duration is specified in milliseconds
    this.controller = new AnimationController({
      duration: 1000  // 1 second
    });
  }
 
  dispose() {
    this.controller.dispose();
    super.dispose();
  }
}

2. Tween

Defines interpolation between start and end values:

// Number Tween
const sizeTween = new Tween({
  begin: 50,
  end: 200
});
 
// Color Tween
const colorTween = new ColorTween({
  begin: '#3b82f6',
  end: '#ef4444'
});
 
// Offset Tween (position movement)
const positionTween = new Tween({
  begin: new Offset(0, 0),
  end: new Offset(100, 100)
});

3. Animation

Connects Tween and AnimationController to generate actual animation values:

const animation = sizeTween.animated(this.controller);
 
// Apply easing effects with CurvedAnimation
const curvedAnimation = new CurvedAnimation({
  parent: this.controller,
  curve: Curves.easeInOut
});
 
const animation = sizeTween.animated(curvedAnimation);

4. Animation Listeners

Add listeners to rebuild widgets whenever animation values change:

class MyWidgetState extends State<MyWidget> {
  controller!: AnimationController;
  animation!: Animation<number>;
 
  initState(context: BuildContext) {
    super.initState(context);
    this.controller = new AnimationController({ duration: 1000 });
    this.animation = new Tween({ begin: 0, end: 100 }).animated(this.controller);
 
    // Add listener - call setState whenever animation value changes
    this.controller.addListener(() => {
      this.setState();
    });
  }
 
  build(context: BuildContext): Widget {
    return Container({
      width: this.animation.value,
      height: this.animation.value,
      color: '#3b82f6'
    });
  }
}

Animation Control

// Start animation
this.controller.forward();
 
// Reverse animation
this.controller.reverse();
 
// Repeat animation
this.controller.repeat();
 
// Animate to specific value
this.controller.animateTo(0.5);
 
// Stop animation
this.controller.stop();
 
// Reset animation
this.controller.reset();

Practical Examples

1. Pulse Animation

class PulseAnimation extends StatefulWidget {
  createState() {
    return new PulseAnimationState();
  }
}
 
class PulseAnimationState extends State<PulseAnimation> {
  controller!: AnimationController;
  animation!: Animation<number>;
 
  initState(context: BuildContext) {
    super.initState(context);
 
    this.controller = new AnimationController({
      duration: 1000  // 1 second
    });
 
    this.animation = new Tween({
      begin: 1.0,
      end: 1.2
    }).animated(new CurvedAnimation({
      parent: this.controller,
      curve: Curves.easeInOut
    }));
 
    this.controller.repeat({ reverse: true });
  }
 
  dispose() {
    this.controller.dispose();
    super.dispose();
  }
 
  build(context: BuildContext): Widget {
    return Transform.scale({
      scale: this.animation.value,
      child: Container({
        width: 100,
        height: 100,
        decoration: new BoxDecoration({
          color: '#3b82f6',
          shape: 'circle'
        })
      })
    });
  }
}

2. Sequential Animation

class SequentialAnimation extends StatefulWidget {
  createState() {
    return new SequentialAnimationState();
  }
}
 
class SequentialAnimationState extends State<SequentialAnimation> {
  controller!: AnimationController;
  slideAnimation!: Animation<Offset>;
  fadeAnimation!: Animation<number>;
 
  initState(context: BuildContext) {
    super.initState(context);
 
    this.controller = new AnimationController({
      duration: 2000  // 2 seconds
    });
 
    // Slide animation
    this.slideAnimation = new Tween({
      begin: new Offset(-1, 0),
      end: new Offset(0, 0)
    }).animated(new CurvedAnimation({
      parent: this.controller,
      curve: Curves.easeOut
    }));
 
    // Fade animation
    this.fadeAnimation = new Tween({
      begin: 0.0,
      end: 1.0
    }).animated(new CurvedAnimation({
      parent: this.controller,
      curve: Curves.easeIn
    }));
 
    // Add animation listener
    this.controller.addListener(() => {
      this.setState();
    });
 
    this.controller.forward();
  }
 
  dispose() {
    this.controller.dispose();
    super.dispose();
  }
 
  build(context: BuildContext): Widget {
    return Transform.translate({
      offset: new Offset(
        this.slideAnimation.value.dx * 200,  // x-axis movement
        this.slideAnimation.value.dy * 0     // y-axis movement
      ),
      child: Opacity({
        opacity: this.fadeAnimation.value,
        child: Container({
          width: 200,
          height: 100,
          color: '#10b981',
          child: Center({
            child: Text("Sequential Animation", {
              style: new TextStyle({
                color: '#ffffff',
                fontSize: 18
              })
            })
          })
        })
      })
    });
  }
}

3. Custom Curve Animation

class CustomCurveAnimation extends StatefulWidget {
  createState() {
    return new CustomCurveAnimationState();
  }
}
 
class CustomCurveAnimationState extends State<CustomCurveAnimation> {
  controller!: AnimationController;
  bounceAnimation!: Animation<number>;
 
  initState(context: BuildContext) {
    super.initState(context);
 
    this.controller = new AnimationController({
      duration: 1500  // 1.5 seconds
    });
 
    // Custom curve for bounce effect
    this.bounceAnimation = new Tween({
      begin: 0,
      end: 300
    }).animated(new CurvedAnimation({
      parent: this.controller,
      curve: Curves.bounceOut
    }));
 
    // Add animation listener
    this.controller.addListener(() => {
      this.setState();
    });
 
    this.controller.forward();
  }
 
  dispose() {
    this.controller.dispose();
    super.dispose();
  }
 
  build(context: BuildContext): Widget {
    return Transform.translate({
      offset: new Offset(this.bounceAnimation.value, 0),
      child: Container({
        width: 50,
        height: 50,
        decoration: new BoxDecoration({
          color: '#10b981',
          shape: 'circle'
        })
      })
    });
  }
}

Important Notes

1. Performance Optimization

Animations can impact performance, so optimization is important:

// Bad example: Rebuilding entire widget tree
class MyWidgetState extends State<MyWidget> {
  build(context: BuildContext): Widget {
    return Column({
      children: [
        ComplexWidget({}),  // Recreated every time
        Container({
          width: this.animation.value,
          height: 100
        })
      ]
    });
  }
}
 
// Good example: Separate only parts affected by animation
class MyWidgetState extends State<MyWidget> {
  complexWidget = ComplexWidget({});  // Created only once
 
  build(context: BuildContext): Widget {
    return Column({
      children: [
        this.complexWidget,  // Reused
        Container({
          width: this.animation.value,
          height: 100
        })
      ]
    });
  }
}

2. Memory Management

AnimationController must be disposed:

dispose() {
  this.controller.dispose();  // Required!
  super.dispose();
}

3. AnimationController Setup

Create and manage AnimationController directly in your State class:

class MyState extends State<MyWidget> {
  controller!: AnimationController;
 
  initState(context: BuildContext) {
    super.initState(context);
    this.controller = new AnimationController({
      duration: 1000
    });
  }
 
  dispose() {
    this.controller.dispose();
    super.dispose();
  }
}

Built-in Animation Widgets

Flitter provides built-in widgets for commonly used animations:

  • AnimatedContainer: Automatic animation when properties change
  • AnimatedOpacity: Opacity animation
  • AnimatedPositioned: Position animation within Stack
  • AnimatedScale: Size animation
  • AnimatedRotation: Rotation animation
  • AnimatedSlide: Slide animation

These widgets enable simple animations without AnimationController.