Flutter_进阶
Flutter 中的路由
Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过
Navigator组件管理路由导航.并提供了管理堆栈的方法。如:Navigator.push和Navigator.popFlutter中给我们提供了两种配置路由跳转的方式:
基本路由命名路由
普通路由
普通路由基本用法跳转
1 | Navigator.push(context, MaterialPageRoute(builder: (context) => MyShoppingPage(id: '54234324123423'))); |
返回上一页1 | Navigator.pop(context); |
路由传参传参: 跳转路由 返回的是 部件
构造函数,我们可以通过构造函数传参!
1 | ElevatedButton( |
有状态组件: 接参
1 | class MyShoppingPage extends StatelessWidget { |
无状态组件: 接惨1 | // 无状态组件 可以 通过 widget.id 来获取参数 |
注意:
statefulWidget 有状态组件中可以widget.id 方式来获取参数, statelessWidget 无状态组件中无法使用widget.id获取参数!main.dart
main.dart
1 | import 'package:flutter/material.dart'; |
route.dart
route.dart
1 | import 'package:flutter/material.dart'; |
shopping.dart
shopping.dart
1 | import 'package:flutter/material.dart'; |
命名路由
命名路由: 可以统一管理路由部件!,根据别名可进行路由跳转!
配置方式
注意:
MyShoppingPage这里只定义了 一个 路由部件,/searchPage这种的可自行定义对应路由部件searchPage;
配置方式
在MaterialApp() 下加入 routes 配置!
1 | routes: { |
1 | import 'package:flutter/material.dart'; |
路由跳转
路由跳转
Navigator.pushNamed通过此方法可以根据别名定义进行跳转!
1 | Navigator.pushNamed(context, '/shopping') |
1 | import 'package:flutter/material.dart'; |
命名路由传参
定义 Map Route 对象!命名路由传参
1
2
3
4
5Map routes = {
'/shopping':(context) => MyShoppingPage(), // 商品页
// arguments 是命名参数 命名参数通过 { ... } 获取
'/individualCenter':(context, { arguments }) => MyIndividualCenterPage( arguments: arguments ), // 个人中心页
};在
MaterialApp(onGenerateRoute: ( settings ){} )配置onGenerateRoute()方法配置 `onGenerateRoute`
onGenerateRoute注意:当路由发生跳转时,会触发!onGenerateRoute路由拦截器 中间件,可以做权限判断啥的!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15onGenerateRoute:(RouteSettings settings) {
// '/shopping':(context) => MyShoppingPage(),
final String? routeName = settings.name; // /shopping
final Function? routeFunction = routes[routeName]; // MyShoppingPage()
// 找到对应的路由部件进行跳转
if ( routeFunction != null ) {
// 判断跳转路由是否携带参数
if ( settings.arguments != null ) {
return MaterialPageRoute(builder: (context) => routeFunction( context, arguments: settings.arguments ));
}
// 返回路由对象
Route route = MaterialPageRoute(builder: (context) => routeFunction( context ));
return route;
}
},实现路由``传参跳转传参跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24return Center(
child: Column(
children: [
// 商品页 未带有参数
ElevatedButton(
onPressed: () => {
Navigator.pushNamed(context, '/shopping')
},
child: const Text('商品页'),
),
// 个人中心页 带有参数
ElevatedButton(
onPressed: () => {
Navigator.pushNamed(context, '/individualCenter', arguments: {
"source": "source routeName!",
"id": "592379217387128974"
})
},
child: const Text('个人中心页'),
),
],
)
);跳转目标路由``接参目标路由接参
这里以
个人中心页演示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 个人中心
import 'package:flutter/material.dart';
class MyIndividualCenterPage extends StatefulWidget {
// 接受命名路由传参
final Map? arguments;
const MyIndividualCenterPage({super.key, this.arguments});
State<MyIndividualCenterPage> createState() => _MyIndividualCenterPageState();
}
class _MyIndividualCenterPageState extends State<MyIndividualCenterPage> {
void initState() {
// TODO: implement initState
super.initState();
print('个人中心');
print(widget.arguments);
// arguments ? 不为空 的时候获取! arguments参数是可空参数!
print(widget.arguments?['id']);
print(widget.arguments?['source']);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('个人中心'),),
body: Center(
child: Text('个人中心', style: Theme.of(context).textTheme.headline5),
),
);
}
}
完整代码
main.dart 引入 shopping.dart 和 individualCenter.dart
main.dart
1 | import 'package:flutter/material.dart'; |
routeName.dart 这个界面有两个按钮: 商品页 个人中心页!
routeName.dart
传参
1 | Navigator.pushNamed(context, '/individualCenter', arguments: { |
1 | import 'package:flutter/material.dart'; |
shopping.dart
shopping.dart
1 | import 'package:flutter/material.dart'; |
individualCenter.dart
individualCenter.dart
1 | // 个人中心 |
routes [路由封装]
initialRoute: 主要功能主要是控制app启动的时候进入的是哪个页面。
1 | import 'package:flutter/material.dart'; |
创建
lib/routes/routes.dart引入shopping.dartindividualCenter.dart
1 | import 'package:flutter/material.dart'; |
返回上个路由
1 | Navigator.of(context).pop(); |
默认路由
在
MaterialApp( initialRoute : '/')下配置
1 | initialRoute: '/' |
路由替换
比如我们从
用户中心页面跳转到了registerFirst页面,然后从registerFirst页面通过pushReplacementNamed跳转到了registerSecond页面。这个时候当我们点击registerSecond的返回按钮的时候它会直接返回到用户中心。registerFirst将当前的路由 替换成registerSecond路由!
1 | Navigator.of(context).pushreplacementNamed('/registerSecond'); |
返回到跟路由
比如我们从
用户中心跳转到registerFirst页面,然后从registerFirst页面跳转到registerSecond页面,然后从registerSecond跳转到了registerThird页面。这个时候我们想的是registerThird注册成功后返回到用户中心。这个时候就用到了返回到根路由的方法。
Flutter 中的弹窗
在 Flutter中 弹窗是通过
showDialog()来定义的,该widget可以返回AlertDialogSimpleAlertDialogshowModalBottomSheet部件!
| 属性 | 描述 | 参数是否必须 |
|---|---|---|
context | 一般为当前路由的context | required |
barrierDismissible | 控制弹窗点击空白区域是否可以关闭弹窗,默认为true | no required |
child | 根据需求自定义的Widget | no required |
builder | 这个参数是一个返回widget 的回调函数,child 和 builder 只能使用一个 | required |
useRootNavigator | 默认为true,用于确定是否将弹窗推送到“context”最远的Navigator,true 为推送到最远,用于处理多个Navigator的情况,一般保持默认就好了 | no required |
routeSettings | 包含弹窗路由的配置信息,如路由的名称和传递的参数 | no required |
新建dialog.dart
dialog
1 | import 'package:flutter/material.dart'; |
main
1 | import 'package:flutter/material.dart'; |
AlertDialog确认框
普通弹窗
AlertDialog
1 | void alertDialogFn() async{ |
SimpleDialog信息选择框
可选弹窗
SimpleDialog
1 | void simpleDialogOptionFn() async{ |
ShowModalBottomSheet底部弹框
底部弹窗
ShowModalBottomSheet
1 | void showModelBottomSheetFn() async{ |
ShowToast浮动弹窗
Toast官方没有 相关widget需要从 fluttertoast 这个地址下载第三方插件!
安装
1 | flutter pub add fluttertoast |
引入1 | import 'package:fluttertoast/fluttertoast.dart'; |
toast
1 | Fluttertoast.showToast( |
自定义弹窗
新建
myDialog.dartmyDialog.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76import 'package:flutter/material.dart';
// 继承 Dialog
class MyDialog extends Dialog{
final String title;
final String content;
// 可空类型的 参数
final Function()? onTap;
const MyDialog({
Key? key,
required this.title,
required this.content,
required this.onTap
}) : super(key: key);
Widget build(BuildContext context) {
// TODO: implement build
return Material(
// 设置透明度
type: MaterialType.transparency,
child: Padding(
padding: const EdgeInsets.all(15),
child: Center(
child: Container(
width: 400,
height: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: const [
BoxShadow(
color: Colors.black45, // 阴影的颜色
offset: Offset(2.0, 2.0), // 阴影的偏移(水平 垂直)
blurRadius: 10.0, // 阴影的模糊度
)
]
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(15),
child: Stack(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(title, style: Theme.of(context).textTheme.headline6),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: const Icon(Icons.close),
onTap: onTap,
),
),
],
),
),
const Divider(),
Padding(
padding: EdgeInsets.all(15),
child: Container(
width: double.infinity,
padding: EdgeInsets.all(15),
child: Text(content),
),
)
],
)
),
),
),
);
}
}在
dialog.dart中引入myDialog.dartdialog.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160import './widget/myDialog.dart';
class MyDialogDemo extends StatefulWidget {
const MyDialogDemo({super.key});
State<MyDialogDemo> createState() => _MyDialogDemoState();
}
class _MyDialogDemoState extends State<MyDialogDemo> {
void alertDialogFn() async{
var result = await showDialog(
// 点击背景是否消失
barrierDismissible: true,
context: context, builder: ((context) {
return AlertDialog(
title: const Text('提示信息:'),
content: const Text('确定要删除吗?'),
actions: [
TextButton(onPressed: (){
print('ok');
// 关闭 dialog 窗口 也可以向外卖呢传递参数
Navigator.of(context).pop(true);
}, child: const Text('确定')),
TextButton(onPressed: (){
print('cacel');
// 关闭 dialog 窗口 也可以向外卖呢传递参数
Navigator.of(context).pop(false);
}, child: const Text('取消'))
],
);
}));
print(result);
}
void simpleDialogOptionFn() async{
var result = await showDialog(
// 点击背景是否消失
barrierDismissible: true,
context: context, builder: ((context) {
return SimpleDialog(
title: Text('请选择语言:'),
children: [
SimpleDialogOption(
onPressed: () {
print('中文');
// Navigator.of(context).pop('中文');
Navigator.pop(context, '中文');
},
child: const Text('中文', textAlign: TextAlign.center,),
),
Divider(),
SimpleDialogOption(
onPressed: () {
print('日语');
// Navigator.of(context).pop('日语');
Navigator.pop(context, '日语');
},
child: const Text('日语', textAlign: TextAlign.center,),
)
],
);
}));
print(result);
}
void showModelBottomSheetFn() async{
var result = await showModalBottomSheet(
isDismissible: true,
context: context, builder: (context) {
return SizedBox(
height: 240,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListTile(
title: const Text('分享', textAlign: TextAlign.center),
// 点击触发 onLongPress 长按触发
onTap: () {
print('分享');
Navigator.pop(context, '分享');
},
),
const Divider(),
ListTile(
title: const Text('其他', textAlign: TextAlign.center),
// 点击触发 onLongPress 长按触发
onTap: () {
print('其他');
Navigator.pop(context, '其他');
},
),
const Divider(),
ListTile(
title: const Text('取消', textAlign: TextAlign.center),
// 点击触发 onLongPress 长按触发
onTap: () {
print('取消');
Navigator.pop(context, '取消');
},
),
],
),
);
});
}
// 自定义 Dialog
void showMyDialog() async{
var reuslt = await showDialog(
// 点击背景是否消失
barrierDismissible: false,
context: context, builder: (context) {
return MyDialog(
title: '提示:',
content: 'My Customer Dialog!',
onTap: () {
// 关闭弹窗
Navigator.pop(context, 'close');
},
);
});
print('reuslt');
}
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 普通弹窗
ElevatedButton(onPressed: alertDialogFn, child: const Text('AlertDialog')),
const SizedBox(height: 20),
// 可选弹窗
ElevatedButton(onPressed: simpleDialogOptionFn, child: const Text('SimpleDialogOption')),
const SizedBox(height: 20),
// 底部弹窗
ElevatedButton(onPressed: showModelBottomSheetFn, child: const Text('ShowModelBottomSheet')),
const SizedBox(height: 20),
// toast
ElevatedButton(onPressed: () {
Fluttertoast.showToast(
msg: "This is Top Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP_LEFT,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 16.0
);
}, child: const Text('Toast')),
// 自定义弹窗
ElevatedButton(onPressed: showMyDialog, child: const Text('自定义Dialog'))
],
),
);
}
}在
main.dart中引入dialog.dartmain.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import './dialog.dart';
void main(List<String> args) {
return runApp(
DevicePreview(enabled: true , builder: ((context) => MyApp()))
);
}
class MyApp extends StatelessWidget {
/* Map routes = {
'/shopping':(context) => MyShoppingPage(), // 商品页
'/individualCenter':(context, { arguments }) => MyIndividualCenterPage(arguments: arguments), // 个人中心页
}; */
MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
useInheritedMediaQuery: true,
debugShowCheckedModeBanner: false,
locale: DevicePreview.locale(context),
builder: DevicePreview.appBuilder,
home: Scaffold(
appBar: AppBar(
leading: IconButton(onPressed: (() {
}), icon: const Icon(Icons.menu)),
title: const Text('Dialog Demo'),
),
body: const MyDialogDemo()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/', // 默认打开的路由
// onGenerateRoute: onGenerateRoute
);
}
}
PageView滑动组件
PageView组件 可以实现屏幕滑动效果,类似于抖音,可上下滑动 左右滑动切换屏幕!
| 属性 | 描述 |
|---|---|
scrollDirection | Axis.horizonta 水平方向 Axis.vertical 垂直方向 |
children | 多个子页面 |
allowImplicitScrolling | 缓存当前页面的前后两页 |
onPageChanged | page 改变的时候触发 |
PageView
pageView.dart
1 | import 'package:flutter/material.dart'; |
main.dart
1 | import 'package:flutter/material.dart'; |
PageViewBuilder
最终结果与
PageView一致!
pageViewBuilder.dart
1 | import 'package:flutter/material.dart'; |
PageView轮播图
listData.dart
listData
1 | List resData = [ |
pageViewSwiper.dart
pageViewSwiper
1 | import 'package:flutter/material.dart'; |
main.dart
main.dart
1 | import 'package:flutter/material.dart'; |
实现自动轮播
PageController和Timer实现自动轮播!with AutomaticKeepAliveClientMixin这个可以帮助我们实现页面缓存!
通过 继承AutomaticKeepAliveClientMixin该类后可以通过以下方式来设置页面是否缓存!
1 |
|
注意: PageView 缓存的话,页面比较多的情况下,缓存会耗费内存,一般在界面少的情况下缓存才是需要的!
swiper.dart
swiper.dart
1 | import 'dart:async'; |
main.dart
main.dart
1 | import 'package:flutter/material.dart'; |
Flutter Key 获取 子Widget 状态 和方法
我们平时一定接触过很多的
Widget,比如Container、Row、Column等,它们在我们绘制界面的过程中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个Widget的构造函数中,都有一个共同的参数,它们通常在参数列表的第一个,那就是Key。在Flutter中,
Key是不能重复使用的,所以Key一般用来做准一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到key。
没有 Key 会发生什么奇怪现象?
key代表该组件的唯一值, 如果 在开发过程中,使用的
组件类型是一致的,比如有个children:[Box(1),Box(2)...]这样的组件,在没有key的情况下,数据和视图更新会不同步的!- 另一种,
组件类型不一致,比如有个children:[Center(1),Box(2)...],会导致数据状态的丢失!
组件顺序改变
上面截图,通过随机刷新按钮,排序 Box()组件! 在没有key的情况下,刷新排序,界面变化的就只能是组件样式在变化,其内部数据状态是不会有所改变的!
不同组件引起状态丢失
局部Key(localKey)
局部 key 中有三种方式可表示:
ValueKeyObjectKeyUniqueKey
ValueKey
参数 是 任意
String类型!
1 | const Box(key: ValueKey('any')) |
ObjectKey
参数 是 任意
Object类型! 也可以组件widget类型!
1 | const Box(key: ObjectKey(Box())) |
UniqueKey
不用传用参数,会自动生成唯一的
key
1 | const Box(key: UniqueKey()) |
Demo
WidgetKey
WidgetKey.dart
1 | import 'package:flutter/material.dart'; |
全局Key(GlobalKey)
基本使用
全局
key中有两种方式可表示:GlobalKeyGlobalObjectKey
全局key和 局部key的差异:
全局key: 可以保证数据状态下不会丢失,不仅 组件状态改变的情况下 或者 组件类型不一致的情况下,都可以保证数据不丢失的可用性!
局部key: 可以保证数据状态下不丢失,但仅仅只是组件状态改变下,不同的是 当组件类型不一致的时候无法保证数据丢失的可能性!
local key的问题:
手机竖屏状态下使用了
columns widget
手机横屏状态下使用了rows widget
由于widget不一致 导致状态丢失问题!
global key 的使用
Global key
1 | import 'package:flutter/material.dart'; |
GlobalKey 获取子组件
globalKey.currentState可以获取子组件的态,执行子组件的方法,globalKey.currentWidget可以获取子组件的属性,_globalKey.currentContext.findRenderObject()可以获取渲染的属性
globalKey.currentState1
var widgetKeyDemoState = globalKey1.currentState as _BoxState
globalKey.currentWidget1
var widgetKeyDemoState = globalKey1.currentWidget as Box
_globalKey.currentContext.findRenderObject()1
var renderObject = _globalKey.currentContext.findRenderObject() as RenderBox;
AnimatedList 动画列表
AnimatedList和ListView的功能大体相似,不同的是,AnimatedList可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验。AnimatedList是一个StatefulWidget,它对应的State类型为AnimatedListState,添加和删除元素的方法位于AnimatedListState中:
1 | void insertItem(int index, [ Duration duration = _kDuration }); |
案例
code
animatedList.dart
1 | import 'dart:async'; |
main.dart
1 | import 'package:flutter/material.dart'; |
动画组件
动画原理
在任何系统的UI框架中,动画实现的原理都是相同的,即: 在一段时间内,快速地多次改变UI外观,由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS (Frame Per Second) ,即每秒的动画数。很明显,率越高则动画就会越流畅!一般情况下对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。
FLutter中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画、和Hero动画
隐式动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,
FLutter中提供的AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。
隐式动画中可以通过duration配置动画时长、可以通过curve(曲线)来配置动画过程
AnimatedContainer
Container容器发生改变的时候产生动画!
code
animatedContainer.dart
1 | import 'package:flutter/material.dart'; |
AnimatedPadding
Padding边距发生改变的时候产生动画!
AnimatedPositioned
Positioned定位发生改变的时候产生动画!
AnimatedOpacity
Opacity透明度发生变化的时候产生动画
AnimatedDefaultTextStyle
TextStyle文本样式发生变化的时候产生动画
AnimatedSwitcher
sub widget子组件 发生变化的时候产生动画
code
animatedSwitcherDemo.dart
1 | // 子组件发生变化的时候产生动画 |
显示动画
常见的显式动画有
RotationTransition、FadeTransition、ScaleTransition、SlideTransition、Animatedlcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController控制动画的开始、暂停、重置、跳转、倒播等。
AnimationController
控制动画的
开始、暂停、重置、跳转、倒播的方法和动画自动播放的方法,以及控制动画的时间!
| 方法 | 描述 |
|---|---|
forward() | 执行一次 |
stop() | 停止 |
reset() | 重置 |
repeat() | 重复 |
reverse() | 翻转 |
drive() | drive(Tween(begin: 0.5, end: 1.2)) 配置运动参数 |
RotationTransition
旋转动画
rotationTransitionDemo.dart
1 | import 'package:flutter/material.dart'; |
FadeTransition
透明度动画
1 | Container( |
ScaleTransition
缩放动画
1 | Container( |
SlideTransition
位移动画
1 | Container( |
Animatedlcon
图标
Icon动画
animatedIcon
MyAnimatedIconDemo.dart
1 | import 'package:flutter/material.dart'; |
自定义交错式动画
从
search icon切换到close icon, 在从close icon切换到search icon!
ScaleTransitionstaggered.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 import 'dart:ffi';
import 'package:flutter/material.dart';
class StaggeredAnimatedDemo extends StatefulWidget {
const StaggeredAnimatedDemo({super.key});
State<StaggeredAnimatedDemo> createState() => _StaggeredAnimatedDemoState();
}
class _StaggeredAnimatedDemoState extends State<StaggeredAnimatedDemo> with SingleTickerProviderStateMixin {
late AnimationController _animationController; // Animation controller for the animation. 描述如何在Widget中使用AnimationController。Animation controller是一个持
bool flag = false;
void initState() {
// TODO: implement initState
super.initState();
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 500)); // Animation controller for the animation. 描述如何在Widget中使用AnimationController。Animation
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
// 设置动画过程
children: [
// 0-0.5秒 search消失0.5到1秒的时候让 close显示
ScaleTransition(
scale: _animationController.drive(Tween(begin: 0.0, end: 1.0).chain(CurveTween(curve: const Interval(0.5, 1.0)))),
child: const Icon(Icons.close, size: 40,),
),
// 0.0, 0.5 秒 search消失的时候让 close显示 发送消息 发送消息成功 睡1秒钟 让 close 显示
ScaleTransition(
scale: _animationController.drive(Tween(begin: 1.0, end: 0.0).chain(CurveTween(curve: const Interval(0.0, 0.5)))),
child: const Icon(Icons.search, size: 40,),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 执行动画 为 true 的时候 图标 正向缩放到消失,到 0.5 的时候 close 图标会从反方向放大显示!
flag ? _animationController.forward() : _animationController.reverse();
flag = !flag;
print(flag);
},
child: const Icon(Icons.add)
),
);
}
}
Slidertransition
1 | import 'package:flutter/material.dart'; |
Hero动画
Route
route.dart
1 | import 'package:flutter/material.dart'; |
gridViewList.dart
gridViewList.dart
1 | import 'package:flutter/material.dart'; |
viewDetail.dart
viewDetail.dart
1 | import 'package:flutter/material.dart'; |
















