前言
在 Flutter
中使用 Navigator.of(context).push(Route route);
方法進行路由跳轉時就需要傳一個 Route
物件,通常使用 MaterialPageRoute(builder: () {});
建立,使用時會在路由跳轉過程中新增預設的過渡動畫。當需要自定義路由過渡動畫時,就要使用 PageRouteBuilder
,它是 Flutter
提供的用來建立自定義的路由的一個類,例項化這個類會得到一個路由物件 Route
,要做的就是建立一個自定義的 Route
。
PageRouteBuilder
使用 PageRouteBuilder
建立自定義路由過渡動畫時需要傳入兩個回撥函式作為引數,一個必要引數 pageBuilder
,這個函式用來建立跳轉的頁面,另一個函式 transitionsBuilder
,這個函式就是實現過渡動畫的地方。
transitionsBuilder
的child
引數是pageBuilder
函式返回的一個transitionsBuilder widget
部件,pageBuilder
方法僅會在第一次構建路由的時候被呼叫,Flutter
能夠自動避免做額外的工作,整個過渡期間child
儲存了同一個例項。
PageRouteBuilder(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return widget;
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
},
);
複製程式碼
建立自定義路由需要繼承 PageRouteBuilder
,然後實現自定義路由的建構函式。
// 定義
class YourRoute extends PageRouteBuilder {
final Widget page;
YourRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return child;
},
);
}
// 使用
Navigator.of(context).push(YourRoute(NewPage()));
複製程式碼
示例
使用 FirstPage
和 SecondPage
兩個頁面展示效果
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
elevation: 0.0,
backgroundColor: Colors.purple,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
child: Text('Next Page'),
),
)
],
),
backgroundColor: Colors.purple,
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
elevation: 0.0,
backgroundColor: Colors.deepPurpleAccent,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
)
],
),
backgroundColor: Colors.deepPurpleAccent,
);
}
}
複製程式碼
FadeTransition
class FadeRoute extends PageRouteBuilder {
final Widget page;
FadeRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}
複製程式碼
ScaleTransition
class ScaleRoute extends PageRouteBuilder {
final Widget page;
ScaleRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return ScaleTransition(
alignment: Alignment.bottomLeft,
scale: Tween(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
),
),
child: child,
);
},
transitionDuration: Duration(seconds: 1),
);
}
Navigator.of(context).push(ScaleRoute(SecondPage()));
複製程式碼
RotationTransition
class RotationRoute extends PageRouteBuilder {
final Widget page;
RotationRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
Animation myAnimation = CurvedAnimation(
parent: animation,
curve: Curves.easeInBack,
);
return RotationTransition(
turns: myAnimation,
child: child,
);
},
transitionDuration: Duration(seconds: 1),
);
}
Navigator.of(context).push(RotationRoute(SecondPage()));
複製程式碼
ScaleRotationRoute
結合兩個過渡動畫
class ScaleRotationRoute extends PageRouteBuilder {
final Widget page;
ScaleRotationRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return ScaleTransition(
scale: animation,
child: RotationTransition(
turns: Tween(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(parent: animation, curve: Curves.linear),
),
child: child,
),
);
},
transitionDuration: Duration(milliseconds: 800),
);
}
Navigator.of(context).push(ScaleRotationRoute(SecondPage()));
複製程式碼
TransformRoute
使用 Transform
部件創造 3D
效果
import 'dart:math' show pi;
class TransformRoute extends PageRouteBuilder {
final Widget page;
TransformRoute(this.page)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return Transform(
transform: Matrix4.identity()
// 類似於 CSS 裡面 `perspective` 屬性,確定 z=0 平面與使用者之間的距離
..setEntry(3, 2, 0.0001)
..rotateX(animation.value * pi * 2)
..rotateY(animation.value * pi * 2),
alignment: FractionalOffset.center,
child: child,
);
},
transitionDuration: Duration(seconds: 2),
);
}
Navigator.of(context).push(TransformRoute(SecondPage()));
複製程式碼
EnterExitRoute
同時為進入頁面和退出頁面新增動畫
class EnterExitRoute extends PageRouteBuilder {
final Widget enterPage;
final Widget exitPage;
EnterExitRoute(this.enterPage, this.exitPage)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return exitPage;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) =>
Stack(
children: [
SlideTransition(
position: Tween<Offset>(
begin: Offset(0.0, 0.0),
end: Offset(-1.0, 0.0),
).animate(
CurvedAnimation(parent: animation, curve: Curves.easeIn),
),
child: enterPage,
),
SlideTransition(
position: Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset.zero,
).animate(
CurvedAnimation(parent: animation, curve: Curves.easeInOut),
),
child: exitPage,
)
],
),
);
}
Navigator.of(context).push(
EnterExitRoute(FirstPage(), SecondPage()),
);
複製程式碼
使用 Navigator.pushNamed
方法跳轉
在 onGenerateRoute
對跳轉路由的 name
進行判斷,對特定的路由新增過渡動畫。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage(),
onGenerateRoute: (settings) {
switch (settings.name) {
case '/second':
return ScaleRoute(SecondPage());
break;
default:
return null;
}
},
);
}
}
Navigator.pushNamed(context, '/second', arguments: {});
複製程式碼
設定全域性的路由過渡動畫
Flutter
的預設路由過渡動畫是由 buildTransitions
方法建立的,它使用的是 Theme.of(context).pageTransitionsTheme
方法,因此可以定義全域性的路由跳轉過渡動畫。
@override
Widget buildTransitions(context, animation, secondaryAnimation, child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
}
複製程式碼
首先自定義一個 TransitionBuilder
, buildTransitions
方法返回跳轉頁面。然後配置 theme
的 pageTransitionsTheme
,設定對應的平臺,最後在使用 MaterialPageRoute
或者 CupertinoPageRoute
進行頁面跳轉時就會有自定義的過渡動畫了。
class ScaleTransitionBuilder extends PageTransitionsBuilder {
@override
Widget buildTransitions<T>(
route,
context,
animation,
secondaryAnimation,
child,
) {
return ScaleTransition(
scale: CurvedAnimation(parent: animation, curve: Curves.easeIn),
child: child,
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage(),
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: ScaleTransitionBuilder(),
TargetPlatform.iOS: ScaleTransitionBuilder(),
},
),
),
);
}
}
Navigator.push(context, MaterialPageRoute(builder: (ctx) => SecondPage()));
複製程式碼
將動畫封裝成一個庫
將自定義的路由過渡動畫封裝起來方便使用。
enum TransitionType {
fade,
scale,
rotate,
transform,
}
class PageTransition extends PageRouteBuilder {
PageTransition(TransitionType type, Widget page, Duration time)
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) {
return page;
},
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
switch (type) {
case TransitionType.fade:
return FadeTransition(opacity: animation, child: child);
break;
case TransitionType.scale:
return ScaleTransition(
scale: Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: animation, curve: Curves.easeInOut),
),
child: child,
);
break;
case TransitionType.rotate:
return RotationTransition(
turns: CurvedAnimation(
parent: animation,
curve: Curves.easeInBack,
),
child: child,
);
break;
case TransitionType.transform:
return Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0001)
..rotateX(animation.value * pi * 2)
..rotateY(animation.value * pi * 2),
alignment: FractionalOffset.center,
child: child,
);
break;
default:
return child;
};
},
transitionDuration: time,
);
}
// 使用
Navigator.push(
context,
PageTransition(
TransitionType.rotate,
SecondPage(),
Duration(milliseconds: 800),
),
}
複製程式碼
參考文章
Everything you need to know about Flutter page route transition