Add beautiful animated effects & builders in Flutter, via an easy, highly customizable unified API.

Overview

Flutter Animate

A performant library that makes it simple to add almost any kind of animated effect in Flutter.

  1. Pre-built effects, like fade, scale, slide, blur, shake, shimmer, and color effects (saturation and tint).
  2. Easy custom effects
  3. Simplified animated builders
  4. Synchronized events

All via a simple, unified API without fussing with AnimationController and StatefulWidget.

NOTE: This library is currently in prerelease. Some aspects of the API will change as it is refined. Your feedback is welcome via Github issues.

Example Image

Above: An example showing some features of Flutter Animate in ~15 simple lines of code.

Duration extensions

This package includes extension methods for num, to make specifying durations easier. For example: 2.seconds, 0.1.minutes, or 300.ms.

Basics

Syntax

To apply effects, wrap the target widget in Animate, and specify a list of effects:

Animate(
  effects: [FadeEffect(), ScaleEffect()],
  child: Text("Hello World!"),
)

It also adds an .animate() extension method to all widgets, which wraps the widget in Animate(). Each effect also adds a chainable extension method to Animate to enable a shorthand syntax:

Text("Hello World!").animate().fade().scale()

NOTE: The shortform style is used in this README, but all functionality is available in either format.

Delay, duration, curve

Effects have optional delay, duration, and curve parameters. Effects run in parallel, but you can use a delay to run them sequentially:

Text("Hello").animate()
  .fade(duration: 500.ms)
  .scale(delay: 500.ms) // runs after fade.

Note that effects are "active" for the duration of the full animation, so for example, two fade effects on the same target can have unexpected results (SwapEffect detailed below, can help address this).

If not specified (or null), these values are inherited from the previous effect, or from Animate.defaultDuration and Animate.defaultCurve if it is the first effect:

Text("Hello World!").animate()
  .fadeIn() // uses `Animate.defaultDuration`
  .scale() // inherits duration from fadeIn
  .move(delay: 300.ms, duration: 600.ms) // runs after the above w/new duration
  .blur(end: 8.0) // inherits the delay & duration from move

Animate also has its own delay parameter, which happens before the animation runs. Unlike the delay on an Effect, it is only applied once if the animation repeats.

Text("Hello").animate(
    delay: 1000.ms, // this delay only happens once at the very start
    onInit: (controller) => controller.repeat(), // loop
  ).fadeIn(delay: 500.ms) // this delay happens at the start of each loop

Sequencing with ThenEffect

ThenEffect is a special "convenience" effect that simply sets its own inheritable delay to the sum of the delay and duration of the previous effect, and its own (optional) delay. This makes it easier to sequence effects.

In the following example, the slide would run immediately after the fade ended, then the blur would run 200ms after the slide ended.

Text("Hello").animate()
  .fadeIn(delay: 300.ms, duration: 500.ms)
  .then() // sets own delay to 800ms (300+500)
  .slide(duration: 400.ms) // inherits the 800ms delay
  .then(delay: 200.ms) // sets delay to 1400ms (800+400+200)
  .blur() // inherits the 1400ms delay
  // Explicitly setting delay overrides the inherited value.
  // This move effect will run BEFORE the initial fade:
  .move(delay: 0.ms)

Animating lists

The AnimateList class offers similar functionality for lists of widgets, with the option to offset each child's animation by a specified interval:

Column(children: AnimateList(
  interval: 400.ms,
  effects: [FadeEffect(duration: 300.ms)],
  children: [Text("Hello"), Text("World"),  Text("Goodbye")],
))

// or shorthand:
Column(
  children: [Text("Hello"), Text("World"),  Text("Goodbye")]
    .animate(interval: 400.ms).fade(duration: 300.ms),
)

Shared effects

Because Effect instances are immutable, they can be reused. This makes it easy to create a global collection of effects that are used throughout your app and updated in one place. This is also useful for design systems.

MyGlobalEffects.transitionIn = <Effect>[
  FadeEffect(duration: 100.ms, curve: Curves.easeOut),
  ScaleEffect(begin: 0.8, curve: Curves.easeIn)
]

// then:
Text('Hello').animate(effects: MyGlobalEffects.transitionIn)

Custom effects & builders

It is easy to write new resuable effects by extending Effect, but you can also easily create one-off custom effects by using CustomEffect, ToggleEffect, and SwapEffect.

CustomEffect

CustomEffect lets you build custom animated effects. Simply specify a builder function that accepts a context, value, and child. The child is the target of the animation (which may already have been wrapped in other effects).

For example, this would add a background behind the text and fade it from red to blue:

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, // child is the Text widget being animated
  )
)

By default it provides a value from 0-1 (though some curves can generate values outside this range), based on the current time, duration, and curve. You can also specify begin and end values as demonstrated in the example below.

Animate can be created without a child, so you use CustomEffect as a simplified builder. For example, this would build text counting down from 10, and fading out:

Animate().custom(
  duration: 10.seconds,
  begin: 10,
  end: 0,
  builder: (_, value, __) => Text(value.round()),
).fadeOut()

ToggleEffect

ToggleEffect also provides builder functionality, but instead of a double, it provides a boolean value equal to true before the end of the effect and false after (ie. after its duration).

Animate().toggle(
  duration: 2.seconds,
  builder: (_, value, __) => Text(value ? "Before" : "After"),
)

This can also be used to activate "Animated" widgets, like AnimatedContainer, by toggling their values with a minimal delay:

Animate().toggle(
  duration: 1.ms,
  builder: (_, value, __) => AnimatedContainer(
    duration: 1.second,
    color: value ? Colors.red : Colors.green,
  ),
)

SwapEffect

SwapEffect lets you swap out the whole target widget at a specified time:

Text("Before").animate().swap(duration: 900.ms, builder: (_) => Text("After"))

This can also be useful for creating sequential effects, by swapping the target widget back in, effectively wiping all previous effects:

Widget text = Text("Hello World!");

// then:
text.animate().fadeOut(300.ms) // fade out & then...
  .swap(builder: (_) => text.fadeIn()) // swap in original widget & fade back in

Events & callbacks

There are onInit and onComplete callbacks on Animate that trigger when the whole animation starts or ends. Use the provided AnimationController to manipulate the animation (ex. repeat, reverse, etc).

Text("Horrible Pulsing Text")
  .animate(onInit: (controller) => controller.repeat(reverse: true))
  .fadeOut(curve: Curves.easeInOut)

For more nuanced callbacks, use CallbackEffect or ListenEffect.

CallbackEffect

CallbackEffect lets you add a callback to an arbitrary postion in your animations. For example, adding a callback halfway through a fade:

Text("Hello").animate().fadeIn(duration: 600.ms)
  .callback(duration: 300.ms, callback: () => print('halfway'))

As with other effects, it will inherit the delay and duration of prior effects:

Text("Hello").animate().scale(delay: 200.ms, duration: 400.ms)
  .callback(callback: () => print('scale is done'))

ListenEffect

ListenEffect lets you register a callback to receive the animation value (as a double) for a given delay, duration, curve, begin, and end.

Text("Hello").animate().fadeIn(curve: Curves.easeOutExpo)
  .listen(callback: (value) => print('current opacity: $value'))

The above example works, because the listen effect inherits duration and curve from the fade, and both use begin=0, end=1 by default.

Installation

Grab it from pub.dev.

Comments
  • feat(loop): add a loop extension for the AnimationController

    feat(loop): add a loop extension for the AnimationController

    The loop extension allows to repeat X times before stops.

    I try my implementation for the issue #14 to add a loop extension to allow running the animation a number of times. As suggested on the issue, I mirror the repeat method, and adds a timeout on the TickerFuture when the counter reach the number of times set.

    Please feel free to say if it's what you have in mind.

    opened by Thithip 15
  • Use animateTo on value update with the ValueNotifierAdapter

    Use animateTo on value update with the ValueNotifierAdapter

    Before this update, the new value was directly set to the controller. The controller directly jumps to the new value, an no animation was computed. Now, the controller will animate to the new value. Previously set animation parameters (Duration, Curve) will be preserved.

    I've updated the adapter's example with this animation to provide an example of the adpater:

    Before: Before

    After: After

    Plus, I update the Slider's animation example, from a ValueNotifierAdpater to a ValueAdapter.

    opened by Thithip 9
  • Allow Scale Effect for only one Axis

    Allow Scale Effect for only one Axis

    It would be great if the existing Scale effect could be used on only one axis as well, either by allowing (x, y) values be defined for the parameters begin and end or by adding ScaleX and ScaleY effects as done for the Shake effect.

    The Flame game engine allows this and therefore e.g. a game card flip effect (which is done solely on the x-axis) is possible with these lines of code:

    animalSprite.add(SequenceEffect([
      ScaleEffect.to(Vector2(0, 1), EffectController(duration: 0.2)),
      ScaleEffect.to(Vector2(1, 1), EffectController(duration: 0.2))
    ]));
    backSideSprite.add(ScaleEffect.to(Vector2(0, 1), EffectController(duration: 0.2)));
    
    enhancement good first issue 
    opened by rivella50 8
  • Allow for AnimateLists with different effects

    Allow for AnimateLists with different effects

    Currently, AnimateLists required setting the effects at the level of the list, so all children will be animated using the same effect. I couldn't find a straightforward way of overriding the effects for specific children (e.g. the third child in the list is animated using a different effect from the others) without magic numbers or manually calculating the delay.

    enhancement info needed 
    opened by timcreatedit 7
  • Prevent initial state to get applied when a controller is attached

    Prevent initial state to get applied when a controller is attached

    Is there a way to prevent the initial state of any effect to get applied before it's triggered using the controller? In the following example, I don't want to show the container as faded to 0.5 initially:

    Animate(
      controller: controller,
      adapter: adapter,
      effects: [
        FadeEffect(
          delay: 10.ms,
          duration: 1000.ms,
          begin: 0.5,
          end: 1.0,
        )
      ],
      child: Container(
        height: 100,
        width: 100,
        decoration: BoxDecoration(
          color: Colors.blueGrey,
          borderRadius: BorderRadius.circular(16),
        ),
      ),
    )
    

    The above example might not be a good use case, but I need to do it with some other animation effects.

    info needed 
    opened by sbis04 6
  • Fix blur effect throw an error on web

    Fix blur effect throw an error on web

    On Web, the blur effect throw an error when the Widget was fully unblur (so sigma values are close to 0.0). This happen on the begin of the blur animation, and on the end of the unblur one.

    Here we disable the ImageFiltered Widget when we are close to zero value. This error is still open on Flutter side: https://github.com/flutter/flutter/issues/89433

    I'm not very convinced by my hack btw...

    opened by Thithip 3
  • Something wrong with `buildSubAnimation`

    Something wrong with `buildSubAnimation`

    It's hard to produce minimal. You can run my project to get this error.

    If I try to decrease the grid cell when the grid cell increase animation has not been completed, it generates this error.

    ═══════ Exception caught by widgets library ═══════════════════════════════════
    The following assertion was thrown building Animate-[<'point : 0 | depth : 0 | false'>](state: _AnimateState#0613f(ticker active)):
    'package:flutter/src/animation/curves.dart': Failed assertion: line 183 pos 12: 'end <= 1.0': is not true.
    package:flutter/…/animation/curves.dart:183
    2
    
    Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
    In either case, please report this assertion by filing a bug on GitHub:
      https://github.com/flutter/flutter/issues/new?template=2_bug.md
    
    The relevant error-causing widget was
    Animate-[<'point : 0 | depth : 0 | false'>]
    lib/grid_zoom.dart:554
    When the exception was thrown, this was the stack
    #2      Interval.transformInternal
    package:flutter/…/animation/curves.dart:183
    #3      ParametricCurve.transform
    package:flutter/…/animation/curves.dart:38
    #4      Curve.transform
    package:flutter/…/animation/curves.dart:92
    #5      CurvedAnimation.value
    package:flutter/…/animation/animations.dart:462
    #6
    
    opened by definev 3
  • Add a test or two :)

    Add a test or two :)

    A couple of example tests that demonstrate how to use flutter_test would be a great starting point. It would then be easy for others in the community to extend this to a broader list that provides good coverage for the package as a whole.

    tests 
    opened by timsneath 2
  • Add a way to clear or reset the animation.

    Add a way to clear or reset the animation.

    It would be useful to have an effect similar to swap, but which returns the original child:

    Currently to create a complex sequence you can do something like this (from the README):

    Widget text = Text("Hello World!");
    
    // then:
    text.animate().fadeOut(300.ms) // fade out & then...
      .swap(builder: (_) => text.animate().fadeIn()) // swap in original widget & fade back in
    

    But the need to save off the original child is a pain point. We could do something like this:

    Text("Hello World!").animate().fadeOut(300.ms) // fade out & then...
      .clear(builder: (child) => child.animate().fadeIn()) // swap in original widget & fade back in
    
    opened by gskinner 2
  • [Question] - How to restart animation?

    [Question] - How to restart animation?

    I love your package so much it hides almost the boilerplate part. So declarative animation. I make this effect but I got an issue about restarting the animation.

    An animation I'm trying to replicate this animation.

    Currently, my implementation is pretty close but I don't know how to restart animation so It's a bit weird.

    My reproduce: https://gist.github.com/definev/d1aacf50122f62e79a956eb7245e495c

    https://user-images.githubusercontent.com/62325868/173096991-1be7683c-0e27-446c-b0c8-1eaeace5e113.mov

    The UI is separated into two-part:

    • Center: white block.
    • Border: yellow block.

    https://user-images.githubusercontent.com/62325868/173099633-d6ddbd90-2db3-400f-8249-f4528b6cde4c.mp4

    My border is regenerating every user increasing depth so it auto animates. I want to trigger the center to re-animate when changing depth.

    opened by definev 2
  • ShakeEffect does not work if the duration is less than 1000 milliseconds.

    ShakeEffect does not work if the duration is less than 1000 milliseconds.

    Working Code

    const Icon(Icons.favorite, size: 28.0)
                .animate()
                .shake(duration: 1000.ms),  // <--- work fine
    

    NOT working Code

    const Icon(Icons.favorite, size: 28.0)
                .animate()
                .shake(duration: 999.ms),  // <--- NOT work
    

    I think it because entry.duration.inSeconds return 0 if it is less than 1000 milliseconds.

    https://github.com/gskinner/flutter_animate/blob/4228b68a817f3692d3378d6748322f1cff0c2faf/lib/effects/shake_effect.dart#L64

    (Bug link)

    opened by ProZhar 1
  • Blur is using a inexistent

    Blur is using a inexistent "enabled" attribute

    flutter_animated version 2.0.1 has Blur effect with a bug.

    In blur_effect.dart ImageFiltered is using a inexistent "enabled" attribute on BlurEffect class.

    I downgraded Dart SDK from 2.17.1 to 2.15.1 and not resolved.

    blur_error

    flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel stable, 3.0.1, on Microsoft Windows [versÆo 10.0.22621.819], locale pt-BR) [√] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc1) [√] Chrome - develop for the web [√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.2.3) [√] Android Studio (version 2021.1) [√] Connected device (4 available) [√] HTTP Host Availability

    • No issues found!

    dart --version Dart SDK version: 2.15.1 (stable) (Tue Dec 14 13:32:21 2021 +0100) on "windows_x64"

    opened by chazgps 2
  • Matrix4 entries must be finite

    Matrix4 entries must be finite

    'dart:ui/painting.dart': Failed assertion: line 50 pos 10: '<optimized out>': Matrix4 entries must be finite., #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
    #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
    #2      _matrix4IsValid (dart:ui/painting.dart:50:10)
    #3      new Gradient.linear (dart:ui/painting.dart:3726:34)
    #4      LinearGradient.createShader (package:flutter/src/painting/gradient.dart:436:24)
    #5      ShimmerEffect.build.<anonymous closure>.<anonymous closure> (package:flutter_animate/effects/shimmer_effect.dart:70:48)
    #6      RenderShaderMask.paint (package:flutter/src/rendering/proxy_box.dart:1188:35)
    #7      RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #8      PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #9      PaintingContext.pushLayer (package:flutter/src/rendering/object.dart:460:12)
    #10     PaintingContext.pushClipPath (package:flutter/src/rendering/object.dart:600:7)
    #11     RenderClipPath.paint (package:flutter/src/rendering/proxy_box.dart:1830:25)
    #12     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #13     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #14     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15)
    #15     RenderDecoratedBox.paint (package:flutter/src/rendering/proxy_box.dart:2371:11)
    #16     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #17     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #18     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15)
    #19     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #20     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #21     RenderBoxContainerDefaultsMixin.defaultPaint (package:flutter/src/rendering/box.dart:2900:15)
    #22     RenderStack.paintStack (package:flutter/src/rendering/stack.dart:654:5)
    #23     RenderStack.paint (package:flutter/src/rendering/stack.dart:670:7)
    #24     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #25     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #26     RenderShiftedBox.paint (package:flutter/src/rendering/shifted_box.dart:84:15)
    #27     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #28     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #29     PaintingContext.pushLayer (package:flutter/src/rendering/object.dart:460:12)
    #30     PaintingContext.pushOpacity (package:flutter/src/rendering/object.dart:693:5)
    #31     RenderOpacity.paint (package:flutter/src/rendering/proxy_box.dart:954:21)
    #32     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #33     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #34     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15)
    #35     RenderTransform.paint (package:flutter/src/rendering/proxy_box.dart:2617:17)
    #36     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #37     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13)
    #38     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15)
    #39     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
    #40     PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:155:11)
    #41     PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:98:5)
    #42     PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1116:31)
    #43     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:515:19)
    #44     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:884:13)
    #45     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:378:5)
    #46     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
    #47     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
    #48     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1015:5)
    #49     _invoke (dart:ui/hooks.dart:148:13)
    #50     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)#51     _drawFrame (dart:ui/hooks.dart:115:31)
    

    Unfortunately i can not provide reproducible steps because it's very hard to recreate this issue in a new project. It happens on Windows when i minimize app with custom PostMessage method and use ShimmerEfect on a GridView child. Windows makes app smaller to show hiding animation and this issue appears.

    image It seems that width of gridview's child become 0 after minimizing the app.

    Can something like this be implemented as a fix or do not play animation at all if one of child's constraints is 0? image

    To get the same error output you can just create a container with 0 width and apply this effect to it

    Animate(
                onPlay: (controller) => controller.repeat(reverse: true),
                effects: [
                  ShimmerEffect(
                    color: Colors.white.withOpacity(0.40),
                    size: 2,
                    blendMode: BlendMode.srcATop,
                    delay: const Duration(milliseconds: 350),
                    duration: const Duration(milliseconds: 1000),
                  ),
                ],
                child: Container(
                  color: Colors.black,
                  width: 0,
                  height: 80,
                  child: Text('Hello World'),
                ),
              )
    
    opened by kirill-21 0
  • Restructure src files

    Restructure src files

    Fairly minor thing, but it would be nice if the lib followed the best practices on file structure: image

    Typically the outer file, provider.dart in this case is the name of the lib, and exports all library classes.

    Another provider.dart often exists inside of the src file, defining package level methods etc

    So we'd be looking for:

    flutter_animate.dart  // barrel file, all exports
    /src
       flutter_animate.dart // global stuff like `AnimateManager` and `buildSubAnimation`
       /effects
       /adapters
       etc
    
    opened by esDotDev 0
  • Add TweenSequenceEffect + test

    Add TweenSequenceEffect + test

    What this does

    Allows users to more seamlessly use Flutters built in TweenSequence API to express complex multi-step tweens.

    // fades in, out, and back in
    foo.animate().tweenSequence(
       duration: 1.seconds,
       sequence: TweenSequence<double>([
         TweenSequenceItem(tween: Tween(begin: 0, end: 1), weight: .5),
         TweenSequenceItem(tween: Tween(begin: 1, end: 0), weight: .5),
         TweenSequenceItem(tween: Tween(begin: 0, end: 1), weight: .5),
       ]),
       builder: (_, double value, Widget child) {
         return Opacity(opacity: value, child: child);
       },
     )
    

    Currently using TweenSequence is possile using a CustomEffect but requires slightly more boilerplate.

    Why use a TweenSequence

    • Provides an alternative syntax to declaring complex tweens (as opposed to setting delays and using then(), or manually making calls on the animation controller), which may be easier to read and maintain
    • Avoids issues with stacking effects of the same type, which is inherent to the then or delay approach.
    opened by esDotDev 5
  • Blur is not working on flutter web

    Blur is not working on flutter web

    Hi,

    The blur effect is not working on the flutter web giving the following error.

    ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    The following TypeErrorImpl was thrown building AnimatedBuilder(animation:
    AnimationController#697d7(⏮ 0.000; paused)➩Interval(0.6⋯1)➩Tween<Offset>(Offset(0.0, 0.0) →
    Offset(4.0, 4.0))➩Offset(0.0, 0.0), dirty, state: _AnimatedState#47719):
    Expected a value of type 'JavaScriptObject', but got one of type 'Null'
    
    The relevant error-causing widget was:
      AnimatedBuilder
      AnimatedBuilder:file:///C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_animate-2.0.1/lib/effects/effect.dart:84:12
    

    Can you look on this @gskinner

    Thanks and Regards.

    bug 
    opened by DinithHerath 1
  • Enable declarative effect variations

    Enable declarative effect variations

    What this does

    Initial pass at a solution for #31 and beginnings of #34.

    It sets a core architecture that supports both declarative syntax [ FadeInEffect() ] and imperitive animate().fadeIn() when creating effect variations. The current solution for variations supports only the imperative syntax.

    It also adds some plumbing to more easily make variations of other effects. Whether this is composing two or more existing effect together (FadeEffect + SlideEffect), or simply creating a variation of an existing one (FadeIn or BlurX).

    NOTE: While this touches many files, that is mostly due to a rename. The bulk of the interesting changes are in the effect.dart class and the new variations themselves.

    Architecture

    Effects do not always want to expose a public begin/end value.

    • Some may have multiple begin/ends, of different types, for example a FadeInAndUp may have Offset beginOffset and double beginOpacity
    • Some may not semantically make sense at all like Toggle or Swap.

    Being forced to inherit begin/end fields in these cases is confusing/undesired, so (imo) we need to make this optional.

    To allow this, a new BeginEndEffect<T> class was created, which extends Effect, and the begin/end fields were moved there.

    class BeginEndEffect<T> extends Effect {
      const BeginEndEffect({super.delay, super.duration, super.curve, this.begin, this.end});
    
      /// The begin value for the effect. If null, effects should use a reasonable
      /// default value when appropriate.
      final T? begin;
    
      /// The end value for the effect. If null, effects should use a reasonable
      /// default value when appropriate.
      final T? end;
    
      /// Helper method for concrete effects to easily create an Animation<T> from the current begin/end values.
      Animation<T> buildBeginEndAnimation(AnimationController controller, EffectEntry entry) =>
          entry.buildTweenedAnimation(controller, Tween(begin: begin, end: end));
    }
    

    Most of the existing 'core' effects were modified to extend BeginEndEffect, the exceptions were Then, Toggle, Callback and Swap where we were able to extend Effect directly and remove the weird Effect<double> and Effect<void> stuff that was being forced. There are also no longer phantom begin/end properties on these effects which is nice.

    Finally a CompositeEffectMixin was created, to make it easier to create a new effect from existing ones. The mixin is just a small bit of syntactic sugar, that requires a list of effects, overrides build, and auto-builds all the effects. This allows variations to be created with virtually no boilerplate.

    mixin CompositeEffectMixin on Effect {
      // A list of Effects must be provided by any concrete instances of this mixin
      List<Effect> get effects; 
    
      @override
      /// override build, and call composeEffects(...) so the concrete instances don't have to 
      Widget build(BuildContext context, Widget child, AnimationController controller, EffectEntry entry) =>
          composeEffects(effects, context, child, controller, entry);
    }
    

    Importantly, a new effect can both extend BeginEndEffect<T> and use the CompositeEffectMixin if needed so it's quite flexible and easy to use in different configurations.

    Example 1,

    FadeInEffect, does not want to expose public begin/end values, so it only extends Effect. It uses CompositeEffectMixin to keep boilerplate to a minimum:

    class FadeInEffect extends Effect with CompositeEffectMixin {
      const FadeInEffect({super.delay, super.duration, super.curve});
    
      @override
      List<Effect> get effects => const [FadeEffect(begin: 0, end: 1)];
    }
    

    Example 2,

    BlurX does want a begin and end, so it extends BeginEndEffect and uses CompositeEffectMixin. This shows how a variation can change the type of its core effect, BlurEffect takes an Offset, but this takes a double, straight inheritence would struggle here but the compositional approach has no problems:

    class BlurXEffect extends BeginEndEffect<double> with CompositeEffectMixin {
      const BlurXEffect({super.begin, super.end, super.delay, super.duration, super.curve});
    
      @override
      List<Effect> get effects => [
            BlurEffect(
              begin: Offset(begin ?? BlurEffect.neutralBlur, 0),
              end: Offset(end ?? (begin == null ? BlurEffect.defaultBlur : BlurEffect.neutralBlur), 0),
            )
          ];
    }
    

    Example 3,

    FadeInUp composes FadeIn and SlideInUp effects, which themselves are composed of Fade and Slide, showing advanced multi-level composition. It does not use extends BeginEndEffect, because it would be unclear what properties they refer to. Instead it declares it's own beginY for clarity:

    class FadeInUpEffect extends Effect with CompositeEffectMixin {
      const FadeInUpEffect({this.beginY, super.delay, super.duration, super.curve});
    
      final double? beginY;
    
      @override
      List<Effect> get effects => [
            const FadeInEffect(),
            SlideInUpEffect(beginY: beginY),
          ];
    }
    

    NOTE: In this example its been decided that beginY would make sense but the other values do not (beginOpacity, endOpacity and endY). This is debatable, but also not relevant to the example.

    What is left?

    • Agree this is a good approach to move forward with
    • Implement remaining low level variations that already exist in the lib
      • FlipH/V
      • MoveX/Y
      • Desaturate
      • ScaleX/Y/XY
      • ShakeX/Y
      • SlideX/Y
      • Untint
      • Show/Hide
    • Add some higher level variations, described in issue #34 (FadeInDown, SlideRight, etc)
    opened by esDotDev 0
Releases(v2.0.1)
Owner
Grant Skinner
Grant Skinner
Custom dropdown widget allows to add highly customizable widget in your projects with proper open and close animations and also comes with form required validation.

Custom Dropdown Custom Dropdown package lets you add customizable animated dropdown widget. Features Lots of properties to use and customize dropdown

Abdullah Chauhan 18 Dec 1, 2022
⚡️A highly customizable, powerful and easy-to-use alerting library for Flutter.

Flash ⚡️ A highly customizable, powerful and easy-to-use alerting library for Flutter. Website: https://sososdk.github.io/flash Specs This library all

null 361 Nov 29, 2022
Flutter Image add drag sort, Image add drag sort, support click event, delete, add, long press drag sort.

flutter_image_add_drag_sort Flutter Image add drag sort, Image add drag sort, support click event, delete, add, long press drag sort, support video fi

null 5 Jun 23, 2020
Powerful, helpfull, extensible and highly customizable API's that wrap http client to make communication easier with Axelor server with boilerplate code free.

flutter_axelor_sdk Powerful, helpful, extensible and highly customizable API's that wrap http client to make communication easier with Axelor server w

Abd al-Rahman al-Ktefane 4 Nov 12, 2021
Create highly customizable, simple, and controllable autocomplete fields for Flutter.

Field Suggestion Installing Depend on it Add this to your package's pubspec.yaml file: dependencies: field_suggestion: <latest_version> Install it Y

Ismael Shakverdiev 21 Oct 18, 2022
A highly customizable Flutter color picker.

FlexColorPicker FlexColorPicker is a customizable color picker for Flutter. The ColorPicker can show six different types of color pickers, three of wh

Rydmike 126 Nov 24, 2022
A highly customizable Flutter color picker.

FlexColorPicker FlexColorPicker is a customizable color picker for Flutter. The ColorPicker can show six different types of color pickers, three of wh

Rydmike 125 Nov 17, 2022
A highly customizable Flutter widget to render and interact with JSON objects.

The spreadsheet with superpowers ✨ ! JSON Data Explorer A highly customizable widget to render and interact with JSON objects. Features Expand and col

rows 13 Oct 21, 2022
A package that provides a highly customizable sheet widget that snaps to different vertical & horizontal positions

Snapping Sheet A package that provides a highly customizable sheet widget that snaps to different vertical & horizontal positions Can adapt to scrolla

Adam Jonsson 365 Nov 24, 2022
The flutter_calendar_widget is highly customizable calendar widget.

flutter_calendar_widget The flutter_calendar_widget is highly customizable calendar widget. Not only can you change the style, but you can also change

dooboolab 4 Jun 24, 2022
Beautiful Weather App using API with support for dark mode. Created by Jakub Sobański ( API ) and Martin Gogołowicz (UI, API help)

Flutter Weather App using API with darkmode support Flutter 2.8.1 Null Safety Beautiful Weather App using https://github.com/MonsieurZbanowanYY/Weathe

Jakub Sobański 5 Nov 29, 2022
A Flutter slidable widget that provides an easy to use configuration. Highly customisable. Just as you want it!

A Flutter slidable widget that provides an easy to use configuration. Highly customisable. Just as you want it!

Ravi Kavaiya 88 Sep 26, 2022
Flutter-Animated-Library-of-Books - Flutter App - Animated Book Library

bookshelf A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if thi

Ulfhrafn 0 Jan 2, 2022
Animated dialog box - A pure dart package for showing animated alert box.

animated_dialog_box A pure dart package for showing animated alert box. Getting Started https://github.com/Shubham-Narkhede/animated_dialog_box/blob/m

Shubham-Narkhede 10 Jul 24, 2022
Animated shimmer - A simple & lightweight widget to display an animated shimmer effect

Animated Shimmer Supports Null Safety A simple & lightweight widget to display a

Shubham Soni 7 Apr 27, 2022
The easiest way to create your animated splash screen in a fully customizable way.

Animated Splash Screen Check it out at Pub.Dev Do it your way Assets image Custom Widget Url image IconData Or just change PageTransition and/or Splas

Clean Code 104 Nov 10, 2022
A customizable toggle switch widget to add asset background images to the toggle switch.

A customizable toggle switch widget to add asset background images to the toggle switch. Features Use this package to give fancy background images to

null 3 Jul 26, 2022
Flutter plugin for building pull to refresh effects with PullToRefreshNotification and PullToRefreshContainer quickly.

pull_to_refresh_notification Language: English | 中文简体 widget to build pull to refresh effects. Web demo for PullToRefreshNotification Chinese blog pul

FlutterCandies 164 Oct 17, 2022
An all-in-one Fllutter package for state management, reactive objects, animations, effects, timed widgets etc.

Frideos An all-in-one package for state management, streams and BLoC pattern, animations and timed widgets, effects. Contents 1. State management Gett

Francesco Mineo 189 Sep 27, 2022