Navigating between routes is quite bland by default. Flutter graciously provides you with the MaterialPageRoute
and CupertinoPageRoute
classes and, while their transition animations don't look bad, there's certainly something more we can do.
Could we, for example, animate parts of the pushed page independently and even make the animation staggered? Of course we can! We're in Flutter, after all ?
The project we're going to build
By the end of this tutorial, you are going to know how to build a fully custom page transition consisting of translating two Container
s. You can then take this approach and apply it to all kinds of crazy effects - anything is possible with the Transform
widget.
Exploring the default transition
The starter project contains two pages. After pressing the floating action button in the FirstPage
, the following code is executed.
first_page.dart
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
We're then taken to the SecondPage
while seeing an uninspiring "full page" slide transition. This page contains two simple Container
s inside a Column
.
second_page.dart
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
),
),
Expanded(
child: Container(
color: Colors.green,
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).pop();
},
label: Text('Navigate Back'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
The FloatingActionButton
held inside a Scaffold
is animated for free as a Hero
widget. While that livens up the transition quite a bit, we want to go more extreme. Let's animate the Container
s.
Goodbye, MaterialPageRoute ?
Fully custom page transition is not possible with the MaterialPageRoute
because it hides a whole lot of objects from us. In order to get hold of the transition Animation
object, we need to use a PageRouteBuilder
.
As with any animation, even page transitions can be configured to have a certain duration. By default, it takes 300 milliseconds but we're going to set the duration to a full second. Lastly, we want to pass the animation
parameter of the pageBuilder
method to the SecondPage
.
first_page.dart
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return SecondPage(
transitionAnimation: animation,
);
},
transitionDuration: Duration(seconds: 1),
),
);
PageRouteBuilder
s inside the onGenerateRoute
method.Slide animation
Now we need to add the transitionAnimation
field into the SecondPage
class and use it!
The most versatile choice would be to use the Transform
widget, in this case, the simplified Transform.translate
constructor. With it, Skia is the limit ?
Since we're only going to slide the widgets, we may as well use the SlideTransition
because it's a bit simpler to use. The transitionAnimation
is passed by reference just like any other object in Dart. This means that it will be updated as usual and we can listen to these updates using an AnimatedBuilder
widget. Sprinkle in some Tween
and you have things moving on the screen!
second_page.dart
Column(
children: <Widget>[
// Expanded remains as the direct child of a Column
Expanded(
child: AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
// X, Y - Origin (0, 0) is in the upper left corner.
begin: Offset(1, 0),
end: Offset(0, 0),
).animate(transitionAnimation),
child: child,
);
},
child: Container(
color: Colors.red,
),
),
),
Expanded(
child: AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(-1, 0),
end: Offset(0, 0),
).animate(transitionAnimation),
child: child,
);
},
child: Container(
color: Colors.green,
),
),
),
],
),
Apart from the horrible code duplication (which we'll address in just a second), there's something else that doesn't play right. After running the app, we see that the animation is not staggered as in the video above.
Staggered Animation
Making the red container arrive sooner than the green one is very simple. When you think about it, both of the animations are linear, going from 0 to 1.
What we want to do is to change the curve so that the red animation will arrive at the value 1 first and only then start changing the value of the green animation.
This is achievable by using an Interval
curve in conjunction with a CurvedAnimation
which will be driven by our transitionAnimation
. While we're at it, we can also swap the unexciting linear nature of the animation for something more smooth, for example, Curves.easeOutCubic
. This is how the red widget will look like:
second_page.dart
Expanded(
child: AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(1, 0),
end: Offset(0, 0),
).animate(
CurvedAnimation(
curve: Interval(0, 0.5, curve: Curves.easeOutCubic),
parent: transitionAnimation,
),
),
child: child,
);
},
child: Container(
color: Colors.red,
),
),
),
Changes to the green widget will be almost identical, only the Interval
will go from 0.5 to 1.
second_page.dart
// Green widget
CurvedAnimation(
curve: Interval(0.5, 1, curve: Curves.easeOutCubic),
parent: transitionAnimation,
),
Removing duplication with a Provider
We have only two widgets and duplication makes the code hard to maintain even now. We must get rid of it and because we don't want to pass the transitionAnimation
through a bunch of constructors, we're going to use the provider package. Let's add it as a dependency.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
provider: ^4.1.2
Inside the FirstPage
, we now want to provide the animation to the SecondPage
. Because Animation
is a subclass of Listenable
, we have to use the ListenableProvider
in order not to get any errors although we would be fine with a regular Provider
in this case too.
first_page.dart
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return ListenableProvider(
create: (context) => animation,
child: SecondPage(),
);
},
transitionDuration: Duration(seconds: 1),
),
);
We can now extract the duplicated widget (all the way from AnimatedBuilder
) from the SecondPage
. We'll call it SlidingContainer
and make initialOffsetX
, intervalStart
, intervalEnd
, and color
configurable. The transition animation will not be passed through any constructor. Instead, it will be gotten directly inside the SlidingContainer
widget as highlighted below.
second_page.dart
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: SlidingContainer(
color: Colors.red,
initialOffsetX: 1,
intervalStart: 0,
intervalEnd: 0.5,
),
),
Expanded(
child: SlidingContainer(
color: Colors.green,
initialOffsetX: -1,
intervalStart: 0.5,
intervalEnd: 1,
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).pop();
},
label: Text('Navigate Back'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
class SlidingContainer extends StatelessWidget {
final double initialOffsetX;
final double intervalStart;
final double intervalEnd;
final Color color;
const SlidingContainer({
Key key,
this.initialOffsetX,
this.intervalStart,
this.intervalEnd,
this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final animation = Provider.of<Animation<double>>(context, listen: false);
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(initialOffsetX, 0),
end: Offset(0, 0),
).animate(
CurvedAnimation(
curve: Interval(
intervalStart,
intervalEnd,
curve: Curves.easeOutCubic,
),
parent: animation,
),
),
child: child,
);
},
child: Container(
color: color,
),
);
}
}
And there you have it! A fully custom staggered page transition animation in Flutter. Now that you know the principles, make sure to apply them in the most ingenious of ways!
Can we use it with auto route?
The factory’s skilled workforce employs advanced manufacturing techniques to produce fittings that meet international standards and specifications. Elitepipe Plastic Factory
obviously like your web-site but you need to test the spelling on quite a few of your posts. Several of them are rife with spelling problems and I to find it very troublesome to inform the reality on the other hand I’ll certainly come back again.
My brother recommended I might like this web site. He was totally right. This post actually made my day. You cann’t imagine just how much time I had spent for this information! Thanks!
I loved as much as you will receive carried out right here. The sketch is tasteful, your authored subject matter stylish. nonetheless, you command get got an edginess over that you wish be delivering the following. unwell unquestionably come further formerly again as exactly the same nearly very often inside case you shield this hike.
My brother suggested I might like this website. He was totally right. This post actually made my day. You cann’t imagine just how much time I had spent for this information! Thanks!
I do believe all the ideas you’ve presented for your post. They are really convincing and will certainly work. Nonetheless, the posts are too short for novices. May just you please lengthen them a little from subsequent time? Thanks for the post.
Usually I do not read article on blogs, however I would like to say that this write-up very compelled me to take a look at and do so! Your writing taste has been amazed me. Thanks, quite nice post.
I just could not leave your web site before suggesting that I really enjoyed the standard information a person supply to your visitors? Is gonna be again steadily in order to check up on new posts.
you are in reality a just right webmaster. The site loading velocity is incredible. It seems that you are doing any unique trick. In addition, The contents are masterwork. you have performed a wonderful task on this topic!
Thanks, I have just been looking for information about this subject for a long time and yours is the best I’ve discovered till now. However, what in regards to the bottom line? Are you certain in regards to the supply?
I was recommended this website by my cousin. I am not sure whether this post is written by him as nobody else know such detailed about my trouble. You are amazing! Thanks!
Hello, i think that i saw you visited my weblog so i came to ?eturn the favor텶’m trying to find things to improve my web site!I suppose its ok to use some of your ideas!!