导航与路由
- 导航就是从A页面跳转到B页面
- 路由是导航的一种方式,通过配置的路由名称进行导航 下面示例中配置了页面的跳转动画和路由map
class NavigatorApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
pageTransitionsTheme: PageTransitionsTheme(//配置转场动画
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
},
),
),
routes: <String, WidgetBuilder>{//配置路由名称
'app_main': (context) => HomePageState(),
'learn_start': (context) => LearnWidget(),
'less_group': (context) => LessGroupPage(),
'page1': (context) => Page1App(),
},
home: NavigatorHomePage(),
);
}
}
复制代码
需要注意的是routes界面配置的路由名称是app全局的,别的地方如果配置了相同的路由名称会被覆盖,我们一般会单独建一个文件保存这些路径
app_route.dart
const pageA = "app_main";
const pageB = "learn_start";
const pageC = "less_group";
const pageD = "page1";
var RoutePath = {
"$pageA": (context) => HomePageState(),
"$pageB": (context) => LearnWidget(),
"$pageC": (context) => LessGroupPage(),
"$pageD": (context) => Page1App(),
};
复制代码
路由的几种方式
通过名称导航(该名称实际上就是路由了)
方式一:
Navigator.of(context).pushNamed(routeName);
方式二:
Navigator.pushNamed(context, routeName);
复制代码
这时候我们用如下方式
Navigator.of(context).pushNamed(pageA)
复制代码
pageA是我们配置好的路由名称就能跳转了
通过页面(widget)导航
//方式一:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomePageState()));
//方式二:
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePageState()));
复制代码
关闭页面
Navigator.pop(context);
复制代码
如果你关闭页面的时候出现了黑屏,应该是当前页面和被打开页面配置了多个MaterialApp
一般一个App配置一个MaterialApp即可,剩下的页面可以单独使用Scaffold
Flutter中监听用户手势
GestureDetector
GestureDetector:官方定义为一个检测用户手势widget
_buildColumn(){
return Column(
children: [
GestureDetector(
onTap: () => print("onTap"),
onDoubleTap: () => print("onDoubleTap"),
onLongPress: () => print("onLongPress"),
onTapCancel: () => print("onTapCancel"),
onTapUp: (e) => print("onTapUp"),
onTapDown: (e) => print("onTapDown"),
onPanUpdate: (e) => print("onPanUpdate"),
child: Container(
padding: EdgeInsets.all(60),
decoration: BoxDecoration(color: Colors.green),
child: Text('点我',
style: TextStyle(color: Colors.white, fontSize: 30)),
),
),
],
);
}
复制代码
点击 onTap
I/flutter ( 2161): onTapDown
I/flutter ( 2161): onTapUp
I/flutter ( 2161): onTap
复制代码
双击 onDoubleTap
I/flutter ( 2161): onDoubleTap
复制代码
长按 onLongPress
I/flutter ( 2161): onTapDown
I/flutter ( 2161): onTapCancel
I/flutter ( 2161): onLongPress
复制代码
滑动 onPanUpdate
I/flutter ( 2161): onTapDown
I/flutter ( 2161): onTapCancel
I/flutter ( 2161): onPanUpdate
复制代码
案例:一个随着手指移动的小球
- 在onPanUpdate中监听用户手指滑动的距离并更新小球的布局
class GestureState extends State<GesturePage> {
var string = '';
double moveX = 0, moveY = 0;//定义小球初始位置
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("如何检测用户手势"),
),
body: FractionallySizedBox(
widthFactor: 1,
heightFactor: 1,
child: Stack(
children: [
_buildColumn(),
Positioned(
left: moveX,
top: moveY,
child: GestureDetector(
onPanUpdate: (e) => _doMove(e),//监听用户手指移动并更新小球位置
child: Container(//小球布局
width: 70,
height: 70,
decoration: BoxDecoration(
color: Colors.pinkAccent,
borderRadius: BorderRadius.circular(35)),
),
)),
],
),
),
);
}
_buildColumn();
_doMove(DragUpdateDetails e) {
setState(() {
moveX += e.delta.dx;//这个是每次手指移动的位置,不累加
moveY += e.delta.dy;
});
}
}
复制代码
e.delta.dx和e.delta.dy:是不累加的,是该次位置的距离 效果
Flutter中导入资源和使用资源
以图片资源为例,
1.新建images文件夹并将图片导入
2. 在pubspec.yaml配置图片路径
flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- images/
# - images/cat.jpg
# - images/tiger.jpg
# - images/strawberry.jpg
复制代码
可以整体导入,也可以单独文件导入 路径和格式千万不能配置出错,比如空格,缩进等....要不会有很多坑
使用资源文件
使用时注意是使用全路径+名称
class ResourceState extends State<ResourcePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter中引用资源'),
),
body: Container(
margin: EdgeInsets.all(20),
child:Row(
children: [
Image.asset('images/car.jpg',width: 100, height: 100,fit: BoxFit.cover),
Image.asset('images/cat.jpg',width: 100, height: 100,fit: BoxFit.cover),
Image.asset('images/tiger.jpg',width: 100, height: 100,fit: BoxFit.cover),
],
),
),
);
}
}
复制代码
Flutter 打开第三方应用
在我们的日常开发中需要打开浏览器,打电话或者别的指定app,我们需要用到官方提供的插件
url_launcher
复制代码
将该库引入项目,并导入其文件即可实现相关功能
使用
class LaunchState extends State<LaunchPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter打开第三方应用'),
),
body: Center(
child: Column(
children: [
RaisedButton(
onPressed: _openUrl,
child: Text('打开浏览器'),
),
RaisedButton(
onPressed: _openJingDong,
child: Text('打开京东'),
),
],
),
),
);
}
//打开浏览器
_openUrl() async{
const url="https://flutter.cn";
if(await canLaunch(url)){
await launch(url);
}else{
throw 'Could not launch $url';
}
}
//打开京东某个页面
_openJingDong() async{
const id="100016437630";
const url="openapp.jdmobile://virtual?params=%7B%22sourceValue%22:%220_productDetail_97%22,%22des%22:%22productDetail%22,%22skuId%22:%22$id%22,%22category%22:%22jump%22,%22sourceType%22:%22PCUBE_CHANNEL%22%7D";
if(await canLaunch(url)){
await launch(url);
}else{
throw 'Could not launch $url';
}
}
}
复制代码
点击按钮之后即可打开京东
Flutter页面生命周期
由于StatelessWidget 生命周期简单就两个生命周期,不在赘述
StatefullWidget根据阶段可以将其分为:
1. 初始化阶段
- createState():
当我们构建一个新的StatefulWidget时,这个会立即调用,并且这个方法必须被覆盖
- initState():
这是创建widget时调用的除构造方法外的第一个方法:类似于Android的:onCreate() 与iOS的 viewDidLoad(),在这个方法中通常会做一些初始化工作,比如channel的初始化,监听器的初始化等
2. 更新阶段
- didChangeDependencies():
当依赖的State对象改变时会调用,在第一次构建widget时,在initState()之后立即调用此方法;如果的StatefulWidgets依赖于InheritedWidget,那么当当前State所依赖InheritedWidget中的变量改变时会再次调用它
拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享,可参考:book.flutterchina.club/chapter7/in…
- build()
这是一个必须实现的方法,在这里实现你要呈现的页面内容,它会在在didChangeDependencies()之后立即调用,另外当调用setState后也会再次调用该方法
- didUpdateWidget()
这是一个不常用到的生命周期方法,当父组件需要重新绘制时才会调用;该方法会携带一个oldWidget参数,可以将其与当前的widget进行对比以便执行一些额外的逻辑
3. 销毁阶段
- deactivate()
很少使用,在组件被移除时调用在dispose之前调用
- dispose()
组件被销毁时调用,通常在该方法中执行一些资源的释放工作比如,监听器的卸载,channel的销毁等
Flutter App生命周期
- WidgetsBinding.instance.addObserver()通过该方法监听生命周期方法
- WidgetsBinding.instance.removeObserver()通过该方法,移除生命周期 flutter应用的生命周期,在各个平台上有所差异
enum AppLifecycleState {
resumed,//应用进去前台
inactive,//应用进入后台
paused,//不常用:应用程序处于非活动状态,并且未接收用户输入时调用,比如:来了个电话
detached,//不常用:应用程序被挂起是调用,它不会在iOS上触发
}
复制代码
示例
class AppLifeState extends State<AppLifeCirclePage> with WidgetsBindingObserver {
@override
void initState() {
WidgetsBinding.instance.addObserver(this);//注册监听
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App生命周期'),
),
body: Container(),
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('state = $state');
if(state==AppLifecycleState.resumed){
//应用进去前台
}else if(state==AppLifecycleState.paused){
//应用进入后台
}else if(state==AppLifecycleState.detached){
//不常用:应用程序被挂起是调用,它不会在iOS上触发
}else if(state==AppLifecycleState.inactive){
//不常用:应用程序处于非活动状态,并且未接收用户输入时调用,比如:来了个电话
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);//移除监听
super.dispose();
}
}
复制代码
Flutter主题和字体设置
修改flutter应用主题
通过给MaterialApp{}中brightness 动态修改其值即可
var _theme=Brightness.light;
MaterialApp{
theme: ThemeData(
brightness: _theme,
)
}
//修改
setState(() {
if(_theme==Brightness.light){
_theme=Brightness.dark;
}else{
_theme=Brightness.light;
}
});
复制代码
修改Flutter字体
-
将字体的ttf添加到项目中
-
将字体配置到项目中 打开pubspec.yaml,添加字体配置(注意缩进和格式)
pubspec.yaml文件
flutter:
fonts:
- family: StarFall//字体名称
fonts:
- asset: fonts/StarFall.ttf//字体资源文件
复制代码
- 使用字体
- 全局字体
- 某文字字体
Text(
'动态修改flutter主题',
style: TextStyle(fontFamily: 'StarFall'),
)
复制代码
使用字体中的icon
Icon(IconData(0x88ff8,fontFamily: "StarFall"))
复制代码
其中值:0x88ff8是字体库中自带的icon索引
学习flutter的常用项目
Flutter项目常用插件
-
图片缓存框架 cached_network_image
-
图片选择库 image_picker
-打开第三方应用