一个完整的路由页可能会包含导航栏、抽屉菜单(Drawer)以及底部 Tab 导航菜单等。如果每个路由页面都需要开发者自己手动去实现这些,这会是一件非常麻烦且无聊的事。幸运的是,Flutter Material 组件库提供了一些现成的组件来减少我们的开发任务。Scaffold 是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。
1 Scaffold 的构成
const Scaffold({
super.key,
this.appBar, // 顶部导航栏
this.body, // 内容也
this.floatingActionButton, // 一个悬浮的button
this.floatingActionButtonLocation, // 这个悬浮button 的定位 ,
this.floatingActionButtonAnimator, // 这个悬浮button 的动画 ,
this.persistentFooterButtons, //纸墨设计中定义的固定在界面底部的常用按钮
this.persistentFooterAlignment = AlignmentDirectional.centerEnd,//位置
this.drawer,//抽屉 (左)
this.onDrawerChanged,//抽屉 (左)切换的回调
this.endDrawer,//抽屉 (右)
this.onEndDrawerChanged,//抽屉 (右)切换的回调
this.bottomNavigationBar, //底部导航栏
this.bottomSheet,//位于底部的一个控件 ,返回Widget
this.backgroundColor,//大约是 body的背景色
this.resizeToAvoidBottomInset, // 弹出软键盘遮挡界面的相关操作
//该脚手架是否显示在屏幕顶部。
// 如果为真,那么[appBar]的高度将被高度扩展 屏幕的状态栏,即[MediaQuery]的顶部填充
this.primary = true,
this.drawerDragStartBehavior = DragStartBehavior.start,
// 如果为true,和[bottomNavigationBar]或[persistentFooterButtons]
//,然后[body]延伸到脚手架的底部,
//而不是只延伸到[底部导航栏]的顶部
//或者是[persistentFooterButtons]。
//如果为true,则显示一个底部填充与高度匹配的[MediaQuery]小部件
//[底部导航栏]将被添加到脚手架的[主体]上方。
//当[bottomNavigationBar]有这个属性时,这个属性通常很有用
//一种非矩形形状,如[CircularNotchedRectangle],其中
//在工具条的上边缘添加一个[FloatingActionButton]大小的缺口。
//在这种情况下,指定' extendBody: true '确保脚手架的
//身体将通过底部导航栏的缺口可见。
this.extendBody = false,
//如果为true,并且指定了[appBar],则[body]的高度为
//扩展到包括应用程序栏的高度和身体的顶部
//与应用程序栏的顶部对齐。
//如果应用程序栏的[AppBar。不是
//完全不透明。
//此属性默认为false。它不能为空。
this.extendBodyBehindAppBar = false,
///当抽屉打开时,用于模糊主要内容的棉布的颜色。
///如果这是null,则[DrawerThemeData. com]使用scrimColor]。如果这
///也是null,然后默认为[Colors.black54]。
this.drawerScrimColor,
///横向滑动将打开的区域的宽度
///默认情况下,使用的值是20.0添加到的填充边
/// MediaQuery.of(上下文)。与周围环境相对应的填充
///[TextDirection]。这确保了带槽设备的拖动区域是
///不被遮蔽。例如,如果' TextDirection.of(context) '设置为
///[TextDirection。Ltr], 20.0将添加到
///' MediaQuery.of(上下文).padding.left”。
this.drawerEdgeDragWidth,
///确定[脚手架。可以用拖动器打开的抽屉
///移动手势。
///在桌面平台上,抽屉是不可拖动的。
///默认情况下,拖动手势在移动端是启用的。
this.drawerEnableOpenDragGesture = true,
///确定[脚手架。尾抽屉]可以用一个
///移动手势。
///在桌面平台上,抽屉是不可拖动的。
///默认情况下,拖动手势在移动端是启用的。
this.endDrawerEnableOpenDragGesture = true,
this.restorationId,
}) :
2 AppBar
属性有很多 主要用到属性有下面这几个
AppBar({
super.key,
this.leading, // 左边的按钮 , 返回 ,或 菜单栏按钮
this.automaticallyImplyLeading = true, // 按钮相关(和drawer有关联)
this.title, // 标题
this.actions, // 右边的按钮组 IconButton 或 PopupMenuButton
this.flexibleSpace, // Widget - 一个显示在 AppBar 下方的控件,高度和 AppBar 高度一样,可以实现一些特殊的效果,该属性通常在 SliverAppBar 中使用。
this.bottom, // AppBar的下方的 导航栏 ,(例 全部,未读,已读)
this.elevation,// double - 控件的 z 坐标顺序,默认值为 4,对于可滚动的 SliverAppBar,当 SliverAppBar 和内容同级的时候,该值为 0, 当内容滚动 SliverAppBar 变为 Toolbar 的时候,修改 elevation 的值。
this.scrolledUnderElevation,
this.notificationPredicate = defaultScrollNotificationPredicate,
this.shadowColor,//阴影颜色
this.surfaceTintColor,
this.shape,
this.foregroundColor,
this.backgroundColor,//Color - Appbar 的颜色,默认值为 ThemeData.primaryColor。改值通常和下面的三个属性一起使用。
this.brightness,// Brightness - Appbar 的亮度,有白色和黑色两种主题,默认值为 ThemeData.primaryColorBrightness。
this.iconTheme,// IconThemeData - Appbar 上图标的颜色、透明度、和尺寸信息。默认值为 ThemeData.primaryIconTheme。
this.textTheme,//TextTheme - Appbar 上的文字样式。
this.actionsIconTheme,
this.primary = true,
this.centerTitle,
this.excludeHeaderSemantics = false,
this.titleSpacing,
this.toolbarOpacity = 1.0,
this.bottomOpacity = 1.0,
this.toolbarHeight,
this.leadingWidth,
this.backwardsCompatibility,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
}) :
AppBar 部分属性标注 及下方示例 转载自
作者:iwakevin
链接:https://www.jianshu.com/p/77f8b7ee8460
来源:简书
// 返回每个隐藏的菜单项
SelectView(IconData icon, String text, String id) {
return new PopupMenuItem<String>(
value: id,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Icon(icon, color: Colors.blue),
new Text(text),
],
)
);
}
appBar: new AppBar(
title: new Text('首页'),
leading: new Icon(Icons.home),
backgroundColor: Colors.blue,
centerTitle: true,
actions: <Widget>[
// 非隐藏的菜单
new IconButton(
icon: new Icon(Icons.add_alarm),
tooltip: 'Add Alarm',
onPressed: () {}
),
// 隐藏的菜单
new PopupMenuButton<String>(
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
this.SelectView(Icons.message, '发起群聊', 'A'),
this.SelectView(Icons.group_add, '添加服务', 'B'),
this.SelectView(Icons.cast_connected, '扫一扫码', 'C'),
],
onSelected: (String action) {
// 点击选项的时候
switch (action) {
case 'A': break;
case 'B': break;
case 'C': break;
}
},
),
],
),
3 drawer endDrawer 抽屉 ,或 菜单栏
上面这两个字段的返回是Widget 但一般与Drawer 关联使用
const Drawer({
super.key,
this.backgroundColor,
this.elevation,
this.shape,
this.width,
this.child,
this.semanticLabel,
}) : assert(elevation == null || elevation >= 0.0);
使用技巧
1 MediaQuery.removePaddin
factory MediaQuery.removePadding({
Key? key,
required BuildContext context,
bool removeLeft = false,
bool removeTop = false,
bool removeRight = false,
bool removeBottom = false,
required Widget child,
})
removeTop: true,//移除抽屉菜单顶部默认留白
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key? key,
}) : super(key: key);
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(
"assets/images/caver.webp",
width: 60,
height: 60,
),
),
),
Text(
"名称",
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'),
),
],
),
),
],
),
),
);
}
}
4 bottomNavigationBar 底部导航栏
BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.business), label: "Business"),
BottomNavigationBarItem(icon: Icon(Icons.school), label: "School"),
],
fixedColor: Colors.blue,
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
重点
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
bottomNavigationBar:
BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home),onPressed: (){
},),
SizedBox(), //中间位置空出
IconButton(icon: Icon(Icons.business),onPressed: (){},),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
persistentFooterButtons: [//纸墨设计中定义的固定在界面底部的常用按钮
IconButton(icon: Icon(Icons.business),onPressed: (){},),
IconButton(icon: Icon(Icons.business),onPressed: (){},),
IconButton(icon: Icon(Icons.business),onPressed: (){},),
],
persistentFooterAlignment: AlignmentDirectional.topCenter,
红色的那一块是
bottomSheet: Container(
width: double.infinity,
height: 200,
color: Colors.red,
),
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class MyScaffoldState extends StatefulWidget {
const MyScaffoldState({Key? key}) : super(key: key);
State<MyScaffoldState> createState() => _MyState();
}
class _MyState extends State<MyScaffoldState> {
int _selectedIndex = 1;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// leading: IconButton(icon: Icon(Icons.arrow_back_ios),onPressed: (){},),
title: const Text("MyScaffoldState"),
centerTitle: true,
actions: <Widget>[
IconButton(onPressed: (){}, icon: Icon(Icons.share)),
IconButton(onPressed: (){}, icon: Icon(Icons.share)),
],
),
drawer: MyDrawer(), //抽屉
bottomNavigationBar:
//
BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.business), label: "Business"),
BottomNavigationBarItem(icon: Icon(Icons.school), label: "School"),
],
fixedColor: Colors.blue,
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _onAdd,
),
primary: true,
persistentFooterButtons: [//纸墨设计中定义的固定在界面底部的常用按钮
IconButton(icon: Icon(Icons.business),onPressed: (){},),
IconButton(icon: Icon(Icons.business),onPressed: (){},),
IconButton(icon: Icon(Icons.business),onPressed: (){},),
],
persistentFooterAlignment: AlignmentDirectional.topCenter,
bottomSheet: Container(
width: double.infinity,
height: 200,
color: Colors.red,
),
backgroundColor: Colors.yellow[100],
body: ConstrainedBox(
constraints: BoxConstraints.tightFor(width: double.infinity),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("XXXXX"),
],
),
),
);
}
void _onItemTapped(int index){
setState(() {
_selectedIndex = index;
});
}
void _onAdd(){
}
}
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key? key,
}) : super(key: key);
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(
"assets/images/caver.webp",
width: 60,
height: 60,
),
),
),
Text(
"名称",
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'),
),
],
),
),
],
),
),
);
}
}
//