Flutter之drawer详细分析(你要的操作都有)
1. 简介
这篇文章主要讲解有关drawer的一切。2. 初探 我们先来看看简单的
另:接Flutter相关项目,需要的私信或通过QQ:708959817,联系我
drawer
在Flutter的应用class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}class _HomePageState extends State {
@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'),
);
}
然后运行一下项目:
如下图所示
文章图片
image.png
文章图片
image.png 可以看到,根据我们对
drawer
的认识,并不是想要的结果,所以这个drawer
并不完整,然后我们继续添加代码,修改drawer
///...get _drawer => Drawer(
///edit start
child: ListView(
children: [
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
=> 一个名为"设置"的点击项然后我们热部署一下
文章图片
image.png
Oh,emmm....还是很丑的一个
drawer
嘢!上面那坨灰色的东西是怎么肥事!不急不急,我们慢慢来分析
3 . 解决Drawer灰色头部 因为加了一个
DrawerHeader
,所以,我们需要看看DrawerHeader
里面是什么原因导致添加灰色的地方DrawerHeader
源码:文章图片
image.png
可以看到:
Container
=>限制高度(默认高度+状态栏高度)BoxDecoration
=> 底部添加毫无用处的分割线AnimatedContainer
=>动画版的Container
添加默认内边距+顶部状态栏高度的内边距嗯,感觉没错啊,这是怎么肥事,
MediaQuery.of(context).padding.top
是获取状态栏的高度,然后自身高度加上状态栏的高度,应该是显示蓝色才对,那会不会跟ListView
有关系呢?我们将
DrawerHeader
去掉看看get _drawer => Drawer(
child: ListView(
children: [
///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('设置'),
)
],
),
);
文章图片
image.png
确实,跟
ListView
有关,这是什么原因导致
ListView
加上一个
statusBarHeight
大小的内边距呢?我们可以继续找
ListView
的源码
文章图片
image.png
可以直接点击
ListView
的构造方法,跳转到455行可看到
1.当
ListView
的属性
padding
为空时,获取
MediaQueryData
的信息
2.因为
ListView
的滚动方向默认为垂直,会使用mediaQueryVerticalPadding
3.
sliver
添加一层MediaQuery
,这个表明sliver
的子部件会使用该MediaQuery
的值,根据判断,子部件会使用mediaQueryHorizontalPadding
,而上面的两个复制:mediaQueryHorizontalPadding
=>将原有的MediaQuery
的padding复制为top
和bottom
都为0,该值会被子部件使用,所以可以知道,DrawerHeader使用了该值,导致statusBarHeader为0mediaQueryVerticalPadding
=>将原有的MediaQuery
的padding复制为left
和right
都为0所以,我们只要不让ListView
的padding
属性为空就可以了,这里我传入一个zero给ListView,然后把DrawerHeader的注释去掉,热部署一下
get _drawer => Drawer(
child: ListView(
///edit start
padding: EdgeInsets.zero,
///edit end
children: [
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('设置'),
)
],
),
);
文章图片
image.png
ok,我们成功解决了Drawer灰色头部
4. 定制Drawer的滑出大小 我们来看看
drawer
的源码,其实看源码并不是一件痛苦的事,我们一般直接跳到build方法就好文章图片
image.png
可以看到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: [
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('设置'),
)
],
),
);
文章图片
image.png
可以看到,我们成功的修改了
Drawer
弹出的大小
5.监听Drawer的弹出和关闭 监听
Drawer
这里官方给我们埋了一个坑监听我们以
Tab
为例,Flutter会给我我们一个XXXController
部件,而Drawer
会不会也会有个DrawerController
呢?文章图片
image.png
可以看到,Flutter是有一个
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
}
我们来运行一下吧
文章图片
image.png
当我点击
AppBar
中左边的按钮是发现,弹出了一个蒙版,
Drawer
并没有弹出来,这是怎么回事?别急,我们开启一下布局边界
文章图片
image.png
点击Toggle Debug Paint按钮
文章图片
image.png
会发现,你的布局左边有一条矩形,这个是什么,我们在左边矩形区域拖动一下看看
文章图片
image.png
诶!我们的
Drawer
出现了,这是什么回事?为什么要拖动两遍才出现,神奇了?
别急,这一切都可以分析
我们先来看看
Scaffold
是怎么定义
Drawer
的
Scaffold
源码
文章图片
image.png
该代码比较简单:
1.先判断
drawer
是否为空,若不为空添加drawer
_addIfNonNull
该方法从命名可以看出若不为空添加到children里面
- 这里被添加了一个
DrawerController
,可知道Flutter写死了一个DrawerController(这个真的很郁闷,还不把callback
放出来给用户)
由此可以点击_drawerOpendCallback
看看做了什么操作
_drawerOpendCallback
部分代码:
文章图片
image.png
这里将值给了_drawerOpened
,用于
文章图片
image.png
给endDrawer打开做判断,emmm....这个不合理吧!
到这里,我们可以总结:那么,到这里,我们基本上想要监听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 {///add start
@override
void initState() {
print('initState');
super.initState();
}
@override
void dispose() {
print('dispose');
super.dispose();
}
///add end///edit xxx 2width.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 2width.xxx end
先把
SmartDrawer
的父类由StatelessWidget
改为StatefulWidget
,然后添加部件的两个生命周期(创建和销毁)然后继续热部署进行使用,正常的打开和关闭
Drawer
文章图片
image.png
诶,可以看到,每次的打开会触发
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 {@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
的打开了,完美!6.定制弹出Drawer的按钮 到目前为止,我们使用的
drawer
打开按钮都是Scaffold
默认给我们添加的,我们可以通过Scaffold
源码看到Scaffold
源码:文章图片
image.png
可以看到,获取
leading
参数的内容,然后判断是否为空和是否自动添加
leading
,若为空,如果存在
Drawer
,
Scaffold
会默认给我们添加一个
Icon
为
Icons.menu
的
IconButton
,如果不存在,会判断是否能返回,如果能返回,就添加返回按钮。
我们这里只需要知道,
Scaffold
为我们默认添加一个IconButton
现在,我们来看一下默认添加的
IconButton
的点击事件
onPressed
做了什么
文章图片
image.png
调用
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()
方法7.禁止手势侧滑出Drawer 有同学问我如何禁止手势侧滑出Drawer,我们只需要修改一个属性即可
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar,
drawer: _drawer,
//new start
drawerEdgeDragWidth: 0.0,
//new end
);
}
【Flutter之drawer详细分析(你要的操作都有)】目前遇到上面的定制问题,本篇文章会继续更新,请持续关注!
如果这篇文章对你有所帮助,希望能讨个赞,谢谢!
推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天
- python学习之|python学习之 实现QQ自动发送消息