Custom Transitions in Flutter and How to Make Them – Splashing Colors

Pawel Franitza

Aug 10, 2021 • 7 min read
woman pointing out to a computer

Nothing makes an app better than some fancy animations

What do we even need page transitions/animations in general? Well, without it the apps would look blank and raw. It might feel and look fast, but also some users might ask:

What just happened?
Where did this thing disappear?

Animations provide a solution to these problems. They communicate intent and provide feedback to the user on what and why it is happening. The same goes for transitions between views. Some well-documented examples are Material Motion transitions, which provide animations with spatial relationships, where the direction of motion is important and provides information on how elements relate to each other in terms of similarity, for example, next page of the same category or different category.

When to use custom transitions? I can not find the purpose.

Although there are excellent pre-canned animations from Flutter's team, and simple yet beautiful built-in transitions, the project may benefit from custom transitions to provide unique value to the users. These custom transitions can be anything from expanding shapes, translations of UI elements, fading, flashing, blinking, or anything incorporating a mix of all of the above. Even if anything is possible, it is probably better to use simple effects that will not make anyone dizzy or overly distracted from the content of the app.

How to design and make custom animation/transition in Flutter.

So it is decided. You want to create custom transitions for your next project and need a glimpse of inspiration. One of the interesting transitions that are not bundled in Flutter that can be found in the wilds is Circle Reveal Animation that can be generalized to Any Shape Reveal Animation. In general, it is a great idea to get inspired by css animations. In the next paragraphs, we will walk through the process of making one.
To design a custom transition you should follow the corresponding steps:

  • Find or make an animation/s that the transition will be using
  • Create a custom clipper that will be animated
  • Optionally create an additional class extending PageTransitionsBuilder if you want to make a transition that can be used globally in the app in the Theme Data

Making an animation class

First things first. To animate anything in Flutter we need Animation objects and a way to rebuild UI when they change. For this purpose AnimatedBuilder can be used. Below snippet presents the build method of stateless widget

  @override
  Widget build(BuildContext _) => AnimatedBuilder(
        animation: animation,
        builder: (_, __) => ClipPath(
          clipper: CircularClipper(
            fraction: animation.value,
            centerOffset: centerOffset,
          ),
          child: child,
        ),
      );

As you can see, the main part of the widget is ClipPath that takes a custom clipper (in this case CircularClipper). Every time animation is evaluated a new Clipper will be returned.

Creating custom clipper.

We are using ClipPath which requires a class extending CustomClipper<Path>. Generally creating paths in Flutter may look troublesome, but few methods allow for creating custom shapes like circles and rectangles, constructing such shapes is relatively easy (take a look at getClip method).

  final double fraction;
  final Offset? centerOffset;

  static double maxRadius(Size size, Offset center) {
    final width = max(center.dx, size.width - center.dx);
    final height = max(center.dy, size.height - center.dy);
    return sqrt(pow(width, 2) + pow(height, 2));
  }

  @override
  Path getClip(Size size) {
    final center = centerOffset ?? Offset(size.width / 2, size.height / 2);

    return Path()
      ..addOval(
        Rect.fromCircle(
          center: center,
          radius: lerpDouble(0, maxRadius(size, center), fraction)!,
        ),
      );
  }

The main purpose of this class is to animate clippers when animation changes. Fraction represents how big the clipper is (and in the result how much of a view is visible at a time). There is also a function maxRadius that returns a distance that covers whole view.

The same approach can be used to define more finesse shapes, and it only requires adjusting the getClip method. For example, to return a star we would use something like this:

  final int peaks;
// Rest of the snippet as above

@override Path getClip(Size size) { final center = centerOffset ?? Offset(size.width / 2, size.height / 2); final path = Path(); final innerRadius = lerpDouble(0, maxRadius(size, center), fraction)!; final outerRadius = 1.5 * innerRadius; for (var k = 0; k < peaks; k++) { /// radial offset between peaks /// coordinates of points are [𝑟cos(2𝜋𝑘/n+𝜋/2), 𝑟sin(2𝜋𝑘/5+𝜋/2)] final _bAngle = 2 * pi * k / peaks + pi / 2; final _sAngle = 2 * pi * k / peaks + pi / 2 + pi / peaks; final outerVertices = Offset(outerRadius * cos(_bAngle), outerRadius * sin(_bAngle)); final innerVertices = Offset(innerRadius * cos(_sAngle), innerRadius * sin(_sAngle)); if (k == 0) { path ..moveTo(outerVertices.dx, outerVertices.dy) ..lineTo(innerVertices.dx, innerVertices.dy); } else { path ..lineTo(outerVertices.dx, outerVertices.dy) ..lineTo(innerVertices.dx, innerVertices.dy); } } return Path()..addPath(path..close(), center); }

When it comes to easier creation of paths, it is possible to convert .svg files to Path objects (this is not covered in this article).

Final step: Pushing route with new animations.

When you want to transition to a new view, use PageBuilder that uses new animation in its transitionsBuilder. We are also passing tap positions to alter clipper's center offset.

  Route _colorRotationRoute({Offset? offset}) {
    final nextColor = colorsRotation.random();
    return PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => ColorView(
        background: nextColor.values.first,
        text: nextColor.keys.first,
        animationType: animationType,
      ),
      transitionsBuilder: (context, animation, secondaryAnimation, child) =>
          CircularRevealAnimation(
        animation: animation,
        centerOffset: offset,
        child: child,
      ),
    );
  }  

Final product: Splashing colors with custom shapes

Here are the results, circular reveal animation and star reveal animation. The second one is quite interesting because thin spikes create blending, whereas the first one is really sharp and not blended.

Circular Reveal

Circular clipper represents splashes of paint. Edges are super sharp and that is probably the effect you are looking for when implementing transitions like this in the UI.

Summary

Creating animations in Flutter is fascinating and quite flexible. Every property (for example, opacity, size and position) can be animated to create some amazing effects.

There are many ways to animate and way more widgets to cover. One of the examples is DualTransitionBuilder which you could use when creating animation for AnimatedSwitcher. It is not covered in this article because reveal animation is working better with page transitions or animation controllers.

I hope you will find this tutorial useful and motivating to create and upgrade animations you are familiar with (No matter if it is a css animation or something from iOS/ Android world) in Flutter!

Related topics

More posts by this author

Pawel Franitza

codestories