Flutter 之 Scaffold (四十五)
1. Scaffold
Scaffold
是一个路由页的骨架,我们可以使用它创建导航栏、抽屉菜单(Drawer)以及底部 Tab 导航菜单等
Scaffold 定义
const Scaffold({
Key? key,
this.appBar,
this.body,
this.floatingActionButton,
this.floatingActionButtonLocation,
this.floatingActionButtonAnimator,
this.persistentFooterButtons,
this.drawer,
this.onDrawerChanged,
this.endDrawer,
this.onEndDrawerChanged,
this.bottomNavigationBar,
this.bottomSheet,
this.backgroundColor,
this.resizeToAvoidBottomInset,
this.primary = true,
this.drawerDragStartBehavior = DragStartBehavior.start,
this.extendBody = false,
this.extendBodyBehindAppBar = false,
this.drawerScrimColor,
this.drawerEdgeDragWidth,
this.drawerEnableOpenDragGesture = true,
this.endDrawerEnableOpenDragGesture = true,
this.restorationId,
})
Scaffold 提供了比较常见的页面属性。
Scaffold属性 | 介绍 |
---|---|
appBar | 页面上方导航条 |
body | 页面容器 |
floatingActionButton | 悬浮按钮 |
floatingActionButtonLocation | 悬浮按钮位置 |
floatingActionButtonAnimator | 悬浮按钮动画 |
persistentFooterButtons | 显示在底部导航条上方的一组按钮 |
drawer | 左侧菜单 |
onDrawerChanged | 左侧菜单回调 |
endDrawer | 右侧菜单 |
onEndDrawerChanged | 右侧菜单回调 |
bottomNavigationBar | 底部导航条 |
bottomSheet | 一个持久停留在body下方,底部控件上方的控件 |
backgroundColor | 背景色 |
resizeToAvoidBottomInset | 默认为 true,防止一些小组件重复 |
primary | 是否在屏幕顶部显示Appbar, 默认为 true,Appbar 是否向上延伸到状态栏,如电池电量,时间那一栏 |
drawerDragStartBehavior | 控制 drawer 的一些特性 |
extendBody | 默认false body 是否延伸到底部控件 |
extendBodyBehindAppBar | 默认 false,为 true 时,body 会置顶到 appbar 后,如appbar 为半透明色,可以有毛玻璃效果 |
drawerScrimColor | 侧滑栏拉出来时,用来遮盖主页面的颜色 |
drawerEdgeDragWidth | 侧滑栏拉出来的宽度 |
drawerEnableOpenDragGesture | 默认 true 左侧侧滑栏是否可以滑动 |
endDrawerEnableOpenDragGesture | 默认 true 右侧侧滑栏是否可以滑动 |
restorationId | 恢复ID 用于保存和恢复Scaffold,不为空时,无论侧滑栏是否打开,Scaffold都将保存和恢复 |
2. 基本使用
class MSScaffoldDemo1 extends StatelessWidget {
const MSScaffoldDemo1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scaffold Demo"),
backgroundColor: Colors.orange,
),
body: Center(child: Text("Scaffold")),
);
}
}
image.png
3. FloatingActionButton
class MSScaffoldDemo2 extends StatelessWidget {
const MSScaffoldDemo2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pets),
onPressed: () {},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
image.png
4. AppBar
AppBar是一个Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等
AppBar({
Key? key,
this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
this.title,// 页面标题
this.actions, // 导航栏右侧菜单
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, //标题是否居中
this.backgroundColor,
... //其它属性见源码注释
})
示例1
class MSScaffoldDemo3 extends StatelessWidget {
const MSScaffoldDemo3({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.dashboard),
onPressed: () {},
),
title: Text("Scaffold Demo"),
elevation: 10.0, // 导航栏阴影
shadowColor: Colors.yellow,
actions: [
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
backgroundColor: Colors.pink,
),
);
}
}
image.png
示例2
class MSScaffoldDemo4 extends StatelessWidget {
MSScaffoldDemo4({Key? key}) : super(key: key);
List _tabs = ["新闻", "历史", "图片"];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
appBar: AppBar(
title: Text("Scaffold Demo"),
bottom: TabBar(
tabs: _tabs.map((e) {
return Container(height: 30, child: Text(e));
}).toList()),
),
body: TabBarView(
children: _tabs.map((e) {
return Center(
child: Text(e, textScaleFactor: 1.5),
);
}).toList(),
),
),
);
}
}
image.png
5. 底部Tab导航栏 bottomNavigationBar
示例1
可以通过Scaffold的bottomNavigationBar属性来设置底部导航,如本节开始示例所示,我们通过Material组件库提供的BottomNavigationBar和BottomNavigationBarItem两种组件来实现Material风格的底部导航栏
class MSScaffoldDemo5 extends StatefulWidget {
const MSScaffoldDemo5({Key? key}) : super(key: key);
@override
State<MSScaffoldDemo5> createState() => _MSScaffoldDemo5State();
}
class _MSScaffoldDemo5State extends State<MSScaffoldDemo5> {
var _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
BottomNavigationBarItem(icon: Icon(Icons.search), label: "搜索"),
],
onTap: (value) {
setState(() {
_currentIndex = value;
});
},
),
body: IndexedStack(
index: _currentIndex,
children: [
MSHomePage(),
MSSearchPage(),
],
),
);
}
}
class MSHomePage extends StatelessWidget {
const MSHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Center(
child: Text("Home", textScaleFactor: 1.5),
),
);
}
}
class MSSearchPage extends StatelessWidget {
const MSSearchPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("搜素")),
body: Center(
child: Text("Search", textScaleFactor: 1.5),
),
);
}
}
image.png
示例2
Material组件库中提供了一个BottomAppBar 组件,它可以和FloatingActionButton配合实现这种“打洞”效果
class MSScaffoldDemo6 extends StatelessWidget {
const MSScaffoldDemo6({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(onPressed: () {}, icon: Icon(Icons.home)),
SizedBox(), // 中间位置空出
IconButton(onPressed: () {}, icon: Icon(Icons.search)),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
image.png
打洞的位置取决于FloatingActionButton的位置
BottomAppBar的shape属性决定洞的外形,CircularNotchedRectangle实现了一个圆形的外形
6. persistentFooterButtons
persistentFooterButtons 显示在底部导航条上方的一组按钮
示例1
class MSScaffoldDemo7 extends StatelessWidget {
const MSScaffoldDemo7({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
persistentFooterButtons: [
IconButton(onPressed: () {}, icon: Icon(Icons.music_note)),
IconButton(onPressed: () {}, icon: Icon(Icons.list)),
IconButton(onPressed: () {}, icon: Icon(Icons.movie)),
],
bottomNavigationBar: BottomAppBar(
color: Colors.orange,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {},
icon: Icon(Icons.home),
padding: EdgeInsets.all(20)),
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
padding: EdgeInsets.all(20)),
],
),
),
);
}
}
image.png
示例2
class MSScaffoldDemo8 extends StatelessWidget {
const MSScaffoldDemo8({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
persistentFooterButtons: [
Row(
children: [
IconButton(onPressed: () {}, icon: Icon(Icons.music_note)),
Expanded(child: SizedBox()),
IconButton(onPressed: () {}, icon: Icon(Icons.movie)),
],
)
],
bottomNavigationBar: BottomAppBar(
color: Colors.orange,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {},
icon: Icon(Icons.home),
padding: EdgeInsets.all(20)),
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
padding: EdgeInsets.all(20)),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
image.png
7. bottomSheet
一个持久停留在body下方,底部控件上方的控件
class MSScaffoldDemo9 extends StatelessWidget {
const MSScaffoldDemo9({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(onPressed: () {}, icon: Icon(Icons.home)),
IconButton(onPressed: () {}, icon: Icon(Icons.search)),
],
),
),
bottomSheet: BottomSheet(
enableDrag: false,
elevation: 10.0,
backgroundColor: Colors.yellow,
builder: (ctx) {
return Container(
height: 60,
color: Colors.cyan,
child: Text('Bottom Sheet'),
alignment: Alignment.center,
);
},
onClosing: () {
print("onClosing");
},
),
);
}
}
image.png
8. drawer / endDrawer
drawer / endDrawer 可以通过点击左上角,右上角按键触发,也可以左滑,右滑触发。
drawerEnableOpenDragGesture 默认为 true,设置 drawer 是否右滑触发
endDrawerEnableOpenDragGesture 默认为 true,设置 endDrawer 是否左滑触发
class MSScaffoldDemo10 extends StatelessWidget {
const MSScaffoldDemo10({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
// 自定义 左侧按钮,弹出抽屉
leading: Builder(builder: (ctx) {
return IconButton(
icon: Icon(Icons.people),
onPressed: () {
Scaffold.of(ctx).openDrawer();
},
);
}),
),
body: Center(
child: Text("Demo"),
),
drawer: MSLeftDrawer(),
endDrawer: MSRightDrawer(),
);
}
}
class MSLeftDrawer extends StatelessWidget {
const MSLeftDrawer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Padding(
padding: EdgeInsets.all(15),
child: ClipOval(
child: Image.asset("assets/images/4.jpeg",
width: 50, height: 50, fit: BoxFit.cover),
),
),
Text("mshi", textScaleFactor: 1.5),
],
),
Expanded(
child: ListView(
children: [
ListTile(leading: Icon(Icons.account_box), title: Text("账号")),
ListTile(leading: Icon(Icons.settings), title: Text("设置")),
],
),
),
],
),
);
}
}
class MSRightDrawer extends StatelessWidget {
const MSRightDrawer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.favorite, color: Colors.red),
Text("我的收藏"),
],
),
),
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: [
ListTile(leading: Icon(Icons.music_note), title: Text("音乐")),
ListTile(leading: Icon(Icons.movie_creation), title: Text("影视")),
],
),
],
),
);
}
}
76.gif
注意
- 默认情况下,如果我们配置了AppBar和Drawer,AppBar左侧会显示一个默认按钮,点击按钮可以打开抽屉。但如果我们自己配置了AppBar的leading,就需要通过Scaffold.of(context).openDrawer()弹出抽屉。
- 直接把ListView 作为Column的子widget,会报错。hasSize。
我们需要约束ListView的高度,有下面几种方式,1.使用Expanded,让ListView自己缩放。2. ListView的shrinkWrap 设置为true,让它包裹内容。3.将ListView 包裹在设定高度的Container中。
- 直接把ListView 作为Column的子widget,会报错。hasSize。
参考:https://www.jianshu.com/p/a0fcb755a7b8
https://book.flutterchina.club/chapter5/material_scaffold.html