Flutter-drawer
Posted ssjf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter-drawer相关的知识,希望对你有一定的参考价值。
来源:https://www.jianshu.com/p/728692143b09
1. 简介
这篇文章主要讲解有关drawer的一切。
2. 初探
我们先来看看简单的drawer
在Flutter的应用
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‘), ); }
然后运行一下项目:
如下图所示
可以看到,根据我们对drawer
的认识,并不是想要的结果,所以这个drawer
并不完整,然后我们继续添加代码,修改drawer
///... 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
=> 一个名为"设置"的点击项
然后我们热部署一下
Oh,emmm....还是很丑的一个
drawer
嘢!上面那坨灰色的东西是怎么肥事!不急不急,我们慢慢来分析
3 . 解决Drawer灰色头部
因为加了一个DrawerHeader
,所以,我们需要看看DrawerHeader
里面是什么原因导致添加灰色的地方DrawerHeader
源码:
可以看到:Container
=>限制高度(默认高度+状态栏高度)BoxDecoration
=> 底部添加毫无用处的分割线AnimatedContainer
=>动画版的Container
添加默认内边距+顶部状态栏高度的内边距
嗯,感觉没错啊,这是怎么肥事,MediaQuery.of(context).padding.top
是获取状态栏的高度,然后自身高度加上状态栏的高度,应该是显示蓝色才对,那会不会跟ListView
有关系呢?
我们将DrawerHeader
去掉看看
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
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: <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(‘设置‘), ) ], ), );
ok,我们成功解决了Drawer灰色头部
4. 定制Drawer的滑出大小
我们来看看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
弹出的大小
5.监听Drawer的弹出和关闭
监听Drawer
这里官方给我们埋了一个坑
监听我们以Tab
为例,Flutter会给我我们一个XXXController
部件,而Drawer
会不会也会有个DrawerController
呢?
可以看到,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 }
我们来运行一下吧
当我点击
AppBar
中左边的按钮是发现,弹出了一个蒙版,Drawer
并没有弹出来,这是怎么回事?别急,我们开启一下布局边界点击Toggle Debug Paint按钮
会发现,你的布局左边有一条矩形,这个是什么,我们在左边矩形区域拖动一下看看
诶!我们的
Drawer
出现了,这是什么回事?为什么要拖动两遍才出现,神奇了?别急,这一切都可以分析我们先来看看
Scaffold
是怎么定义Drawer
的Scaffold
源码
该代码比较简单:
1.先判断drawer
是否为空,若不为空添加drawer
-
_addIfNonNull
该方法从命名可以看出若不为空添加到children里面 -
这里被添加了一个
DrawerController
,可知道Flutter写死了一个DrawerController(这个真的很郁闷,还不把callback
放出来给用户)
由此可以点击_drawerOpendCallback
看看做了什么操作_drawerOpendCallback
部分代码:
这里将值给了_drawerOpened
,用于
给endDrawer打开做判断,emmm....这个不合理吧!
到这里,我们可以总结:
Scaffold
为我们添加了一个DrawerController
后,我们又添加了一个DrawerController
导致需要滑动两次才能显示我们的Drawer
,所以,我们可以猜测DrawerController
就是控制弹出跟关闭的一个部件
那么,到这里,我们基本上想要监听drawer
的弹出跟关闭就是死路一条了。
要怎样监听呢?我们可不可以通过我们定制的SmartDrawer
去监听呢?
这里先做一个埋点,先来看一段代码
先把SmartDrawer
的父类由StatelessWidget
改为StatefulWidget
,然后添加部件的两个生命周期(创建和销毁)
然后继续热部署进行使用,正常的打开和关闭Drawer
诶,可以看到,每次的打开会触发
initState
,每次的关闭会触发dispose
,这个不就是我们一直想要的Drawer
打开和关闭吗?于是可以改成这样:
现在就可以监听到drawer
的打开了,完美!
6.定制弹出Drawer的按钮
到目前为止,我们使用的drawer
打开按钮都是Scaffold
默认给我们添加的,我们可以通过Scaffold
源码看到Scaffold
源码:
可以看到,获取
leading
参数的内容,然后判断是否为空和是否自动添加leading
,若为空,如果存在Drawer
,Scaffold
会默认给我们添加一个Icon
为Icons.menu
的IconButton
,如果不存在,会判断是否能返回,如果能返回,就添加返回按钮。我们这里只需要知道,
Scaffold
为我们默认添加一个IconButton现在,我们来看一下默认添加的
IconButton
的点击事件onPressed
做了什么调用
Scaffold.of(context).openDrawer()
打开drawer,所以,我们定制弹出Drawer
按钮可以如下这样写:
然后就可以通过该按钮进行点击了,有人可能问,能不能换成其他的按钮形式,答案是可以的,只要点击事件里面调用的是_handlerDrawerButton()
方法
7.禁止手势侧滑出Drawer
有同学问我如何禁止手势侧滑出Drawer,我们只需要修改一个属性即可
@override Widget build(BuildContext context) { return Scaffold( appBar: _appbar, drawer: _drawer, //new start drawerEdgeDragWidth: 0.0, //new end ); }
以上是关于Flutter-drawer的主要内容,如果未能解决你的问题,请参考以下文章