为页面切换加入动画效果
在不同路由(或界面)之间进行切换的时候,许多设计语言,例如 Material 设计,都定义了一些标准行为。但有时自定义路由会让 app 看上去更加的独特。为了更好的完成这一点,PageRouteBuilder
提供了一个 Animation
对象。这个 Animation
能够通过结合
Tween
以及 Curve
对象来自定义路由转换动画。这篇指南将会展示如何在两个路由之间切换时使用从屏幕底部动画出来的路由。
A design language, such as Material, defines standard behaviors when
transitioning between routes (or screens). Sometimes, though, a custom
transition between screens can make an app more unique. To help,
PageRouteBuilder
provides an Animation
object.
This Animation
can be used with Tween
and
Curve
objects to customize the transition animation.
This recipe shows how to transition between
routes by animating the new route into view from
the bottom of the screen.
要创建这个自定义路由动画,这篇指南使用了以下步骤:
To create a custom page route transition, this recipe uses the following steps:
-
搭建一个 PageRouteBuilder
Set up a PageRouteBuilder
-
创建一个
Tween
Create a
Tween
-
添加一个
AnimatedWidget
Add an
AnimatedWidget
-
使用
CurveTween
Use a
CurveTween
-
组合这两个
Tween
Combine the two
Tween
s
1. 搭建一个 PageRouteBuilder
1. Set up a PageRouteBuilder
我们从使用一个 PageRouteBuilder
来创建一个 Route
。
PageRouteBuilder
有两个回调,第一个是创建这个路由的内容(pageBuilder
),另一个则是创建一个路由的转换器(transitionsBuilder
)。
To start, use a PageRouteBuilder
to create a Route
.
PageRouteBuilder
has two callbacks, one to build the content of the route
(pageBuilder
), and one to build the route’s transition (transitionsBuilder
).
下面的样例将会创建两个路由:一个主页路由,包含了 “Go!” 按钮,还有第二个路由,包含了一个显示 “Page 2 的标题。
The following example creates two routes: a home route with a “Go!” button, and a second route titled “Page 2”.
import 'package:flutter/material.dart'; void main() { runApp( const MaterialApp( home: Page1(), ), ); } class Page1 extends StatelessWidget { const Page1({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: ElevatedButton( onPressed: () { Navigator.of(context).push(_createRoute()); }, child: const Text('Go!'), ), ), ); } } Route _createRoute() { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const Page2(), transitionsBuilder: (context, animation, secondaryAnimation, child) { return child; }, ); } class Page2 extends StatelessWidget { const Page2({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const Center( child: Text('Page 2'), ), ); } }
2. 创建一个 Tween
2. Create a Tween
为了使新页面从底部动画出来,它应该从 Offset(0,1)
到 Offset(0, 0)
进行动画。(通常我们会使用 Offset.zero
构造器。)在这个情况下,对于 ‘FractionalTranslation’ widget 来说偏移量是一个 2D 矢量值。将 dy
参数设为 1,这代表在竖直方向上切换整个页面的高度。
To make the new page animate in from the bottom, it should animate from
Offset(0,1)
to Offset(0, 0)
(usually defined using the Offset.zero
constructor). In this case, the Offset is a 2D vector for the
‘FractionalTranslation’ widget.
Setting the dy
argument to 1 represents a vertical translation one
full height of the page.
transitionsBuilder
的回调有一个 animation
参数。它其实是一个 Animation<double>
,提供 0 到 1 的值。使用 Tween 来将 Animation
The transitionsBuilder
callback has an animation
parameter. It’s an
Animation<double>
that produces values between 0 and 1. Convert the
Animation
transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnimation = animation.drive(tween); return child; },
3. 使用 AnimatedWidget
3. Use an AnimatedWidget
Flutter 有一堆继承自 AnimatedWidget
的 widget,它们能够在动画的值发生改变时自动重建自己。举个例子,SlideTransition 拿到一个 Animation<Offset>
并在动画改变时使用 FractionalTranslation widget 转换其子级。
Flutter has a set of widgets extending AnimatedWidget
that rebuild themselves when the value of the animation changes. For instance,
SlideTransition takes an Animation<Offset>
and translates its child (using a
FractionalTranslation widget) whenever the value of the animation changes.
AnimatedWidget 返回了一个带有 Animation<Offset>
的 SlideTransition
,以及 child widget:
AnimatedWidget Return a SlideTransition
with the Animation<Offset>
and the child widget:
transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnimation = animation.drive(tween); return SlideTransition( position: offsetAnimation, child: child, ); },
4. 使用 CurveTween
4. Use a CurveTween
Flutter 提供了一系列缓和曲线,可以调整一段时间内的动画速率。
Curves
类提供了一个提前定义的用法相似的 curves。例如,Curves.easeOut
将会让动画开始很快结束很慢。
Flutter provides a selection of easing curves that
adjust the rate of the animation over time.
The Curves
class
provides a predefined set of commonly used curves.
For example, Curves.easeOut
makes the animation start quickly and end slowly.
要使用 Curve,创建一个 CurveTween
并传一个 Curve:
To use a Curve, create a new CurveTween
and pass it a Curve:
var curve = Curves.ease; var curveTween = CurveTween(curve: curve);
新的 Tween 依然提供 0 到 1 之间的值。在下一步中,它将会结合第二步中提到的 Tween<Offset>
。
This new Tween still produces values from 0 to 1. In the next step, it will be
combined the Tween<Offset>
from step 2.
5. 结合两个 Tween
5. Combine the two Tweens
为了结合两个 tween,请使用 chain()
:
To combine the tweens,
use chain()
:
const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
它们通过把这个 tween 传递给 animation.drive()
来创建一个新的 Animation<Offset>
,然后你就能把它传给 SlideTransition
widget:
Then use this tween by passing it to animation.drive()
. This creates a new
Animation<Offset>
that can be given to the SlideTransition
widget:
return SlideTransition( position: animation.drive(tween), child: child, );
这个新的 Tween(或者是能够动画的东西)通过评估 CurveTween
来提供 Offset
,然后评估 Tween<Offset>
。当动画运行时,值都被这条命令计算出:
This new Tween (or Animatable) produces Offset
values by first evaluating the
CurveTween
, then evaluating the Tween<Offset>.
When the animation runs, the
values are computed in this order:
-
这个动画提供了从 0 到 1 的值。(通过 transitionsBuilder 的值提供)
The animation (provided to the transitionsBuilder callback) produces values from 0 to 1.
-
这个 CurveTween 根据其将这些值映射到介于 0 和 1 之间的新曲线值。
The CurveTween maps those values to new values between 0 and 1 based on its curve.
-
Tween<Offset>
将double
值映射为Offset
值。The
Tween<Offset>
maps thedouble
values toOffset
values.
使用缓动曲线创建 Animation<Offset>
的另一种方法是使用 CurvedAnimation
:
Another way to create an Animation<Offset>
with an easing curve is to use a
CurvedAnimation
:
transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; final tween = Tween(begin: begin, end: end); final curvedAnimation = CurvedAnimation( parent: animation, curve: curve, ); return SlideTransition( position: tween.animate(curvedAnimation), child: child, ); }
交互式样例
Interactive example
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: Page1(),
),
);
}
class Page1 extends StatelessWidget {
const Page1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(_createRoute());
},
child: const Text('Go!'),
),
),
);
}
}
Route _createRoute() {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const Page2(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: const Center(
child: Text('Page 2'),
),
);
}
}