Implementing Animations in Flutter: From Animatable and Tween to a Custom Bezier Curve Motion
This article explains how Flutter renders animations by repeatedly calling setState, describes the roles of Animatable, Animation, Tween, and AnimationController, and provides a complete example of creating a custom BezierTween to achieve a bezier‑curve motion with full source code.
Animation Implementation Approach
In Flutter, calling setState() triggers a new frame render, so continuously updating position information while calling this method creates a translation animation; the same principle applies to other animation types.
Flutter's Core Classes
Animatable
The Animatable class controls the type of animation by defining how values such as x , y , or color change over time. For a translation animation it manipulates x and y , while for a color animation it manipulates the color components.
Animation
The Animation class controls the animation’s progress (forward, reverse, easing) without caring about the animation type. To animate from 0 to 200 you first create an appropriate Animatable (Flutter already provides many, e.g., Tween ).
T lerp(double t) {
return begin + (end - begin) * t;
}
assert(begin != null);
assert(end != null);
return begin + (end - begin) * t;After creating an Animatable , you call animate() with an Animation (usually an AnimationController ) to start the animation; the callback runs on every frame.
AnimationController Role
AnimationController represents the animation as a value that linearly progresses from 0.0 to 1.0 (forward) or from 1.0 to 0.0 (reverse). It receives vertical‑sync signals via a Ticker , updates the value each tick, and notifies listeners.
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds / Duration.microsecondsPerSecond;
assert(elapsedInSeconds >= 0.0);
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward)
? AnimationStatus.completed
: AnimationStatus.dismissed;
stop(canceled: false);
_checkStatusChanged();
}
notifyListeners();
}Calling forward() stops any current animation, starts the ticker, and drives the value from the current position toward the upper bound.
TickerFuture forward({double from}) {
_direction = _AnimationDirection.forward;
if (from != null) value = from;
return _animateToInternal(upperBound);
}Tween Role
A Tween maps the controller’s 0‑1 range to a concrete range (e.g., 0 → 200). Its lerp method implements a simple linear interpolation:
T lerp(double t) {
return begin + (end - begin) * t;
}For multi‑dimensional values (e.g., a Rect ) you would implement a custom tween such as RectTween .
Custom BezierTween
To animate along a quadratic Bézier curve you create a class extending Animatable<_Point> and override transform() with the Bézier formula.
class _Point {
const _Point({this.x, this.y});
final double x;
final double y;
}
class _BezierTween extends Animatable<_Point> {
_BezierTween({required this.p0, required this.p1, required this.p2})
: assert(p0 != null),
assert(p1 != null),
assert(p2 != null);
final _Point p0; // start
final _Point p1; // control
final _Point p2; // end
@override
_Point transform(double t) {
double x = (1 - t) * (1 - t) * p0.x +
2 * t * (1 - t) * p1.x +
t * t * p2.x;
double y = (1 - t) * (1 - t) * p0.y +
2 * t * (1 - t) * p1.y +
t * t * p2.y;
return _Point(x: x, y: y);
}
}Usage Example
In a StatefulWidget you create an AnimationController , define three points, build a _BezierTween , and animate it. The listener updates the UI on each frame.
class _SimpleRouteState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late _Point _p0, _p1, _p2;
late Animation<_Point> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(seconds: 2), vsync: this);
_p0 = _Point(x: 30, y: 30);
_p1 = _Point(x: 30, y: 200);
_p2 = _Point(x: 200, y: 200);
_animation = _BezierTween(p0: _p0, p1: _p1, p2: _p2).animate(_controller)
..addListener(() {
setState(() {});
});
}
}Running this code moves a widget along the defined Bézier curve, demonstrating how Flutter’s animation system can be extended with custom interpolations.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.