4. 路由導航和包管理
代碼位址
這次學習筆記主要有一下幾個部分
- 路由導航
- 簡單路由
- 路由表導航
- 路由傳參
- 路由參數回流
- 包管理
- 本地元件引用
- 外部包引用
- 建構本地包并引用
1. 路由導航
導航實作頁面的切換,在Flutter中實作路由切換包括非具名路由和具名路由兩種,如果是非具名路由需要指定具體到的路由元件(通常是一個頁面),具名路由通常使用路由表建構的,建立了路由和頁面元件之間一一對應的關系,接下來是具體實作。
1. 簡單路由
簡單路由主要通過Navigator.push來實作
static Future**<T>** push<T extends Object**>(BuildContext context, Route<T>** route)
- 定義一個新頁面的元件
- 使用Navigator.push方法指定到特定的頁面
- 第二個參數通過MaterialPageRoute進行頁面導航
定義一個路由頁面元件
class Counter extends StatefulWidget {
Counter({Key key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Container(
child: Column(children: <Widget>[
Text('現在的數字是$_count'),
FlatButton(
onPressed: () {
setState(() {
++_count;
});
},
child: Text('按我++'),
color: Colors.red,
)
]),
);
}
}
其他頁面通過Navigator進行路由
class NextRoute extends StatelessWidget {
const NextRoute({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Next Route'),
),
body: Center(
child: Column(children: <Widget>[
Text('自定義的Route包'),
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Counter()));
},
child: Text('簡單路由'),
)
])),
);
}
}
2. 使用路由表
路由表定義方法:
- 定義路由頁面
- MaterialApp中的routes中以Map<String, Function>的形式定義路由表
- 頁面跳轉的時候使用pushNamed進行路由連結
static Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
}) {
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
直接使用pushNamed會調用Navigator.of(context).pushNamed,是以我們也可以直接使用Navigator.of來進行路由跳轉
代碼實作
路由表定義
使用MaterialApp的routes字段
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Message> list =
List.generate(20, (i) => Message('我是資訊$i, 讀完給我回個資訊', '廣告商人', '重要資訊$i'));
return MaterialApp(
title: 'Center Message',
home: MessageCenter(messageList: list),
routes: {
"home": (context) => MessageCenter(messageList: list),
"route1": (context) => NewRoute(),
"route2": (context) => NewRoute2(),
"route3": (context) => Route3.NextRoute(),
"route4": (context) => Route4.NextRoute(),
"route4/counter": (context) => Route4.Counter()
},
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(builder: (context) {
print(
'settings name = ${settings.name}, arguements=${settings.arguments}');
return NewRoute2();
});
},
);
}
}
利用路由表導航
點選按鈕後會自動路由到route1的頁面
// 省略上下文代碼
RaisedButton(
onPressed: () {
Navigator.pushNamed(context, 'route1');
},
child: Text('去路由頁面1'),
),
3. 路由傳參
非具名路由傳參,主要通過函數類傳參的方式,在調用時候傳參,然後将對應的參數直接拿來用就好了
// 定義元件
class WithParam extends StatelessWidget {
final String text;
const WithParam({Key key, @required this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('代參路由'),
),
body: Text('$text'),
),
);
}
}
傳參調用
// 在路由調用該類的時候傳參
RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WithParam(
text: '顯示Param',
);
}));
},
child: Text('顯示Param'),
)
路由表傳參
- 元件中通過ModalRoute.of(context).settings.arguments來獲得通過路由傳遞的參數
- 調用的時候通過Navigator.of(context).pushNamed(Widget(), arguments: param), 将參數加到路由上
定義元件
class NewRoute2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
dynamic args = ModalRoute.of(context).settings.arguments;
DateTime newTime;
Function getNewTime(DateTime time) {
return () {
newTime = time;
};
}
print(args);
return Scaffold(
appBar: AppBar(
title: Text('路由頁面2'),
),
body: Center(
child: Center(
child: Column(
children: <Widget>[
Text('使用者名: ${args['name']}'),
Text('生日: ${args['birth']}'),
Text('喜好: ${args['hobby'] ?? '沒有興趣'}'),
RaisedButton(
onPressed: () {
Navigator.pop(context, "這是pop的資訊1");
},
child: Text('Pop 資訊1'),
),
RaisedButton(
onPressed: () {
Navigator.pop(context, '這是pop的資訊2');
},
child: Text('Pop 資訊2'),
),
PickTime(
onChange: getNewTime,
)
],
)),
),
);
}
}
// 将參數傳遞繼承到路由表中
// 通過這種方法需要在NewRoute2中添加變量text
// 之後通過text來完成後面的邏輯
routes: {
"home": (context) => MessageCenter(messageList: list),
"route1": (context) => NewRoute(),
"route2": (context) => NewRoute2(text: ModalRoute.of(context).settings.arguments),
"route3": (context) => Route3.NextRoute(),
"route4": (context) => Route4.NextRoute(),
"route4/counter": (context) => Route4.Counter()
}
帶參的路由跳轉
通過pushNamed的arguments參數會将參數注冊到Context中,之後通過context中的setting即可完成後面的操作了
RaisedButton(
onPressed: () async {
Navigator.of(context)
.pushNamed('route2', arguments: userInfo)
.then((reply) {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('回傳内容'),
content: Text('$reply'),
);
});
});
},
child: Text('去路由頁面2'),
)
4. 路由參數回流
有些業務場景比如點選打開表單或者選擇選擇一些東西,之後需要将選擇的内容傳回第一個頁面中,這種場景的話,需要路由得到的參數能夠回流到之前的頁面的這種應用
非路由表路由傳參
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
push和pushNamed方法本身是一個Future,類似于Promise,通過類似async/await文法可以得到結果,也可以通過.then方法得到結果
- async和await回調函數的例子
_navigateWithParam(BuildContext context, Message message) async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MessageDetail(
message: message,
)));
print(result);
}
- 使用.then的例子
Navigator.of(context)
.pushNamed('route2', arguments: userInfo)
.then((reply) {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('回傳内容'),
content: Text('$reply'),
);
});
});
上面這段和下面這個實作效果相同
onPressed: () async {
var reply = await Navigator.of(context)
.pushNamed('route2', arguments: userInfo);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('回傳内容'),
content: Text('$reply'),
);
});
},
2. 包管理
1. 引用本地元件
想要在main.dart中調用nextRoute.dart的檔案
使用下面這兩個辦法都行
import './nextRoute.dart' as Route3;
import 'package:navigator/nextRoute.dart' show NextRoute;
- 通過as 改變該包的名稱,之後通過Route3.componentName來調用對應元件子產品
- 通過show來實作部分導入,這裡通過第二條導入語句隻導入NextRoute這一個元件
2. 調用外部包
dart的包管理主要通過pubspec.yaml檔案,通過配置該檔案之後通過 flutter package get會自動擷取類似依賴,使用辦法
- 換源,為了保證速度建議換源
export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
- 配置yaml檔案
- name為該包的名稱
- description是描述
- environment是運作環境
- version版本号
上面的主要對外部釋出的包比較重要,如果是項目中應用,我們隻需要設定dependecies即可,輸入對應依賴的版本号即可
國内查詢包的位址:flutter鏡像站
使用示例
- 找到flutter_dateTime_picker
- 編寫dependencies
- 儲存檔案後,會自動收集依賴,依賴收內建功後通過檔案即可導入
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
class PickTime extends StatefulWidget {
final Function onChange;
PickTime({Key key, @required this.onChange}) : super(key: key);
@override
PickTimeState createState() => PickTimeState(onChange: onChange);
}
class PickTimeState extends State<PickTime> {
final Function onChange;
DateTime dateTime;
PickTimeState({Key key, @required this.onChange});
@override
Widget build(BuildContext context) {
return Container(
child: FlatButton(
child: Text('${dateTime ?? '請選擇正确時間'}'),
onPressed: () {
DatePicker.showDatePicker(context,
showTitleActions: true,
minTime: DateTime(2018, 3, 5),
maxTime: DateTime(2019, 6, 7), onChanged: (date) {
print('change $date');
}, onConfirm: (date) {
print('confirm $date');
setState(() {
dateTime = date;
onChange(date);
});
}, currentTime: DateTime.now(), locale: LocaleType.zh);
},
),
);
}
}
3. 編寫一個自己的包
- 至少一個dart元件(編寫的元件)
- 有一個pubspec.yaml檔案(引用這個包所用到的庫)
dart和.yaml檔案是我們需要寫的,其他都會自動生成
引入自己包的辦法:使用path指向自己所在的相對位址