ui_animations_pro 0.1.0
ui_animations_pro: ^0.1.0 copied to clipboard
ui_animations_pro — production-grade cinematic UI Animations framework for Flutter. Plugin-based Transitions, Composable Choreographer, Timeline Sequencer, Spring Physics Engine, Performance Monitor, [...]
ui_animations_pro #
Production-grade cinematic UI animations for Flutter.
Plugin-based Transitions · Composable Choreographer · Timeline Sequencer · Spring Physics Engine · Frame Budgeter · Accessibility-aware · Reactive Streams · Zero native dependencies.
🎬 Motion Demo Release #
Download the full showcase application:
Includes:
- Full cinematic animation showcase
- Transition playground
- Physics simulation demos
- Route transition examples
- Gesture-driven motion examples
- Accessibility & reduce-motion previews
- Performance monitor overlay
- Adaptive quality demonstrations
Why ui_animations_pro? #
| Before | After |
|---|---|
Manual AnimationController + Tween + Curve per widget |
Declarative Scene DSL — 5 lines |
dispose() management in every State |
Pool-managed tickers — automatic cleanup |
| Synchronising N animations via callback hell | Choreographer orchestrates everything |
| No FPS monitoring or adaptive quality | FrameBudgeter + PerformanceMonitor built-in |
| Accessibility left as an afterthought | ReduceMotionPolicy + AccessibilityBridge always-on |
| Custom transition = rewrite from scratch | Register a TransitionPlugin — one file |
Architecture #
┌──────────────────────────────────────────────────────┐
│ FLUTTER APPLICATION │
│ │
│ AnimationsPro.instance │
│ .scene('checkout') │
│ .stagger([ │
│ SingleStep(id:'logo', transition: Fade()), │
│ SingleStep(id:'title', transition: SlideUp()), │
│ SingleStep(id:'cta', transition: Spring()), │
│ ], interval: 80ms) │
│ .play() │
└────────────────────┬─────────────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ PUBLIC API (AnimationsPro) │
└────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ CHOREOGRAPHY LAYER (Scene · Timeline · Steps) │
└────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ TRANSITION LAYER (12 Plugin-based effects) │
└────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ CURVES & PHYSICS (Spring · Friction · Bezier) │
└────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ ENGINE (FrameBudgeter · Pool · Lifecycle) │
└─────────────────────────────────────────────────┘
Installation #
dependencies:
ui_animations_pro: ^0.1.0
flutter pub get
Quick start #
1. Initialise once in main() #
import 'package:ui_animations_pro/ui_animations_pro.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AnimationsPro.initialize(
config: const AnimationsConfig(
defaultDurationMs: 350,
respectReduceMotion: true,
enablePerformanceMonitor: true,
debug: true,
),
);
runApp(const MyApp());
}
2. Wrap your app (optional, recommended) #
@override
Widget build(BuildContext context) {
return ReduceMotionPolicy(
behavior: ReduceMotionBehavior.crossFade,
child: MotionProvider(
child: MaterialApp(home: const HomeScreen()),
),
);
}
3. Play a choreographed scene #
import 'package:ui_animations_pro/transitions.dart';
final handle = AnimationsPro.instance
.scene('onboarding')
.stagger([
SingleStep(id: 'logo', transition: FadeTransitionPlugin()),
SingleStep(id: 'title', transition: SlideTransitionPlugin(),
params: {'direction': SlideDirection.up}),
SingleStep(id: 'cta', transition: SpringTransitionPlugin()),
], interval: const Duration(milliseconds: 80))
.play();
// Control
handle.pause();
handle.resume();
handle.seek(0.5); // jump to 50%
handle.cancel();
await handle.dispose();
Core concepts #
Scene — immutable, composable #
Every .stagger, .sequence, .parallel, .then returns a new Scene.
The original is never mutated — enabling temporal debugging and replay.
final scene = AnimationsPro.instance
.scene('checkout')
.sequence([step1, step2]) // sequential
.parallel([step3, step4]) // simultaneous
.stagger([step5, step6, step7], // staggered
interval: const Duration(milliseconds: 60))
.delay(const Duration(milliseconds: 200))
.then(step8);
Choreography steps #
| Step | Description |
|---|---|
SingleStep |
One transition on one target |
SequenceStep |
Steps play one after another |
ParallelStep |
Steps play simultaneously |
StaggerStep |
Steps start with progressive delay |
DelayStep |
Silent pause in the timeline |
LoopStep |
Repeats infinitely (until cancelled) |
RepeatStep |
Repeats exactly N times |
ConditionalStep |
Runtime branch — if predicate → ifTrue else ifFalse |
MotionHandle — full playback control #
final handle = AnimationsPro.instance.playScene(myScene);
handle.state; // MotionState enum
handle.progress; // 0.0 → 1.0
handle.events; // Stream<AnimationEvent>
handle.pause();
handle.resume();
handle.seek(0.75);
handle.cancel();
await handle.dispose();
AnimationEvent stream #
AnimationsPro.instance.events.listen((event) {
switch (event) {
case SceneStartedEvent(): print('Started: ${event.sceneId}');
case SceneCompletedEvent(): print('Done in ${event.totalDuration}');
case ScenePausedEvent(): print('Paused at ${event.progress}');
case FrameDropEvent(): print('Frame drop: ${event.frameMs}ms');
}
});
Transition Plugins (12 built-in) #
Import package:ui_animations_pro/transitions.dart.
| Plugin | id |
Expensive |
|---|---|---|
FadeTransitionPlugin |
fade |
No |
SlideTransitionPlugin |
slide |
No |
ScaleTransitionPlugin |
scale |
No |
RotateTransitionPlugin |
rotate |
No |
SpringTransitionPlugin |
spring |
No |
Flip3DTransitionPlugin |
flip3d |
No |
MorphTransitionPlugin |
morph |
No |
ParallaxTransitionPlugin |
parallax |
No |
ShimmerTransitionPlugin |
shimmer |
No |
BlurTransitionPlugin |
blur |
Yes |
RippleTransitionPlugin |
ripple |
Yes |
LiquidTransitionPlugin |
liquid |
Yes |
Using a plugin directly on a widget #
MotionWidget(
transition: SpringTransitionPlugin(),
duration: const Duration(milliseconds: 600),
curve: CurveLibrary.appleSpring,
child: MyCard(),
)
Writing a custom plugin #
class GlowPlugin implements TransitionPlugin {
@override String get id => 'glow';
@override bool get isExpensive => false;
@override List<AnimatableProperty> get affectedProperties =>
[AnimatableProperty.opacity];
@override
Widget build({
required Animation<double> animation,
required Widget child,
Map<String, Object?> params = const {},
}) {
return AnimatedBuilder(
animation: animation,
builder: (_, c) => DecoratedBox(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.amber.withOpacity(animation.value),
blurRadius: 24 * animation.value,
spreadRadius: 4 * animation.value,
),
],
),
child: c,
),
child: child,
);
}
}
// Register globally
AnimationsPro.instance.registerTransition(GlowPlugin());
Curves #
CurveLibrary presets #
CurveLibrary.appleSpring // Apple-like spring
CurveLibrary.emphasized // Material 3 emphasized
CurveLibrary.emphasizedDecelerate
CurveLibrary.emphasizedAccelerate
CurveLibrary.overshoot // Overshoots target then settles
CurveLibrary.anticipate // Pulls back before moving
CurveLibrary.stripe // Stripe.com style
CurveLibrary.linearFlow // Smooth ease
CurveLibrary.elasticOut
CurveLibrary.bounceOut
CurveLibrary.smoothStartEnd
SpringCurve — physics-based #
SpringCurve.gentle() // stiffness: 120, damping: 14
SpringCurve.wobbly() // stiffness: 180, damping: 12
SpringCurve.stiff() // stiffness: 210, damping: 20
SpringCurve.slow() // stiffness: 280, damping: 60
SpringCurve.molasses() // stiffness: 280, damping: 120
// or fully custom:
SpringCurve(stiffness: 160, damping: 16, mass: 1.0)
BezierEditor — CSS curves in Dart #
// From code:
final curve = BezierEditor(x1: 0.25, y1: 0.1, x2: 0.25, y2: 1.0).build();
// From CSS string:
final curve = BezierEditor.fromCss('cubic-bezier(0.4, 0.0, 0.2, 1.0)');
Physics simulators #
Import package:ui_animations_pro/physics.dart.
// Spring — mass-spring-damper
final sim = SpringSim(from: 0.0, to: 1.0, stiffness: 180, damping: 12);
sim.position(0.5); // position at t=0.5s
sim.velocity(0.5); // velocity at t=0.5s
sim.isDone(2.0); // has it settled?
// Friction — exponential deceleration
final friction = FrictionSim(position0: 0.0, velocity0: 800.0, drag: 0.135);
// Gravity — uniform acceleration
final gravity = GravitySim(position0: 0.0, velocity0: 0.0, acceleration: 9.81);
// Magnetic — inverse-square attraction / snap
final magnet = MagneticSim(position0: 0.0, target: 100.0, strength: 50.0);
Widgets #
StaggerList — animated list #
StaggerList(
transition: SpringTransitionPlugin(),
interval: const Duration(milliseconds: 60),
itemDuration: const Duration(milliseconds: 400),
children: myItems.map((item) => ItemCard(item)).toList(),
)
ShimmerBox — skeleton loading #
// Wrap any widget:
ShimmerBox(
enabled: isLoading,
child: MyContentWidget(),
)
// Quick rect placeholder:
ShimmerBox.rect(width: 200, height: 16, borderRadius: 8)
RevealWidget — clip-path reveal #
RevealWidget(
reveal: _isVisible,
direction: RevealDirection.radial, // left, right, top, bottom, center, radial
duration: const Duration(milliseconds: 600),
curve: CurveLibrary.appleSpring,
onRevealed: () => print('Revealed!'),
child: HeroCard(),
)
MorphContainer — shape morphing #
MorphContainer(
expanded: _isExpanded,
collapsedDecoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(12)),
expandedDecoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(28)),
collapsedSize: const Size(56, 56),
expandedSize: const Size(double.infinity, 300),
duration: const Duration(milliseconds: 450),
curve: CurveLibrary.appleSpring,
child: myContent,
)
HeroMotion — enhanced Hero #
HeroMotion(
tag: 'product-image-42',
curve: CurveLibrary.appleSpring,
fadeDuring: true,
scaleToFit: true,
child: ProductImage(),
)
ParallaxView — depth layers #
// Scroll-driven:
ParallaxView(
scrollController: _ctrl,
maxDisplacement: 60.0,
layers: [
(child: BackgroundImage(), depth: 0.2),
(child: MidgroundShapes(), depth: 0.5),
(child: ForegroundText(), depth: 1.0),
],
)
// Pointer-driven (set imperatively):
ParallaxView(
offset: _pointerOffset,
layers: [ ... ],
)
Animated Routes #
// Fade through
Navigator.push(context, FadeThroughRoute(page: const DetailPage()));
// Shared axis
Navigator.push(context, SharedAxisRoute(page: const DetailPage(), axis: SharedAxis.x));
// Container transform (scale)
Navigator.push(context, ContainerTransformRoute(page: const DetailPage()));
// Any custom transition:
Navigator.push(context, MotionPageRoute(
page: const DetailPage(),
transition: Flip3DTransitionPlugin(),
duration: const Duration(milliseconds: 700),
curve: CurveLibrary.appleSpring,
));
// Via RouteAdapter:
final route = RouteAdapter.push(context, DetailPage(), transition: SpringTransitionPlugin());
Navigator.push(context, route);
Gestures #
// Drag → progress
GestureMotion(
maxDrag: 300.0,
axis: Axis.vertical,
onRelease: (progress) => print('Released at $progress'),
builder: (ctx, progress) => MyDraggable(progress: progress),
)
// Swipe to dismiss
SwipeDismiss(
threshold: 0.4,
onDismissed: () => removeItem(),
child: MyListTile(),
)
// Pinch to zoom
PinchZoomMotion(minScale: 1.0, maxScale: 4.0, child: MyImage())
// Mouse/touch parallax
PanParallax(strength: 20.0, child: MyCard())
Accessibility #
ui_animations_pro is WCAG 2.1 AA compliant out of the box.
// Policy at app root (detects OS "reduce motion" automatically)
ReduceMotionPolicy(
behavior: ReduceMotionBehavior.crossFade, // disable | crossFade | scaleDuration | ignore
durationScale: 0.2, // used with scaleDuration
child: MaterialApp(...),
)
// Semantic wrapper
AccessibilityBridge(
label: 'Loading indicator',
hint: 'Content is loading',
liveRegion: true,
child: ShimmerBox(enabled: true, child: placeholder),
)
// Announce animation milestones to screen readers
AnimationAnnouncer(
animation: _controller,
onStartMessage: 'Menu opening',
onCompleteMessage: 'Menu opened',
child: AnimatedMenu(),
)
// Resolve duration against policy
MotionAwareBuilder(
baseDuration: const Duration(milliseconds: 500),
builder: (ctx, policy, resolved) {
return MotionWidget(duration: resolved, transition: ..., child: ...);
},
)
Performance #
FrameBudgeter #
Automatically monitors every frame. On 120 Hz devices it targets 8.33 ms; on 60 Hz — 16.67 ms.
// Access diagnostics
print(AnimationsPro.instance.diagnostics);
// {
// activeScenes: 3,
// currentFps: '58.2',
// droppedFrames: 2,
// isLowPerformance: false,
// ...
// }
// Live FPS stream
AnimationsPro.instance.fpsStream.listen((fps) {
// Update an in-app debug overlay
});
Adaptive quality #
Mark expensive plugins with isExpensive = true. When FPS drops below AnimationsConfig.minFpsThreshold (default 50 fps), a host app can conditionally switch:
MotionWidget(
transition: AnimationsPro.instance.isLowPerformanceDevice
? FadeTransitionPlugin() // lightweight fallback
: BlurTransitionPlugin(), // full quality
child: myWidget,
)
MotionEngine — headless testing #
final engine = MotionEngine();
// Sample timeline at specific timestamps (no Flutter UI required)
final values = engine.sampleAt(myScene, 250 /* ms */);
print(values['logo-step']); // 0.83
// Sample physics simulation
final positions = engine.samplePhysics(
SpringSim(from: 0, to: 1, stiffness: 180, damping: 12),
durationSec: 2.0,
sampleHz: 60,
);
Integration — ThemeBridge #
final bridge = ThemeBridge.of(context);
MotionWidget(
duration: bridge.mediumDuration, // 300ms
curve: bridge.emphasizedCurve, // Material 3 emphasized
child: MyCard(),
)
// Derived shimmer colours matching your theme
ShimmerBox(
baseColor: bridge.shimmerBase,
highlightColor: bridge.shimmerHighlight,
child: placeholder,
)
// Stagger offset helpers
for (int i = 0; i < items.length; i++) {
Future.delayed(bridge.staggerOffset(i), () => showItem(i));
}
AnimationsConfig reference #
AnimationsConfig(
defaultDurationMs: 300, // Default transition duration
defaultStaggerMs: 50, // Default stagger interval
frameBudgetMs: 16.67, // 60fps target (use 8.33 for 120fps)
adaptiveQuality: true, // Auto-degrade on slow devices
respectReduceMotion: true, // Honour OS reduce-motion flag
enablePerformanceMonitor: true,
minFpsThreshold: 50, // Below this → low-performance mode
allowExpensiveEffects: true, // Blur, shadow, ripple
physicsTicksPerSecond: 120,
physicsRestVelocity: 0.001,
debug: false, // Enable debug overlays
scenePrefix: 'scene_', // Namespace for registered scenes
tickerPoolSize: 16, // AnimationController pool size
pauseOnAppBackground: true, // Auto-pause when app is backgrounded
precompileTimelines: true, // Cache compiled timelines
)
Project structure #
ui_animations_pro/
├── lib/
│ ├── ui_animations_pro.dart ← Main barrel
│ ├── transitions.dart ← Transition plugins barrel
│ ├── physics.dart ← Physics simulators barrel
│ └── src/
│ ├── core/ ← Facade, config, exceptions, handle, state
│ ├── choreography/ ← Scene, Timeline, Choreographer, 8 steps
│ ├── transitions/ ← TransitionPlugin + 12 plugins
│ ├── curves/ ← CurveLibrary, Spring, Elastic, Back, Bezier
│ ├── physics/ ← Spring, Friction, Gravity, Magnetic
│ ├── engine/ ← MotionEngine, FrameBudgeter, Pool, Scheduler
│ ├── widgets/ ← MotionWidget, StaggerList, ShimmerBox …
│ ├── routes/ ← Animated page routes
│ ├── gestures/ ← GestureMotion, SwipeDismiss, PinchZoom …
│ ├── accessibility/ ← ReduceMotionPolicy, Bridge, Announcer
│ └── integration/ ← MotionProvider, Observer, ThemeBridge
├── example/motion_demo/ ← Full showcase app
├── test/
│ ├── unit/ ← Pure Dart unit tests
│ ├── widget/ ← Flutter widget tests
│ └── integration/ ← End-to-end scene playback tests
├── pubspec.yaml
├── README.md
└── CHANGELOG.md
Running the tests #
flutter test
Run with coverage:
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
Running the example #
cd example/motion_demo
flutter pub get
flutter run
Roadmap #
| Phase | Version | Feature |
|---|---|---|
| MVP | v0.1 | 12 Transitions · 8 Steps · 5 Curves · 4 Physics · Choreographer · FrameBudgeter ✅ |
| Code Gen | v0.2 | build_runner — @Scene / @AnimateProperty annotations |
| Path Animations | v0.3 | Animate along SVG Path — logos, signatures, stroke progress |
| Particle System | v0.4 | Confetti, smoke, stars emitter at 60 fps |
| Lottie/Rive Bridge | v0.5 | Import .lottie / .riv as native Scene |
| Skeleton AutoLoader | v0.6 | Auto-shimmer matched-shape on loading widgets |
| Live Timeline Editor | v0.7 | In-app drag-and-drop editor → export Dart code |
| Web Motion Exporter | v0.8 | Export Scene as CSS animations / WAAPI keyframes |
| Motion Golden Tester | v1.0 | Pixel-by-pixel animation regression testing |
License #
MIT — see LICENSE.
Acknowledgements #
Architecturally consistent with the TIMSoft ecosystem:
timsoftdz_core · timsoft_os · timsoft_rdl_designer · timsoft_rdl_engine · blogger_orm · flutter_chats_ui
Inspired by: Framer Motion · GSAP · Rive · Lottie · React Spring