Skip to content

Animations

Fluvie provides a powerful, frame-based animation system designed for deterministic video rendering. Animations in Fluvie work with progress values (0.0 to 1.0) rather than time, making them perfectly synchronized with frame capture.

Overview

Topic Description
PropAnimation Base animation class for transforms
EntryAnimation Specialized reveal animations
StaggerConfig Sequential child animations
interpolate() Keyframe-based value interpolation
Easing Animation curves reference

Quick Examples

Basic Animation

AnimatedProp(
  duration: 30,
  animation: PropAnimation.slideUp(),
  child: Text('Hello World'),
)

Combined Animation

AnimatedProp(
  duration: 45,
  animation: PropAnimation.combine([
    PropAnimation.slideUp(distance: 50),
    PropAnimation.fadeIn(),
    PropAnimation.zoomIn(start: 0.8),
  ]),
  curve: Easing.easeOutCubic,
  child: MyContent(),
)

Staggered List

VColumn(
  stagger: StaggerConfig.slideUp(delay: 10, duration: 30),
  children: [
    Text('Item 1'),  // Starts at frame 0
    Text('Item 2'),  // Starts at frame 10
    Text('Item 3'),  // Starts at frame 20
  ],
)

Keyframe Animation

TimeConsumer(
  builder: (context, frame, child) {
    final opacity = interpolate(
      frame,
      [0, 30, 60, 90],
      [0.0, 1.0, 1.0, 0.0],
      curve: Easing.easeInOut,
    );
    return Opacity(opacity: opacity, child: child);
  },
  child: Text('Fade in and out'),
)

Animation Concepts

Progress-Based Animation

All animations in Fluvie work with a progress value from 0.0 (start) to 1.0 (end):

// PropAnimation.apply(child, progress)
animation.apply(myWidget, 0.0);  // Initial state
animation.apply(myWidget, 0.5);  // Halfway
animation.apply(myWidget, 1.0);  // Final state

Frame-Based Timing

Durations are specified in frames, not milliseconds:

// At 30fps:
// 30 frames = 1 second
// 15 frames = 0.5 seconds
// 60 frames = 2 seconds

AnimatedProp(
  duration: 45,  // 1.5 seconds at 30fps
  ...
)

Easing Curves

Curves transform linear progress into shaped motion:

// Linear: constant speed
// easeOut: fast start, slow end (most common)
// easeInOut: slow start and end
// easeOutBack: overshoot at end

AnimatedProp(
  curve: Easing.easeOutCubic,
  ...
)

Animation Types

Transform Animations

Move, scale, rotate, or fade elements:

Method Effect
PropAnimation.translate() Move by offset
PropAnimation.scale() Scale up/down
PropAnimation.rotate() Rotate by radians
PropAnimation.fade() Change opacity

Convenience Animations

Pre-configured common animations:

Method Effect
PropAnimation.slideUp() Slide from below
PropAnimation.slideDown() Slide from above
PropAnimation.slideLeft() Slide from right
PropAnimation.slideRight() Slide from left
PropAnimation.zoomIn() Scale up
PropAnimation.zoomOut() Scale down
PropAnimation.fadeIn() Fade from 0 to 1
PropAnimation.fadeOut() Fade from 1 to 0
PropAnimation.slideUpFade() Combined slide + fade

Entry Animations

Dramatic reveal effects:

Type Effect
EntryAnimation.elasticPop() Spring-like pop with overshoot
EntryAnimation.strobeReveal() Flickering reveal
EntryAnimation.glitchSlide() RGB echo glitch effect
EntryAnimation.maskedWipe() Shape-based reveal

Continuous Animations

For looping motion:

Method Effect
PropAnimation.float() Oscillating movement
PropAnimation.pulse() Oscillating scale

Using with Widgets

AnimatedProp Widget

The primary way to apply animations:

AnimatedProp(
  animation: PropAnimation.slideUpFade(),
  duration: 30,
  startFrame: 60,
  curve: Easing.easeOut,
  child: Text('Animated text'),
)

VColumn/VRow with Stagger

Automatic sequential animation:

VColumn(
  stagger: StaggerConfig(
    delay: 15,
    duration: 30,
    fadeIn: true,
    slideIn: true,
    slideOffset: Offset(0, 40),
  ),
  children: myWidgets,
)

TimeConsumer with interpolate()

Full manual control:

TimeConsumer(
  builder: (context, frame, child) {
    final scale = interpolate(frame, [0, 30], [0.5, 1.0]);
    final rotation = interpolate(frame, [0, 60], [0.0, 6.28]);

    return Transform.scale(
      scale: scale,
      child: Transform.rotate(
        angle: rotation,
        child: child,
      ),
    );
  },
  child: Icon(Icons.star),
)

Best Practices

1. Use Convenience Methods

// Good: Clear intent
PropAnimation.slideUpFade()

// Verbose: Same effect
PropAnimation.combine([
  PropAnimation.translate(start: Offset(0, 30), end: Offset.zero),
  PropAnimation.fade(start: 0.0, end: 1.0),
])

2. Choose Appropriate Curves

// Elements entering: easeOut (fast start, gentle landing)
curve: Easing.easeOutCubic

// Elements exiting: easeIn (gentle start, fast exit)
curve: Easing.easeInCubic

// Emphasis/attention: easeOutBack (overshoot)
curve: Easing.easeOutBack

3. Keep Durations Reasonable

// Text/small elements: 15-30 frames
AnimatedProp(duration: 20, ...)

// Large elements/containers: 30-45 frames
AnimatedProp(duration: 40, ...)

// Dramatic reveals: 45-60 frames
AnimatedProp(duration: 50, ...)

4. Stagger Appropriately

// Fast stagger: items appear quickly
StaggerConfig(delay: 5, duration: 20)

// Medium stagger: comfortable reading pace
StaggerConfig(delay: 10, duration: 25)

// Slow stagger: dramatic reveals
StaggerConfig(delay: 20, duration: 40)