Photo by Judith Browne on Unsplash

We continue reviewing animations in Flutter, and today it’s turn for tween animations. Can we use them to build a foldable widget? Let’s see!


A foldable widget is a component with some fancy transformation that bends the item over any of its axis.

Board with foldable characters.
Source: microstocksec from

When it comes down to animations, Flutter offers a wide range of possibilities. So there are several ways to build a component like this one, most of them involving explicit animations, animation controllers, mixins… Nevertheless, the goal of this article is to provide a simpler implementation, based on tweens but also making use of some old friends, like transform.

Flutter API

As usual, the question is… which are the classes available to build a widget like this?


A “Tween” (abbreviation for “in-between”) is an object that stores a sequence of values. For example, using a tween you can store a list of consecutive numbers. But more abstract data types can be stored as well: positions, offsets, colors, etc.

Since tweens are quite flexible, this class is defined as generic, so it takes as a parameter the data type of the objects that it will contain:

//XXX: a tween of doubles...
Tween<double> rotationTween = Tween<double>();

//XXX: another one for colors...
Tween<Color> colorTween = Tween<Color>();

When it comes down to animations, tweens will do a lot of heavy-lifting, because they are responsible for traversing the whole list of items and calculate the mid values in between. Tween’s sequential output can be applied on any widget that requires an input that changes over time.

Internally, a tween transforms the initial sequence to another one, always within the range [0.0 – 1.0]. Among other properties, the class declares:

  • begin: initial value for the sequence
  • end: final value for the sequence
Tween<double> rotationTween = Tween<double>(begin: 0.0, end: 180.0);


Before talking about curves, let’s remember some important concepts:

  • When we animate a component, we are just changing the value of some of its properties. In the following example, we animate a container by changing its height:
Animated container.
  • Animations always have a duration, even when they are played in loop mode.

Taking into account these considerations, the curve of an animation can be described as the “amount of change” that we apply over time while the animation is running.

Curves are included in many programming languages. In Flutter, they are represented with the “Curve” hierarchy, that contains several classes. All of them allow us to customize the behaviour of our animations. You can see available curves in action here.

The following video, for instance, depicts a linear curve. X axis represents time (duration of the animation) and Y axis contains animation values. Since this curve is linear, the “amount of change” applied at every time is constant.

Linear curve. Source:


“TweenAnimationBuilder” is a convenience class used to animate a widget using the values provided by a tween sequence.

It declares the following fields:

  • tween: sequence of values used for the animation.
  • duration: transformation running time.
  • builder: method called for every value traversed in the associated tween.
  • onEnd: callback invoked automatically when animation ends. It allows us to hook custom actions, like chaining one animation with another.


As we saw in the previous article, this widget is used to apply modifications (in shape, size, position or point of view) to its child widget.

“Transform” is the last important piece required to build our widget. But how do we fit all of them together?

The foldable widget

Which are the features we have to implement this time?

  1. Fold the widget. This effect, as we mentioned before, is just a rotation, so a simple transformation will do.
  2. Get a source of values to modify the properties of the widget. As you may guess, tween objects will solve this one. We will configure them to range from 0 to 180, since our rotation must take up half a circle.
  3. Chain consecutive operations. Since tweens also notify us when they have traversed the whole list of values (so the animation is finished), we will be able to launch one animation after another.
  4. Crop the contents of a widget, so we only see the “unfolded” parts.
PI Rotation (180 degrees in radians).
Source: wikipedia

At this point, almost all requirements are pretty straight-forward. Since the last one may seem a little bit tricky, let’s explore it in detail.

Cropping content

Crop effect can be implemented by using “ClipRect” and “Align” classes in collusion, nesting them. The former allows us to crop any widget using a rectangular size. On the other hand, the latter can be given a partial width or a height plus an alignment, thus creating some sort of viewport over a component.

The following snippet shows the usage of this widgets combination:

return ClipRect(
        child: Align(
          alignment: Alignment.topRight,
          width: 0.5, //XXX: half the w
          height: 0.5, //XXX: half the h   
          child: Image(...),

…and this picture displays the result when applying the crop effect over an image widget:

Original image and cropped image using ClipRect and Align

Some more implementation details

In our example, the target widget will be folded first over its horizontal axis, from bottom to top. After that, folding will be done on the vertical axis, from right to left.

In order to keep things simple, the layout will be a combination of rows and columns:

Foldable widget layout

Each one of these portions are containers that include as its child the target widget (the image that will be folded), but “cropping” the content accordingly to the current position.

So the first cell, for instance, takes half the width and half the height, and displays only top-left contents:

return Container(
      child: ClipRect(
        child: Align(
          alignment: Alignment.topLeft,
          child: ...,

Similarly, the lower row takes all the width available, but only half the height, and uses the bottom part of the contained element.

return Container(
      child: ClipRect(
        child: Align(
          alignment: Alignment.bottomCenter,
          heightFactor: 0.5,
          widthFactor: 1.0,
          child: Image(...),

Additionally, each cell that bends over another one is enclosed with some animation builder, so we can apply the corresponding transformation over time by feeding the tween’s current value to transform:

   duration: Duration(seconds: ANIM_DURATION_IN_SECS),
   //XXX: avoid storing tweens as class props
   tween: Tween<double>(begin: 0.0, end: 3.141592),
   builder: (BuildContext cntxt, double value, Widget child) {
      return Transform(
         transform: Matrix4.identity()..rotateY(value),
         child: ...

How do we launch the whole process? In this case, since the main component is a stateful widget, animations are triggered using an integer value that is incremented every time a fold is done:

static const int NO_FOLDING = 0;
static const int FOLDING_HORIZONTAL = 1;
static const int FOLDING_VERTICAL = 2;
void _startMotion() {
    this.setState(() {
      this._numFolds = FOLDING_HORIZONTAL;

And finally, the chaining of operations: when the first animation is finished, the fold counter is increased again, causing the widget to rebuild and launching the next animation:

onEnd: () {

This video displays the final result:

Our foldable widget containing an image


So that would be all! Combining tweens and animation builders, we can set up a source of continuous data that brings motion to any other widget. And adding transformations into the mix, we can rotate the item to achieve the desired effect.

Piece of cake! After all, a foldable component could have been a lot harder to “bend”, right…?

Bender bending the hard way…

Write you next time. As usual, full source code is available at:

Leave a comment

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: