Flutter布局基础——自定义BottomNavigationBar

背景

这里我们来尝试实现一个不规则的 BottomNavigationBar,首先了解两个系统组件: floatingActionButtonBottomAppBar

floatingActionButton

floatingActionButton用于创建一个浮动的按钮,有两种样式:一种是圆形的纯图标的;一种是椭圆的带有图标或文案的。

示例如下:

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

void main() => runApp(const MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

static const String _title = 'Flutter Code Sample';

@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}

/// This is the stateless widget that the main application instantiates
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Floating Action Button'),
),
body: const Center(
child: Text('Press the button below'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Add your onPressed code here!
},
child: const Icon(Icons.navigation),
backgroundColor: Colors.green,
),
);
}
}

显示效果:

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

void main() => runApp(const MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

static const String _title = 'Flutter Code Sample';

@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}

/// This is the stateless widget that the main application instantiates
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Floating Action Button Label'),
),
body: const Center(
child: Text('Press the button width a label below!'),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Add your onPressed code here!
},
label: const Text('Approve'),
icon: const Icon(Icons.thumb_up),
backgroundColor: Colors.pink,
),
);
}
}

显示效果:

BottomAppBar

要实现的效果如下:

下面来分析如何实现:

body 是 ListView,ListView 的内容从上到下是:

  • 两个SwitchListTile
  • 一个Padding
  • 四个RadioListTile

再然后是 floatingActionButton
再然后是 BottomAppBar

如下:

代码如下:

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


void main() {
runApp(const BottomAppBarDemo());
}

class BottomAppBarDemo extends StatefulWidget {
const BottomAppBarDemo({Key? key}) : super(key: key);

@override
State createState() => _BottomAppBarDemoState();
}

class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
bool _showFab = true;
bool _showNotch = true;
FloatingActionButtonLocation _fabLocation =
FloatingActionButtonLocation.endDocked;

void _onShowNotchChanged(bool value) {
setState(() {
_showNotch = value;
});
}

void _onShowFabChanged(bool value) {
setState(() {
_showFab = value;
});
}

void _onFabLocationChanged(FloatingActionButtonLocation? value) {
setState(() {
_fabLocation = value ?? FloatingActionButtonLocation.endDocked;
});
}

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text('Bottom App Bar Demo'),
),
body: ListView(
padding: const EdgeInsets.only(bottom: 88),
children: <Widget>[
SwitchListTile(
value: _showFab,
onChanged: _onShowFabChanged,
title: const Text('Floating Action Button'),
),
SwitchListTile(
value: _showNotch,
onChanged: _onShowNotchChanged,
title: const Text('Notch'),
),
const Padding(
padding: EdgeInsets.all(16),
child: Text('Floating action button position'),
),
RadioListTile(
value: FloatingActionButtonLocation.endDocked,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
title: const Text('Docked - End'),
),
RadioListTile(
value: FloatingActionButtonLocation.centerDocked,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
title: const Text('Docked - Center'),
),
RadioListTile(
value: FloatingActionButtonLocation.endFloat,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
title: const Text('Floating - End'),
),
RadioListTile(
value: FloatingActionButtonLocation.centerFloat,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
title: const Text('Floating - Center'),
),
],
),
floatingActionButton: _showFab
? FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
tooltip: 'Create',
)
: null,
floatingActionButtonLocation: _fabLocation,
bottomNavigationBar: _DemoBottomAppBar(
fabLocation: _fabLocation,
shape: _showNotch ? const CircularNotchedRectangle() : null),
),
);
}
}

class _DemoBottomAppBar extends StatelessWidget {
const _DemoBottomAppBar({
this.fabLocation = FloatingActionButtonLocation.endDocked,
this.shape = const CircularNotchedRectangle(),
});

final FloatingActionButtonLocation fabLocation;
final NotchedShape? shape;

static final List<FloatingActionButtonLocation> centerLocations =
<FloatingActionButtonLocation>[
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.centerFloat,
];

@override
Widget build(BuildContext context) {
return BottomAppBar(
shape: shape,
color: Colors.blue,
child: IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary),
child: Row(
children: <Widget>[
IconButton(
tooltip: 'Open navigation menu',
icon: const Icon(Icons.menu),
onPressed: () {},
),
if (centerLocations.contains(fabLocation)) const Spacer(),
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
tooltip: 'Favorite',
icon: const Icon(Icons.favorite),
onPressed: () {},
),
],
),
),
);
}
}

要注意floatingActionButtonLocation的设置

参考