Flutter了解之常用组件2
目录
11. 剪裁
12. 标签 Chip、ActionChip、FilterChip、ChoiceChip
13. 表格 DataTable、PaginatedDataTable
14. 分割线Divider
15. ListTile
16. ButtonBarTheme
17. Material
18. MaterialApp
19. Scaffold
11. 剪裁
Flutter中提供了一些剪裁函数,用于对组件进行剪裁
1. ClipOval
子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆
2. ClipRRect
将子组件剪裁为圆角矩形
3. ClipRect
剪裁子组件到实际占用的矩形大小(溢出部分剪裁)
const ClipOval({Key? key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget? child})
const ClipRRect({
Key? key,
this.borderRadius = BorderRadius.zero,
this.clipper,
this.clipBehavior = Clip.antiAlias,
Widget? child,
})
const ClipRect({ Key? key, this.clipper, this.clipBehavior = Clip.hardEdge, Widget? child })
例
import 'package:flutter/material.dart';
class ClipTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 头像
Widget avatar = Image.asset("imgs/avatar.png", width: 60.0);
return Center(
child: Column(
children: <Widget>[
avatar, // 不剪裁
ClipOval(child: avatar), // 剪裁为圆形
ClipRRect( // 剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
widthFactor: .5,// 宽度设为原来宽度一半,另一半会溢出
child: avatar,
),
Text("你好世界", style: TextStyle(color: Colors.green),)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRect( // 将溢出部分剪裁
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5, // 宽度设为原来宽度一半
child: avatar,
),
),
Text("你好世界",style: TextStyle(color: Colors.green))
],
),
],
),
);
}
}
![](https://img.haomeiwen.com/i5111884/54a6ca55fdf14a67.png)
例(自定义剪裁区域)
截取图片中部40×30像素的范围
1. 首先,自定义一个CustomClipper:
class MyClipper extends CustomClipper<Rect> {
@override
// getClip()是用于获取剪裁区域的接口,由于图片大小是60×60,计算之后即图片中部40×30像素的范围。
Rect getClip(Size size) => Rect.fromLTWH(10.0, 15.0, 40.0, 30.0);
@override
// shouldReclip() 接口决定是否重新剪裁。如果在应用中,剪裁区域始终不会发生变化时应该返回false,这样就不会触发重新剪裁,避免不必要的性能开销。如果剪裁区域会发生变化(比如在对剪裁区域执行一个动画),那么变化后应该返回true来重新执行剪裁。
bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
}
2. 然后,通过ClipRect来执行剪裁,为了看清图片实际所占用的位置,设置一个红色背景:
DecoratedBox(
decoration: BoxDecoration(
color: Colors.red
),
child: ClipRect(
clipper: MyClipper(), // 使用自定义的clipper
child: avatar
),
)
剪裁成功了,但是图片所占用的空间大小仍然是60×60(红色区域),这是因为剪裁是在layout完成后的绘制阶段进行的,所以不会影响组件的大小,这和Transform原理是相似的。
![](https://img.haomeiwen.com/i5111884/e17f04e485976802.png)
12. 标签 Chip、ActionChip、FilterChip、ChoiceChip
const Chip({
Key? key,
this.avatar, // 在左侧显示
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.deleteIcon, // 右侧删除图标
this.onDeleted, // 点击右侧删除图标后回调
this.deleteIconColor, // 右侧删除图标颜色
this.useDeleteButtonTooltip = true, // 长按右侧删除图标是否提示
this.deleteButtonTooltipMessage, // 长按右侧删除图标的提示文本
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor, // 背景色
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
})
可点击的标签
const ActionChip({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
required this.onPressed, // 点击后回调
this.pressElevation,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
})
选中后左侧出现对勾
const FilterChip({
Key? key,
this.avatar,
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.selected = false, // 是否选中
required this.onSelected, // 选中状态改变后回调
this.pressElevation,
this.disabledColor,
this.selectedColor, // 选中背景色
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor, // 阴影色
this.selectedShadowColor,// 选中后的阴影色
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
})
单选标签
const ChoiceChip({
Key? key,
this.avatar,
required this.label, // 文本
this.labelStyle, //
this.labelPadding, //
this.onSelected, // 选中状态改变后回调
this.pressElevation,
required this.selected, // 是否选中
this.selectedColor, // 选中背景色
this.disabledColor,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
this.selectedShadowColor,
this.avatarBorder = const CircleBorder(),
})
/*
const CircleAvatar({
Key? key,
this.child,
this.backgroundColor, // 背景色
this.backgroundImage, // 背景图片
this.onBackgroundImageError,
this.foregroundColor,
this.radius,
this.minRadius,
this.maxRadius,
})
*/
13. 表格 DataTable、PaginatedDataTable
DataTable({
Key? key,
required this.columns, // 顶部的栏目内容
this.sortColumnIndex, // 排序栏的索引号(右侧出现箭头)
this.sortAscending = true, // 排序方式,true升序
this.onSelectAll,
this.decoration,
this.dataRowColor,
this.dataRowHeight,
this.dataTextStyle,
this.headingRowColor,
this.headingRowHeight,
this.headingTextStyle,
this.horizontalMargin,
this.columnSpacing,
this.showCheckboxColumn = true,
this.showBottomBorder = false,
this.dividerThickness,
required this.rows, // 每一行内容
})
栏目
const DataColumn({
required this.label, // 文本
this.tooltip,
this.numeric = false,
this.onSort, // 点击后回调,进行排序,参数(index,isAscending)
})
一行
const DataRow({
this.key,
this.selected = false, // 是否选中,左侧会有选择框
this.onSelectChanged, // 选中状态改变后回调
this.color, // 背景色
required this.cells, //
})
DataRow.byIndex({
int? index,
this.selected = false,
this.onSelectChanged,
this.color,
required this.cells,
})
单元格
const DataCell(
this.child, {
this.placeholder = false,
this.showEditIcon = false,
this.onTap,
})
分页表格
PaginatedDataTable({
Key? key,
this.header, // 表格标题
this.actions,
required this.columns, // 顶部栏目
this.sortColumnIndex, //
this.sortAscending = true, //
this.onSelectAll,
this.dataRowHeight = kMinInteractiveDimension,
this.headingRowHeight = 56.0,
this.horizontalMargin = 24.0,
this.columnSpacing = 56.0,
this.showCheckboxColumn = true,
this.initialFirstRowIndex = 0,
this.onPageChanged, // 页面改变后回调
this.rowsPerPage = defaultRowsPerPage,
this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
this.onRowsPerPageChanged,
this.dragStartBehavior = DragStartBehavior.start,
required this.source, // 创建类,继承DataTableSource,实现相关方法。
})
14. 分割线Divider
const Divider({
Key? key,
this.height, // 高度
this.thickness,
this.indent, // 缩进
this.endIndent,
this.color, // 背景色
})
15. ListTile
1. leading
头部widget
2. trailing
尾部widget
3. minLeadingWidth
头部最小宽(默认40.0)
4. title
标题
5. subtitle
副标题
6. minVerticalPadding
最小的纵向间距(默认4.0)
7. horizontalTitleGap
标题距离头部、尾部的距离(默认16.0)
8. isThreeLine
9. tileColor
未选中的背景色
10. selectedTileColor
选中时的背景色
11. selected
是否选中(默认false)
12. hoverColor
指针悬停时的背景色
13. focusColor
获取焦点时的背景色
14. autofocus
是否自动获取焦点(默认false)
focusNode
16. mouseCursor
在内部或悬停时的鼠标样式
17. shape
形状
18. visualDensity
紧凑程度
19. dense
20. contentPadding
内部边距
21. onTap
点击回调
22. onLongPress
长按回调
23. enableFeedback
是否提供听觉/触觉反馈
24. enabled
是否可交互(默认true)
例
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
),
16. ButtonBarTheme
继承自InheritedWidget
const ButtonBarTheme({
Key? key,
required this.data,
required Widget child,
})
const ButtonBar({
Key? key,
this.alignment,
this.mainAxisSize,
this.buttonTextTheme,
this.buttonMinWidth,
this.buttonHeight,
this.buttonPadding,
this.buttonAlignedDropdown,
this.layoutBehavior,
this.overflowDirection,
this.overflowButtonSpacing,
this.children = const <Widget>[],
})
17. Material
const Material({
Key? key,
this.type = MaterialType.canvas,
this.elevation = 0.0, // 阴影
this.color, //
this.shadowColor, // 阴影色
this.textStyle, // 文本样式
this.borderRadius, // 圆角
this.shape, // 形状
this.borderOnForeground = true,
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
})
18. MaterialApp
用于快速搭建APP框架
通过它可以设置应用的名称、主题、语言、首页及路由列表等。
home:首页widget
initialRoute:使用命名路由时,设置对应的首页路径(默认是'/')
routes:命名路由列表
theme:主题
debugShowCheckedModeBanner :是否显示右上角debug角标
例
MaterialApp(
title: ProjectConfig.packageInfo.appName,
theme: ProjectTheme.theme,
routes: {
'/':(context)=>BootstrapPage(),
'/login':(context)=>LoginPage(),
'/register':(context)=>RegisterPage(),
'/tab':(context)=>TabPage(),
},
)
19. Scaffold
用于快速搭建页面
包含了一个路由页的骨架,有导航栏(AppBar)、抽屉菜单(MyDrawer)、底部导航栏(BottomNavigationBar)、悬浮按钮(FloatingActionButton)。
appBar:导航栏
body:页面内容
bottomNavigationBar:底部tabbar
floatingActionButton:悬浮按钮
floatingActionButtonLocation:悬浮按钮位置
backgroundColor:背景色
drawer:左侧抽屉
endDrawer:右侧抽屉
例
实现一个页面,包含:
一个导航栏
导航栏右边有一个分享按钮
有一个抽屉菜单
有一个底部导航
右下角有一个悬浮的动作按钮
![](https://img.haomeiwen.com/i5111884/92515649bd3eff68.png)
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: 'Flutter Tutorial',
home: new ScaffoldRoute(),
));
}
class ScaffoldRoute extends StatefulWidget {
@override
_ScaffoldRouteState createState() => _ScaffoldRouteState();
}
class _ScaffoldRouteState extends State<ScaffoldRoute> {
int _selectedIndex = 1;
@override
Widget build(BuildContext context) {
// Material必须有Scaffold
return Scaffold(
appBar: AppBar( //导航栏
title: Text("App Name"),
actions: <Widget>[ //导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
// leading: new IconButton(
// icon: new Icon(Icons.menu),
// tooltip: 'Navigation menu',
// onPressed: null,
// ),
),
// body: this._pageList[this._selectedIndex],
drawer: new MyDrawer(), //抽屉
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: <BottomNavigationBarItem>[ // items
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
currentIndex: _selectedIndex, // 当前选中
fixedColor: Colors.blue, // 选种颜色
onTap: _onItemTapped, // 点击后调用
// iconSize:35, // 图标大小
// type: BottomNavigationBarType.fixed 按钮过多时允许设置多个
),
floatingActionButton: FloatingActionButton( //悬浮按钮
child: Icon(Icons.add),
onPressed:_onAdd // 可为null
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onAdd(){
}
}
![](https://img.haomeiwen.com/i5111884/0fb32b65f2a7fc30.png)
AppBar (导航栏)
一个Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等。
AppBar({
Key key,
this.leading, // 导航栏最左侧Widget(常见为抽屉菜单按钮或返回按钮,在首页一般显示logo)。Icon、IconButton
this.automaticallyImplyLeading = true, // 如果leading为null,是否自动实现默认的leading按钮
this.title, // 页面标题Widget
this.actions, // 导航栏右侧Widget列表,在右边 从左至右显示
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, // 标题是否居中
this.backgroundColor, // 背景色
iconTheme // 图标样式
textTheme // 文本样式
centerTitle // 标题是否居中
})
leading
如果给Scaffold添加了抽屉菜单,默认情况下Scaffold会自动将AppBar的leading设置为菜单按钮, 点击它便可打开抽屉菜单。
如果想自定义菜单图标,可以手动来设置leading,如:
Scaffold(
appBar: AppBar(
title: Text("App Name"),
leading: Builder(builder: (context) {
return IconButton(
icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
onPressed: () {
// 打开抽屉菜单 Scaffold.of(context)可以获取父级最近的Scaffold 组件的State对象。
Scaffold.of(context).openDrawer();
},
);
}),
...
)
![](https://img.haomeiwen.com/i5111884/e7cb7cf123bb94cd.png)
TabBar组件(Tab菜单栏)属于Material组件库
TabBar
1. tabs
Tab列表
2. controller
TabController对象
3. isScrollable
是否可滚动
4. indicatorColor
指示器颜色
5. indicatorWeight
指示器高度
6. indicatorPadding
指示器Padding
7. indicatorSize
指示器大小,TabBarIndicatorSize.label 与文本同宽
8. indicator
自定义指示器
9. labelColor
选中文本色
10. labelStyle
选中文本样式
11. labelPadding
文本Padding
12. unselectedLabelColor
未选中文本色
13. unselectedLabelStyle
未选中文本样式
Tab
Tab({
Key key,
this.text, // 菜单文本
this.icon, // 菜单图标
this.child, // 自定义组件样式
})
例(方式1: with SingleTickerProviderStateMixin)
class _ScaffoldRouteState extends State<ScaffoldRoute>
with SingleTickerProviderStateMixin {
TabController _tabController; //需要定义一个Controller
List tabs = ["新闻", "历史", "图片"];
@override
void initState() {
super.initState();
// 创建Controller ,用于控制/监听Tab菜单切换
_tabController = TabController(length: tabs.length, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
... //省略无关代码
bottom: TabBar( //生成Tab菜单
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()
),
),
... //省略无关代码
);
}
![](https://img.haomeiwen.com/i5111884/34296f8813ab8b3e.png)
通过TabBar只能生成一个静态的菜单,真正的Tab页(通过TabBarView来实现)还没有实现。
由于Tab菜单和Tab页的切换需要同步,需要通过TabController去监听Tab菜单的切换去切换Tab页
在initState方法中,创建完成后,添加监听
_tabController.addListener((){
switch(_tabController.index){
case 1: ...;
case 2: ... ;
}
});
如果Tab页可以滑动切换的话,还需要在滑动过程中更新TabBar指示器的偏移!显然,要手动处理这些是很麻烦的。
为此,Material库提供了一个TabBarView组件,通过它不仅可以轻松的实现Tab页,而且可以非常容易的配合TabBar来实现同步切换和滑动状态同步:
Scaffold(
appBar: AppBar(
... //省略无关代码
bottom: TabBar(
controller: _tabController, // 配置controller
tabs: tabs.map((e) => Tab(text: e)).toList()),
),
),
drawer: new MyDrawer(),
body: TabBarView(
controller: _tabController, // 配置controller
children: tabs.map((e) { // 创建3个Tab页,分别对应TabBar中的tabs
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
),
... // 省略无关代码
);
现在,无论是点击导航栏Tab菜单还是在页面上左右滑动,Tab页面都会切换,并且Tab菜单的状态和Tab页面始终保持同步。
TabBar和TabBarView正是通过同一个controller来实现菜单切换和滑动状态同步的。
![](https://img.haomeiwen.com/i5111884/745f6ba8059b809f.png)
例(方式2:DefaultTabController)
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Home'),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: _searchFunc,
)
],
bottom: TabBar(
tabs: <Widget>[
Tab(text: '热门'),
Tab(text: '推荐')
],
),
),
drawer: Drawer(
child: Text('左侧抽屉'),
),
endDrawer: Drawer(
child: Text('右侧抽屉'),
),
body: TabBarView(
children: <Widget>[
ListView(
children: <Widget>[
ListTile(
title: Text('Hot1'),
),
ListTile(
title: Text('Hot2'),
),
],
),
ListView(
children: <Widget>[
ListTile(
title: Text('Reconmend1'),
),
ListTile(
title: Text('Reconmend2'),
),
],
)
],
),
),
);
}
void _searchFunc(){
}
抽屉菜单(Drawer)
Scaffold的drawer和endDrawer属性可以分别接受一个Widget来作为页面的左、右抽屉菜单。
如果开发者提供了抽屉菜单,那么当用户手指从屏幕左(或右)侧向里滑动时便可打开抽屉菜单。
路由跳转
Navigator.pop(); // 隐藏侧边栏
Navigator.pushNamed(context, "/new_page"); // 跳转到新页面
例(一个左抽屉菜单)
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
// 移除抽屉菜单顶部默认留白
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 38.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ClipOval(
child: Image.asset(
"imgs/avatar.png",
width: 80,
),
),
),
Text(
"Wendux",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
child: ListView(
children: <Widget>[
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Manage accounts'),
),
],
),
),
],
),
),
);
}
}
抽屉菜单通常将Drawer组件作为根节点,它实现了Material风格的菜单面板,MediaQuery.removePadding可以移除Drawer默认的一些留白(比如Drawer默认顶部会留和手机状态栏等高的留白)
DrawerHeader 抽屉头部
1. child
子组件
2. decoration
装饰
3. padding
内边距
4. margin
外边距
UserAccountsDrawerHeader 抽屉头部
左侧依次显示头像、名称、邮箱
1. decoration
装饰
2. margin
外边距
3. currentAccountPicture
账户头像
4. accountName
账户名
5. accountEmial
账户邮箱
6. otherAccountsPictures
右侧图片列表(和头像顶部对齐)
FloatingActionButton 浮动按钮
悬浮在页面的某一个位置作为某种常用动作的快捷入口
可以通过Scaffold的floatingActionButton属性来设置一个FloatingActionButton,同时通过Scaffold的floatingActionButtonLocation属性来指定其在页面中悬浮的位置
const FloatingActionButton({
Key? key,
this.child, //
this.tooltip,
this.foregroundColor,
this.backgroundColor,
this.focusColor,
this.hoverColor,
this.splashColor,
this.heroTag = const _DefaultHeroTag(),
this.elevation, // 阴影
this.focusElevation,
this.hoverElevation,
this.highlightElevation,
this.disabledElevation,
required this.onPressed, // 点击后的回调
this.mouseCursor,
this.mini = false,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.materialTapTargetSize,
this.isExtended = false,
})
例
...
floatingActionButton: FloatingActionButton(
onPressed: (){
},
child: Icon(Icons.add,color: Colors.black,size: 55),
tooltip: 'hello', // 长按时显示
backgroundColor: Colors.blue, // 背景色
elevation: 4.0, // 未点击时的阴影
highlightElevation: 12.0, // 点击时的阴影,默认12.0
shape:CircularNotchedRectangle(), // 形状
mini: // 是否是min类型,默认false
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, // centerFloat底部中间,centerDocked底部中间略向下(打洞),默认在底部右侧,
通过在外层添加Container设置大小。Container中设置margin移动位置。
Tab底部导航栏(TabBar、BottomAppBar、BottomNavigationBar)
可以通过Scaffold的bottomNavigationBar属性来设置底部导航
BottomAppBar({
Key? key,
this.color, // 背景色
this.elevation, // 阴影
this.shape,
this.clipBehavior = Clip.none,
this.notchMargin = 4.0,
this.child, //
})
BottomNavigationBar({
Key? key,
required this.items, // BottomNavigationBarItem
this.onTap, // 点击后的回调
this.currentIndex = 0, // 当前选中下标
this.elevation,
this.type, // 布局类型
Color? fixedColor, //
this.backgroundColor, // 背景色
this.iconSize = 24.0, // 图标大小
Color? selectedItemColor, // 选中色
this.unselectedItemColor, // 未选中色
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedFontSize = 14.0, // 选中字体
this.unselectedFontSize = 12.0, // 未选中字体
this.selectedLabelStyle, // 选中字体样式
this.unselectedLabelStyle, // 未选中字体样式
this.showSelectedLabels,
this.showUnselectedLabels,
this.mouseCursor,
})
/*
BottomNavigationBarItem({
required this.icon,
this.title, // 弃用
this.label,
Widget? activeIcon,
this.backgroundColor,
})
*/
例(打洞)(BottomAppBar)
BottomAppBar组件可以和FloatingActionButton配合也可以实现“打洞”效果,源码如下:
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home)),
SizedBox(), //中间位置空出
IconButton(icon: Icon(Icons.business)),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
),
)
上面代码中没有控制打洞位置的属性,实际上,打洞的位置取决于FloatingActionButton的位置,上面FloatingActionButton的位置为:
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
所以打洞位置在底部导航栏的正中间。
BottomAppBar的shape属性决定洞的外形,CircularNotchedRectangle实现了一个圆形的外形,我们也可以自定义外形
![](https://img.haomeiwen.com/i5111884/42116a703e0ea2dc.png)
例(BottomNavigationBar)
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: <BottomNavigationBarItem>[ // items
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
)