Animated Custom Dialogs in Flutter

Tomb Toad features nicely animated little pop-up menus throughout. I am using Flame for a lot of game-related functionality but these were created using standard Flutter widgets. I’ve been asked how to create them, so I’m writing this little tutorial. Hopefully someone learns something!

First, here is a video of the menus we want to achieve:

As you can see the, the menu animates up into position smoothly, but it also simultaneously fades in. The foundation for this effect is going to be Flutter’s showGeneralDialog function. This displays a generic dialog popup and gives the developer a lot of customization options. Our core method will look something like this:

displayDialog({
  BuildContext context,
  Widget Function(BuildContext, Animation<double>, Animation<double>, Widget) transitionBuilder,
  int duration = 250 }) {
  showGeneralDialog(
    context: context,
    pageBuilder: (context, anim1, anim2) {return null;},
    barrierDismissible: false,
    barrierColor: Colors.black54,
    transitionDuration: Duration(milliseconds: duration),
    transitionBuilder: transitionBuilder
  );
}

The important thing here is going to be the ‘transitionBuilder’. More on that in a moment. ‘pageBuilder’ can be left as an empty function that returns null for this exercise.

Now using this method is really simple, here is an example:

displayDialog(
  context: context,
  transitionBuilder: (context, animation, secondaryAnimation, child) {
    return SimplePopup(
      onPressed: dismiss,
      anim: a1
    );
  },
  duration: 1000,
);

As you can see, the function we pass to ‘transitionBuilder’ returns a custom Stateless Widget we created called “SimplePopup”. Also notice how we are passing the ‘animation’ value to the SimplePopup constructor. This is an animated value generated by Flutter that keeps track of the transition’s progress. It ranges from 0 to 1, where 0 is the beginning and 1 is the end of the transition. You can use this value to do whatever you want. (we can ignore ‘secondaryAnimation’ for now too, it’s used for other route transitions) Let’s look at our SimplePopup class now to see how we use the ‘animation’ value:

class SimplePopup extends StatelessWidget {
  final Function onPressed;
  final Animation anim;
  SimplePopup({Key key, this.onPressed, this.anim}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final curvedValue = Curves.easeInOutQuad.transform(anim.value);
    return Transform(
      transform: Matrix4.translationValues(0.0, (1.0-curvedValue)*200.0, 0.0),
      child: Opacity(
        opacity: curvedValue,
        child: GestureDetector(
          onTap: onPressed,
          child: Dialog(
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.zero,
            ),      
            elevation: 0.0,
            child: Container(
              // Add your content in here
            ),
          ),
        ),
      ),
    );
  }
}

There’s a bunch going on here, so let’s break it down.

First, you can see that we are creating a ‘curvedValue’ from our anim value. You can use the raw anim value, but it will create a boring linear transition. So we transform it into a Quadratic easing curve. There are a lot of built-in animation curve functions, so you can experiment to get all sorts of different looks.

Next, we use the ‘curvedValue’ and apply it to the y-coordinate of a Transform widget. Using 1.0-curvedValue, the menu will animate up from the bottom (rather than down from the top). And 200 is the distance it moves.

Nested below is an Opacity widget. Here, we the curvedValue as the opacity, causing the widget to fade in from 0 to 1 opacity.

Below these two is a simple GestureDetector with an onTap event to dismiss the popup. When the popup is dismissed, Flutter will automatically play the transition animation backwards! Pretty cool. To dismiss this popup, simply add this tiny method to the widget that originally called ‘displayDialog’:

dismiss() {
  Navigator.of(context).pop();
}

The last thing in our SimplePopup class is the actual Dialog widget. This is a special Flutter widget that darkens the background and creates a new view on top of the rest of the app. This has a bunch of customization options too, but I’m not going to get into that. But inside this Dialog widget is where you would create all of your menu content.

Well, that’s about all there is to it. It’s not a terribly complex process and hope someone finds it useful.

 

david