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.

pub version Flutter Dart License: MIT


🎬 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());
}
@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

Libraries

physics
physics.dart — Barrel for all physics simulators.
transitions
transitions.dart — Barrel for all 12 built-in TransitionPlugins.
ui_animations_pro
ui_animations_pro — Production-grade cinematic UI animations for Flutter.