在 Flutter 里實現(xiàn)頁面的導航需要使用兩個類:Navigator和 Route。Navigator 負責頁面的棧結構處理,Route 復雜頁面的路徑處理。
在 Flutter 里導航器使用棧的結構管理頁面,當需要添加一個頁面時,使用入棧的方式。當需要退出一個頁面時,使用出棧的方式。也可以不入棧而是直接替換當前頁面。
路由表
移動應用程序通常管理大量的路由,通過名稱來引用它們通常是最容易的。
簡單的做法是在 runApp 里,就定義好所有的路由,這樣可以做集中式管理,也是非常建議的做法。
一個 MaterialApp 是最簡單的設置方式,MaterialApp 的 home 成為導航器堆棧底部的路由。要在堆棧上推送(push)一個新路由,可以創(chuàng)建一個具有構建器功能的MaterialPageRoute 實例。
void main() {
runApp(new MaterialApp(
home: new MyAppHome(),
// 路由
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => new MyPage(title: 'page A'),
'/b': (BuildContext context) => new MyPage(title: 'page B'),
'/c': (BuildContext context) => new MyPage(title: 'page C'),
},
));
}
當需要路由切換的時候,可以這樣處理。
Navigator.pushNamed(context, '/b');
傳遞數(shù)據(jù)
很多時候在切換路由時需要傳遞一些數(shù)據(jù),比如 id 之類的,那么應該怎么做?
在使用動態(tài)路由時,可以通過閉包環(huán)境來傳遞數(shù)據(jù)。
String id = 'abc'; // <--- id
Navigator.push(context, new MaterialPageRoute<void> (
// 新的頁面
builder: (BuildContext context) {
return new Scaffold(
body: new Center(
child: new FlatButton(
child: new Text('POP'),
onPressed: () {
print(this.id); // <--- id
Navigator.pop(context);
},
),
),
);
},
));
在使用路由表的時候,只能提供全局變量來傳遞信息。
var id = 'abc';
void main() {
runApp(new MaterialApp(
home: new MyAppHome(),
// 路由
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => new MyPage(id: id),
},
));
}
入棧一個頁面
當需要入棧一個頁面時,使用 Navigator.push
函數(shù),這個函數(shù)接受一個上下文和新的頁面結構。第二個參數(shù)是一個路由器 MaterialPageRoute
。
// 在某個點擊事件里
Navigator.push(context, new MaterialPageRoute<void> (
// 新的頁面
builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('My Page')),
body: new Center(
child: new FlatButton(
child: new Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));
出棧一個頁面
當需要出棧一個頁面時,使用 Navigator.pop
函數(shù),這個函數(shù)接受一個上下文。
new FlatButton(
child: new Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
接受返回值
實際上 pushNamed
是一個異步函數(shù),它返回一個 Future
,在使用 Navigator.pop
的時候,第二個參數(shù)就是返回的值。
Navigator.pop(context, 'abc');
Navigator.pushNamed(context, '/router/second').then((value) {
print(value); // --> abc
});
導航切換動畫
默認的導航切換動畫,在 Android 上是一個從底端到頂端的彈出過程,在 iOS 上是一個從右邊到左邊的平移過程。
為了統(tǒng)一,可以通過自定義一個路由,包括路由模態(tài)屏障的顏色和行為以及路由其他方面的動畫轉換。
// 動畫效果,要把它放在外面去,并且是 static 的。
static SlideTransition createTransition(Animation<double> animation, Widget child) {
return new SlideTransition(
// 從右到左
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: child,
);
}
Navigator.of(context).push(
new PageRouteBuilder(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// 目標頁
return new PageView();
},
transitionsBuilder: ( // --> 固定寫法
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) => HomePageState.SlideTransition(animation, child),
));
實際上,默認在 Android 上的效果是:
// 從下到上
position: new Tween<Offset>(
begin: const Offset(0.0, 1.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
這里只是把 x 與 y 對換就變成了平移。
得出幾個不同的方向動畫。
// 從上到下
position: new Tween<Offset>(
begin: const Offset(0.0, -1.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
// 從左到右
position: new Tween<Offset>(
begin: const Offset(-1.0, 0.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
神奇的是 begin 可以使用對角移動的方式,也就是 (1.0, 1.0)、(-1.0, -1.0)
。
除了平移之外,還可以定義一些旋轉的動畫。
static FadeTransition createFadeTransition(Animation<double> animation, Widget child) {
return new FadeTransition(
opacity: animation,
child: new RotationTransition(
turns: new Tween<double>(
begin: 0.8,
end: 1.0
).animate(animation),
child: child,
),
);
}
當 begin 與 end 都為 1.0 時,F(xiàn)adeTransition 是一個透明漸變過程。