This article is launched on the wechat public account “AndroidTraveler”.
background
We know that if you switch directly between pages, it will be stiff, but also make users feel very abrupt, user experience is not very good.
Therefore, it is common to add animations to smooth transitions between pages.
Also, sometimes we don’t like the system’s default animation and want to be able to customize it.
Based on this, this article describes how to add custom animations to Flutter page transitions.
The default effect
First of all, what does the default look like?
It looks good. The code is as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return _getCenterWidget(RaisedButton(
child: Text('Go to next page->'),
onPressed: () {
Navigator.of(context).push(_createRoute());
}));
}
}
Route _createRoute() {
return MaterialPageRoute(builder: (BuildContext context) => Page2());
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _getCenterWidget(Text('Page2'));
}
}
Widget _getCenterWidget(Widget child) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: child,
),
);
}
Copy the code
You can see that two pages MyApp and Page2 have been created.
The first page, MyApp, has a button, and the second page, Page2, has text.
The key switch is in the _createRoute() route creation method.
If we click on the Source of the MaterialPageRoute, we can see it
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
}
Copy the code
With the initial comment, you can see that this is the default interface transition.
/// See also:
///
/// * [PageTransitionsTheme], which definesthe default page transitions used
/// by [MaterialPageRoute.buildTransitions].
Copy the code
Also, you can see that the default animation length is 300ms, and we can’t customize it.
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
Copy the code
Next we’ll talk about how to customize our interface transitions and how long we can customize the animation.
Custom animation
1. Set the PageRouteBuilder
From the above analysis we know that the key is in creating the routing method _createRoute().
So let’s change it first. Instead of using the default MaterialPageRoute, let’s use PageRouteBuilder.
Route _createRoute() {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
returnchild; }); }Copy the code
You can see that we specify the routing page through pageBuilder and the page transition effect through transitionsBuilder.
In addition, the parameters here we do not need to memorize, we click into the source code to see, as follows:
/// Signature for the function that builds a route's primary contents.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildPage] for complete definition of the parameters.
typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
/// Signature for the function that builds a route's transitions.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);
Copy the code
If we run the code, there should be no animation since the child is returned directly. When we run it, it looks like this:
2. Add Tween and SlideTransition
The default transition is from the right to the left, so our custom demo transitions from the bottom to the top.
It’s important to understand that Tween is a linear interpolator between the start and end values.
In addition, we can see that the first animation parameter of transitionsBuilder is 0.0 to 1.0.
We are working from the bottom up, so the Y-axis offset is 1.0 to 0.0, representing the vertical direction from the top of the entire page to no offset (already at the top).
Therefore, about Tween and animation, we can get:
var begin = Offset(0.0.1.0);
var end = Offset(0.0.0.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);
Copy the code
Since we want to slide, we can process the offset animation through SlideTransition and return it with the modified routing code as follows:
Route _createRoute() {
return PageRouteBuilder(
transitionDuration: Duration(seconds: 5),
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
var begin = Offset(0.0.1.0);
var end = Offset(0.0.0.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);
returnSlideTransition( position: offsetAnimation, child: child, ); }); }Copy the code
The effect is as follows:
See the above effect, there may be a small partner will have a question.
Question 1: I can understand that you open the page from bottom to top, but why return the page from top to bottom in reverse?
We follow the source code of transitionsBuilder, you can see
/// Used to build the route's transitions.
///
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
final RouteTransitionsBuilder transitionsBuilder;
Copy the code
Following through (by clicking on) the buildTransitions comment, we can see that the animation is described as follows:
/// * [animation]: Whenthe [Navigator] pushes a route on the top of its stack,
/// theNew route's primary [animation] runs from 0.0 to 1.0.whenthe [Navigator]
/// popstheTopmost Route This animation runs from 1.0 to 0.0.
Copy the code
You can see that the loading and unloading animations are opposite, and this is consistent with our understanding.
Question 2: Now the effect is from bottom to top. If I want to achieve top to bottom, do I just switch the Offset of begin and end?
In fact, if you understand what I said above
We are working from the bottom up, so the Y-axis offset is 1.0 to 0.0, representing the vertical direction from the top of the entire page to no offset (already at the top).
You’ll see what happens if you go from 0.0 to 1.0.
So let’s change it and show it in a real demo.
The modified code is as follows:
Route _createRoute() {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
var begin = Offset(0.0.0.0);
var end = Offset(0.0.1.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);
returnSlideTransition( position: offsetAnimation, child: child, ); }); }Copy the code
Only the Offset of begin and end is swapped.
The running effect is as follows:
Although you can see some clues, as we mentioned earlier, the default animation time is 300 ms, so it is relatively fast, which is not easy to analyze.
We can set the duration of the animation using the transitionDuration property of PageRouteBuilder.
Let’s set 3s to see the effect. The code is as follows:
Route _createRoute() {
return PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
var begin = Offset(0.0.0.0);
var end = Offset(0.0.1.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);
returnSlideTransition( position: offsetAnimation, child: child, ); }); }Copy the code
The running effect is as follows:
You see, the reverse is true, starting at 0.0 from the top to 1.0 (100%) from the top.
So what if I want to go from top to bottom?
Let’s give you a picture. I’m sure you’ll understand.
We know from this graph that if we go from the bottom up, y should go from 1.0 to 0.0. If you want to go from the top down, y should go from -1.0 to 0.0.
So we modify the code as follows:
Route _createRoute() {
return PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
var begin = Offset(0.0.1.0);
var end = Offset(0.0.0.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);
returnSlideTransition( position: offsetAnimation, child: child, ); }); }Copy the code
The running effect is as follows:
3. CurveTween to accelerate
When we set the animation duration to 3s, we can obviously see that our animation speed seems to be uniform.
So if I want to modify the speed of animation, such as fast in, slow end, can it?
The answer is yes.
We can do this by CurveTween.
The key to use lies in the choice of curve, so which curve should we choose?
One of the things I like about Flutter is that the code is well commented and has a demo.
Let’s go to the Curves source code, using Curves. Ease as an example:
/// A cubic animation curve that speeds up quickly and ends slowly.
///
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
static const Cubic ease = Cubic(0.25.0.1.0.25.1.0);
Copy the code
The notes say it starts fast and ends slow, and there’s a link to mp4. Click on it and you’ll see something like this:
We can see how it’s going, by looking at the slope, it’s fast in the early stages, it’s slow in the late stages, and there’s a preview of the four animations on the right.
We set the CurveTween code as follows:
var curveTween = CurveTween(curve: Curves.ease);
Copy the code
As you can see, it’s easy to pick a trend you want.
4. Mix Tween and CurveTween
This is also relatively simple. Tween’s own chain method can be used as follows:
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: Curves.ease));
Copy the code
Offset(0.0, 0.0) can be written as offset.zero.
The modified code is:
Route _createRoute() {
return PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder:(context, animation, secondaryAnimation, child) {
var begin = Offset(0.0.1.0);
var end = Offset.zero;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: Curves.ease));
returnSlideTransition( position: animation.drive(tween), child: child, ); }); }Copy the code
The running effect is as follows:
5. Complete example
With that in mind, we can add animations from all four directions without delay. In addition, for the convenience of demonstration, we will directly open the delay 1s return.
The code is as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return _getCenterWidget(Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_getBtn(context, 'right in',
Tween(begin: Offset(1.0.0.0), end: Offset.zero)),
_getBtn(context, 'left in',
Tween(begin: Offset(1.0.0.0), end: Offset.zero)),
_getBtn(context, 'bottom in',
Tween(begin: Offset(0.0.1.0), end: Offset.zero)),
_getBtn(context, 'top in',
Tween(begin: Offset(0.0.1.0), end: Offset.zero)),
],
));
}
}
Widget _getBtn(BuildContext context, String textContent, Tween<Offset> tween) {
return RaisedButton(
child: Text(textContent),
onPressed: () {
Navigator.of(context).push(_createRoute(tween));
});
}
Route _createRoute(Tween<Offset> tween) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => Page2(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position:
animation.drive(tween.chain(CurveTween(curve: Curves.ease))),
child: child,
);
});
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
Future.delayed(Duration(seconds: 1), () {
Navigator.of(context).pop();
});
return _getCenterWidget(Text('Page2'));
}
}
Widget _getCenterWidget(Widget child) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: child,
),
);
}
Copy the code
The effect is as follows:
conclusion
At this point, the transition between the Flutter interfaces is basically clear.
Other things like rotation, zooming, transparency, and even composite animation, you can do your own DIY with the basics above.
Here attach the zoom effect as follows:
See GitHub: flutter_page_Transition for details
Animate a Page Route Transition Tween class
Read more about Flutter: Learn to Play with Flutter series blog Flutter & Dart