567 lines
11 KiB
Markdown
567 lines
11 KiB
Markdown
# 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
|
|
|
|
```dart
|
|
Curves.linear
|
|
```
|
|
|
|
No easing - straight line from 0 to 1. Use when you want constant speed.
|
|
|
|
### Ease (Sigmoid)
|
|
|
|
```dart
|
|
Curves.ease
|
|
Curves.easeIn
|
|
Curves.easeOut
|
|
Curves.easeInOut
|
|
```
|
|
|
|
**Curves.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)
|
|
|
|
```dart
|
|
Curves.easeInCubic
|
|
Curves.easeOutCubic
|
|
Curves.easeInOutCubic
|
|
```
|
|
|
|
Stronger easing than basic ease. More dramatic slow/fast zones.
|
|
|
|
### Elastic (Bouncy)
|
|
|
|
```dart
|
|
Curves.elasticIn
|
|
Curves.elasticOut
|
|
Curves.elasticInOut
|
|
```
|
|
|
|
**Curves.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
|
|
|
|
```dart
|
|
Curves.bounceIn
|
|
Curves.bounceOut
|
|
Curves.bounceInOut
|
|
```
|
|
|
|
**Curves.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)
|
|
|
|
```dart
|
|
Curves.backIn
|
|
Curves.backOut
|
|
Curves.backInOut
|
|
```
|
|
|
|
**Curves.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)
|
|
|
|
```dart
|
|
Curves.decelerate
|
|
```
|
|
|
|
Fast start, very slow end. Similar to easeOut but more dramatic.
|
|
|
|
### Fast Out Slow In
|
|
|
|
```dart
|
|
Curves.fastOutSlowIn
|
|
```
|
|
|
|
Very fast start, very slow middle, very fast end. Dramatic.
|
|
|
|
### Custom Curves in Flutter API
|
|
|
|
Flutter includes specialized curves:
|
|
|
|
```dart
|
|
// Material design curves
|
|
Curves.fastLinearToSlowEaseIn
|
|
Curves.slowMiddle
|
|
|
|
// Specific curves for certain transitions
|
|
Curves.ease
|
|
```
|
|
|
|
## Using Curves
|
|
|
|
### With Implicit Animation
|
|
|
|
```dart
|
|
AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
width: _expanded ? 200 : 100,
|
|
child: const FlutterLogo(),
|
|
)
|
|
```
|
|
|
|
### With Explicit Animation (CurvedAnimation)
|
|
|
|
```dart
|
|
animation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.easeInOut,
|
|
);
|
|
```
|
|
|
|
### With Interval
|
|
|
|
```dart
|
|
animation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: const Interval(
|
|
0.0,
|
|
0.5,
|
|
curve: Curves.easeIn,
|
|
),
|
|
);
|
|
```
|
|
|
|
### With Curves.elasticOut
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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:**
|
|
```dart
|
|
AnimatedContainer(
|
|
curve: CustomCurve(),
|
|
duration: const Duration(milliseconds: 300),
|
|
width: _expanded ? 200 : 100,
|
|
child: const FlutterLogo(),
|
|
)
|
|
```
|
|
|
|
### Shake Curve
|
|
|
|
```dart
|
|
class ShakeCurve extends Curve {
|
|
@override
|
|
double transform(double t) {
|
|
return sin(t * pi * 2);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Smooth Step Curve
|
|
|
|
```dart
|
|
class SmoothStep extends Curve {
|
|
final double steps;
|
|
|
|
const SmoothStep({this.steps = 5});
|
|
|
|
@override
|
|
double transform(double t) {
|
|
return (t * steps).floor() / steps;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Exponential Curve
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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:**
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
// 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
|
|
|
|
```dart
|
|
// 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
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
Curve getAdaptiveCurve(BuildContext context) {
|
|
if (MediaQuery.of(context).disableAnimations) {
|
|
return Curves.linear; // No easing when animations disabled
|
|
}
|
|
return Curves.easeInOut;
|
|
}
|
|
```
|
|
|
|
### Reduced Motion
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
_animation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.linear, // Constant speed
|
|
);
|
|
```
|
|
|
|
### Success Checkmark
|
|
|
|
```dart
|
|
_animation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.elasticOut, // Bouncy celebration
|
|
);
|
|
```
|
|
|
|
### Page Transition
|
|
|
|
```dart
|
|
_enterAnimation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.easeIn, // Accelerate in
|
|
);
|
|
|
|
_exitAnimation = CurvedAnimation(
|
|
parent: _controller,
|
|
curve: Curves.easeOut, // Decelerate out
|
|
);
|
|
```
|
|
|
|
### Modal Popup
|
|
|
|
```dart
|
|
_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)
|