这篇文章主要讲解有关drawer的一切。android
另:接Flutter相关项目,须要的私信或经过QQ:708959817,联系我markdown
咱们先来看看简单的drawer
在Flutter的应用app
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar,
drawer: _drawer,
);
}
get _appbar=>AppBar(
title: Text('Drawer Test'),
);
get _drawer =>Drawer(
child: Text('This is Drawer'),
);
}
复制代码
而后运行一下项目: 以下图所示 less
能够看到,根据咱们对drawer
的认识,并非想要的结果,因此这个drawer
并不完整,而后咱们继续添加代码,修改drawer
ide
///...
get _drawer => Drawer(
///edit start
child: ListView(
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.lightBlueAccent,
),
child: Center(
child: SizedBox(
width: 60.0,
height: 60.0,
child: CircleAvatar(
child: Text('R'),
),
),
),
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
)
],
),
///edit end
);
复制代码
我这里添加了 ListView
=> 装载抽屉的部件 DrawerHeader
=>抽屉的头部 SizeBox
=> 用于限制CircleAvatar的大小 CircleAvatar
=> 头像部件 ListTile
=> 一个名为"设置"的点击项 而后咱们热部署一下 布局
drawer
嘢!上面那坨灰色的东西是怎么肥事!不急不急,咱们慢慢来分析
由于加了一个DrawerHeader
,因此,咱们须要看看DrawerHeader
里面是什么缘由致使添加灰色的地方 DrawerHeader
源码: 动画
能够看到: Container
=>限制高度(默认高度+状态栏高度) BoxDecoration
=> 底部添加毫无用处的分割线 AnimatedContainer
=>动画版的Container
添加默认内边距+顶部状态栏高度的内边距 嗯,感受没错啊,这是怎么肥事,MediaQuery.of(context).padding.top
是获取状态栏的高度,而后自身高度加上状态栏的高度,应该是显示蓝色才对,那会不会跟ListView
有关系呢? 咱们将DrawerHeader
去掉看看ui
get _drawer => Drawer(
child: ListView(
children: <Widget>[
///edit start
// DrawerHeader(
// decoration: BoxDecoration(
// color: Colors.lightBlueAccent,
// ),
// child: Center(
// child: SizedBox(
// width: 60.0,
// height: 60.0,
// child: CircleAvatar(
// child: Text('R'),
// ),
// ),
// ),
// ),
///edit end
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
)
],
),
);
复制代码
ListView
有关,这是什么缘由致使
ListView
加上一个
statusBarHeight
大小的内边距呢?咱们能够继续找
ListView
的源码
ListView
的构造方法,跳转到455行可看到 1.当
ListView
的属性
padding
为空时,获取
MediaQueryData
的信息
2.由于ListView
的滚动方向默认为垂直,会使用mediaQueryVerticalPadding
this
3.sliver
添加一层MediaQuery
,这个代表sliver
的子部件会使用该MediaQuery
的值,根据判断,子部件会使用mediaQueryHorizontalPadding
,而上面的两个复制:spa
mediaQueryHorizontalPadding
=>将原有的MediaQuery
的padding复制为top
和bottom
都为0,该值会被子部件使用,因此能够知道,DrawerHeader使用了该值,致使statusBarHeader为0 mediaQueryVerticalPadding
=>将原有的MediaQuery
的padding复制为left
和right
都为0
因此,咱们只要不让
ListView
的padding
属性为空就能够了,这里我传入一个zero给ListView,而后把DrawerHeader的注释去掉,热部署一下
get _drawer => Drawer(
child: ListView(
///edit start
padding: EdgeInsets.zero,
///edit end
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.lightBlueAccent,
),
child: Center(
child: SizedBox(
width: 60.0,
height: 60.0,
child: CircleAvatar(
child: Text('R'),
),
),
),
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
)
],
),
);
复制代码
咱们来看看drawer
的源码,其实看源码并非一件痛苦的事,咱们通常直接跳到build方法就好
能够看到Drawer这个部件就是咱们日常的一些部件组合而成 Semantics
=> 语义,用于给无障碍的 ConstrainedBox
=> 限制Drawer的宽度的,以致于Drawer
不会铺满你的屏幕 Material
=> 添加阴影的 咦!听我这样解(Hu)释(Che),是否是对Drawer
这个部件清晰了很多呀! 因此,其实Drawer
就是一个普通的StatelessWidget
,咱们彻底能够定(Fu)制(Zhi)咱们的Drawer
,好比定制Drawer
的滑出大小
class SmartDrawer extends StatelessWidget {
final double elevation;
final Widget child;
final String semanticLabel;
///new start
final double widthPercent;
///new end
const SmartDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
///new start
this.widthPercent = 0.7,
///new end
}) :
///new start
assert(widthPercent!=null&&widthPercent<1.0&&widthPercent>0.0)
///new end
,super(key: key);
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
String label = semanticLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
label = semanticLabel;
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
label = semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel;
}
///new start
final double _width=MediaQuery.of(context).size.width*widthPercent;
///new end
return Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: label,
child: ConstrainedBox(
///edit start
constraints: BoxConstraints.expand(width: _width),
///edit end
child: Material(
elevation: elevation,
child: child,
),
),
);
}
}
复制代码
我这里将原来的Drawer
代码基础上修改_kWidth
的值,把它暴露给用户本身去定制,让他能传入一个double
类型的宽度百分比,弹出根据屏幕的百分之几的Drawer
,该值只容许传入大于0小于1的值,默认为0.7 下面咱们将上面的Drawer改成咱们的SmartDrawer
///edit
get _drawer => SmartDrawer(
widthPercent: 0.4,
///edit
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.lightBlueAccent,
),
child: Center(
child: SizedBox(
width: 60.0,
height: 60.0,
child: CircleAvatar(
child: Text('R'),
),
),
),
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
)
],
),
);
复制代码
Drawer
弹出的大小
监听Drawer
这里官方给咱们埋了一个坑 监听咱们以Tab
为例,Flutter会给我咱们一个XXXController
部件,而Drawer
会不会也会有个DrawerController
呢?
DrawerController
的,而后咱们就将
DrawerController
添加到咱们的
_drawer
中去
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar,
///edit start
drawer: DrawerController(
child: _drawer,
alignment: DrawerAlignment.start,
drawerCallback: (isOpen) {
print('打开状态:$isOpen');
},
),
);
///edit end
}
复制代码
咱们来运行一下吧
AppBar
中左边的按钮是发现,弹出了一个蒙版,
Drawer
并无弹出来,这是怎么回事?别急,咱们开启一下布局边界
Drawer
出现了,这是什么回事?为何要拖动两遍才出现,神奇了?
别急,这一切均可以分析 咱们先来看看
Scaffold
是怎么定义
Drawer
的
Scaffold
源码
该代码比较简单: 1.先判断drawer
是否为空,若不为空添加drawer
_addIfNonNull
该方法从命名能够看出若不为空添加到children里面
这里被添加了一个DrawerController
,可知道Flutter写死了一个DrawerController(这个真的很郁闷,还不把callback
放出来给用户) 由此能够点击_drawerOpendCallback
看看作了什么操做 _drawerOpendCallback
部分代码:
_drawerOpened
,用于
到这里,咱们能够总结:
Scaffold
为咱们添加了一个DrawerController
后,咱们又添加了一个DrawerController
致使须要滑动两次才能显示咱们的Drawer
,因此,咱们能够猜想DrawerController
就是控制弹出跟关闭的一个部件
那么,到这里,咱们基本上想要监听drawer
的弹出跟关闭就是死路一条了。 要怎样监听呢?咱们可不能够经过咱们定制的SmartDrawer
去监听呢? 这里先作一个埋点,先来看一段代码
///edit start
class SmartDrawer extends StatefulWidget {
///edit end
final double elevation;
final Widget child;
final String semanticLabel;
final double widthPercent;
const SmartDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.widthPercent,
}) : assert(widthPercent < 1.0 && widthPercent > 0.0),
super(key: key);
///edit start
@override
_SmartDrawerState createState() => _SmartDrawerState();
///edit end
}
class _SmartDrawerState extends State<SmartDrawer> {
///add start
@override
void initState() {
print('initState');
super.initState();
}
@override
void dispose() {
print('dispose');
super.dispose();
}
///add end
///edit xxx 2 width.xxx start
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
String label = widget.semanticLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
label = widget.semanticLabel;
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
label = widget.semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel;
}
final double _width = MediaQuery.of(context).size.width * widget.widthPercent;
return Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: label,
child: ConstrainedBox(
constraints: BoxConstraints.expand(width: _width),
child: Material(
elevation: widget.elevation,
child: widget.child,
),
),
);
}
}
///edit xxx 2 width.xxx end
复制代码
先把SmartDrawer
的父类由StatelessWidget
改成StatefulWidget
,而后添加部件的两个生命周期(建立和销毁) 而后继续热部署进行使用,正常的打开和关闭Drawer
initState
,每次的关闭会触发
dispose
,这个不就是咱们一直想要的
Drawer
打开和关闭吗? 因而能够改为这样:
class SmartDrawer extends StatefulWidget {
final double elevation;
final Widget child;
final String semanticLabel;
final double widthPercent;
///add start
final DrawerCallback callback;
///add end
const SmartDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.widthPercent,
///add start
this.callback,
///add end
}) : assert(widthPercent < 1.0 && widthPercent > 0.0),
super(key: key);
@override
_SmartDrawerState createState() => _SmartDrawerState();
}
class _SmartDrawerState extends State<SmartDrawer> {
@override
void initState() {
///add start
if(widget.callback!=null){
widget.callback(true);
}
///add end
super.initState();
}
@override
void dispose() {
///add start
if(widget.callback!=null){
widget.callback(false);
}
///add end
super.dispose();
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
String label = widget.semanticLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
label = widget.semanticLabel;
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
label = widget.semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel;
}
final double _width = MediaQuery.of(context).size.width * widget.widthPercent;
return Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: label,
child: ConstrainedBox(
constraints: BoxConstraints.expand(width: _width),
child: Material(
elevation: widget.elevation,
child: widget.child,
),
),
);
}
}
复制代码
如今就能够监听到drawer
的打开了,完美!
到目前为止,咱们使用的drawer
打开按钮都是Scaffold
默认给咱们添加的,咱们能够经过Scaffold
源码看到 Scaffold
源码:
leading
参数的内容,而后判断是否为空和是否自动添加
leading
,若为空,若是存在
Drawer
,
Scaffold
会默认给咱们添加一个
Icon
为
Icons.menu
的
IconButton
,若是不存在,会判断是否能返回,若是能返回,就添加返回按钮。
咱们这里只须要知道,Scaffold
为咱们默认添加一个IconButton 如今,咱们来看一下默认添加的
IconButton
的点击事件
onPressed
作了什么
Scaffold.of(context).openDrawer()
打开drawer,因此,咱们定制弹出
Drawer
按钮能够以下这样写:
//.....
//new start
void _handlerDrawerButton() {
Scaffold.of(context).openDrawer();
}
//new end
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar,
drawer: _drawer,
);
}
get _appbar=>AppBar(
//edit start
leading: IconButton(icon: Icon(Icons.storage), onPressed: _handlerDrawerButton),
//edit end
title: Text('Drawer Test'),
);
//...
复制代码
而后就能够经过该按钮进行点击了,有人可能问,能不能换成其余的按钮形式,答案是能够的,只要点击事件里面调用的是_handlerDrawerButton()
方法
有同窗问我如何禁止手势侧滑出Drawer,咱们只须要修改一个属性便可
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar,
drawer: _drawer,
//new start
drawerEdgeDragWidth: 0.0,
//new end
);
}
复制代码
目前遇到上面的定制问题,本篇文章会继续更新,请持续关注! 若是这篇文章对你有所帮助,但愿能讨个赞,谢谢!