PageRouteBuilder

With the help of PageRouteBuilder we can create our own transition animation of the pages. We know that to show a page we need the following statement:

Navigator.push(context, MaterialPageRoute(builder: (context) => NoteBooks()));

Here the push method takes a Route object as the second parameter. The class MaterialPageRoute creates a route object and returns it. This class is derived from PageRoute, and PageRoute is derived from ModalRoute.

There are three class derived from PageRoute and they are MaterialPageRoute, CupertinoPageRoute and PageRouteBuilder.

A PageRoute is a modal route that replaces the entire screen.

The MaterialPageRoute creates a PageRoute object based on the given page, and then adds transition to it. CupertinoPageRoute does the similar thing but it adds different style animation (iOS style). The class PageRouteBuilder has the ability to create PageRoute and adding transition to the page but a developer must provide the transition effect.

So, we can extend the class PageRouteBuilder to create our own transition effect. We can also use the constructor. So lets first create a custom transition using the constructor:

Using Constructor

The constructor has a required parameter pageBuilder. This takes a callback. The callback should return the page that we are going to push.

Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation){
    return NewPage();
  }
));

The animation parameter is a Animation<double> that generate values between 0 and 1. You can use this parameter to create transition.

Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation){
    return FadeTransition(
      opacity: animation,
      child: NoteBooks(),
    );
  }
));

The above code will animate the page which fades in. To customize the duration you can pass transitionDuration property:

Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation){
    return FadeTransition(
      opacity: animation,
      child: NoteBooks(),
    );
  },
  transitionDuration: Duration(seconds: 2),
));

The constructor takes another parameter which is transitionsBuilder, this takes a callback. The purpose of this parameter is to seperate the transition part from the main Page construction. Thus, the pageBuilder should be only responsible for creating the page and transitionsBuilder should be responsible for adding the transition effect to the page.

Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation){
    return NoteBooks();
  },
  transitionsBuilder: (context, animation, secondaryAnimation, child){
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  },
  transitionDuration: Duration(seconds: 2),
));

The method pageBuilder runs first and it returns the page widget. Then it calls the transitionsBuilder callback and passes the returned page from the pageBuilder as child parameter. You can then use the child parameter to incorporate within the transition widget. As both callback receives animation and secondaryAnimation parameter we can build the transition in either of the builder method. It is more effecient to built the transition part within the pageBuilder as the page is generally contained in seperate file and we only uses the constructor to build the page.

secondaryAnimation

Both the callback receives secondaryAnimation parameter. This is a Animation<double> animation. We can use this controller to show animation when another another page pushes on top of this one. This animation is run in the following two cases:

Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation){
    return Test();
  },
  transitionsBuilder: (context, animation, secondaryAnimation, child){
    Animation<double> ani = Tween<double>(begin: 1, end: 0).animate(secondaryAnimation);
      return FadeTransition(
        opacity: animation,
        child: ScaleTransition(
          scale: ani,
          child: child,
        ),
      );

  },
));

Extending Class

You can extend the class PageRouteBuilder to create a custom PageRoute object. The class should accept a parameter child which is the actual page. In the constructor you must call super and pass the required parameter pageBuilder.

The class PageRouteBuilder defines two metehod, pageBuilder and transitionsBuilder. You can override them to customize the transition. Following is an example:

import 'package:flutter/material.dart';

class BeautyTransition extends PageRouteBuilder{
  Widget child;

  BeautyTransition(this.child) : super(
    pageBuilder: (BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation){
      return child;
    },
    transitionDuration: Duration(milliseconds: 70),
  );

  @override
  buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child){
    Animation<double> scaleAnimation = Tween<double>(begin: 0.6, end: 1.0).animate(CurvedAnimation(
        parent: animation,
        curve: Interval(0.0, 0.8, curve: Curves.easeIn),
      )
    );
    Animation<double> opacityAnimation = Tween<double>(begin: 0.0, end: 1).animate(
      CurvedAnimation(
        parent: animation,
        curve: Interval(0.0, 1.0, curve: Curves.easeIn),
      ),
    );

    return ScaleTransition(
      scale: scaleAnimation,
      child: FadeTransition(
        opacity: opacityAnimation,
        child: child
      )
    );
  }
}

In the above example, we have initialize the parent constructor using the following:

BeautyTransition(this.child) : super(
  pageBuilder: (BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation){
    return child;
  },
  transitionDuration: Duration(milliseconds: 70),
);

In the above constructor the required parameter pageBuilder takes a callback and it just returns the child and does nothing else. Most of the time its sufficient for parent constructor to be like this. We have also passed transitionDuration parameter to parent constructor to override the default duration of the animation. The parameter transitioniDuration is optional.

Next, we are overriding the method buildTransitions, this is where our custom transition will be defined. You can create any animation you want and return the animated version of the Page from this method.

Note that we have not defined buildPage method. As it is not necessary. If you define this method then it will override the pageBuilder in the super constructor. As there is no customization needed for pages, the method buildPage won't do anything extra. It is rarely defined.

@override
buildPage(BuildConext context, Animation<double> animation, Animation<double> secondaryAnimation){
  return child;
}

Just like in the constructor you can also define the animation within the pageBuild method. Like the following:

import 'package:flutter/material.dart';

class BeautyTransition extends PageRouteBuilder{
  Widget child;

  BeautyTransition(this.child) : super(pageBuilder: (context, animation, secondaryAnimation)=> child);

  @override
  buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation){
    Animation<double> scaleAnimation = Tween<double>(begin: 0.6, end: 1.0).animate(CurvedAnimation(
        parent: animation,
        curve: Interval(0.0, 0.8, curve: Curves.easeIn),
      )
    );
    Animation<double> opacityAnimation = Tween<double>(begin: 0.0, end: 1).animate(
      CurvedAnimation(
        parent: animation,
        curve: Interval(0.0, 1.0, curve: Curves.easeIn),
      ),
    );

    return ScaleTransition(
      scale: scaleAnimation,
      child: FadeTransition(
        opacity: opacityAnimation,
        child: child
      )
    );
  }
}

As you can see you can use either of the method to define the transition. But the initialization of parent constructor is must.

Now you can use the custom class in Navigator call like following:

Navigator.push(context, BeautyTransition(NewPage()));