motor 1.0.0-dev.4
motor: ^1.0.0-dev.4 copied to clipboard
A unified motion system for Flutter - physics-based springs and duration-based curves under one API.
Motor #
A unified motion system that brings together physics-based springs, duration-based curves, and Flutter's animation system under one consistent API.
Features 🎯 #
- 🎨 Unified Motion API - One consistent interface for springs, curves, and custom motions
- 💡 Physics & Duration Based - Choose between spring physics or traditional duration/curve animations
- 🍎 Apple Design System - Built-in CupertinoMotion presets matching iOS animations
- 🎨 Material Design 3 - MaterialSpringMotion tokens following Google's motion guidelines
- 📱 Multi-dimensional - Animate complex types like Offset, Size, and Rect with independent physics per dimension
- 🔄 Interactive Widgets - Motion-driven draggable widgets with natural return animations
- 🎯 Flutter Integration - Works seamlessly with existing Flutter animation patterns
Try it out #
Installation 💻 #
❗ In order to start using Motor you must have the Dart SDK installed on your machine.
Add to your pubspec.yaml
:
dependencies:
motor: ^1.0.0-dev.0
Or install via dart pub
:
dart pub add motor
Usage 💡 #
Motion #
The core of Motor's unified motion system is the Motion
class. It represents the type of motion that will drive your animation, whether physics-based or duration-based.
// Duration-based motion (traditional Flutter approach)
final linear = LinearMotion(Duration(seconds: 1));
final withCurve = CurvedMotion(
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
// Physics-based motion (natural, responsive)
final spring = CupertinoMotion.bouncy();
final material = MaterialSpringMotion.standardSpatialDefault;
Motor provides several motion types out of the box, with the ability to create custom motions by implementing the Motion
interface:
CurvedMotion
- Traditional duration-based motion with curves. Perfect for predictable, timed animations.LinearMotion
- LikeCurvedMotion
but always linear.SpringMotion
- Physics-based motion using Flutter SDK's SpringDescription. Provides natural, responsive animations that feel alive.CupertinoMotion
- Predefined spring configurations matching Apple's design system.MaterialSpringMotion
- Material Design 3 spring motion tokens for expressive animations.
This unified approach means you can easily switch between physics and duration-based animations without changing your widget code.
CupertinoMotion #
CupertinoMotion
is a subclass of SpringMotion
that provides predefined spring configurations matching Apple's design system. These motions are designed to feel natural and familiar to iOS users, as they mirror the spring animations used throughout Apple's platforms.
CupertinoMotion
offers several predefined constants that correspond to SwiftUI's animation presets:
CupertinoMotion()
- The default iOS spring with smooth motion and no bounceCupertinoMotion.smooth()
- A smooth spring animation with no bounce, ideal for subtle transitionsCupertinoMotion.bouncy()
- A bouncy spring with higher bounce, perfect for playful interactionsCupertinoMotion.snappy()
- A snappy spring with small bounce that feels responsiveCupertinoMotion.interactive()
- An interactive spring with lower response, designed for user-driven animations
You can also create custom CupertinoMotion
instances:
final customMotion = CupertinoMotion(
duration: Duration(milliseconds: 600),
bounce: 0.3,
);
Since CupertinoMotion
extends SpringMotion
(which extends Motion
), you can use it directly wherever a Motion
is expected.
MaterialSpringMotion #
MaterialSpringMotion
provides Material Design 3 spring motion tokens for creating expressive and natural animations that follow Google's design guidelines. The tokens are organized into two main categories with three speed variants each:
Spatial Motion - For animating position, size, and layout changes:
MaterialSpringMotion.standardSpatialFast
- Quick spatial animations (damping: 0.9, stiffness: 1400)MaterialSpringMotion.standardSpatialDefault
- Balanced spatial animations (damping: 0.9, stiffness: 700)MaterialSpringMotion.standardSpatialSlow
- Gentle spatial animations (damping: 0.9, stiffness: 300)MaterialSpringMotion.expressiveSpatialFast
- Dynamic spatial with bounce (damping: 0.6, stiffness: 800)MaterialSpringMotion.expressiveSpatialDefault
- Moderate expressive spatial (damping: 0.8, stiffness: 380)MaterialSpringMotion.expressiveSpatialSlow
- Gentle expressive spatial (damping: 0.8, stiffness: 200)
Effects Motion - For animating visual properties like opacity and color:
MaterialSpringMotion.standardEffectsFast
- Quick effects animations (damping: 1, stiffness: 3800)MaterialSpringMotion.standardEffectsDefault
- Balanced effects animations (damping: 1, stiffness: 1600)MaterialSpringMotion.standardEffectsSlow
- Gentle effects animations (damping: 1, stiffness: 800)MaterialSpringMotion.expressiveEffectsFast
- Quick expressive effects (damping: 1, stiffness: 3800)MaterialSpringMotion.expressiveEffectsDefault
- Moderate expressive effects (damping: 1, stiffness: 1600)MaterialSpringMotion.expressiveEffectsSlow
- Gentle expressive effects (damping: 1, stiffness: 800)
You can also create custom MaterialSpringMotion
instances:
final customMaterial = MaterialSpringMotion(
damping: 0.8,
stiffness: 500,
);
These motion tokens follow the Material Design 3 Motion Guidelines and are designed to create consistent, expressive animations across Material Design applications.
Simple Animation #
Use SingleMotionBuilder
for basic, one-dimensional animations:
SingleMotionBuilder(
motion: CupertinoMotion.bouncy(),
value: targetValue, // Changes trigger smooth spring animation
builder: (context, value, child) {
return Container(
width: value,
height: value,
color: Colors.blue,
);
},
)
If you want to animate more complex types, such as Offset
, Size
, or Rect
, you can use MotionBuilder
and pass a so-called MotionConverter
to it:
MotionBuilder(
motion: CupertinoMotion.bouncy(),
value: const Offset(100, 100),
from: Offset.zero,
converter: OffsetMotionConverter(),
builder: (context, value, child) {
return Transform.translate(
offset: value,
child: child,
);
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
For Material Design applications, you can use MaterialSpringMotion tokens:
MotionBuilder(
motion: MaterialSpringMotion.expressiveSpatialDefault,
value: const Offset(100, 100),
from: Offset.zero,
converter: OffsetMotionConverter(),
builder: (context, value, child) {
return Transform.translate(
offset: value,
child: child,
);
},
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
)
MotionConverter #
One of Motor's key advantages is its ability to animate complex types with independent motion per dimension. While Flutter's basic animation system typically uses single animations with Tween
s, Motor's unified motion system can simulate each dimension independently.
This is crucial for natural-feeling animations. For example, when animating a draggable icon, the user might fling it horizontally (high horizontal velocity) while it settles vertically (low vertical velocity). Traditional single-animation approaches would lose this dimensional independence, making the motion feel artificial.
MotionConverter
s solve this by breaking any type into multiple dimensions, allowing each dimension to follow the same motion pattern but with independent physics simulation.
This works with any motion type - whether you're using spring physics or duration-based curves, each dimension animates independently for maximum fidelity.
For often-used Flutter types, these are already implemented:
OffsetMotionConverter
SizeMotionConverter
RectMotionConverter
AlignmentMotionConverter
However, you might want your very custom type to be animated as well. For this, you can implement your own MotionConverter
and pass it to the MotionBuilder
constructor.
class My3DMotionConverter implements MotionConverter<Vector3> {
@override
List<double> normalize(Vector3 value) => [value.x, value.y, value.z];
@override
Vector3 denormalize(List<double> values) => Vector3(values[0], values[1], values[2]);
}
Widget build(BuildContext context) {
return MotionBuilder(
motion: CupertinoMotion.bouncy(),
value: Vector3(100, 100, 100),
converter: My3DMotionConverter(),
// ...
);
}
Or, just use MotionConverter
directly and pass the converter functions to its constructor:
final converter = MotionConverter(
normalize: (value) => [value.x, value.y, value.z],
denormalize: (values) => Vector3(values[0], values[1], values[2]),
);
Motion Draggable #
Motor includes a MotionDraggable
widget that demonstrates the power of the unified motion system. You can drag widgets around the screen and watch them return with any motion type - from bouncy springs to smooth curves.
It works just like Flutter's Draggable
widget and supports native DragTarget
s, but with motion-driven return animations and enhanced physics simulation.
MotionDraggable(
motion: CupertinoMotion.bouncy(),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
data: 'my-draggable-data',
)
Low-level Motion Control #
For maximum control, Motor provides MotionController
for complex types and SingleMotionController
for one-dimensional animations. These controllers work with any motion type in the unified system.
final controller = MotionController(
motion: CupertinoMotion.bouncy(), // or Motion.duration(), etc.
vsync: this,
);
Motion controllers work similarly to Flutter's AnimationController
but with key advantages:
- Motion-agnostic: Switch between springs and curves without changing controller code
- Velocity preservation: Maintains velocity when changing targets (crucial for natural motion)
- Multi-dimensional: Each dimension can have independent physics simulation
Bounded vs. Unbounded Motion
In Flutter, the AnimationController
can be either bounded or unbounded. MotionController
s come in both flavors as well, but they differ in key ways:
MotionController
:
- By default,
MotionController
s are unbounded. - Unbounded
MotionController
s don't haveforward
orreverse
methods, since they don't make sense in multi-dimensional space.
BoundedMotionController
:
- requires you to specify a
lowerBound
andupperBound
in the constructor. - exposes
forward
andreverse
methods, which internally animate towards theupperBound
andlowerBound
respectively. - will clamp the animation value to be within the bounds, but they can still overshoot as part of their
Motion
simulation.
Custom Springs 🔧 #
For predefined spring configurations, see the CupertinoMotion
section above.
You can also create completely custom springs:
// Using CupertinoMotion constructor
final mySpring = CupertinoMotion(
duration: Duration(milliseconds: 500),
bounce: 0.2, // Bounce amount (-1 to 1)
);
// Or using Flutter SDK's SpringDescription directly
final customSpring = SpringMotion(
SpringDescription.withDurationAndBounce(
duration: Duration(milliseconds: 500),
bounce: 0.2,
),
);
Acknowledgements #
Motor's unified motion system builds upon excellent work from the Flutter community:
- Spring physics implementation was partially adapted from and heavily inspired by fluid_animations
- CupertinoMotion presets are designed to match Apple's SwiftUI animation system
- The Motion abstraction unifies concepts from Flutter's animation framework with modern physics-based approaches