Flutter 中的路由

Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过Navigator组件管理路由导航.并提供了管理堆栈的方法。如: Navigator.pushNavigator.popFlutter中给我们提供了两种配置路由跳转的方式:

  1. 基本路由
  2. 命名路由

普通路由

普通路由基本用法
跳转

1
2
Navigator.push(context, MaterialPageRoute(builder: (context) => MyShoppingPage(id: '54234324123423')));
// Navigator.of(context).push(MaterialPageRoute(builder: (context) => const MyShoppingPage()));

返回上一页
1
2
3
Navigator.pop(context);
// ir
Navigator.of(context).pop();

路由传参

传参: 跳转路由 返回的是 部件构造函数,我们可以通过构造函数传参!

1
2
3
4
5
6
7
ElevatedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => MyShoppingPage(id: '54234324123423')));
// Navigator.of(context).push(MaterialPageRoute(builder: (context) => const MyShoppingPage()));
},
child: const Text('商品页!'),
),

有状态组件: 接参

1
2
3
4
5
6
7
8
9
class MyShoppingPage extends StatelessWidget {
String id;

MyShoppingPage({super.key, required this.id});
@override
Widget build(BuildContext context) {
print(id);
}
}

无状态组件: 接惨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 无状态组件 可以 通过  widget.id 来获取参数
class MyShoppingPages extends StatefulWidget {
final String id;
const MyShoppingPages({super.key, required this.id});

@override
State<MyShoppingPages> createState() => _MyShoppingPagesState();
}
class _MyShoppingPagesState extends State<MyShoppingPages> {
// 生命周期函数
@override
void initState() {
// TODO: implement initState
super.initState();
print(widget.id);
}
@override
Widget build(BuildContext context) {

}
}

注意: statefulWidget 有状态组件中可以widget.id 方式来获取参数, statelessWidget 无状态组件中无法使用widget.id获取参数!

main.dart

main.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
import 'package:flutter/material.dart';

import './route.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
leading: IconButton(onPressed: (() {

}), icon: const Icon(Icons.menu)),
title: const Text('Route Demo'),
),
body: const MyRouteApp()
),
);
}
}

route.dart

route.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
import 'package:flutter/material.dart';
import './pages/routePage/shopping.dart';
class MyRouteApp extends StatelessWidget {
const MyRouteApp({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => MyShoppingPage(id: '54234324123423')));
// Navigator.of(context).push(MaterialPageRoute(builder: (context) => const MyShoppingPage()));
},
child: const Text('商品页!'),
),
// MaterialButton(onPressed: (() => {}), child: const Text('MaterialButton!'),)
],
),
);
}
}

shopping.dart

shopping.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
import 'package:flutter/material.dart';

class MyShoppingPage extends StatelessWidget {
String id;

MyShoppingPage({super.key, required this.id});

@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// 返回到上一页
Navigator.pop(context);
},
child: Icon(Icons.home),
),
appBar: AppBar(
title: const Text('Shopping Page'),
actions: [
IconButton(onPressed: (() {

}), icon: const Icon(Icons.more_horiz_outlined))
],
),
body: Center(
child: Text('this is shopping page id: ${id}!', style: Theme.of(context).textTheme.headline5,),
),
);
}
}

// 无状态组件 可以 通过 widget.id 来获取参数
class MyShoppingPages extends StatefulWidget {
final String id;
const MyShoppingPages({super.key, required this.id});

@override
State<MyShoppingPages> createState() => _MyShoppingPagesState();
}

class _MyShoppingPagesState extends State<MyShoppingPages> {
// 生命周期函数
@override
void initState() {
// TODO: implement initState
super.initState();
print(widget.id);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping Page'),
actions: [
IconButton(onPressed: (() {

}), icon: const Icon(Icons.more_horiz_outlined))
],
),
body: Center(
child: Text('this is shopping page id: ${widget.id}!', style: Theme.of(context).textTheme.headline5,),
),
);
}
}

命名路由

命名路由: 可以统一管理路由部件!,根据别名可进行路由跳转!

配置方式

注意: MyShoppingPage 这里只定义了 一个 路由部件, /searchPage 这种的可自行定义对应路由部件 searchPage;

配置方式

MaterialApp() 下加入 routes 配置!

1
2
3
4
5
routes: {
'/':(context) => MyShoppingPage(id: '12312412'),
'/shopping':(context) => MyShoppingPage(id: '12312412'),
'/searchPage':(context) => MyShoppingPage(id: '12312412'),
},

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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import './route.dart';

import './pages/routePage/shopping.dart';
void main(List<String> args) {
return runApp(
DevicePreview(enabled: true , builder: ((context) => const MyApp()))
);
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
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('Route Demo'),
),
body: const MyRouteApp()
),
routes: {
'/':(context) => MyShoppingPage(id: '12312412'),
'/shopping':(context) => MyShoppingPage(id: '12312412'),
'/searchPage':(context) => MyShoppingPage(id: '12312412'),
},
);
}
}

路由跳转

路由跳转

Navigator.pushNamed 通过此方法可以根据别名定义进行跳转!

1
Navigator.pushNamed(context, '/shopping')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:flutter/material.dart';

class MyRouteMameApp extends StatefulWidget {
const MyRouteMameApp({super.key});

@override
State<MyRouteMameApp> createState() => _MyRouteMameAppState();
}

class _MyRouteMameAppState extends State<MyRouteMameApp> {
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () => {
Navigator.pushNamed(context, '/shopping')
},
child: const Text('商品页'),
),
);
}
}

命名路由传参

  1. 定义 Map Route 对象!

    命名路由传参
    1
    2
    3
    4
    5
    Map routes = {
    '/shopping':(context) => MyShoppingPage(), // 商品页
    // arguments 是命名参数 命名参数通过 { ... } 获取
    '/individualCenter':(context, { arguments }) => MyIndividualCenterPage( arguments: arguments ), // 个人中心页
    };
  2. MaterialApp(onGenerateRoute: ( settings ){} )配置onGenerateRoute()方法

    配置 `onGenerateRoute`

    onGenerateRoute 注意:当路由发生跳转时,会触发!
    onGenerateRoute 路由拦截器 中间件,可以做权限判断啥的!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    onGenerateRoute:(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;
    }
    },
  3. 实现路由``传参跳转

    传参跳转
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    return 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('个人中心页'),
    ),
    ],
    )
    );
  4. 跳转目标路由``接参

    目标路由接参

    这里以个人中心页演示:

    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});

    @override
    State<MyIndividualCenterPage> createState() => _MyIndividualCenterPageState();
    }

    class _MyIndividualCenterPageState extends State<MyIndividualCenterPage> {

    @override
    void initState() {
    // TODO: implement initState
    super.initState();
    print('个人中心');
    print(widget.arguments);
    // arguments ? 不为空 的时候获取! arguments参数是可空参数!
    print(widget.arguments?['id']);
    print(widget.arguments?['source']);
    }
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: const Text('个人中心'),),
    body: Center(
    child: Text('个人中心', style: Theme.of(context).textTheme.headline5),
    ),
    );
    }
    }

完整代码

main.dart 引入 shopping.dartindividualCenter.dart

main.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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';

import './routeName.dart';

import './pages/routePage/shopping.dart'; // 商品页
import './pages/routePage/individualCenter.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});

@override
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('Route Demo'),
),
body: const MyRouteMameApp()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
onGenerateRoute:(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;
}
},
);
}
}

routeName.dart 这个界面有两个按钮: 商品页 个人中心页!

routeName.dart

传参

1
2
3
4
Navigator.pushNamed(context, '/individualCenter', arguments: {
"source": "source routeName!",
"id": "592379217387128974"
})

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
import 'package:flutter/material.dart';

class MyRouteMameApp extends StatefulWidget {
const MyRouteMameApp({super.key});

@override
State<MyRouteMameApp> createState() => _MyRouteMameAppState();
}

class _MyRouteMameAppState extends State<MyRouteMameApp> {
@override
Widget build(BuildContext context) {
return 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('个人中心页'),
),
],
)
);
}
}

shopping.dart

shopping.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
import 'package:flutter/material.dart';

class MyShoppingPage extends StatelessWidget {
// id 为可空 可传 可不传
String? id;

MyShoppingPage({super.key, this.id});

@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// 返回到上一页
Navigator.pop(context);
},
child: Icon(Icons.home),
),
appBar: AppBar(
title: const Text('Shopping Page'),
actions: [
IconButton(onPressed: (() {

}), icon: const Icon(Icons.more_horiz_outlined))
],
),
body: Center(
child: Text('this is shopping page id: ${id}!', style: Theme.of(context).textTheme.headline5,),
),
);
}
}

individualCenter.dart

individualCenter.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
// 个人中心
import 'package:flutter/material.dart';

class MyIndividualCenterPage extends StatefulWidget {
// 接受命名路由传参
final Map? arguments;
const MyIndividualCenterPage({super.key, this.arguments});

@override
State<MyIndividualCenterPage> createState() => _MyIndividualCenterPageState();
}

class _MyIndividualCenterPageState extends State<MyIndividualCenterPage> {

@override
void initState() {
// TODO: implement initState
super.initState();
print('个人中心');
print(widget.arguments);
// arguments ? 不为空 的时候获取! arguments参数是可空参数!
print(widget.arguments?['id']);
print(widget.arguments?['source']);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('个人中心'),),
body: Center(
child: Text('个人中心', style: Theme.of(context).textTheme.headline5),
),
);
}
}

routes [路由封装]

initialRoute: 主要功能主要是控制app启动的时候进入的是哪个页面。

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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';

import './routeName.dart';
import './routes/routes.dart';
// import './pages/routePage/shopping.dart';
// import './pages/routePage/individualCenter.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});

@override
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('Route Demo'),
),
body: const MyRouteMameApp()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/', // 默认加载的路由
onGenerateRoute: onGenerateRoute
);
}
}

创建 lib/routes/routes.dart 引入 shopping.dart individualCenter.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
import 'package:flutter/material.dart';
import '../pages/routePage/shopping.dart';
import '../pages/routePage/individualCenter.dart';

Map routes = {
'/shopping':(context) => MyShoppingPage(), // 商品页
'/individualCenter':(context, { arguments }) => MyIndividualCenterPage(arguments: arguments), // 个人中心页
};
// 配置 onGenerateRoute
var onGenerateRoute = (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
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 可以返回 AlertDialog SimpleAlertDialog showModalBottomSheet 部件!

属性描述参数是否必须
context一般为当前路由的contextrequired
barrierDismissible控制弹窗点击空白区域是否可以关闭弹窗,默认为trueno required
child根据需求自定义的Widgetno required
builder这个参数是一个返回widget 的回调函数,childbuilder 只能使用一个required
useRootNavigator默认为true,用于确定是否将弹窗推送到“context”最远的Navigator,true 为推送到最远,用于处理多个Navigator的情况,一般保持默认就好了no required
routeSettings包含弹窗路由的配置信息,如路由的名称和传递的参数no required

新建dialog.dart

dialog
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
import 'package:flutter/material.dart';

class MyDialogDemo extends StatefulWidget {
const MyDialogDemo({super.key});

@override
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, '取消');
},
),
],
),
);
});
}

@override
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,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 16.0
);
}, child: const Text('Toast')),
],
),
);
}
}
main
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
import '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});

@override
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('Route Demo'),
),
body: const MyDialogDemo()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/',
// onGenerateRoute: onGenerateRoute
);
}
}

AlertDialog确认框

普通弹窗

AlertDialog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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); // true / false
}

SimpleDialog信息选择框

可选弹窗

SimpleDialog
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
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);
}

ShowModalBottomSheet底部弹框

底部弹窗

ShowModalBottomSheet
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
void showModelBottomSheetFn() async{
var result = await showModalBottomSheet(
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, '取消');
},
),
],
),
);
});
}

ShowToast浮动弹窗

Toast 官方没有 相关 widget 需要从 fluttertoast 这个地址下载第三方插件!

安装

1
flutter pub add fluttertoast

引入
1
import 'package:fluttertoast/fluttertoast.dart';

toast
1
2
3
4
5
6
7
8
9
10
Fluttertoast.showToast(
msg: "This is Top Short Toast",
toastLength: Toast.LENGTH_SHORT,
// 显示位置
gravity: ToastGravity.TOP,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 16.0
);

自定义弹窗

  1. 新建 myDialog.dart

    myDialog.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
    import '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);

    @override
    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),
    ),
    )
    ],
    )
    ),
    ),
    ),
    );
    }
    }
  2. dialog.dart中引入myDialog.dart

    dialog.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
    160
    import './widget/myDialog.dart';
    class MyDialogDemo extends StatefulWidget {
    const MyDialogDemo({super.key});

    @override
    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');
    }

    @override
    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'))
    ],
    ),
    );
    }
    }
  3. main.dart 中引入 dialog.dart

    main.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
    import '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});

    @override
    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 组件 可以实现屏幕滑动效果,类似于抖音,可上下滑动 左右滑动切换屏幕!

属性描述
scrollDirectionAxis.horizonta 水平方向 Axis.vertical 垂直方向
children多个子页面
allowImplicitScrolling缓存当前页面的前后两页
onPageChangedpage 改变的时候触发

PageView

pageView.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
import 'package:flutter/material.dart';


class MyPageViewDemo extends StatefulWidget {
const MyPageViewDemo({super.key});

@override
State<MyPageViewDemo> createState() => _MyPageViewDemoState();
}

class _MyPageViewDemoState extends State<MyPageViewDemo> {
@override
Widget build(BuildContext context) {
return PageView(
// 上下滑动 vertical 左右滑动 horizonta
scrollDirection: Axis.vertical,
children: [
Center(
child: Text('第1屏', style: Theme.of(context).textTheme.headline2,)
),
Center(
child: Text('第2屏', style: Theme.of(context).textTheme.headline2,)
),
Center(
child: Text('第3屏', style: Theme.of(context).textTheme.headline2,)
),
Center(
child: Text('第4屏', style: Theme.of(context).textTheme.headline2,)
),
Center(
child: Text('第5屏', style: Theme.of(context).textTheme.headline2,)
),
Center(
child: Text('第6屏', style: Theme.of(context).textTheme.headline2,)
),
],
);
}
}

main.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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import './pageView.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});

@override
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('PageView Demo'),
),
body: const MyPageViewDemo()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/',
// onGenerateRoute: onGenerateRoute
);
}
}

PageViewBuilder

最终结果与 PageView 一致!

pageViewBuilder.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
import 'package:flutter/material.dart';

class MyPageViewBuilderDemo extends StatefulWidget {
const MyPageViewBuilderDemo({super.key});

@override
State<MyPageViewBuilderDemo> createState() => _MyPageViewBuilderDemoState();
}

class _MyPageViewBuilderDemoState extends State<MyPageViewBuilderDemo> {
@override
Widget build(BuildContext context) {
return PageView.builder(
// 上下滚动
scrollDirection: Axis.vertical,
itemCount: 10,
itemBuilder: (context, index) {
return Center(
child: Text("第${index+1}屏", style: Theme.of(context).textTheme.headline3,),
);
}
);
}
}

PageView轮播图

listData.dart

listData
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
List resData = [
{
"title": "title_1",
"author": "author_1" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
},
{
"title": "title_2",
"author": "author_2" ,
"imageUrl": "https://img3.mukewang.com/szimg/60dea91109143a3005400304.png"
},
{
"title": "title_3",
"author": "author_3" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
},
{
"title": "title_4",
"author": "author_4" ,
"imageUrl": "https://img2.mukewang.com/szimg/63a26398099ff25405400304.png"
},
{
"title": "title_5",
"author": "author_5" ,
"imageUrl": "https://img1.mukewang.com/szimg/639989a5081b677f05400304.jpg"
},
{
"title": "title_6",
"author": "author_6" ,
"imageUrl": "https://img4.mukewang.com/szimg/632d59840927c04b05400304.png"
}
];

pageViewSwiper.dart

pageViewSwiper
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
import 'package:flutter/material.dart';
import './data/listData.dart';

class MyPageViewSwiperDemo extends StatefulWidget {
const MyPageViewSwiperDemo({super.key});

@override
State<MyPageViewSwiperDemo> createState() => _MyPageViewSwiperDemoState();
}

class _MyPageViewSwiperDemoState extends State<MyPageViewSwiperDemo> {

int currentIndex = 0;

@override
Widget build(BuildContext context) {
// 层叠组件
return Stack(
children: [
// 设置宽高盒子
SizedBox(
width: double.infinity,
height: 200,
// 滑动视图组件
child: PageView.builder(
onPageChanged: (value) {
setState(() {
currentIndex = currentIndex == 0 ? resData.length : value % resData.length;
print(currentIndex);
});
},
itemCount: 1000,
itemBuilder: (context, index) {
/*
index: 1000
resData.length: 4
index % 4
result [0 % 4] 0 || [1 % 4] 1 || [2 % 4] 2 || [3 % 4] 3 || [4 % 4] 0
该算法结果 index % 4 无限
0 1 2 3 , 0 1 2 3...
*/
return Container(
width: double.infinity,
height: 200,
child: Image.network(resData[index%resData.length]['imageUrl'], fit: BoxFit.cover,),
);
}
)
),
Positioned(
left: 0,
right: 0,
bottom: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(resData.length, (index) =>
Container(
width: 10,
height: 10,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: currentIndex == index ? Colors.blue : Colors.grey
),
)
).toList(),
)
)
],
);
}
}

main.dart

main.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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';

import './pageViewSwiper.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});

@override
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('PageViewBuilder Demo'),
),
body: const MyPageViewSwiperDemo()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/',
// onGenerateRoute: onGenerateRoute
);
}
}

实现自动轮播

PageControllerTimer 实现自动轮播!
with AutomaticKeepAliveClientMixin 这个可以帮助我们实现页面缓存!
通过 继承 AutomaticKeepAliveClientMixin 该类后可以通过以下方式来设置页面是否缓存!

1
2
3
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true; //返回 true 表示缓存该页面!

注意: PageView 缓存的话,页面比较多的情况下,缓存会耗费内存,一般在界面少的情况下缓存才是需要的!

swiper.dart

swiper.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
import 'dart:async';
import 'dart:developer';

import 'package:flutter/material.dart';
import '../../data/listData.dart';

class MySwiperDemo extends StatefulWidget {
const MySwiperDemo({super.key});

@override
State<MySwiperDemo> createState() => _MySwiperDemoState();
}

class _MySwiperDemoState extends State<MySwiperDemo> with AutomaticKeepAliveClientMixin{

int currentIndex = 0;

late PageController _pageController;

late Timer _timer;

Widget pageViewBuilder(context, index) {
return Container(
width: double.infinity,
height: 200.0,

decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
image: NetworkImage(resData[currentIndex % resData.length]['imageUrl']),
fit: BoxFit.cover,
)
)
);
}


@override
void initState() {
// TODO: implement initState
super.initState();

// debugger(when: true, message: '初始化了');
// 页面控制器
_pageController = PageController(initialPage: 0);

// 定时器 每隔 5 秒执行
_timer = Timer.periodic( const Duration(seconds: 5), (timer) {
// int currentPageIndex = (currentIndex + 1) % resData.length;
setState(() {
print('currentIndex ${currentIndex}');
_pageController.animateToPage(
// 当前页
(currentIndex++),
// 滚动时长 minutes 分钟 milliseconds 毫秒
duration: const Duration(milliseconds: 200),
// 动画形式
curve: Curves.linear
);
});

});
}

// 生命周期 结束
@override
void dispose() {
// TODO: implement dispose
super.dispose();
// 销毁 避免内存占用
_pageController.dispose();
_timer.cancel();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Stack(
children: [
Container(
width: double.infinity,
height: 200.0,
child: PageView.builder(
onPageChanged: (value) {
setState(() {
currentIndex = value % resData.length;
print('setState ${currentIndex} ${value} ${resData.length} ${resData.length}');
});
},
controller: _pageController,
itemCount: 1000,
itemBuilder: pageViewBuilder
),
),
Positioned(
left: 0,
right: 0,
bottom: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(resData.length, (index)=>
Container(
width: 10.0,
height: 10.0,
margin: EdgeInsets.only(right: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: currentIndex % resData.length == index ? Colors.lightBlue : Colors.white30
),
)
).toList(),
)
)
],
),
);
}
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true; //返回 true 表示缓存该页面!
}

main.dart

main.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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';

import './demo/page-view/swiper.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});

@override
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('AutoPageView Demo'),
),
body: ListView(
children: const [
MySwiperDemo()
],
)
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/',
// onGenerateRoute: onGenerateRoute
);
}
}

Flutter Key 获取 子Widget 状态 和方法

我们平时一定接触过很多的 Widget,比如 Container、Row、Column 等,它们在我们绘制界面的过程中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同的参数,它们通常在参数列表的第一个,那就是 Key

在Flutter中,Key是不能重复使用的,所以Key一般用来做准一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到key

没有 Key 会发生什么奇怪现象?

key 代表该组件的唯一值, 如果 在开发过程中,使用的

  1. 组件类型是一致的,比如有个children:[Box(1),Box(2)...]这样的组件,在没有key的情况下,数据视图更新会不同步的!
  2. 另一种,组件类型不一致,比如有个children:[Center(1),Box(2)...],会导致数据状态的丢失!

组件顺序改变

上面截图,通过随机刷新按钮,排序 Box()组件! 在没有key的情况下,刷新排序,界面变化的就只能是组件样式在变化,其内部数据状态是不会有所改变的!

不同组件引起状态丢失

局部Key(localKey)

局部 key 中有三种方式可表示: ValueKey ObjectKey UniqueKey

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
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
import 'package:flutter/material.dart';

class WidgetKeyDemo extends StatefulWidget {
const WidgetKeyDemo({super.key});

@override
State<WidgetKeyDemo> createState() => _WidgetKeyDemoState();
}

class _WidgetKeyDemoState extends State<WidgetKeyDemo> {
// ValueKey 值不能重复
/* List<Widget> boxList = [
Box(color: Colors.black12, key: ValueKey('1'),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: ValueKey('2')),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: ValueKey('3')),
]; */
// UniqueKey
/* List<Widget> boxList = [
Box(color: Colors.black12, key: UniqueKey(),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: UniqueKey()),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: UniqueKey()),
]; */
// ObjectKey 对象实例不能重复
List<Widget> boxList = [
Box(color: Colors.black12, key: ObjectKey(Box(color: Colors.black12)),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: ObjectKey(Box(color: Colors.blue))),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: ObjectKey(Box(color: Colors.deepOrange))),
];

@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: boxList
),
),
Positioned(
bottom: 10,
right: 10, //child's bottom and right will be 10 pixels from each other.
child: ElevatedButton.icon(onPressed: (){
setState(() {
boxList.shuffle();
});
}, label: Text('Refresh'), icon: const Icon(Icons.refresh)),
)
],
)
);
}
}



class Box extends StatefulWidget {

final Color color;

const Box({super.key, required this.color, });

@override
State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {

int _count = 0;

@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
child: ElevatedButton(child: Text('${_count}', style: Theme.of(context).textTheme.headlineMedium,), onPressed: () {
setState(() {
_count++;
});
}, style: ButtonStyle(backgroundColor: MaterialStateProperty.all(widget.color)),),
);
}
}

全局Key(GlobalKey)

基本使用

全局 key 中有两种方式可表示: GlobalKey GlobalObjectKey
全局 key 和 局部 key 的差异:
全局 key: 可以保证数据状态下不会丢失,不仅 组件状态改变的情况下 或者 组件类型不一致的情况下,都可以保证数据不丢失的可用性!
局部 key: 可以保证数据状态下不丢失,但仅仅只是组件状态改变下,不同的是 当组件类型不一致的时候无法保证数据丢失的可能性!

local key的问题:

手机竖屏状态下使用了columns widget
手机横屏状态下使用了rows widget
由于 widget 不一致 导致 状态丢失问题!

global key 的使用

Global key
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
import 'package:flutter/material.dart';

class WidgetKeyDemo extends StatefulWidget {
const WidgetKeyDemo({super.key});

@override
State<WidgetKeyDemo> createState() => _WidgetKeyDemoState();
}

class _WidgetKeyDemoState extends State<WidgetKeyDemo> {
List<Widget> boxList = [];
final GlobalKey globalKey1 = new GlobalKey();
final GlobalKey globalKey2 = new GlobalKey();
final GlobalKey globalKey3 = new GlobalKey();
// ValueKey 值不能重复
/* List<Widget> boxList = [
Box(color: Colors.black12, key: ValueKey('1'),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: ValueKey('2')),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: ValueKey('3')),
]; */
// UniqueKey
/* List<Widget> boxList = [
Box(color: Colors.black12, key: UniqueKey(),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: UniqueKey()),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: UniqueKey()),
]; */
// ObjectKey 对象实例不能重复
/* List<Widget> boxList = [
Box(color: Colors.black12, key: ObjectKey(Box(color: Colors.black12)),),
// SizedBox(height: 15),
Box(color: Colors.blue, key: ObjectKey(Box(color: Colors.blue))),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: ObjectKey(Box(color: Colors.deepOrange))),
]; */

// globkey 要放在初始化函数内
@override
void initState() {
// TODO: implement initState
super.initState();

boxList = [
Box(color: Colors.black12, key: globalKey1),
// SizedBox(height: 15),
Box(color: Colors.blue, key: globalKey2),
// SizedBox(height: 15),
Box(color: Colors.deepOrange, key: globalKey3),
];
}

@override
Widget build(BuildContext context) {
// MediaQuery.of(context).orientation == Orientation.portrait 判断手机 是否为竖屏模式
print(MediaQuery.of(context).orientation);

return Container(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Center(
child: MediaQuery.of(context).orientation == Orientation.portrait ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: boxList
) : Row(
mainAxisAlignment: MainAxisAlignment.center,
children: boxList
),
),
Positioned(
bottom: 10,
right: 10, //child's bottom and right will be 10 pixels from each other.
child: ElevatedButton.icon(onPressed: (){
setState(() {
boxList.shuffle();
});
}, label: Text('Refresh'), icon: const Icon(Icons.refresh)),
)
],
)
);
}
}



class Box extends StatefulWidget {

final Color color;

const Box({super.key, required this.color, });

@override
State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {

int _count = 0;

@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
child: ElevatedButton(child: Text('${_count}', style: Theme.of(context).textTheme.headlineMedium,), onPressed: () {
setState(() {
_count++;
});
}, style: ButtonStyle(backgroundColor: MaterialStateProperty.all(widget.color)),),
);
}
}

GlobalKey 获取子组件

globalKey.currentState 可以获取子组件的态,执行子组件的方法,
globalKey.currentWidget可以获取子组件的属性,
_globalKey.currentContext.findRenderObject()可以获取渲染的属性

  1. globalKey.currentState
    1
    var widgetKeyDemoState = globalKey1.currentState as _BoxState
  2. globalKey.currentWidget
    1
    var widgetKeyDemoState = globalKey1.currentWidget as Box
  3. _globalKey.currentContext.findRenderObject()
    1
    var renderObject = _globalKey.currentContext.findRenderObject() as RenderBox;

AnimatedList 动画列表

AnimatedListListView 的功能大体相似,不同的是,AnimatedList 可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验。
AnimatedList 是一个 StatefulWidget,它对应的 State 类型为 AnimatedListState,添加和删除元素的方法位于 AnimatedListState 中:

1
2
3
void insertItem(int index, [ Duration duration = _kDuration });

void removeItem(int index, AnimatedListRemoveditemBuilder builder, { Durationduration = _kDuration }) ;

案例

code

animatedList.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
import 'dart:async';

import 'package:flutter/material.dart';
// AnimatedListDemo List
class AnimatedListDemo extends StatefulWidget {
const AnimatedListDemo({super.key});

@override
State<AnimatedListDemo> createState() => _AnimatedListDemoState();
}

class _AnimatedListDemoState extends State<AnimatedListDemo> {

final GlobalKey<AnimatedListState> _scaffoldKey = GlobalKey<AnimatedListState>();

List<String> _listData = ['第一条数据', '第二条数据'];

bool isCanDel = true;

Widget _listTile(int index) {
return ListTile(
title: Text(_listData[index]),
trailing: IconButton(
onPressed: (){
_deleteItem(index);
},
icon: const Icon(Icons.delete),
),
);
}

_deleteItem(int index) {
if ( isCanDel == true ) {
isCanDel = false;
var removeItem = _listTile(index);
_listData.removeAt(index);
_scaffoldKey.currentState!.removeItem(index, (context, animation) {
// ScaleTransition
return FadeTransition(
opacity: animation,
child: removeItem
);
});
// 处理 快速点击删除 引起异常问题 待动画执行完后执行下一个删除!
Timer.periodic(Duration(milliseconds: 500), (timer) {
isCanDel = true;
timer.cancel();
});
}

}

@override
Widget build(BuildContext context) {
return Stack(
children: [
AnimatedList(
key: _scaffoldKey,
initialItemCount: _listData.length,
itemBuilder: (context, index, animation) {
// animation 从 0 到 1
// FadeTransition 隐藏和显示动画
// ScaleTransition 缩放动画
/* return FadeTransition(
opacity: animation,
child: _listTile(index),
); */
return ScaleTransition(
scale: animation,
child: _listTile(index),
);
}
),
Positioned(
bottom: 45, // Offset(0, 460), // Offset(0, 460), // Offset(0,
right: 35, // Offset(0, 460), // Offset(0, 460), // Offset(0

child: IconButton(
onPressed: (){
_listData.add('我是新增数据');
// currentState 可能会为空 需要 加 非空断言
_scaffoldKey.currentState!.insertItem(_listData.length - 1);
},
icon: const Icon(Icons.add),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
elevation: MaterialStateProperty.all(4)
),
),
)
],

);
}
}

main.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
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import 'animateList.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});

@override
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('AnimatedList Demo'),
),
body: AnimatedListDemo()
),
// routes: {
// // '/':(context) => MyShoppingPage(id: '12312412'),
// '/shopping':(context) => MyShoppingPage(id: '12312412')
// },
initialRoute: '/',
// onGenerateRoute: onGenerateRoute
);
}
}

动画组件

动画原理

在任何系统的UI框架中,动画实现的原理都是相同的,即: 在一段时间内,快速地多次改变UI外观,由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS (Frame Per Second) ,即每秒的动画数。很明显,率越高则动画就会越流畅!一般情况下对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。

FLutter中的动画主要分为: 隐式动画显式动画自定义隐式动画自定义显式动画、和 Hero 动画

隐式动画

通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,
FLutter中提供的 AnimatedContainer、AnimatedPaddingAnimatedPositionedAnimatedOpacityAnimatedDefaultTextStyleAnimatedSwitcher都属于隐式动画
隐式动画中可以通过 duration 配置动画时长、可以通过 curve (曲线)来配置动画过程

AnimatedContainer

Container容器发生改变的时候产生动画!

code

animatedContainer.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
import 'package:flutter/material.dart';

class AnimatedContainerDemo extends StatefulWidget {
const AnimatedContainerDemo({super.key});

@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
double _width = 120;
double _height = 120;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedContainer(
// 设置动画效果 默认 linear
curve: Curves.easeInCirc,
width: _width,
height: _height,
// color: Colors.lightBlue,
duration: const Duration(seconds: 1, milliseconds: 500),
decoration: const BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.all(Radius.circular(25)),
boxShadow: [
BoxShadow(
color: Colors.black,
offset: Offset(0, 0),
blurRadius: 15,

)
]
),
child: const Center(
child: Text(
'AnimatedContainer',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold
),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => {
setState(() {
_width+=20.0;
_height+=20.0;
})
},
child: const Icon(Icons.add),
),
);
}
}

AnimatedPadding

Padding边距发生改变的时候产生动画!

AnimatedPositioned

Positioned 定位发生改变的时候产生动画!

AnimatedOpacity

Opacity 透明度发生变化的时候产生动画

AnimatedDefaultTextStyle

TextStyle 文本样式发生变化的时候产生动画

AnimatedSwitcher

sub widget子组件 发生变化的时候产生动画

code

animatedSwitcherDemo.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
// 子组件发生变化的时候产生动画
import 'package:flutter/material.dart';

class AnimatedSwitcherDemo extends StatefulWidget {
const AnimatedSwitcherDemo({super.key});

@override
State<AnimatedSwitcherDemo> createState() => _AnimatedSwitcherDemoState();
}

class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
bool flag = false;

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.blue[400],
borderRadius: const BorderRadius.all(Radius.circular(20)),
shape: BoxShape.rectangle,
boxShadow: const [
BoxShadow(
color: Colors.black38,
offset: Offset(1, 1),
blurRadius: 3.0,
)
]
),
child: AnimatedSwitcher(
// 自定义动画
transitionBuilder: (child, animation) {
// 缩放
return ScaleTransition(
scale: animation,
// 显示隐藏
child: FadeTransition(opacity: animation, child: child,),
);
},
duration: const Duration(milliseconds: 800),
child: !flag ? const CircularProgressIndicator(
color: Colors.white, // Colors.blue[400] // Colors.grey[100] // Colors.grey[200] // Colors
) : Image.network('https://img3.mukewang.com/szimg/60dea91109143a3005400304.png',fit: BoxFit.fill,),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.transcribe)
),
);
}
}

显示动画

常见的显式动画有RotationTransitionFadeTransitionScaleTransitionSlideTransitionAnimatedlcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController控制动画的开始暂停重置跳转倒播等。

AnimationController

控制动画的开始、暂停、重置、跳转、倒播的方法和动画自动播放的方法,以及控制动画的时间!

方法描述
forward()执行一次
stop()停止
reset()重置
repeat()重复
reverse()翻转
drive()drive(Tween(begin: 0.5, end: 1.2)) 配置运动参数

RotationTransition

旋转动画

rotationTransitionDemo.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
import 'package:flutter/material.dart';

class RotationTransitionDemo extends StatefulWidget {
const RotationTransitionDemo({super.key});

@override
State<RotationTransitionDemo> createState() => _RotationTransitionDemoState();
}

class _RotationTransitionDemoState extends State<RotationTransitionDemo> with SingleTickerProviderStateMixin {
late AnimationController _animationController;

@override
void initState() {
// TODO: implement initState
super.initState();
_animationController = AnimationController(
//Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
vsync: this,
// 设置动画值 0 - 1 运动区间
// lowerBound: 0.5,
// upperBound: 1,
duration: const Duration(seconds: 1)
)..repeat(reverse: true); // repeat 反复执行
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.only(top: 25),
child: RotationTransition(
turns: _animationController,
child: const FlutterLogo(size: 70,),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(onPressed: (){
// 顺时针执行一次
_animationController.forward();
}, child: const Text('forward')),
ElevatedButton(onPressed: (){
// 重复执行
_animationController.repeat();
}, child: const Text('repeat')),
ElevatedButton(onPressed: (){
// 停止执行
_animationController.stop();
}, child: const Text('stop')),
ElevatedButton(onPressed: (){
// 重置widget状态
_animationController.reset();
}, child: const Text('reset')),
ElevatedButton(onPressed: (){
// 翻转
_animationController.reverse();
}, child: const Text('reverse'))
],
)
] // <----- [SCAFOLD] [START] [END] [DEMO] [START] [END]
);
}
}

FadeTransition

透明度动画

1
2
3
4
5
6
7
Container(
padding: const EdgeInsets.only(top: 25),
child: FadeTransition(
opacity: _animationController,
child: const FlutterLogo(size: 70,),
),
),

ScaleTransition

缩放动画

1
2
3
4
5
6
7
Container(
padding: const EdgeInsets.only(top: 25),
child: ScaleTransition(
scale: _animationController,
child: const FlutterLogo(size: 70,),
),
),

SlideTransition

位移动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Container(
padding: const EdgeInsets.only(top: 25),
child: SlideTransition(
// Offset(0, 0) x, y 0.5 widget 宽度的 0.5 倍
position: _animationController.drive(
Tween(
begin: const Offset(0, 0),
end: const Offset(0.5, 0)
).chain(CurveTween(curve: Curves.bounceInOut)) // 设置动画效果
// 20% 开始 80% 结束
.chain(CurveTween(curve: const Interval(0.2, 0.8))) // 设置动画 每一次执行间隔为20-80%的时间间隔,也可以自定义间隔动画。
),
// or .animate()
// position: Tween(begin: const Offset(0, 0), end: const Offset(0.5, 0)).animate(_animationController),
child: const FlutterLogo(size: 70,),
),
),

Animatedlcon

图标 Icon 动画

animatedIcon

MyAnimatedIconDemo.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
import 'package:flutter/material.dart';

class MyAnimatedIconDemo extends StatefulWidget {
const MyAnimatedIconDemo({super.key});

@override
State<MyAnimatedIconDemo> createState() => _MyAnimatedIconDemoState();
}

class _MyAnimatedIconDemoState extends State<MyAnimatedIconDemo> with SingleTickerProviderStateMixin {

late AnimationController _controller;
bool flag = false;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedIcon(icon: AnimatedIcons.arrow_menu, progress: _controller, size: 40,),
SizedBox(height: 40,),
AnimatedIcon(icon: AnimatedIcons.home_menu, progress: _controller, size: 40,),
SizedBox(height: 40,),
AnimatedIcon(icon: AnimatedIcons.search_ellipsis, progress: _controller, size: 40,),
],
)
),
floatingActionButton: FloatingActionButton(onPressed: (){
flag ? _controller.forward() : _controller.reverse();
flag = !flag;
// _controller.reverse();
// _controller.repeat();
print('floatingActionButton');
},child: const Icon(Icons.refresh),),
);
}
}

自定义交错式动画

search icon 切换到 close icon, 在从 close icon 切换到 search icon!

ScaleTransition

staggered.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});

@override
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;

@override
void initState() {
// TODO: implement initState
super.initState();
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 500)); // Animation controller for the animation. 描述如何在Widget中使用AnimationController。Animation

}
@override
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
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
import 'package:flutter/material.dart';

class SlideTransitionDemo extends StatefulWidget {
const SlideTransitionDemo({super.key});

@override
State<SlideTransitionDemo> createState() => _SlideTransitionDemoState();
}

class _SlideTransitionDemoState extends State<SlideTransitionDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool flag = false;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(35.4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
/* SlideTransition(
position: _controller.drive(Tween(begin: const Offset(0.0, 0.0), end: const Offset(0.5, 0.0)).chain(CurveTween(curve: const Interval(0.0, 0.3)))),
child: Container(
width: 150,
height: 40,
decoration: BoxDecoration(
color: Colors.blue[300]
),
),
),

SlideTransition(
position: _controller.drive(Tween(begin: const Offset(0.0, 0.0), end: const Offset(0.5, 0.0)).chain(CurveTween(curve: const Interval(0.3, 0.7)))),
child: Container(
width: 150,
height: 40,
decoration: BoxDecoration(
color: Colors.blue[500]
),
),
),
SlideTransition(
position: _controller.drive(Tween(begin: const Offset(0.0, 0.0), end: const Offset(0.5, 0.0)).chain(CurveTween(curve: const Interval(0.5, 1.0)))),
child: Container(
width: 150,
height: 40,
decoration: BoxDecoration(
color: Colors.blue[700]
),
),
), */
SlideTransitionBox(controller: _controller, begin: 0.0, end: 0.3, color: Colors.blue[300],),
SlideTransitionBox(controller: _controller, begin: 0.3, end: 0.7, color: Colors.blue[500],),
SlideTransitionBox(controller: _controller, begin: 0.5, end: 1.0, color: Colors.blue[700],),
]
),
)
),
floatingActionButton: FloatingActionButton( onPressed: () {
flag ? _controller.forward() : _controller.reverse();
flag = !flag;
}, child: const Icon(Icons.refresh),)
);
}
}

class SlideTransitionBox extends StatelessWidget {
final AnimationController controller;
final double begin;
final double end;
final Color? color;
// final double? begin;
// 这种带有 ? 表达式 表示 该参数为可空类型,可传可不传,如果没有 ? 表达式,则这个参数 要么 为 required ,要么给默认值!
const SlideTransitionBox({super.key, required this.controller, this.begin = 0.0, this.end = 1.0, this.color });

@override
Widget build(BuildContext context) {
return SlideTransition(
position: controller.drive(Tween(begin: const Offset(0.0, 0.0), end: const Offset(0.5, 0.0)).chain(CurveTween(curve: Interval(begin, end)))),
child: Container(
width: 150,
height: 40,
decoration: BoxDecoration(
color: color
),
),
);
}
}

Hero动画

Route

route.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
import 'package:flutter/material.dart';
import '../pages/routePage/shopping.dart';
import '../pages/routePage/individualCenter.dart';
import '../animated/hero/gridViewList.dart';
import '../animated/hero/viewDetail.dart';

Map routes = {
// '/shopping':(context) => MyShoppingPage(), // 商品页
// '/individualCenter':(context, { arguments }) => MyIndividualCenterPage(arguments: arguments), // 个人中心页
'/gridViewList': (context) => const MyGridViewListPage(), // 游戏中心页面的列表页面, 不要加载数据
'/viewDetail': (context, { arguments }) => ViewDetailPage(arguments: arguments), //游戏中心页面的列表页

};
// 配置 onGenerateRoute
var onGenerateRoute = (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;
}
};

gridViewList.dart

gridViewList.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
import 'package:flutter/material.dart';
import '../../data/listData.dart';

class MyGridViewListPage extends StatefulWidget {
const MyGridViewListPage({super.key});

@override
State<MyGridViewListPage> createState() => _MyGridViewListPageState();
}

class _MyGridViewListPageState extends State<MyGridViewListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GridViewListPage'),
),
body: Padding(
padding: const EdgeInsets.all(6.0),
child: GridView.builder(
itemCount: resData.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //每行有两个项。 (或者使用 SliverGridDelegate 也可以的话
),
itemBuilder: (context, index){
return InkWell(
onTap: () {
print('InkWell');
Navigator.pushNamed(
context, '/viewDetail',
arguments: {
"detail": resData[index],
"photoList": resData,
"initalIndex": index
}
);
},
child: Card(
// 设置圆角
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
// 阴影
elevation: 20.0,
// 修剪 隐藏 card 超出部分的内容
clipBehavior: Clip.antiAlias,
child: Column(
// 主轴对齐
// mainAxisAlignment: MainAxisAlignment.start,
// 横轴对齐
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 5 / 3,
// tag 是唯一值
child: Hero(tag: resData[index]['author'], child: Image.network(resData[index]['imageUrl'], fit: BoxFit.cover,)),
),

Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
resData[index]['title'],
maxLines: 1,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
)
),
const SizedBox(height: 5,),
Text(
resData[index]['subtitle'],
// 设置显示的行数
maxLines: 2,
style: const TextStyle(
fontSize: 12.0,
color: Colors.black45,
// 文字溢出方案 省略
overflow: TextOverflow.ellipsis,
)
),
],
),
),
],
),
)
);
}
)
),
);
}
}

viewDetail.dart

viewDetail.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
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';

class ViewDetailPage extends StatelessWidget {

final Map arguments;

const ViewDetailPage({super.key, required this.arguments});

@override
Widget build(BuildContext context) {
int initalIndex = arguments['initalIndex'];
List photoList = arguments['photoList'];
int currentPage = initalIndex + 1;
// 手势事件
return GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
Center(

// hero 放大 缩放
// child: Hero(tag: arguments['detail']['author'], child: Image.network(arguments['detail']['imageUrl'])) ,

// photo_view 单张图片预览
/* child: PhotoView(
imageProvider: NetworkImage(arguments['detail']['imageUrl'])
), */

// photo_view 多张预览
child: PhotoViewGallery.builder(
// 配置初始化的图片 就是默认当前点击展示的图片
pageController: PageController(initialPage: initalIndex),
onPageChanged: (index){
initalIndex = index;
},
itemCount: photoList.length,
builder: (context, index) {
return PhotoViewGalleryPageOptions(imageProvider: NetworkImage(photoList[index]['imageUrl']));
}
),
),

Positioned(
left: 0,
right: 0,
bottom: 12,
child: Align(
alignment: Alignment.center,
child: Text(
'${currentPage}/${photoList.length}',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white60),
),
)
)
],
)
),
);
}
}