Git Product home page Git Product logo

flutter_animate's People

Contributors

arthurbcd avatar domesticmouse avatar drown0315 avatar ellet0 avatar esdotdev avatar gskinner avatar lootwig avatar nohli avatar storm265 avatar thithip avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flutter_animate's Issues

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.

Allow pixel values for SlideEffect

In the current SlideEffect implementation, it's possible to set the begin and end offsets only with respect to the widget width and height:

SlideEffect(
  duration: animInfo.duration.ms,
  begin: Offset(-1, 0),
  end: Offset(0, 0),
  curve: animInfo.curve,
)

So, in the above example, if the effect is applied on a widget having height: 100 and width: 100 then beginning Offset(-1, 0) signifies that the slide animation will start from 100 pixels to the left of the widget.

I believe there should be a way to allow setting the begin and end offsets in terms of raw pixel values, so that we can use something like begin Offset(-85, 0) to signify the slide starts from 85 pixels left of the widget.

Alternative architecture for variations / presets

The current approach to effect variations / presets has some drawbacks. Use shake as an example.

  • shakeX and shakeY variations exist, but only in the form of method extensions
  • This favors the extension syntax, and does not provide access to these presets using declarative syntax, ie, we can not do: Animate(effects: [ ShakeXEffect() ]). This is confusing / annoying.
  • As more variations are created, this will build up more and more functionality that is closed off from the declarative syntax.
  • It will become a mish-mash of EffectName and effect.variations(), for example, you might have .fadeIn() and FadeUpAndIn both existing. With the class FadeIn not existing... quite weird.

Instead of using extension-method-first approach, a more component-based approach could be adopted. This would standardize the API, and not favor one syntax over the other.

psuedo code, eg:

class FadeEffect {} // core effect, adds .fade() extension

// presets/defaults are defined in the class
class FadeInEffect {
   build() => FadeEffect(begin: 0, end: 1)
}

// extension method just uses the class
extension FadeInEffect on Animate {
   fadeIn() => this.addEffect(FadeInEffect());
}

With this approach the presets are defined at the class level, the extension methods can still exist, but now the declarative form is also unlocked. There will also be no mish-mash, as everything will exist both as a FullEffect and .extensionEffect

Non Breaking

This approach would be non-breaking as the existing extension methods would continue to exist, they would just be augmented an additional syntax for their use.

eg fadeIn() still exists, but now there is also FadeInEffect.

Composition

More complicated presets need to support composition. Not sure exactly how implementation would look, but conceptually we want one effect to be composed of two or more core effects:

class FadeInUpEffect {
   build() => FadeInEffect(
      ...
      child: SlideInUpEffect( ... ));

   // adds .fadeInUp() extension 
}

The fundamental design challenge here is: How can 1 effect use 2 or more other effects? Is this even possible with the current architecture?

[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

Screen.Recording.2022-06-10.at.22.15.17.mov

The UI is separated into two-part:

  • Center: white block.
  • Border: yellow block.
HYNH0059.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.

Implement better smoothing in Adapter

Adapter currently uses AnimationController.animateTo to smooth value changes when animate=true. It works for infrequent changes, but Flutter's implementation gets "change locked" when the value changes constantly.

It'll need access to a tick in a non-Widget class, should respect the animation duration, and should be optimized to disable the tick when it isn't needed.

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

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.

Feature requests

I'm not sure if you have a road map, but I would love to help with some features and learn from you all as a contributor.

What appears to be important to me, based on a project I'm working on, are the items below.

  • JSON Serialization of Effects
  • Dynamic Effects (based on a stateful container that applies the effects)
  • Effects factory to create Effects from a context (JSON)
  • Finite State Machine as an extention to widgets being animated as a child of animate()
  • Understand Global Effects and how to build/use them (it is on the readme, but I couldn't find an example or implementation in the docs)
  • Effect Patterns (solutions) that can be applied, stored, and repeated.

My project is inspired from my Flash days. I'm using components like Stage, Scene, and MovieClip with assets and symbols, as the structure. I'm using that to create a sequential art framework for iOS and Android apps. The list above is on my list for the project.

Add additional presets

Once issue #31 is finalized, it would be nice to add an initial layer of additional presets/variations using the architecture.

While there are unlimited tween behaviors one could think of, some common ones stand out at the top of the list:
(inspired by https://pub.dev/packages/animate_do and https://animate.style/)

FadeInUp
FadeInDown
FadeInLeft
FadeInRight

FadeOutUp
FadeOutDown
FadeOutLeft
FadeOutRight

SlideInUp
SlideInDown
SlideInLeft
SlideInRight

SlideOutUp
SlideOutDown
SlideOutLeft
SlideOutRight

FlipInX
FlipInY
FlipOutX
FlipOutY

ZoomIn
ZoomOut

BounceIn
BounceOut

Notably, some of these are simple variations of existing effects (SlideDown or ZoomIn), while others are composed of multiple effects (FadeInDown). Ideally the new system supports composition, so FadeInDown uses FadeIn + SlideInDown rather than re-creating their logic.

This issue could be thought of as a first pass at the most obvious presets, and then other presets could be considered in the future. Mostly this can serve as dogfood for issue #31

Fade out then fade in results in invisible widget

Hi – I'm noticing that following a fade out with a fade in, results in the widget not appearing at all.

  return Text('example').animate().fadeOut().then().fadeIn();

The other way around, fading in and then out, seems to work as expected.

Make it easier to set ScrollAdapter begin/end relative to a specific widget

When creating scroll driven animations, it is often a lot easier to set anchor points relative to an element in the scroll region (versus absolute pixel values). This is especially true with dynamic or responsive content.

Quick sketch follows for illustration, naming and implementation specifics TBD.

ScrollAdapter(
  begin: ScrollAdapter.getPosition(myWidget, alignment: Alignment.center, offset: Offset(y: -100),
  ...
)

The above would set the begin property to 100px above the middle of myWidget.

Applying Effects within an Animate executes all Effects at once, ignores ThenEffect

I have started a project using this package. I'm implementing a strategy where media assets (images, rive animations, custom painters) can be manipulated as a child within an Animate(). I'm dynamically creating effects from json (which could be another feature) and adding them to the effects: property.

So a list of json objects like below is pushed thru an Effect factory based on this context to get a List that becomes the value for the effects: property.

"onLoadEffects": [
                      {"name": "FadeEffect",
                        "duration": 2000,
                        "delay": 0,
                        "curve": "Curves.easeIn",
                        "begin": 1.0,
                        "end": 0.0
                      },
                      {"name": "MoveEffect",
                        "duration": 2000,
                        "delay": 0,
                        "curve": "Curves.easeOut",
                        "begin_dx": 800.0,
                        "begin_dy": 80.0,
                        "end_dx": 0.0,
                        "end_dy": 80.0
                      },
                      {"name": "ThenEffect",
                        "duration": 0,
                        "delay": 1000
                      },
                      {"name": "ScaleEffect",
                        "delay": 0,
                        "duration": 2000,
                        "curve": "Curves.easeOut",
                        "begin_dx": 0.0,
                        "begin_dy": 0.0,
                        "end_dx": 2.5,
                        "end_dy": 2.5,
                        "alignment_x": 0.0,
                        "alignment_y": 0.0
                      }
                    ],
                    "onFrameEffects": [],
                    "onExitEffects": []

Can be applied like this:

               Animate(
                  effects: effects,
                  controller: _animator,
                  onComplete: _onCompleteLoadEffects,
                  child: asset
                );

The asset, being the media asset, is manipulated by the Effects list.

The problem I am seeing during testing is the "thenEffect" doesn't get respected amongst the list. The animate should apply the move and fade effects for 2 sec, then apply the "wait" or ThenEffect for 1 sec, and finally apply the scale effect.

It seems to apply all of the effects at once. I can manually change the delay on each effect which works, but then there is no real purpose for the "thenEffect".

I could be missing something or expecting the wrong results, but this seems to be a bug based on the initial approach.

I added a callbackEffect to the end of the list and the callback was executed at the onset of the animation, not at the end.

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.

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)));

Add support for declarative playback using `end` field

There are certain cases where it would be nice to just set the end value for a set of tweens, and have the tweens run when that end value changes.

For example:

GridView(
  children: imageWidgets.map((i){
    bool selected = imageWidgets.indexOf(i) == selectedIndex;
    return i.animate().scale(
       duration: 200.ms,
       curve: Curves.easeOut,
       begin: 1, 
       end: selected? 1.2 : 1);
})

We could then easily create this effect where the selected image scales in, while the previously selected image scales out. The others do nothing.

X6VrKADEzP.mp4

Currently the easiest way to do this is still with the built in TweenAnimationBuilder, which is a fine API, but it cant hook into any of Animates effects:

imageWidgets.map((i){
  bool selected = imageWidgets.indexOf(i) == selectedIndex;
    
  return TweenAnimationBuilder<double>(
    tween: Tween(begin: 1, end: selected ? 1.15 : 1),
    duration: 200.ms,
    curve: Curves.easeOut,
    builder: (_, value, child) => Transform.scale(scale: value, child: img),
  )
}

If you matched the declarative behavior of TAB, then the old end would become the new begin if end ever changes, which is quite nice for continuous effects (go from A > B > A). Changes to begin are ignored until the widget state is reloaded.

Typo in example for AnimatedContainer

Currently reads...

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

Animate.autoPlay

Currently, to create an animation that doesn't play automatically, you need to use onInit:

foo.animate(onInit: (controller) => controller.stop()).fade()

This works, but less semantically clear than having an autoPlay param.

However, implementation of such a feature has some ambiguities / concerns:

  1. Should Animate.delay be respected? It is a delay before the animation starts playing, so it's confusing either way.
  2. If the answer to (1) is yes, then how would you start it playing? A method like Animate.play? Thus far all playback control is delegated to the AnimationController, so this would be a possibly confusing departure.
  3. Because onInit runs when the animation plays, you wouldn't have a mechanism to access to controller if there was a delay.
  4. Even with autoPlay you still need to save off the Animate or AnimationController in order to play it later, so I'm not certain this is providing a ton of value.
  5. AnimateList uses Animate.delay for interval, which could cause confusionautoPlay if didn't respect delay

Something to consider, but for now I'm leaning towards not adding this. It's an advanced convenience feature, that may add more confusion than value.

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

Support passing in controller

Could be interesting to support passing in a ValueNotifier, and only instantiate an AnimationController if it is not provided. This would facilitate two things:

  1. Letting the parent widget own creating and destroying the controller at appropriate times.
  2. Support other notifiers (ex. animation on scroll).
foo.animate(controller: myController).etc()

Right now, we update the duration of the controller as new effects are added, so we'd need to decide if this still happens if the controller that's passed in is an AnimationController.

Effects triggered by unmounting a Widget

I'm seeing my animation being applied when the widget is added to my UI, but how would I indicate the animation which should play when the widget is removed from the render tree?

An error is thrown by ScrollAdapter when an animated widget from ListView.builder is disposed.

I am trying to use ScrollAdapter inside one of the children of a ListView.builder, but when the widget is disposed (out of the screen) and we keep scrolling, it will throw an error.

image

Here is the minimum reproducible example:

ListView.builder(
  controller: _scrollController,
  itemCount: 3,
  itemBuilder: (context, index) {
    final text = Text(
      index.toString(),
    );

    return SizedBox(
      height: MediaQuery.of(context).size.height,
      child: index == 0
          ? text
              .animate(adapter: ScrollAdapter(_scrollController))
              .slideY(end: 2.5)
          : text,
    );
  },
)

As you can see, I added an animation to the first child. When the first child goes out of view and is disposed, the error occurs.

Swaping a sequence of images and apply effects

Flutter Web

Running the code below, I am trying to swap a sequence of images and apply effects.

However, due to setState() a new animate object gets initialized. Resulting in numerous calls to update the image after time.
I was not able to cancel the old animation using onComplete: (controller) => controller.stop(), the number of calls keeps piling up.

I thought about leaving out controller.repeat() but then nextImage() gets never called. It is a bit of strange behavior. For me onComplete does not work consistently, I need to use callback() to call nextImage().

Also tried to isolate everything in an Animator object in a semi-singleton, but then the current image gets not updated.

Also tried swap() but it did not work for me either.

Is there a correct approach to solve this?

Thank you!

class _FadeState extends State<Fade> {
  final c = ImageService();
  late Image img;

  @override
  initState() {
    super.initState();
    nextImage();
  }

  nextImage() {
    setState(() => img = c.nextImage());
  }

  @override
  Widget build(BuildContext context) {
    return Container(
            width: ..., height: ..., child: img)
        .animate(
          onPlay: ((controller) => controller.repeat()),
        )
        .tint(
            duration: c.crossFadeDuration.ms,
            color: c.color,
            begin: 1.0,
            end: 0.0)
        .then()
        .tint(
            duration: c.crossFadeDuration.ms,
            color: c.color2,
            begin: 0.0,
            end: 1.0)
        .callback(
            duration: c.crossFadeDuration.ms, callback: (_) => nextImage());
  }
}

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

Swap effect for a range of values

Hey! I'm new to the party but really enjoy flutter_animate so far. The only effect, I've missed so far (and maybe I'm just blind) is a way to swap/fade through a range of widgets/builders similar to https://pub.dev/packages/cross_fade. Would that be something that's already easy to emulate with flutter_animate or something you've considered?

Cheers and keep up the great work!

Triggering the animation when a condition is met

A common use-cases is to trigger an animation stored in a widget when an event is triggered, such as

  • on the press of a button for a visual feedback. E.g. Shake something on the press of a button

Since, the documentation doesn't cover this use-case, the one simple solution I could think of is to make the parent widget stateful to capture the state of a toggle flag. The flag is set to true on the press of a button and false once the playback is complete. While this works, the code is not concise and certainly doesn't make for a good reading.

Is there a better way to accomplish this that may be documented?

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.

How to reverse an animation? E.g., slideX?

Love the package. I can make widgets slide in and get the AnimationController:

Text.animate(onPlay: (controller) {
              _animationController = controller;
            }).slideX(),

Is there a what to make the widget then slide out with the controller? And then back in again?

loop extension causes RTE when leaving view

I am using

.animate(
  onPlay: (controller) =>
   controller.loop(count: 2, reverse: true))
  .shimmer(
    delay: 400.ms,
    duration: 1800.ms,
    color: Colors.deepPurple.shade900)
  .shake(hz: 5, curve: Curves.easeInOutCubic)
  .scaleXY(end: 1.2, duration: 600.ms)
  .then(delay: 600.ms)
  .scaleXY(end: 1 / 1.1)

to animate an icon. When I navigate away from the page, the following crash is seen. How do I say cancel the pending animation?

2023-01-29 19:39:42.737004+0530 Runner[15014:4806242] [VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter/src/animation/animation_controller.dart': Failed assertion: line 533 pos 7: '_ticker != null': AnimationController.animateTo() called after AnimationController.dispose()
AnimationController methods should not be used after calling dispose.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
#2      AnimationController.animateTo (package:flutter/src/animation/animation_controller.dart:533:7)
#3      AnimationControllerLoopExtensions.loop.<anonymous closure> (package:flutter_animate/src/extensions/animation_controller_loop_extensions.dart:51:9)
#4      _RootZone.run (dart:async/zone.dart:1654:54)
#5      Future.timeout.<anonymous closure> (dart:async/future_impl.dart:865:34)
#6      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#7      _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
#8      _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
#9      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:26)
2023-01-29 19:40:01.785378+0530 Runner[15014:4813608] [tcp] tcp_input [C12.1.1.10:3] flags=[R] seq=3463997066, ack=0, win=0 state=FIN_WAIT_1 rcv_nxt=3463997066, snd_una=4155211570

adaptor driven animations with "delay" and "then" semantics?

I know the adaptor is providing a 0 to 1 value, but how does that map in to the time-based effects like delay or duration or then? It's not obvious to me how to set up what would be a time-based animation and drive it by an adaptor. Or, if that isn't supported, could we get that supported somehow?

Animation reset issue

Animation is reset when Animate is used on a Tooltip's child and cursor is leaving->re-entering app's window.

studio64_Fk8tsMUcq5

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Material App Bar'),
        ),
        body: Wrap(
          spacing: 40.0,
          children: [
            Animate(
              onPlay: (controller) => controller.repeat(reverse: true),
              effects: const [
                ShakeEffect(
                  hz: 2,
                  curve: Curves.easeInOutCubic,
                  duration: Duration(milliseconds: 1800),
                  delay: Duration(milliseconds: 450),
                ),
                TintEffect(
                  curve: Curves.easeInOutCubic,
                  color: Colors.blue,
                ),
              ],
              child: const Text('Normal'),
            ),
            Tooltip(
              message: '123',
              child: Animate(
                onPlay: (controller) => controller.repeat(reverse: true),
                effects: const [
                  ShakeEffect(
                    hz: 2,
                    curve: Curves.easeInOutCubic,
                    duration: Duration(milliseconds: 1800),
                    delay: Duration(milliseconds: 450),
                  ),
                  TintEffect(
                    curve: Curves.easeInOutCubic,
                    color: Colors.blue,
                  ),
                ],
                child: const Text('Issue'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Add directionality to ScrollAdapter

There are scenarios where you would only want the animation to run in one way when controlled by a ScrollAdapter.

It could use ScrollDirection.forward/reverse. Though it's worth considering whether this should be added to all adapters.

support slivers

Would be grate if we could do this:

CustomScrollView(
  slivers: [
     ...
  ].animate(interval: 100.ms)
    .move(
      curve: Curves.easeOut,
      duration: 300.ms,
      begin: const Offset(100, 0))
    .fade(duration: 100.ms),
)

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'),
            ),
          )

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.

final int count = (entry.duration.inSeconds * hz).floor();

(Bug link)

Optionally wrap `Animate` with `RepaintBoundary`

Reasoning

As discussed in this talk about flutter_animate, the performance of animations might get significantly improved by wrapping them in a RepaintBoundary.
I argue that this is not apparent to everyone. Especially newer Flutter developers might not be aware of this performance improvement opportunity.

Proposal

Add an optional bool? useRepaintBoundary to the Animate() widget. If set true, the animation should be automatically wrapped with a RepaintBoundary. The parameter should be false by default, as RepaintBoundaries also come with a cost and the usage should be transparent.

It would be vital to also add extensive explanation in the documentation of the parameter about when to use RepaintBoundary and when not to, optimally with examples. This way, people not knowing about the widget would get educated and situations where RepaintBoundary is applied suboptimally get minimized (although not eliminated!).

If the addition of such a parameter comes with too high of a risk of missusage, I propose to at least add a section to the ReadMe/Docs of this package explaining the potential benefits and drawbacks of using a RepaintBoundary in combination with animations.

Additional Context

This matter was discussed briefly in this tweet with the author of the package.

Should TintEffect use a color value?

Right now TintEffect has a color param, and uses a double for its value to indicate the strength of the tint.

It might make sense to update it to remove the color param completely, and change its value to be of type Color. This would be a tiny bit more work for a monochromatic tint since the strength would be determined by the color's alpha, but would enable the ability to animate between different colors of tint.

It's worth noting though that you get a somewhat similar effect now by nesting tints (though this is not identical, and is more expensive).

// current (verbose):
foo.animate().tint(color: Colors.red, begin: 0, end: 1);
foo.animate().tint(color: Colors.blue, begin: 1, end: 0).tint(color: Colors.red, begin: 0, end: 1);

// proposed equivalent (verbose):
foo.animate().tint(begin: Colors.transparent, end: Colors.red);
foo.animate().tint(begin: Colors.blue, end: Colors.red);

I need to retest this, but I believe that Colors.transparent is equivalent to Color(0), and that the default interpolation between a color and transparent affects the rgb channels and not just the alpha channel, leading to a darkening of the color. If that's the case, then devs would likely need to be more specific, like: Colors.red.withOpacity(0). Though the effect could automatically handle this case if begin or end is omitted or null.

Pros: can tint between different colors, and the value type is perhaps more appropriate.
Cons: likely a bit fussier to implement and use.
Considerations: How common is the use case for animating between different colored tints? I'm guessing fairly rare.

Unable to stop animation using a provided `AnimationController`

Consider the following animation:

typedef ValueType = Article;

class AnimatedZoomImage extends StatelessWidget {
  const AnimatedZoomImage({
    Key? key,
    required this.scaleDuration,
    required this.animationController,
  }) : super(key: key);

  final Duration scaleDuration;
  
  
  final AnimationController animationController;

  @override
  Widget build(BuildContext context) {
    return CachedNetworkImage(
      fit: BoxFit.cover,
      imageUrl: 'some-url',
      alignment: Alignment.topCenter,
    ).animate(
      controller: animationController,
      onComplete: (c) => c.repeat(reverse: true),
    ).custom(
        duration: scaleDuration,
        builder: (context, value, child) {
          final scale = 1.0 + (value * .5);
          return Transform(
            alignment: Alignment.center,
            transform: Matrix4.identity().scaled(scale, scale, scale),
            child: child,
          );
        });
  }
}

Calling animationController.stop() does not stop the animation from running aka the builder method is still being called, is that by design?

Change ThenEffect to establish a new baseline time

Currently ThenEffect works by establishing a new inheritable delay:

foo.animate()
  .fadeIn(duration: 700.ms) // begins at 0ms, ends at 700ms
  .then() // sets inheritable delay of 700ms (end of previous)
  .slide() // inherits 700ms delay, begins at 700ms, ends at 1400ms
  .flipX(delay: 0.ms) // overrides delay, begins at 0ms, ends at 700ms

It might be better if ThenEffect instead establishes a new "baseline time", that subsequent effects can modify with relative delays (including negative delays):

foo.animate()
  .fadeIn(duration: 700.ms) // begins at 0ms, ends at 700ms
  .then() // baseline is now 700ms (end of previous)
  .slide(delay: 0.ms) // begins at 700ms, ends at 1400ms
  .flipX(delay: -200.ms) // begins at 500ms, ends at 1200ms
  .then(delay: 300.ms) // baseline is now 1500ms (1200+300)
  .shake() // begins at 1500ms

This would require adding a new property to Animate to track this, and could break existing animations in rare cases (ex. if someone has overridden delay after using then).

Open to feedback on this.

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"

How to record animation frames

Hi, Thanks for the awesome library, but just wondering how to capture every frame of the animation as png, so that I can create a movie?

Weird behavior when widget is not `const`

I do not have a smaller sample but below is some example where I encountered a weird error. If I don't have Positioned marked as const the hearts are not appearing at all (animation not starting). If I make it const, it works just fine.

Video shows how it should behave (it has hardcoded svgImage set to null so it can be const)

          if (rightButtonActive)
            /// does not animate
            Positioned(
              bottom: 105,
              right: 20,
              child: _HeartSpray(svgImage: Assets.illustrations.games.swing.rightWindHeart),
            ),
          if (leftButtonActive)
            /// works as expected, as in the video
            const Positioned(
              bottom: 105,
              left: 20,
              child: _HeartSpray(svgImage: null),
            ),
class _HeartSpray extends StatelessWidget {
  const _HeartSpray({required this.svgImage});

  final SvgGenImage? svgImage;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // spawn 10 hearts
        for (var i = 0; i < 10; i++)
          Padding(
            padding: EdgeInsets.only(
              left: i.isEven ? math.Random().nextDouble() * 50 : 0,
              right: i.isEven ? 0 : math.Random().nextDouble() * 50,
            ),
            child: (svgImage ?? Assets.illustrations.games.swing.rightWindHeart)
                .svg()
                .animate(
                  delay: (i * 0.1).seconds,
                  onPlay: (controller) {
                    // repeat animation when finished
                    controller.repeat();
                  },
                )
                .fade(duration: 0.3.seconds)
                .scale(duration: 0.15.seconds)
                .slideY(begin: 1.0, end: -15, duration: 0.9.seconds)
                .shakeX(hz: 1)
                .fadeOut(delay: 0.2.seconds, duration: (0.9 - 0.4).seconds),
          ),
      ],
    );
  }
}
Screenrecorder-2023-02-04-19-32-27-541.mp4

Thanks for this package btw. Doing animations is much easier and more joyful and we can do magic stuff in just a few lines

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.