flutter_animate

repository·main·Indexed 22 days ago

https://github.com/gskinner/flutter_animate

A performant Flutter library designed to simplify adding complex animated effects to widgets without manually managing AnimationControllers or StatefullWidgets. It provides a fluent chained API via .animate(), a declarative API using the Animate widget, and specialized tools like AnimateList for multiple widgets and ScrollAdapter for scroll-driven animations. The library supports custom effects by extending the Effect class or using the .custom() method.

Tokens
9.1K
Snippets
34
Records
40
Agent score
78%

What's inside flutter_animate

  1. Synchronize animations with Adapters

    main
    While animations are driven by an internal AnimationController by default, you can use an adapter to synchronize animations to external sources like a ScrollController (via ScrollAdapter). In this mode, the external source provides a 0-1 value that drives the animation.
  2. Configure delay, duration, and curve

    main

    Effects have optional delay, duration, and curve parameters.

    • Parallel vs Sequential: By default, effects run in parallel. To run them sequentially, use the delay parameter to offset the start time of subsequent effects.
    • Inheritance: If a parameter is not specified, it is inherited from the previous effect. If it is the first effect in a chain, it inherits from Animate.defaultDuration and Animate.defaultCurve.
    • Animate-level delay: Animate has its own delay parameter which defines a delay before the entire animation sequence begins. Unlike effect-level delays, this is only applied once if the animation repeats.
    // Sequential execution via delay
    Text("Hello").animate()
      .fade(duration: 500.ms)
      .scale(delay: 500.ms) // runs after fade
    
    // Parameter inheritance
    Text("Hello World!").animate()
      .fadeIn() // uses Animate.defaultDuration
      .scale() // inherits duration from fadeIn
      .move(delay: 300.ms, duration: 600.ms) // runs after the above with new duration
      .blurXY() // inherits delay & duration from move
    
    // Animate-level delay (applied once at start)
    Text("Hello").animate(
        delay: 1000.ms,
        onPlay: (controller) => controller.repeat(),
      ).fadeIn(delay: 500.ms) // this delay happens at the start of each loop
  3. Customize iOS launch screen assets

    main

    To change the launch screen image for your iOS application, you can either replace the image files directly in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory or use Xcode for a more visual approach.

    Using Xcode:

    1. Open your Flutter project's iOS workspace by running open ios/Runner.xcworkspace in your terminal.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog to replace the existing launch images.
    open ios/Runner.xcworkspace
  4. Basic Syntax: Apply animations to widgets

    main

    You can apply animations using two different syntaxes:

    1. The Animate wrapper: Wrap a widget in an Animate widget and provide a list of effects.
    2. The .animate() extension: Call .animate() on any widget to wrap it automatically. This allows for a chainable shorthand syntax where each effect is a method call.
    // Using the Animate wrapper
    Animate(
      effects: [FadeEffect(), ScaleEffect()],
      child: Text("Hello World!"),
    )
    
    // Using the .animate() extension shorthand
    Text("Hello World!").animate().fade().scale()
  5. How effects compose and inherit properties

    main

    In flutter_animate, effects are composed (run in parallel), not run sequentially. To run effects in sequence, you must use delay or a ThenEffect.

    Property Inheritance

    Effects inherit properties like duration and curve from the preceding effect in the chain if they are not explicitly specified. This allows for consistent animation styles across a chain.

    Parallel vs Sequential

    • Parallel (Default): myWidget.animate().fadeOut().fadeIn() will likely result in a widget that is invisible because both effects are applied simultaneously (one setting opacity to 0, the other to 1).
    • Sequential: To run them one after another, use delay or ThenEffect (e.g., myWidget.animate().fadeOut().fadeIn(delay: 200.ms)).
    • Swap: For transitions between two states, consider using SwapEffect.
  6. Use the chained API with `Widget.animate()`

    main

    The easiest way to add animations to a widget is by using the .animate() extension method. This wraps your widget in an Animate instance and allows you to chain effect methods directly.

    // Chained API
    myWidget.animate().fade().scale()
    
    // Equivalent to declarative API:
    // Animate(child: myWidget, effects: [FadeEffect(), ScaleEffect()])
    myWidget.animate().fade().scale()
  7. Animate a list of widgets with AnimateList

    main

    Use AnimateList to apply animations to multiple widgets in a list, such as the children of a Column or Row. It wraps each child in an Animate instance and allows you to offset the start time of each animation using an interval.

    You can use it via the .animate() extension method on a List<Widget> for a fluent syntax, or by instantiating AnimateList directly for a declarative approach.

    Key Features:

    • Intervals: Offsets the timing of each widget's animation. If interval is 100ms, the second widget starts 100ms after the first, the third 200ms after the first, etc.
    • Proxying: Calling effect methods (like .fade()) on the AnimateList instance applies that effect to every child.
    • Callbacks: onInit, onPlay, and onComplete are propagated to every individual Animate instance within the list.
    • Ignored Types: By default, certain widget types like Spacer are ignored and not wrapped in an Animate widget. You can customize this via AnimateList.ignoreTypes.
    // Using the extension method (Fluent syntax)
    Column(children: [foo, bar, baz].animate(interval: 100.ms).fade().scale())
    
    // Using the AnimateList constructor (Declarative syntax)
    Column(
      children: AnimateList(
        effects: [FadeEffect(), ScaleEffect()],
        interval: 100.ms,
        children: [foo, bar, baz],
      ),
    )
  8. React to state changes with the target parameter

    main

    You can make an animation react to state changes (similar to AnimatedOpacity) by setting a target value. When the target value changes, the animation automatically transitions to the new position (where 0 is the start and 1 is the end).

    // Animates to 0.8 opacity and 1.1 scale when _over is true
    MyButton().animate(target: _over ? 1 : 0)
      .fade(end: 0.8).scaleXY(end: 1.1)
  9. Animate lists of widgets

    main

    Use AnimateList to animate a collection of widgets. You can use the interval parameter to offset each child's animation by a specific duration, creating a staggered effect.

    // Using AnimateList class
    Column(children: AnimateList(
      interval: 400.ms,
      effects: [FadeEffect(duration: 300.ms)],
      children: [Text("Hello"), Text("World"), Text("Goodbye")],
    ))
    
    // Using shorthand extension
    Column(
      children: [Text("Hello"), Text("World"), Text("Goodbye")]
        .animate(interval: 400.ms).fade(duration: 300.ms),
    )
  10. Toggle and Swap widgets with ToggleEffect and SwapEffect

    main

    Two specialized tools for structural or state-based changes:

    • ToggleEffect: Provides a boolean value to the builder. The value is true before the end of the effect and false after the duration completes.
    • SwapEffect: Swaps the entire target widget for a new one provided by a builder at a specific time in the animation.
    // ToggleEffect: boolean value (true before end, false after)
    Animate().toggle(
      duration: 2.seconds,
      builder: (_, value, __) => Text(value ? "Before" : "After"),
    )
    
    // SwapEffect: replace the target widget
    Text("Before").animate()
      .swap(duration: 900.ms, builder: (_, __) => Text("After"))
  11. Create custom effects with CustomEffect

    main

    You can build one-off custom effects using CustomEffect. It requires a builder function that accepts (context, value, child). The value is a double (typically 0.0 to 1.0) representing the current animation progress.

    // Custom effect adding a background and color lerp
    Text("Hello World").animate().custom(
      duration: 300.ms,
      builder: (context, value, child) => Container(
        color: Color.lerp(Colors.red, Colors.blue, value),
        padding: EdgeInsets.all(8),
        child: child, // the target widget
      ),
    )
    
    // Using Animate without a child to build a standalone widget
    Animate().custom(
      duration: 10.seconds,
      begin: 10,
      end: 0,
      builder: (_, value, __) => Text(value.round()),
    ).fadeOut()