Moving Animation into Variant Triggers (Deprecating Global .animate())
#867
tilucasoli
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Current State
Animation in Mix is declared as a global modifier on the Style:
StyleAnimationBuilderpicks up$animationfrom the resolvedStyleSpecand drives all transitions with the same config — whether the change came from a hover, a dark mode switch, an orientation flip, or a plain state rebuild.This works for the common case but the model has a fundamental flaw: the animation config is attached to the style, not to the trigger. The trigger is the variant. The animation should live there.
The Goal
Deprecate and eventually remove the top-level
.animate()method. Animation is declared at the variant level:Why this is better
1. Trigger and animation are co-located
.onHovered(styler:, animation:)tells you what changes and how it moves in one place. You don't have to hunt for the.animate()call at the end of the chain to understand the transition.2. Different transitions can animate differently
This is impossible to express cleanly today. You can't give
.onHovereda spring and.onDarknothing with a single global config.3. No hidden blast radius
Today,
.animate(.spring(800.ms))silently animates dark/light transitions, orientation changes, breakpoint switches — anything that triggers a style rebuild. Developers typically think they're animating their hover, not their theme. Per-variant animation is explicit by definition.4. The model is complete without a global escape hatch
A global "animate everything" config is appealing as a shortcut but it hides what is actually being animated. Making animation explicit per-trigger is a small cost at the call site and a large gain in clarity and debuggability.
What Happens to Implicit State Animations?
Not everything in Mix goes through a variant. Today you can write:
The animated values come from a Flutter state variable. There is no
ContextVariantbeing toggled. If.animate()is removed as-is, this pattern breaks.The recommended fix: keep
.animate()but require atrigger. The trigger makes the animation explicit — it fires only when the trigger changes, not on every style rebuild. Two possible shapes for the trigger:Option A — Listenable trigger
The animation fires whenever
_appearemits. TheStyleAnimationBuildersubscribes to theListenabledirectly, independent of the widget rebuild cycle.Pros:
phaseAnimationandkeyframeAnimation, which already useListenable? trigger.setState— the notifier can change without rebuilding the widget tree.Cons:
ValueNotifier. More boilerplate for simple cases.boolinsetStateneed a migration step.Option B — Plain value trigger
The framework compares the trigger value between rebuilds (via
==). When it changes, the animation fires. Similar to how Flutter'sImplicitlyAnimatedWidgetworks — set a value, it animates to it.Pros:
.animate()usage.trigger: counterworks as well astrigger: appear.Cons:
StyleAnimationBuilder.didUpdateWidget, which means it's tied to the widget rebuild cycle. If the value changes without a rebuild, nothing fires.phaseAnimation/keyframeAnimationwhich useListenable.Beta Was this translation helpful? Give feedback.
All reactions