Curves Reference
Curves define the easing function for animations, controlling how values interpolate from start to end.
Core Concept
A curve takes an input t (0.0 to 1.0, representing time) and outputs a value (typically 0.0 to 1.0, representing progress).
Built-in Curves
Linear
Curves.linearNo easing - straight line from 0 to 1. Use when you want constant speed.
Ease (Sigmoid)
Curves.ease
Curves.easeIn
Curves.easeOut
Curves.easeInOutCurves.ease - Slow start and end, fast middle (most common)
Curves.easeIn - Slow start, fast end (accelerating)
Curves.easeOut - Fast start, slow end (decelerating)
Curves.easeInOut - Slow start and end, fast middle (combination)
Cubic (Stronger Ease)
Curves.easeInCubic
Curves.easeOutCubic
Curves.easeInOutCubicStronger easing than basic ease. More dramatic slow/fast zones.
Elastic (Bouncy)
Curves.elasticIn
Curves.elasticOut
Curves.elasticInOutCurves.elasticIn - Bounces in (starts behind, shoots forward)
Curves.elasticOut - Bounces out (goes past, comes back)
Curves.elasticInOut - Bounces in and out
Example: Good for playful UI, notifications
Bounce
Curves.bounceIn
Curves.bounceOut
Curves.bounceInOutCurves.bounceIn - Bounces in (less bouncy than elastic)
Curves.bounceOut - Bounces out (less bouncy than elastic)
Curves.bounceInOut - Bounces in and out
Example: Good for feedback animations
Back (Overshoot)
Curves.backIn
Curves.backOut
Curves.backInOutCurves.backIn - Backs up before going in
Curves.backOut - Backs up before coming out
Curves.backInOut - Backs up on both ends
Example: Good for revealing content
Decelerate (Fast then Slow)
Curves.decelerateFast start, very slow end. Similar to easeOut but more dramatic.
Fast Out Slow In
Curves.fastOutSlowInVery fast start, very slow middle, very fast end. Dramatic.
Custom Curves in Flutter API
Flutter includes specialized curves:
// Material design curves
Curves.fastLinearToSlowEaseIn
Curves.slowMiddle
// Specific curves for certain transitions
Curves.easeUsing Curves
With Implicit Animation
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
child: const FlutterLogo(),
)With Explicit Animation (CurvedAnimation)
animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);With Interval
animation = CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.5,
curve: Curves.easeIn,
),
);With Curves.elasticOut
AnimatedContainer(
duration: const Duration(milliseconds: 1000),
curve: Curves.elasticOut,
transform: Matrix4.rotationZ(_rotated ? 0.5 : 0),
child: const FlutterLogo(),
)Custom Curves
Creating a Custom Curve
import 'dart:math' as math;
class CustomCurve extends Curve {
@override
double transform(double t) {
// t is 0.0 to 1.0
// Return 0.0 to 1.0
return math.pow(t, 2); // Quadratic ease in
}
}Usage:
AnimatedContainer(
curve: CustomCurve(),
duration: const Duration(milliseconds: 300),
width: _expanded ? 200 : 100,
child: const FlutterLogo(),
)Shake Curve
class ShakeCurve extends Curve {
@override
double transform(double t) {
return sin(t * pi * 2);
}
}Smooth Step Curve
class SmoothStep extends Curve {
final double steps;
const SmoothStep({this.steps = 5});
@override
double transform(double t) {
return (t * steps).floor() / steps;
}
}Exponential Curve
class ExponentialCurve extends Curve {
final double exponent;
const ExponentialCurve({this.exponent = 2});
@override
double transform(double t) {
return math.pow(t, exponent);
}
}Curve Composition
Cubic Bezier
Flutter doesn't have built-in cubic bezier, but you can approximate:
class CubicBezier extends Curve {
final double p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y;
const CubicBezier({
required this.p0x,
required this.p0y,
required this.p1x,
required this.p1y,
required this.p2x,
required this.p2y,
required this.p3x,
required this.p3y,
});
@override
double transform(double t) {
// Simplified cubic bezier formula
final u = 1 - t;
return (u * u * u * p0y +
3 * u * u * t * p1y +
3 * u * t * t * p2y +
t * t * t * p3y);
}
}Combining Curves
class CombinedCurve extends Curve {
final Curve first;
final Curve second;
final double threshold;
const CombinedCurve({
required this.first,
required this.second,
required this.threshold,
});
@override
double transform(double t) {
if (t < threshold) {
return first.transform(t / threshold) * threshold;
}
return threshold +
second.transform((t - threshold) / (1 - threshold)) * (1 - threshold);
}
}Usage:
AnimatedContainer(
curve: CombinedCurve(
first: Curves.easeIn,
second: Curves.easeOut,
threshold: 0.5,
),
duration: const Duration(milliseconds: 500),
width: _expanded ? 200 : 100,
child: const FlutterLogo(),
)Choosing the Right Curve
Motion Design Guidelines
| Animation Type | Recommended Curve | Why |
|---|---|---|
| Fade in/out | easeInOut | Natural opacity change |
| Size change | easeOut | Decelerate feels natural for growth |
| Position slide | easeInOut | Smooth start and stop |
| Rotation | easeInOut | Natural angular acceleration |
| Color change | linear | Uniform color transition |
| Scale | elasticOut | Playful, bouncy feel |
| Reveal | backOut | Dramatic reveal effect |
| Loading | linear or ease | Consistent speed |
| Success | elasticOut | Celebratory feel |
Platform Conventions
iOS:
- Prefers subtle curves (ease, easeInOut)
- Limited use of elastic/bounce
- Consistent with system animations
Android:
- Wider variety of curves
- More use of elastic/bounce for feedback
- Material design curves (easeIn, fastOutSlowIn)
Web:
- CSS-like curves (ease, ease-in, ease-out, ease-in-out)
- Limited use of complex curves
Curve Combinations
Multi-Stage Animation
// Stage 1: Ease in (0.0 - 0.5)
// Stage 2: Ease out (0.5 - 1.0)
animation1 = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
);
animation2 = CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
);Staggered with Different Curves
// Item 1: Bouncy
item1Animation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.3, curve: Curves.elasticOut),
);
// Item 2: Smooth
item2Animation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.1, 0.4, curve: Curves.easeInOut),
);
// Item 3: Linear
item3Animation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.2, 0.5, curve: Curves.linear),
);Performance Considerations
Curve Complexity
Simple curves (linear, ease) are faster:
- Fewer calculations
- Less GPU work
- Consistent timing
Complex curves (elastic, bounce) are slower:
- More calculations
- Potential for frame drops
- Variable timing
Optimization Tips
- Use simpler curves on low-end devices
- Cache curve calculations if needed
- Test performance on target devices
- Profile with Flutter DevTools
Debugging Curves
Visualize Curve
class CurveVisualizer extends StatelessWidget {
final Curve curve;
const CurveVisualizer({super.key, required this.curve});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _CurvePainter(curve: curve),
);
}
}
class _CurvePainter extends CustomPainter {
final Curve curve;
const CurvePainter({required this.curve});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke;
final path = Path();
for (double t = 0; t <= 1; t += 0.01) {
final x = t * size.width;
final y = size.height - (curve.transform(t) * size.height);
if (t == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(_CurvePainter oldDelegate) => false;
}Print Curve Values
void printCurveValues(Curve curve) {
print('Curve: $curve');
for (double t = 0; t <= 1; t += 0.1) {
print('t=$t, output=${curve.transform(t)}');
}
}
// Usage
printCurveValues(Curves.easeInOut);Accessibility
Respecting User Preferences
Curve getAdaptiveCurve(BuildContext context) {
if (MediaQuery.of(context).disableAnimations) {
return Curves.linear; // No easing when animations disabled
}
return Curves.easeInOut;
}Reduced Motion
AnimatedContainer(
duration: MediaQuery.of(context).disableAnimations
? Duration.zero
: const Duration(milliseconds: 300),
curve: getAdaptiveCurve(context),
width: _expanded ? 200 : 100,
child: const FlutterLogo(),
)Common Patterns
Loading Spinner
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.linear, // Constant speed
);Success Checkmark
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut, // Bouncy celebration
);Page Transition
_enterAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn, // Accelerate in
);
_exitAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut, // Decelerate out
);Modal Popup
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOutBack, // Dramatic reveal
);Curve Comparison
| Curve | Start | Middle | End | Feel |
|---|---|---|---|---|
| linear | Fast | Fast | Fast | Mechanical |
| ease | Slow | Fast | Slow | Natural |
| easeIn | Slow | Fast | Fast | Accelerating |
| easeOut | Fast | Slow | Slow | Decelerating |
| easeInOut | Slow | Fast | Slow | Smooth |
| elasticIn | Back | Fast | Normal | Bouncy start |
| elasticOut | Normal | Fast | Back | Bouncy end |
| bounceIn | Bounce | Fast | Normal | Bouncy start |
| bounceOut | Normal | Fast | Bounce | Bouncy end |
| backIn | Back | Fast | Normal | Overshoot start |
| backOut | Normal | Fast | Back | Overshoot end |
Best Practices
DO
- Use appropriate curves for animation type
- Test on real devices
- Consider platform conventions
- Respect accessibility settings
- Profile performance with DevTools
DON'T
- Use elastic/bounce for everything (distracting)
- Over-combine curves (confusing motion)
- Ignore device performance
- Use same curve for all animations (boring)
- Forget to handle edge cases (t < 0 or t > 1)