Skip to main content

hyper_effects 0.4.0+2 copy "hyper_effects: ^0.4.0+2" to clipboard
hyper_effects: ^0.4.0+2 copied to clipboard

Create beautiful effects and animations with just a few lines of code.

Changelog #

All notable changes to the Hyper Effects package are documented in this file.

0.4.0+2 Sep 28, 2026 #

Documentation #

  • Include example directory in published package for pub.dev.
  • Add dartdoc documentation to public library and constructors.
  • Add examples showcase section to README.

0.4.0 Sep 27, 2026 #

Breaking #

  • Translated widgets now move their hit-test region by default — every .translate*() extension and TranslateEffect now defaults transformHitTests to true, matching Flutter's Transform.translate and FractionalTranslation defaults as well as ScaleEffect. RollEffect's internal FractionalTranslations now inherit that framework default too. Interactive children therefore answer where they are painted instead of leaving an invisible hit target at their pre-transform layout position. Pass transformHitTests: false explicitly on translate effects for deliberate paint-only movement.

  • AlignEffect factors now default to null — widthFactor and heightFactor on AlignEffect and .align() are now double? defaulting to null, matching Flutter's Align: null expands to fill the incoming constraints instead of shrink-wrapping to the child. Previously the hardcoded default of 1 forced every .align(), .alignX(), .alignY(), and .alignXY() to shrink-wrap, which made the alignment a visual no-op under loose constraints. Pass widthFactor: 1 / heightFactor: 1 explicitly to restore the old shrink-wrap behavior. Mixed null/non-null factor endpoints snap to the target value rather than interpolating (there is no numeric interpolation between "fill constraints" and a size factor).

Added #

  • Layout-driven size effects — SizeEffect and the .widthTo(), .heightTo(), .sizeTo(), and .sizeOf() widget extensions animate the actual width and height reported during layout, so surrounding widgets reflow instead of merely seeing paint-time scaling. Each axis can remain unconstrained independently, bouncy springs preserve overshoot and carry velocity across mid-flight retargets, and negative overshoot is floored only when the effect builds its SizedBox.

  • Spring-downgrade diagnostic — in debug builds, driving an effect that doesn't mix in VectorEffect (e.g. .blur(), .clip(), .colorFilter()) with a SpringMotion now prints a debug warning the first time it happens for that effect type, naming the offending effect, spelling out what the silent fallback actually costs — overshoot and settle-back become dependent on whether that effect's lerp extrapolates or clamps, rather than guaranteed vector spring physics, and a mid-flight retarget can no longer hand off velocity, restarting from rest instead — and recommending either mixing in VectorEffect<T> on that effect or switching the .animate() to a CurvedMotion. The check sits at the exact point build() decides to fall back to the normalized lerp, so it cannot fire for isTransition queries, lerpValues: false queries, or curve-driven motions, and it warns once per effect TYPE rather than once per widget instance, since the fix is a one-line change to the effect's class, not to any one call site. It deliberately bypasses FlutterError.onError, so developer guidance isn't forwarded to crash reporting or promoted to a test failure. Assert-gated, so it costs nothing in release or profile builds.

  • Velocity handoff for springs — retargeting a spring-driven .animate() mid-flight (changing the effect's target while it moves) now carries momentum: the new spring starts from the captured position AND the captured velocity, so rapid re-targets whip naturally instead of deadening. Built on the new VectorEffect mixin — SwiftUI-style vector arithmetic (+, -, *, magnitudeSquared) implemented by TranslateEffect, ScaleEffect, RotationEffect, and OpacityEffect — and a closed-form spring solver verified against Flutter's own SpringSimulation. Effects without the mixin (and all curved motions) keep the existing behavior.

  • Motion API — timing is now a first-class object. Every duration: + curve: pair on .animate(), .immediate(), and .step() is sugar for motion: CurvedMotion(duration, curve), and the new motion: parameter accepts spring physics: CupertinoMotion.bouncy() / .smooth() / .snappy() / .interactive(), the Material 3 MaterialSpringMotion tokens, or a custom SpringMotion(SpringDescription). Springs have no fixed duration — the segment's length is a computed physical settling bound (Motion.effectiveDuration), overshoot renders truly past the target, and keyframe handoffs stay exact. Passing both motion: and duration:/curve: throws.

  • Timeline API — a single-controller orchestration tier. Declare absolute keyframes with .step() boundaries and drive them with .timeline():

    icon
        .scale(0).rotate(0)
        .step(duration: const Duration(milliseconds: 350), curve: Curves.easeOutQuart)
        .scale(1.5).rotate(15 * pi / 180)
        .step(duration: const Duration(milliseconds: 300),
              curve: Curves.easeOutBack, delay: const Duration(milliseconds: 150))
        .scale(1).rotate(0)
        .timeline(trigger: isCompleted);
    

    Keyframe values are absolute and hand off between steps — they never stack. A trigger is a pure identity signal: any change restarts the timeline forward. Effect types absent from a keyframe carry their previous value forward; declaring the same effect type twice in one keyframe throws.

  • TimelineController — imperative play / reverse / pause / seek / progress, attachable via .timeline(controller:), ownable outside the widget tree (a State, cubit, or service), with listener notifications.

  • The #immediate trigger sentinel — pass trigger: #immediate to .timeline() OR .animate() to play on mount, once per State lifetime. Rebuilds do not replay it. One channel now answers "when does this play" for both tiers.

  • .immediate() on the animate extension — a shorthand that constructs trigger: #immediate, taking every animate parameter except trigger and startState.

  • repeat: on .timeline() — loop with repeat: -1 (forever) or repeat: n (n extra cycles, resting at the final keyframe; onEnd fires once at the true end).

  • key: on .animate(), forwarded to the underlying AnimatedEffect so an animation can be identified across State disposal and recreation.

  • AnimationStartState overhauled to {eager, lazy}. Both values insert the widget at its STARTING values; they differ only in whether the animation runs on mount:

    • eager plays the animation through to its ending values immediately on mount, once per State lifetime. Rebuilds do not replay it, and trigger stays live: every later change of it plays the animation again.
    • lazy (the default) inserts the widget inert, until it is triggered at least once.

    eager and trigger: #immediate both play on mount and differ in what follows. #immediate spends the trigger slot to say "now" and so can never replay; eager leaves the caller's trigger free. Play-on-mount combined with a live trigger had no expression before this release.

  • Rewritten getting-started guide and portable consumer agent skills for effects, motion, and timelines.

Changed #

  • PaddingEffect and AlignEffect are now VectorEffects — .pad(), .padAll(), .padOnly() and friends, along with .align(), .alignX(), .alignY() and .alignXY(), are driven by real spring physics instead of falling back to a normalized lerp. Two visible consequences:

    • Padding overshoots under spring motions. PaddingEffect.lerp previously clamped its progress to 0..1, so .pad() under CupertinoMotion.bouncy() eased flatly into its target while a sibling .translateY() on the same motion bounced — the same declared motion produced two different curves on one widget. Padding now describes the same curve as everything else. Insets are floored at zero in apply (where RenderPadding requires non-negativity) rather than in lerp, so only the components that actually went negative are touched and the upward half of the bounce survives intact. Interpolation is also unclamped for overshooting CURVES now, not just springs: a .pad() on Curves.easeOutBack will pass its target and come back. If a padding animation must not overshoot, use a monotone curve or a non-bouncy spring such as CupertinoMotion.smooth().
    • Momentum carries across a mid-flight retarget. Re-triggering a padding or alignment animation while it is still moving now starts the new spring from the captured velocity instead of from rest, so rapid re-triggers whip rather than hitch.

    AlignEffect's nullable widthFactor / heightFactor keep their existing semantics exactly: a null factor is the "fill the incoming constraints" layout mode, not a number, so nullability rides along from the left operand of the vector algebra and a mixed null / non-null pair still snaps to the target value rather than interpolating.

Fixed #

  • .animate(delay:) no longer flashes the animation's target value while it waits — a delayed re-trigger now holds the effects at the values the run is about to start from, then animates. Previously the controller kept reporting the value the PREVIOUS run left it at for the whole delay, so every run after the first painted its destination instantly, held it, and snapped back before easing in. Only the very first run looked correct. Interruptible delayed re-triggers also retire superseded timers now, so an older wait cannot wake after a newer trigger, restart the controller, or call onEnd; non-interruptible delayed triggers remain serialized in arrival order.

  • PointerTransition no longer keeps stale hover/bounds state when its target moves beneath a stationary mouse while using the default global pointer router.

  • PointerTransition now clears hover state when a pointer device is removed, fixing indirect-pointer taps in the iOS Simulator that otherwise remained at their hover value after release.

  • .animate() re-triggers now restore the repeat/reverse budget — pulse animations no longer play once and rest stuck at their peak value.

  • Fresh triggers reset the reverse-leg state machine — re-triggering mid-flight plays a complete new run.

  • onEnd fires exactly once per logical run instead of once per repeat/reverse leg.

  • Resetting an interruptable: false animation mid-flight no longer bricks it permanently (stale canceled ticker futures are cleared).

  • Completed play-on-mount (#immediate) animations no longer replay when an inherited dependency (e.g. HyperEffectsAnimationConfig) changes.

  • Migrated deprecated EquatableMixin and Matrix4.translate/scale usages to their replacements.

Removed (BREAKING) #

These were removed outright, with no deprecation window. The timeline API replaces all of them.

  • The equatable dependency. Effects and TimelineSegment now implement ==, hashCode, and toString directly, with identical semantics (including deep list comparison for ColorFilterEffect.matrix). Only affects consumers who relied on hyper_effects to transitively provide equatable.

  • The unused collection dependency (its only consumer was the removed AnimatedGroup).

  • AnimatedGroup / AnimatedChild (experimental) — removed after review found removed children were never disposed (subtrees kept ticking indefinitely), removal re-ran initState on exiting children, slots only ever grew, and reordering was not actually animated. Use AnimatedSwitcher/AnimatedSize with .roll() for the same effects.

  • AnimatedEffectStateRetainer (experimental) — removed after review found it recorded "was mounted" as "has played", fabricating end states for never-played keyed animations on any dependency change, freezing looping animations on remount, and never evicting recycled keys. Track played items yourself with skipIf: () => !played.add(index).

  • .animateAfter() and the entire AnimationTriggerType enum (including AnimationTriggerType.afterLast) — chained segments composed as stacked transforms, could not rest at their declared values, replayed inconsistently, and severed on skipIf/playIf.

    Migration: declare the sequence as timeline keyframes. Before:

    icon
        .scale(1.5).animate(trigger: done, duration: const Duration(milliseconds: 350))
        .scale(1 / 1.5) // reciprocal needed to undo the first segment
        .animateAfter(duration: const Duration(milliseconds: 300))
        .resetAll();
    

    After:

    icon
        .scale(0)
        .step(duration: const Duration(milliseconds: 350))
        .scale(1.5)
        .step(duration: const Duration(milliseconds: 300))
        .scale(1) // absolute — no reciprocals
        .timeline(trigger: done);
    

    Replace resetAll looping with .timeline(trigger: #immediate, repeat: -1), and reverse-on-dismiss patterns with TimelineController.reverse().

  • .oneShot() — replaced by .immediate(), which has the same play-once-on-mount semantics.

    Migration: .oneShot(...) → .immediate(...), or .animate(trigger: #immediate, ...).

  • AnimationStartState.playImmediately, .useCurrentValues and .idle — the enum is now {eager, lazy}. Mounting a widget at its ENDING values is no longer offered: both values mount at the starting values, and the enum now only controls whether the animation runs on mount.

    Migration:

    • startState: AnimationStartState.playImmediately → startState: AnimationStartState.eager, which keeps a live trigger. Use trigger: #immediate (or .immediate()) instead if the animation should never replay.
    • startState: AnimationStartState.idle → startState: AnimationStartState.lazy (also the new default, so it can simply be dropped).
    • startState: AnimationStartState.useCurrentValues has no direct replacement. It existed so that a state-mirroring animation would render its current state on the first frame instead of its from values. Express that in the effects themselves: track the state the widget is animating away from and feed it to from, so that on the very first build from equals the target and the widget mounts at rest. See example/lib/stories/success_card_animation.dart.

Deprecated #

  • .resetAll(), ResetAllAnimationsEffect and ResetAllAnimationsEffectState — auto-resetting chains fire on every completion and cross-talk between unrelated animations. Loop with .timeline(repeat: -1) or rewind with TimelineController.seek(0) instead. They will be removed in 0.5.0.

0.3.0+1 Aug 15, 2025 #

  • Loosen dependency constraints for equatable to >=2.0.5 <3.0.0.

0.3.0 Dec 15, 2024 #

Added #

  • New Effects
    • Padding effect for dynamic padding adjustments.
    • Global roll effect for universal rolling animations.
    • Width & height factor support in align effect.
  • Scroll Transition Enhancements
    • Additional event variables for finer control.
    • Improved transition state management.
  • Pointer Transition Features
    • usePointerRouter option for flexible pointer event handling.
    • Enhanced pointer position tracking.
  • New AnimatedEffect Properties
    • resetValues - Controls value reset behavior.
    • interruptable - Manages animation interruption.
    • skipIf - Conditional animation execution.
    • startState - Initial animation state control.
    • transformHits property for translate effect.
    • rotateIn and rotateOut methods for rotate effect.
  • Added New Examples
    • group_animation.dart
    • rolling_app_bar_animation.dart
    • rolling_pictures_animation.dart
    • scroll_phase_slide.dart
    • scroll_phase_blur.dart
    • success_card_animation.dart

Changed #

  • Breaking Changes
    • Effect apply function's child parameter is now nullable.
    • Text rolling API redesigned for consistency with other effects.
      • New unified interface matching other animation effects.
      • Previous text rolling methods have been deprecated.
    • startImmediately replaced with more flexible startState.
    • Removed unnecessary PostFrame callbacks from pointer transition logic.
  • Improvements
    • Default blur effect state now starts un-blurred.
    • Added characterTapeBuilders to SymbolTapeStrategy for customization.
    • Fixed issues with scroll transitions to provide smoother and more consistent user experience.

0.2.3 Feb 2, 2024 #

Fixed #

  • Resolved Dart analysis issues for better code quality

0.2.2 Feb 2, 2024 #

Added #

  • New startImmediately boolean in .animate()
  • Improved documentation for AnimatedEffect

Fixed #

  • Value clamping for:
    • OpacityEffect (0.0 - 1.0)
    • ClipEffect (0.0 - 1.0)
    • ColorFilterEffect (0.0 - 1.0)
  • Prevents exceptions with out-of-range curves

0.2.1 Dec 28, 2023 #

Fixed #

  • Animation controller state change exception handling

0.2.0 Dec 24, 2023 #

Added #

  • New Effects
    • Rolling Text effect for text animations
    • Shake effect for vibration animations
    • Align effect for alignment control
  • Animation Control
    • oneShot function for immediate animations
    • animateAfter for sequential animations
    • resetAll for animation state reset
    • Repeat parameter for cyclic animations
    • Delay parameter for timed starts
    • playIf for conditional execution

Changed #

  • Breaking Changes
    • Renamed:
      • toggle → trigger in .animate()
      • AnimatedEffect → EffectWidget
      • EffectAnimationValue → EffectQuery
      • PostFrameWidget → PostFrame
    • Enhanced EffectQuery with linearValue and curvedValue
  • Improvements
    • Updated effect extensions with from state support
    • Added convenience methods (slideIn/Out, fadeIn/Out)

0.1.1 Oct 26, 2023 #

Changed #

  • Documentation improvements
  • Added example GIFs in README

0.1.0 Oct 25, 2023 #

Added #

  • Initial release of Hyper Effects
  • Core animation and effect system
  • Basic effect implementations
  • Documentation and examples
355
likes
160
points
489
downloads
screenshot

Documentation

API reference

Publisher

verified publisherhyperdesigned.dev

Weekly Downloads

Create beautiful effects and animations with just a few lines of code.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#animation #effects #transitions #scroll-effects

License

BSD-3-Clause (license)

Dependencies

flutter, meta

More

Packages that depend on hyper_effects