Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add An Equivalent to AnimatedSwitcher #119

Open
caseycrogers opened this issue Dec 7, 2023 · 8 comments
Open

Add An Equivalent to AnimatedSwitcher #119

caseycrogers opened this issue Dec 7, 2023 · 8 comments
Labels
enhancement New feature or request Support Question about using the library

Comments

@caseycrogers
Copy link

caseycrogers commented Dec 7, 2023

One of the things I love the most about flutter_animate is that I can use the extensions to visually separate (in my code) UI elements from the animation effects applied to them. This makes my code MASSIVELY more readable.

The one place where I've struggled is I have a bunch of AnimatedSwitchers and I'm not quite sure how to migrate them. Here's one such example:

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      duration: kFlipDuration,
      transitionBuilder: flipTransitionBuilder,
      child: isFlipped ? secondChild : firstChild,
      switchInCurve: Curves.easeInBack,
      switchOutCurve: Curves.easeInBack.flipped,
    );
  }

  Widget flipTransitionBuilder<T>(
    Widget child,
    Animation<double> animation,
  ) { ... }
}

I think what I've done above could be achieved via something like toggleEffect + flipEffect, but it feels a bit messy so thus far I've just stuck with AnimatedSwitcher while I migrated all my other animations to flutter_animate.
So, this isn't the highest pri, but here's what I'd like (and I may build a prototype myself):
a SwitchEffect that is as close to AnimatedSwitcher as possible.

return SwitchEffect(
  child: isFlipped ? secondChild : firstChild,
).animate().flipEffect( ... ).toggleEffect( ... ); // The effects are only triggered when the child changes.

This feels very close to just return ToggleEffect( ... ) but as far as I can tell toggle effect is dependent on the animation, not on an external variable (like the widget child).

I also just may be missing something in flutter_animate that already does this, if I have please let me know as I'd love to migrate all my vanilla fluttter animation logic into flutter_animate!

@gskinner gskinner added enhancement New feature or request Support Question about using the library labels Jan 30, 2024
@gskinner
Copy link
Owner

gskinner commented Jan 30, 2024

I think you could probably get there with swap, but it might be easier to just write a simple widget that mimics the capabilities of AnimatedSwitcher, but lets you use Animate for the transition?

AnimateSwitcher(
  child: isFlipped ? second : first,
  outEffects: EffectList().fadeOut(),
  inEffects: EffectList().fadeIn(),
)

Where it would run the outEffects on the outgoing child, then the inEffects on the incoming child. Maybe allow for an overlap param, so they can be layered over each other and run simultaneously? Or even an enum for specifying no overlap, layering incoming on top, or outgoing on top.

Feel free to take a stab at this as a PR. If not, I might look at it when I have a bit of time.

@mikes222
Copy link

I had no success in building this widget. Currently the first in effect works as well as the second inEffect for the contemplary widget. The out effects are not working and consecutive effects are also not working.

I played around with different possibilities even with FutureBuilder instead of swap() but with no major success so far.

return SwitchEffect(
  child: pos == 0 ? _icon(Icons.light_mode) : _icon(Icons.dark_mode),
  inEffects: EffectList().fadeIn(duration: 2.seconds),
  outEffects: EffectList().fadeOut(duration: 2.seconds),
);

and the widget itself so far:

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

class SwitchEffect extends StatefulWidget {
  final Widget child;

  final List<Effect>? inEffects;

  final List<Effect>? outEffects;

  SwitchEffect({required this.child, this.inEffects, this.outEffects});

  @override
  State<StatefulWidget> createState() {
    return _SwitchEffectState();
  }
}

////////////////////////////////////////////////////////////////////////////////

class _SwitchEffectState extends State {
  @override
  SwitchEffect get widget => super.widget as SwitchEffect;

  Widget? _lastChild;

  List<Effect>? _lastOutEffects;

  @override
  Widget build(BuildContext context) {
    if (_lastChild != null) {
      return _lastChild!
          .animate()
          .addEffects(_lastOutEffects ?? EffectList().fadeOut())
          .then()
          .swap(
              delay: 0.seconds,
              duration: 0.seconds,
              builder: (BuildContext context, Widget? _) {
                _lastChild = null;
                _lastOutEffects = null;
                return widget.child;
              })
          .then()
          .addEffects(widget.inEffects ?? EffectList().fadeIn());
    }
    return widget.child
        .animate()
        .addEffects(widget.inEffects ?? EffectList().fadeIn());
  }

  @override
  void didUpdateWidget(covariant SwitchEffect oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.child != widget.child) {
      _lastChild = oldWidget.child;
      _lastOutEffects = oldWidget.outEffects;
    }
  }
}

@gskinner
Copy link
Owner

As a bit of quick guidance, the widget you swap in with swap completely replaces the prior target and it's Animate instance (well, not exactly, but it's the easiest way to conceptualize it), so you want something that looks more like this:

Animate(child: oldChild, effects: outEffects).then().swap(
  builder: (_, __) => Animate(child: newChild, effects: inEffects), 
)

Haven't tested that, but I think it should work.

@mikes222
Copy link

Unfortunately it is not fully working that way. With the snippet below the in and out effect for the first icon works as well as the in-effect for the alternate icon. From then on no effect is applied anymore. It seems like the animations are somewhat consumed and cannot be used anymore.

The swap also needs to have the duration and delay setting at default. When setting 0 the animation is not working (better to say not visible) anymore.

@override
Widget build(BuildContext context) {
  if (_lastChild != null) {
    return Animate(
      child: _lastChild!,
      effects: _lastOutEffects ?? EffectList().fadeOut(),
      autoPlay: true,
    ).then().swap(
        //duration: 0.seconds,
        //delay: 0.seconds,
        builder: (_, __) => Animate(
              child: widget.child,
              effects: widget.inEffects ?? EffectList().fadeIn(),
              autoPlay: true,
            ));
  }
  return Animate(
    child: widget.child,
    effects: widget.inEffects ?? EffectList().fadeIn(),
    autoPlay: true,
  );
}

@gskinner
Copy link
Owner

Might be worth trying to add a key to the Animate instances. There's a decent chance that Flutter is just matching them as the same instance (same type, plus same child type), so it doesn't trigger playing.

@mikes222
Copy link

first tests were successful. Will do more tests tomorrow...

@lei-cao
Copy link

lei-cao commented Mar 5, 2024

I also need this feature to have what AnimatedSwitcher is doing. Wondering when will the PR from @mikes222 will be merged.

@pavanpodila
Copy link

pavanpodila commented Oct 30, 2024

Might be worth trying to add a key to the Animate instances. There's a decent chance that Flutter is just matching them as the same instance (same type, plus same child type), so it doesn't trigger playing.

Adding the key worked perfectly in my case and allows fadeIn->fadeOut smoothly even with multiple interactions. Attaching a short video to show the error-animation in action:

error.mp4

Here is the relevant code showing the use of the animate() method with the key:

_ErrorView()
  .animate(key: UniqueKey())
  .fadeIn()
  .then(delay: 2.seconds)
  .fadeOut()
  .toggle(
      builder: (_, value, widget) =>
          value ? widget : const SizedBox.shrink()),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Support Question about using the library
Projects
None yet
Development

No branches or pull requests

5 participants