Mobile Development 9 min read

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.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Implementing Animations in Flutter: From Animatable and Tween to a Custom Bezier Curve Motion

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.

FlutteranimationMobileDevelopmentTweenAnimatableAnimationControllerBezierCurve
Qunar Tech Salon
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.