Creating Special Effects with Various Animated Widgets

In this tutorial, weโ€™ll learn about the various Animated widgets that Flitter provides beyond AnimatedContainer to create more sophisticated and professional animation effects.

๐ŸŽฏ Learning Goals

After completing this tutorial, youโ€™ll be able to:

  • Implement fade in/out effects with AnimatedOpacity
  • Create spacing change animations with AnimatedPadding
  • Implement position movement animations with AnimatedAlign
  • Create size scaling effects with AnimatedScale
  • Implement rotation animations with AnimatedRotation
  • Combine multiple Animated widgets for complex effects

๐ŸŽจ Characteristics of Animated Widgets

Each Animated widget is specialized for specific properties, allowing very efficient and smooth implementation of those effects:

  • AnimatedOpacity: Transparency changes (fade effects)
  • AnimatedPadding: Internal spacing changes
  • AnimatedAlign: Alignment position changes (position movement)
  • AnimatedScale: Size ratio changes
  • AnimatedRotation: Rotation changes
  • AnimatedSlide: Relative position movement
  • AnimatedPositioned: Absolute position changes within Stack

๐Ÿ“‹ Step-by-Step Practice

Step 1: AnimatedOpacity - Fade Effects

Letโ€™s create effects where elements appear or disappear by smoothly changing transparency:

import { AnimatedOpacity, Container, Text, GestureDetector } from "@meursyphus/flitter";

class FadeEffect extends StatefulWidget {
  createState() {
    return new FadeEffectState();
  }
}

class FadeEffectState extends State<FadeEffect> {
  isVisible = true;

  build(context) {
    return Column({
      children: [
        GestureDetector({
          onClick: () => {
            this.setState(() => {
              this.isVisible = !this.isVisible;
            });
          },
          child: AnimatedOpacity({
            duration: 500,                    // Animation duration
            opacity: this.isVisible ? 1.0 : 0.0,  // Opacity (0.0~1.0)
            curve: "easeInOut",              // Animation curve
            child: Container({
              width: 200,
              height: 100,
              color: '#FF6B6B',
              child: Text(this.isVisible ? "Visible!" : "Hidden!")
            })
          })
        }),
        
        Text("Click to see fade effect")
      ]
    });
  }
}

Key properties of AnimatedOpacity:

  • opacity (number): Transparency (0.0 = completely transparent, 1.0 = completely opaque)
  • duration (number): Animation duration (milliseconds)
  • curve (string): Animation curve
  • child (Widget): Child widget to be animated

Step 2: AnimatedPadding - Spacing Animation

Letโ€™s smoothly change internal spacing to adjust content layout:

import { AnimatedPadding, EdgeInsets } from "@meursyphus/flitter";

class PaddingAnimation extends StatefulWidget {
  createState() {
    return new PaddingAnimationState();
  }
}

class PaddingAnimationState extends State<PaddingAnimation> {
  isExpanded = false;

  build(context) {
    return GestureDetector({
      onClick: () => {
        this.setState(() => {
          this.isExpanded = !this.isExpanded;
        });
      },
      child: Container({
        width: 300,
        height: 200,
        color: '#E8F4FD',
        child: AnimatedPadding({
          duration: 400,
          curve: "easeOut",
          padding: this.isExpanded 
            ? EdgeInsets.all(50)     // Large spacing when expanded
            : EdgeInsets.all(10),    // Small spacing when collapsed
          child: Container({
            color: '#2196F3',
            child: Text(
              this.isExpanded ? "Expanded padding" : "Default padding"
            )
          })
        })
      })
    });
  }
}

EdgeInsets usage:

// Same in all directions
EdgeInsets.all(20)

// Symmetric spacing
EdgeInsets.symmetric({
  horizontal: 30,  // Left and right
  vertical: 15     // Top and bottom
})

// Individual specification
EdgeInsets.only({
  top: 20,
  left: 15,
  right: 10,
  bottom: 25
})

// Direct specification
EdgeInsets.fromLTRB(10, 20, 15, 25)  // Left, top, right, bottom

Step 3: AnimatedAlign - Position Movement Animation

Letโ€™s smoothly change the alignment position of child widgets to create movement effects:

import { AnimatedAlign, Alignment } from "@meursyphus/flitter";

class AlignAnimation extends StatefulWidget {
  createState() {
    return new AlignAnimationState();
  }
}

class AlignAnimationState extends State<AlignAnimation> {
  alignmentIndex = 0;
  
  // 9 basic alignment positions
  alignments = [
    Alignment.topLeft,
    Alignment.topCenter,
    Alignment.topRight,
    Alignment.centerLeft,
    Alignment.center,
    Alignment.centerRight,
    Alignment.bottomLeft,
    Alignment.bottomCenter,
    Alignment.bottomRight
  ];

  build(context) {
    return GestureDetector({
      onClick: () => {
        this.setState(() => {
          this.alignmentIndex = (this.alignmentIndex + 1) % this.alignments.length;
        });
      },
      child: Container({
        width: 300,
        height: 200,
        color: '#F0F0F0',
        child: AnimatedAlign({
          duration: 600,
          curve: "easeInOut",
          alignment: this.alignments[this.alignmentIndex],
          child: Container({
            width: 80,
            height: 50,
            color: '#9B59B6',
            child: Text(`Position ${this.alignmentIndex + 1}`)
          })
        })
      })
    });
  }
}

Alignment constants:

// 9 basic positions
Alignment.topLeft        // Top-left
Alignment.topCenter      // Top-center
Alignment.topRight       // Top-right
Alignment.centerLeft     // Center-left
Alignment.center         // Center
Alignment.centerRight    // Center-right
Alignment.bottomLeft     // Bottom-left
Alignment.bottomCenter   // Bottom-center
Alignment.bottomRight    // Bottom-right

// Custom alignment (x, y: -1.0 ~ 1.0)
Alignment(0.5, -0.5)  // Towards top-right

Step 4: AnimatedScale - Size Ratio Animation

Letโ€™s create animations that scale widgets by ratio:

import { AnimatedScale } from "@meursyphus/flitter";

class ScaleAnimation extends StatefulWidget {
  createState() {
    return new ScaleAnimationState();
  }
}

class ScaleAnimationState extends State<ScaleAnimation> {
  isScaled = false;

  build(context) {
    return GestureDetector({
      onClick: () => {
        this.setState(() => {
          this.isScaled = !this.isScaled;
        });
      },
      child: AnimatedScale({
        duration: 400,
        curve: "bounceOut",
        scale: this.isScaled ? 1.5 : 1.0,      // Scale ratio
        alignment: Alignment.center,            // Scale origin point
        child: Container({
          width: 100,
          height: 100,
          color: '#E74C3C',
          child: Text(
            this.isScaled ? "1.5x!" : "Original"
          )
        })
      })
    });
  }
}

Step 5: AnimatedRotation - Rotation Animation

Letโ€™s implement animations that smoothly rotate widgets:

import { AnimatedRotation } from "@meursyphus/flitter";

class RotationAnimation extends StatefulWidget {
  createState() {
    return new RotationAnimationState();
  }
}

class RotationAnimationState extends State<RotationAnimation> {
  rotationCount = 0;

  build(context) {
    return GestureDetector({
      onClick: () => {
        this.setState(() => {
          this.rotationCount += 1;  // Add one rotation each time
        });
      },
      child: AnimatedRotation({
        duration: 800,
        curve: "easeInOut",
        turns: this.rotationCount,              // Number of rotations (1 = 360 degrees)
        alignment: Alignment.center,            // Rotation origin point
        child: Container({
          width: 80,
          height: 80,
          color: '#3498DB',
          child: Text("๐Ÿ”„")
        })
      })
    });
  }
}

Understanding rotation amounts:

turns: 0.25    // 90 degrees rotation
turns: 0.5     // 180 degrees rotation
turns: 1       // 360 degrees (1 rotation)
turns: 1.5     // 540 degrees (1.5 rotations)
turns: -0.5    // Counter-clockwise 180 degrees

Step 6: AnimatedSlide - Relative Position Movement

This animation moves widgets by distances relative to their size:

import { AnimatedSlide, Offset } from "@meursyphus/flitter";

class SlideAnimation extends StatefulWidget {
  createState() {
    return new SlideAnimationState();
  }
}

class SlideAnimationState extends State<SlideAnimation> {
  isSlid = false;

  build(context) {
    return Container({
      width: 300,
      height: 150,
      color: '#F8F9FA',
      child: GestureDetector({
        onClick: () => {
          this.setState(() => {
            this.isSlid = !this.isSlid;
          });
        },
        child: AnimatedSlide({
          duration: 500,
          curve: "easeInOut",
          offset: this.isSlid 
            ? Offset(1.0, 0)    // Right by widget width
            : Offset(0, 0),     // Original position
          child: Container({
            width: 100,
            height: 60,
            color: '#FF9F43',
            child: Text("Slide!")
          })
        })
      })
    });
  }
}

Understanding Offset:

Offset(0, 0)     // Original position
Offset(1, 0)     // Right by widget width
Offset(-1, 0)    // Left by widget width
Offset(0, 1)     // Down by widget height
Offset(0, -1)    // Up by widget height
Offset(1, 1)     // Diagonal bottom-right

๐ŸŽฏ Practice Challenges

TODO 1: Multi-Effect Card

Create an interactive card that combines multiple animations:

class MultiEffectCard extends StatefulWidget {
  createState() {
    return new MultiEffectCardState();
  }
}

class MultiEffectCardState extends State<MultiEffectCard> {
  isHovered = false;
  isClicked = false;

  build(context) {
    return GestureDetector({
      onMouseEnter: () => {
        this.setState(() => {
          this.isHovered = true;
        });
      },
      onMouseLeave: () => {
        this.setState(() => {
          this.isHovered = false;
        });
      },
      onClick: () => {
        this.setState(() => {
          this.isClicked = !this.isClicked;
        });
      },
      child: AnimatedScale({
        duration: 200,
        scale: this.isHovered ? 1.05 : 1.0,
        child: AnimatedRotation({
          duration: 300,
          turns: this.isClicked ? 0.02 : 0,  // Slight tilt
          child: AnimatedOpacity({
            duration: 250,
            opacity: this.isHovered ? 0.9 : 1.0,
            child: Container({
              width: 200,
              height: 120,
              color: '#6C5CE7',
              child: AnimatedPadding({
                duration: 200,
                padding: this.isHovered 
                  ? EdgeInsets.all(25) 
                  : EdgeInsets.all(20),
                child: Text("Multi-effect card")
              })
            })
          })
        })
      })
    });
  }
}

TODO 2: Sequential Animation Menu

Create an animation where multiple menu items appear in sequence:

class SequentialMenu extends StatefulWidget {
  createState() {
    return new SequentialMenuState();
  }
}

class SequentialMenuState extends State<SequentialMenu> {
  isMenuOpen = false;

  createMenuItem(text, delay) {
    return AnimatedSlide({
      duration: 400,
      curve: "easeOut",
      offset: this.isMenuOpen 
        ? Offset(0, 0) 
        : Offset(-1, 0),
      child: AnimatedOpacity({
        duration: 300,
        opacity: this.isMenuOpen ? 1.0 : 0.0,
        child: Container({
          width: 150,
          height: 50,
          color: '#1DD1A1',
          margin: EdgeInsets.only({ bottom: 10 }),
          child: Text(text)
        })
      })
    });
  }

  build(context) {
    // For delay animations, a more complex implementation is actually needed
    // Here we just show the basic concept
    return Column({
      children: [
        GestureDetector({
          onClick: () => {
            this.setState(() => {
              this.isMenuOpen = !this.isMenuOpen;
            });
          },
          child: Container({
            width: 150,
            height: 50,
            color: '#FF6B6B',
            child: Text(this.isMenuOpen ? "Close Menu" : "Open Menu")
          })
        }),
        
        this.createMenuItem("Home", 0),
        this.createMenuItem("Services", 100),
        this.createMenuItem("About", 200),
        this.createMenuItem("Contact", 300)
      ]
    });
  }
}

TODO 3: Loading Indicator

Create a loading animation combining multiple Animated widgets:

class LoadingIndicator extends StatefulWidget {
  createState() {
    return new LoadingIndicatorState();
  }
}

class LoadingIndicatorState extends State<LoadingIndicator> {
  isLoading = false;
  pulseCount = 0;

  startLoading() {
    this.setState(() => {
      this.isLoading = true;
    });
    
    // Timer for pulse animation
    const interval = setInterval(() => {
      if (this.isLoading) {
        this.setState(() => {
          this.pulseCount += 1;
        });
      } else {
        clearInterval(interval);
      }
    }, 800);
  }

  build(context) {
    return Column({
      children: [
        GestureDetector({
          onClick: () => {
            if (!this.isLoading) {
              this.startLoading();
              // Auto-complete after 3 seconds
              setTimeout(() => {
                this.setState(() => {
                  this.isLoading = false;
                });
              }, 3000);
            }
          },
          child: Container({
            width: 120,
            height: 50,
            color: '#3742FA',
            child: Text(this.isLoading ? "Loading..." : "Start Loading")
          })
        }),
        
        // Loading indicator
        AnimatedOpacity({
          duration: 300,
          opacity: this.isLoading ? 1.0 : 0.0,
          child: AnimatedRotation({
            duration: 1000,
            turns: this.isLoading ? this.pulseCount : 0,
            child: AnimatedScale({
              duration: 800,
              curve: "easeInOut",
              scale: (this.pulseCount % 2 === 0) ? 1.0 : 1.2,
              child: Container({
                width: 60,
                height: 60,
                color: '#FF3838',
                child: Text("โญ")
              })
            })
          })
        })
      ]
    });
  }
}

๐ŸŽจ Expected Results

When completed, the following effects should work:

  1. Fade effect: Smooth transparency changes for appearing/disappearing effects
  2. Padding animation: Content expansion/contraction through spacing changes
  3. Position movement: Smooth movement to 9 different positions
  4. Size changes: Scaling effects by ratio
  5. Rotation effect: Rotation with each click
  6. Slide: Movement by relative distances

๐Ÿ’ก Additional Challenges

For more challenges:

  1. Staggered animations: Make multiple elements animate sequentially
  2. Infinite loops: Implement automatically repeating animations
  3. Complex effects: Combine 3 or more Animated widgets
  4. Conditional animations: Execute specific effects only under certain conditions

โš ๏ธ Common Mistakes and Solutions

1. opacity Range Overflow

// โŒ Wrong range
opacity: 1.5   // Exceeds 1.0
opacity: -0.2  // Below 0.0

// โœ… Correct range
opacity: 1.0   // Maximum value
opacity: 0.0   // Minimum value

2. Misunderstanding Alignment Coordinates

// โŒ Wrong understanding
Alignment(100, 200)  // Misunderstood as absolute coordinates

// โœ… Correct understanding
Alignment(1.0, 1.0)  // Relative ratio (-1.0 ~ 1.0)

3. Confusing EdgeInsets Constructors

// โŒ Wrong usage
EdgeInsets(10, 20, 15, 25)  // Direct constructor usage

// โœ… Correct usage
EdgeInsets.fromLTRB(10, 20, 15, 25)  // Factory method usage

4. Performance with Multiple Animation Nesting

// โš ๏ธ Caution: Too much nesting causes performance degradation
AnimatedScale({
  child: AnimatedRotation({
    child: AnimatedOpacity({
      child: AnimatedSlide({
        // Too much nesting
      })
    })
  })
})

// โœ… Use only what's needed
AnimatedScale({
  child: AnimatedOpacity({
    // Combine only necessary effects
  })
})

๐ŸŽ“ Key Summary

Specialized Areas of Each Widget

  1. AnimatedOpacity: Transparency โ†’ Fade in/out
  2. AnimatedPadding: Spacing โ†’ Content expansion/contraction
  3. AnimatedAlign: Alignment โ†’ Position movement
  4. AnimatedScale: Ratio โ†’ Size changes
  5. AnimatedRotation: Rotation โ†’ Rotation effects
  6. AnimatedSlide: Relative movement โ†’ Slide effects

Combination Strategy

  • Single effect: Use one widget for a clear purpose
  • Complex effect: Combine 2-3 widgets for rich effects
  • Performance consideration: Avoid too much nesting
  • User experience: Natural and meaningful animations

With the various Animated widgets learned in this tutorial, you can create professional and sophisticated user interfaces. Understanding the characteristics of each widget and combining them appropriately can provide users with a delightful experience.

๐Ÿš€ Next Steps

In the next tutorial, letโ€™s learn AnimationController Mastery:

  • Controlling explicit animations
  • Using Tween and CurvedAnimation
  • Implementing complex animation sequences
  • Controlling animation playback, stop, and repeat

Next: AnimationController Mastery โ†’