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<SimpleRoute> 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.