Closed timcreatedit closed 2 years ago
Interesting idea. How would you see that working, and what would you see the benefit being vs just doing this with mapIndexed
(from the collection package)?
Quick sketch, excuse errors:
Column(
children: [ ... ].mapIndexed((i, child) =>
child.animate(delay: i * 500.ms).scale(end: i == 3 ? 0.5 : 1.0),
)
This is exactly the approach I ended up taking. For me it's not exactly about it being difficult to achieve, more that the way the package behaves is unintuitive to me.
I would expect this: (also a quick sketch, excuse errors)
...
children: [
Child1(...).animate().fade(),
Child2(...).animate().unblur(),
Child3(...).animate(delay: 500.ms).scale(),
].animate(interval: 500.ms),
...
To animate Child1 at t=0, Child2 at t=500ms and Child3 at t=1500ms. Imo, this is especially misleading, bc the effects parameter of the .animate List extension is optional, but if I understood it correctly, it doesn't do anything without that parameter.
If it is possible in any way, I would suggest that animated widgets inside an AnimateList inherit the delay of their respective interval.
I would actually expect this to go even further. If for example Child2
has animated children, I'd expect these animations to play only after the delay imposed by the animated list. Maybe my main gripe is that all animations on children play already, while the AnimatedList still keeps them hidden.
Given the complexity of implementing this at the Animate level, versus the simplicity of working around it via map
, I don't think it makes sense to include this functionality. AnimateList
is just intended as a very simple convenience for quickly applying identical effects to a list of widgets with an optional interval offset.
Closing this, but open to additional input.
I'm not sure if this would actually be too complicated to implement. Let's say, each animated widget gets wrapped in an InheritedWidget that holds a delay (amongst other parameters maybe) AnimatedList would also wrap each widget in this InheritedWidget and the delays would increase by interval for each element.
Then, each Animate
widget (and the .animate()
extension) could have an optional inheritDelay
parameter that looks up the widget tree and adds the delay from the next ancestor to its own delay.
In my opinion, this would not only make building complex animations simpler, while also going hand in hand with how Flutter works in a lot of other places, where Widgets inherit their configuration from the widget tree (IconThemes, TextStyle, ...). Right now, to achieve complex, nested animations within the tree you have to pass delay values around like crazy, which also decreases readability by a lot if you want to keep all the animation code where it actually happens.
I'm not really familiar with the inner workings of the package yet, but If I find time I could also work on implementing a prototype of this to convince you of its power 👀
Sounds good Tim! I think I'm still struggling to understand specifically how this would be used, but it sounds like you have an interesting vision for this, and I would love to see how it all fits together.
Hey! I ABSOLUTELY love this package. But am running into a very similar issue (I think close enough to necro this issue rather than create a new one).
I have a list of playing cards that animate in from the top of the screen and stack on top of each other. They alternate animating in from just left of center to just right of center with slight random variation applied to each card. Currently, I'm doing this by wrapping the children in an animation that is tuned according to their index. This works, but is quite inelegant. I've commented the parts of the code that were a bit fussy.
return Stack(
children: List.generate(
stackCount,
(index) {
final double rotation = .008 + .01 * (rand.nextDouble() - .5);
return Card(index: index)
.animate()
.slideY(
curve: Curves.easeOut,
begin: -1.5,
end: 0,
duration: kDurationFast,
// Little tricky to tune this.
delay: kDurationFast * index,
)
.rotate(
// This part is really tricky.
// The cards slide in with randomized rotations.
// So after the slide is done, we straighten the cards one by one.
// So the delay is the delay from the slide stage + a delay from
// the top card down (inverted from the slide stage for visual effect).
delay: (kDurationFast * (stackCount + 2)) +
(kDurationFastest * (index - stackCount)),
curve: Curves.easeOut,
// This part is dependent on the index.
begin: (index.isEven ? -1 : 1) * rotation,
end: 0,
duration: kDurationFast,
)
.then()
.callback(callback: (_) {
// The rotation finishes on the 0th card, so we consider the animation
// done when the 0th card completes.
if (index == 0) {
swipeball.onAnimationComplete();
}
});
},
),
);
This works, but it is pretty fiddly and took me a bit to get right. Because the card rotation is randomized and keyed on odd vs even index, this can't be done with animateList
. I imagine this could be solved with something along the lines of:
return Stack(
children: List.generate(
stackCount,
(index) {
final Widget child = (index == stackCount - 1)
? CardFront(
asyncSwipePlayer: swipeball.asyncPlayers.first,
)
: const SizedBox.expand(child: CardBack());
final double rotation = .008 + .01 * (rand.nextDouble() - .5);
return child;
},
)
.animate()
.effectBuilder(interval: kDurationFast, (index) {
// Adds effects only to the given child.
// Effects inherit the delay from the animate list manager
// according to interval.
// Also, I know this should be a SlideEffect, but left SlideY
// for the sake of brevity.
return SlideY(
curve: Curves.easeOut,
begin: -1.5,
end: 0,
// No need to specify delay, that's handled by animateBuilder.
duration: kDurationFast,
);
})
.then()
// After the previous list animation is complete, kick off the next.
.effectBuilder(interval: kDurationFast, (index) {
// Our rotation effects are all delayed to start after the previous animateBuilder.
return RotateEffect(
curve: Curves.easeOut,
begin: (index.isEven ? -1 : 1) * rotation,
end: 0,
// No need to specify delay, that's handled by animateBuilder.
duration: kDurationFast,
);
})
// Applies after the second effectBuilder finishes.
.then()
.callback(
callback: (_) {
setState(() {
showReal = true;
});
swipeball.onAnimationComplete();
},
),
);
This makes it a lot more clear what's happening. But it's also still kind of complicated.
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.