Skip to content

Fix animation transition lifecycle and target correctness #964

Description

@leoafarias

Summary

Repair curve, spring, phase, and keyframe driver transitions so interrupted
animations hold the visible value, removing a configuration reaches the new
target, phase delays use the correct transition index, and completion
listeners fire exactly once. Treat this as one lifecycle-contract change —
fixing only one symptom risks leaving another old/new driver combination
inconsistent.

Why this matters

Several behaviors can render something other than the requested target:

  1. CurveAnimationDriver builds its delay with ConstantTween(_initialSpec).
    When an in-progress transition is interrupted, the next delay snaps back to
    the driver's first target instead of holding the currently visible
    interpolated spec.
  2. StyleAnimationBuilder replaces a driver with config ?? oldConfig on type
    change. That fallback is valid for implicit curve/spring drivers (they
    implement didUpdateSpec) but phase/keyframe drivers do not retarget there.
  3. Removing a looping phase/keyframe config can therefore create a new driver
    from the old config and keep the old sequence running instead of displaying
    the new unanimated target.
  4. Phase tween construction checks configs[currentIndex].delay but weights the
    inserted delay with configs[nextIndex].delay, so placement and duration
    disagree.
  5. _setUpAnimation adds anonymous phase status listeners on every update and
    never removes them, so completion callbacks accumulate.
  6. PhaseAnimationConfig.onEnd exists but the driver reads the last curve
    config's callback instead.

Source anchors

  • packages/mix/lib/src/animation/style_animation_builder.dart
  • packages/mix/lib/src/animation/style_animation_driver.dart
  • packages/mix/lib/src/animation/animation_config.dart

Required behavior

  • Interrupted implicit animations begin from the currently displayed
    StyleSpec; a configured delay holds that value for its full duration with no
    backward jump.
  • Reuse an outgoing config for a null target only when the driver supports
    retargeting (curve, spring). Removing phase/keyframe config must stop/dispose
    the old driver and expose the supplied target on the same update.
  • The full old→new driver matrix (null, curve, spring, phase, keyframe) must
    dispose old listeners/tickers and reach the correct target.
  • Each phase transition uses one consistent destination index for its delay,
    duration, curve, and callback.
  • Choose one phase completion contract and fire it exactly once; do not retain
    two competing onEnd paths.
  • Fail early with actionable errors for empty phase lists, unequal style/
    curve-config counts, negative durations/delays, looping timelines with no
    positive duration, and duplicate keyframe track IDs.

Acceptance criteria

  • Interrupted delayed transitions hold the visible value without snapping.
  • Removing phase/keyframe animation reaches the new target immediately and stops old tickers.
  • Curve/spring outgoing transitions retain their animate-out behavior.
  • Phase delay placement matches configured durations and delays exactly.
  • Completion and trigger listeners never accumulate across config updates.
  • Invalid phase/keyframe configurations fail before controller execution.
  • The full driver-transition matrix is represented by tests.
  • New regression tests fail on main and pass with the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions