内容简介:我们先来看看简单的然后运行一下项目: 如下图所示可以看到,根据我们对
我们先来看看简单的 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
=> 一个名为"设置"的点击项 然后我们热部署一下
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为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('设置'), ) ], ), ); 复制代码
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
呢?
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
去监听呢? 这里先做一个埋点,先来看一段代码
///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
的打开了,完美!
目前遇到上面的定制问题,本篇文章会继续更新,请持续关注! 如果这篇文章对你有所帮助,希望能讨个赞,谢谢!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- RxJava2源码分析(二):操作符原理分析
- Redis如何分析慢查询操作?
- SpringBoot实战分析-MongoDB操作
- 数据分析之Pandas合并操作总结
- 数据分析——SPSS基本操作初识
- JVM指令分析实例五(操作数栈)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
物联网导论(第2版)
刘云浩 / 科学出版社 / 2013-8 / 45.00元
物联网是一个基于互联网、传统电信网等信息承载体,让所有能够被独立寻址的普通物理对象实现互联互通的网络。它具有普通对象设备化、自治终端互联化和普适服务智能化三个重要特征。 《物联网工程专业系列教材:物联网导论(第2版)》从物联网的感知识别层、网络构建层、管理服务层和综合应用层这四层分别进行阐述,深入浅出地为读者拨开萦绕于物联网这个概念的重重迷雾,引领求知者渐渐步入物联网世界,帮助探索者把握第三......一起来看看 《物联网导论(第2版)》 这本书的介绍吧!