hyper_effects 0.4.0+2
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 andTranslateEffectnow defaultstransformHitTeststotrue, matching Flutter'sTransform.translateandFractionalTranslationdefaults as well asScaleEffect. RollEffect's internalFractionalTranslations 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. PasstransformHitTests: falseexplicitly on translate effects for deliberate paint-only movement. -
AlignEffectfactors now default to null —widthFactorandheightFactoronAlignEffectand.align()are nowdouble?defaulting to null, matching Flutter'sAlign: null expands to fill the incoming constraints instead of shrink-wrapping to the child. Previously the hardcoded default of1forced every.align(),.alignX(),.alignY(), and.alignXY()to shrink-wrap, which made the alignment a visual no-op under loose constraints. PasswidthFactor: 1/heightFactor: 1explicitly 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 —
SizeEffectand 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 itsSizedBox. -
Spring-downgrade diagnostic — in debug builds, driving an effect that doesn't mix in
VectorEffect(e.g..blur(),.clip(),.colorFilter()) with aSpringMotionnow 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'slerpextrapolates 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 inVectorEffect<T>on that effect or switching the.animate()to aCurvedMotion. The check sits at the exact pointbuild()decides to fall back to the normalized lerp, so it cannot fire forisTransitionqueries,lerpValues: falsequeries, 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 bypassesFlutterError.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 newVectorEffectmixin — SwiftUI-style vector arithmetic (+,-,*,magnitudeSquared) implemented byTranslateEffect,ScaleEffect,RotationEffect, andOpacityEffect— and a closed-form spring solver verified against Flutter's ownSpringSimulation. 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 formotion: CurvedMotion(duration, curve), and the newmotion:parameter accepts spring physics:CupertinoMotion.bouncy()/.smooth()/.snappy()/.interactive(), the Material 3MaterialSpringMotiontokens, or a customSpringMotion(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 bothmotion:andduration:/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
triggeris 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— imperativeplay/reverse/pause/seek/progress, attachable via.timeline(controller:), ownable outside the widget tree (a State, cubit, or service), with listener notifications. -
The
#immediatetrigger sentinel — passtrigger: #immediateto.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 constructstrigger: #immediate, taking everyanimateparameter excepttriggerandstartState. -
repeat:on.timeline()— loop withrepeat: -1(forever) orrepeat: n(n extra cycles, resting at the final keyframe;onEndfires once at the true end). -
key:on.animate(), forwarded to the underlyingAnimatedEffectso an animation can be identified acrossStatedisposal and recreation. -
AnimationStartStateoverhauled to{eager, lazy}. Both values insert the widget at its STARTING values; they differ only in whether the animation runs on mount:eagerplays the animation through to its ending values immediately on mount, once perStatelifetime. Rebuilds do not replay it, andtriggerstays live: every later change of it plays the animation again.lazy(the default) inserts the widget inert, until it is triggered at least once.
eagerandtrigger: #immediateboth play on mount and differ in what follows.#immediatespends the trigger slot to say "now" and so can never replay;eagerleaves 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 #
-
PaddingEffectandAlignEffectare nowVectorEffects —.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.lerppreviously clamped its progress to 0..1, so.pad()underCupertinoMotion.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 inapply(whereRenderPaddingrequires non-negativity) rather than inlerp, 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()onCurves.easeOutBackwill pass its target and come back. If a padding animation must not overshoot, use a monotone curve or a non-bouncy spring such asCupertinoMotion.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 nullablewidthFactor/heightFactorkeep 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. - Padding overshoots under spring motions.
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 callonEnd; non-interruptible delayed triggers remain serialized in arrival order. -
PointerTransitionno longer keeps stale hover/bounds state when its target moves beneath a stationary mouse while using the default global pointer router. -
PointerTransitionnow 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 therepeat/reversebudget — 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.
-
onEndfires exactly once per logical run instead of once per repeat/reverse leg. -
Resetting an
interruptable: falseanimation 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
EquatableMixinandMatrix4.translate/scaleusages to their replacements.
Removed (BREAKING) #
These were removed outright, with no deprecation window. The timeline API replaces all of them.
-
The
equatabledependency. Effects andTimelineSegmentnow implement==,hashCode, andtoStringdirectly, with identical semantics (including deep list comparison forColorFilterEffect.matrix). Only affects consumers who relied on hyper_effects to transitively provideequatable. -
The unused
collectiondependency (its only consumer was the removedAnimatedGroup). -
AnimatedGroup/AnimatedChild(experimental) — removed after review found removed children were never disposed (subtrees kept ticking indefinitely), removal re-raninitStateon exiting children, slots only ever grew, and reordering was not actually animated. UseAnimatedSwitcher/AnimatedSizewith.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 withskipIf: () => !played.add(index). -
.animateAfter()and the entireAnimationTriggerTypeenum (includingAnimationTriggerType.afterLast) — chained segments composed as stacked transforms, could not rest at their declared values, replayed inconsistently, and severed onskipIf/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
resetAlllooping with.timeline(trigger: #immediate, repeat: -1), and reverse-on-dismiss patterns withTimelineController.reverse(). -
.oneShot()— replaced by.immediate(), which has the same play-once-on-mount semantics.Migration:
.oneShot(...)→.immediate(...), or.animate(trigger: #immediate, ...). -
AnimationStartState.playImmediately,.useCurrentValuesand.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. Usetrigger: #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.useCurrentValueshas no direct replacement. It existed so that a state-mirroring animation would render its current state on the first frame instead of itsfromvalues. Express that in the effects themselves: track the state the widget is animating away from and feed it tofrom, so that on the very first buildfromequals the target and the widget mounts at rest. Seeexample/lib/stories/success_card_animation.dart.
Deprecated #
.resetAll(),ResetAllAnimationsEffectandResetAllAnimationsEffectState— auto-resetting chains fire on every completion and cross-talk between unrelated animations. Loop with.timeline(repeat: -1)or rewind withTimelineController.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
usePointerRouteroption 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.transformHitsproperty for translate effect.rotateInandrotateOutmethods 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.
startImmediatelyreplaced with more flexiblestartState.- Removed unnecessary PostFrame callbacks from pointer transition logic.
- Improvements
- Default blur effect state now starts un-blurred.
- Added
characterTapeBuilderstoSymbolTapeStrategyfor customization. - Fixed issues with scroll transitions to provide smoother and more consistent user experience.
0.2.2 Feb 2, 2024 #
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
oneShotfunction for immediate animationsanimateAfterfor sequential animationsresetAllfor animation state reset- Repeat parameter for cyclic animations
- Delay parameter for timed starts
playIffor conditional execution
Changed #
- Breaking Changes
- Renamed:
toggle→triggerin .animate()AnimatedEffect→EffectWidgetEffectAnimationValue→EffectQueryPostFrameWidget→PostFrame
- Enhanced
EffectQuerywithlinearValueandcurvedValue
- Renamed:
- Improvements
- Updated effect extensions with
fromstate support - Added convenience methods (slideIn/Out, fadeIn/Out)
- Updated effect extensions with
