参考资料:Flutter之路由系列之LocalhistoryRoute
在Flutter之SnackBar原理详解详细的介绍了SnackBar的使用极其原理,SnackBar主要功能是提供了一个简单的消息,虽然跟用户有一定的交互。但是其目的主要是提示性消息。且会自动消失。除了SnackBar之外,Flutter又提供了一个BottomSheet,该组件可以在屏幕底部展示了一个可供用户交互功能的页面。

通过本篇博文你可以了解到:
1、showBottomSheet和showModalBottomSheet的区别
2、关闭BottomSheet的方式
3、BottomSheet的基本原理

Flutter提供了两种展示BottomSheet的方法,showBottomSheet和showModalBottomSheet。下面就来逐一分析这两种方法的区别,看下图(图片展示的demo源码在本篇博文最后提供):

如上图左边的是通过showBottomSheet展示的BottomSheet,右边的是使用showModalBottomSheet展示的BottomSheet。(注意,除了背景颜色不一样,二者布局代码全部一样)。
1、直观上来看二者的不同之处在于:使用showBottomSheet显示的时候,底部导航栏没有被遮挡;而使用了showModalBottomSheet展示的页面会把底部导航栏给遮挡住。
2、操作上来看二者的不同之处在于:点击BottomSheet显示区域之外的地方二者表现有所不同。使用showBottomSheet的方式,点击红色区域之外的地方,BottomSheet不会自动关闭;而使用了showModalBottomSheet,点击绿色区域之外的话,BottomSheet会自动关闭。
3、两者共同之处在于关闭BottomSheet的方式一样,都是用了Navigator.pop(context)关闭:
4、手指按住红色或者绿色区域向下滑动,滑动一定的距离后可以关闭BottomSheet

showBottomSheet实现原理简析

因为使用该方式展示的BottomSheet,点击其他区域的时候该BottomSheet不会消失,所以连续点击左边图的“showBottomSheet”按钮会有如下效果:连续点击的时候会先关闭之前的BottomSheet,然后重新打开一个新的BottomSheet:

下面就来具体分析其原理。在阅读下文之前,建议读者读读Flutter之GlobalKey详解,在这里直接说下Globalkey的作用:持有当前StatefulWidget的StatefulElement对象,我们通过此对象可以获取到当前StatefulWidget的State,从而操控State的方法,比如FormState的validate()方法进行非空校验。Flutter页面随着state的改变,确切的说是随着 setState(() { })方法的调用会调用build方法刷新页面,所以我们可以通过GlobalKey拿到最新的state状态。

1、showBottomSheet简析

下面进入showBottomSheet源码分析阶段,具体是Scaffold.of(context).showBottomSheet<T>方法:

 //ScaffoldState的showBottomSheet方法PersistentBottomSheetController<T> showBottomSheet<T>( WidgetBuilder builder, {//省略部分参数}) {//1、删除目前正在展示的BottomSheet_closeCurrentBottomSheet();//2、为BottomSheet添加动画控制器final AnimationController controller = BottomSheet.createAnimationController(this)..forward();//3、调用setState方法,会引起Scaffold的重绘setState(() {//4、创建新的BottomSheet_currentBottomSheet = _buildBottomSheet<T>(builder,//省略一些其他参数);});return _currentBottomSheet as PersistentBottomSheetController<T>;}

showBottomSheet主要做了如下工作:
1、删除当前正在展示的BottomSheet,具体效果见上面gif图
2、调用_buildBottomSheet方法创建PersistentBottomSheetController对象,赋值给_currentBottomSheet
3、调用setState方法,会调用Scafflod的build,重绘Scaffold。从而展示BottomSheet.
PersistentBottomSheetController顾名思义,用来控制当前展示的BottomSheet,比如BottomSheet的关闭等.这个类的功能有点类似于在SnackBar的ScaffoldFeatureController。现在大致看一下PersistentBottomSheetController的结构,它包含了BottomSheet的布局Widget。这个widget是通过_buildBottomSheet方法创建的,最终showBottomSheet方法的builder参数会构建成一个_StandardBottomSheet 的Widget,并交给PersistentBottomSheetController持有。

2、_StandardBottomSheet 的简单说明

上文通过分析showBottomSheet方法,我们知道其方法参数builder最终会通过_buildBottomSheet构建出一个_StandardBottomSheet 对象,下面就具体看看_buildBottomSheet怎么创建_StandardBottomSheet,进而再将_StandardBottomSheet交给一个PersistentBottomSheetController对象的:

PersistentBottomSheetController<T> _buildBottomSheet<T>(WidgetBuilder builder,bool isPersistent, //默认是false{ }) {//创建一个completer,主要交给PersistentBottomSheetController使用final Completer<T> completer = Completer<T>();final GlobalKey<_StandardBottomSheetState> bottomSheetKey = GlobalKey<_StandardBottomSheetState>();_StandardBottomSheet bottomSheet;bool removedEntry = false;//用来处理关闭BottomSheet的方法void _removeCurrentBottomSheet() {//省略关闭BottomSheet的操作,下面会详细说明}//本地历史实体,主要用来进行路由控制final LocalHistoryEntry entry = isPersistent? null: LocalHistoryEntry(onRemove: () {if (!removedEntry) {_removeCurrentBottomSheet();}});//最终形成一个_StandardBottomSheetbottomSheet = _StandardBottomSheet(key: bottomSheetKey,//省略部方法builder: builder,,);//将entry添加到路由里if (!isPersistent)ModalRoute.of(context).addLocalHistoryEntry(entry);return PersistentBottomSheetController<T>._(bottomSheet,completer,entry != null? entry.remove: _removeCurrentBottomSheet,(VoidCallback fn) { bottomSheetKey.currentState?.setState(fn); },!isPersistent,);}

因为_buildBottomSheet方法里涉及到了LocalHistoryRoute的相关知识,其主要作用就是讲LocalHistoryEntry 添加到LocalHistoryRoute中,这样当 Navigator.of(context).pop()来返回上一步的时候,就会将之前添加的LocalHistoryEntry 弹出来,并执行LocalHistoryEntry 的onRemove方法,从而关闭了当前展示的BottomSheet。关于LocalHistoryRoute可阅读此博客了解更多。在上面_buildBottomSheet代码中初始化LocalHistoryEntry 操作如下:

  //初始化LocalHistoryEntry final LocalHistoryEntry entry = isPersistent? null: LocalHistoryEntry(onRemove: () {if (!removedEntry) {_removeCurrentBottomSheet();}});//将LocalHistoryEntry添加到LocalHistoryRoute中     if (!isPersistent)ModalRoute.of(context).addLocalHistoryEntry(entry);

Navigator.of(context).pop()的时候就会执行LocalHistoryEntry 的onRemove方法,进而执行_removeCurrentBottomSheet方法:

   void _removeCurrentBottomSheet() {removedEntry = true;//如果当前BottomSheet已经删除了,就不在删除if (_currentBottomSheet == null) {return;}_showFloatingActionButton();void _closed(void value) {//执行关闭方法,也就是将_currentBottomSheet设置为null,掉用setState刷新页面setState(() {_currentBottomSheet = null;});if (animationController.status != AnimationStatus.dismissed) {_dismissedBottomSheets.add(bottomSheet);}completer.complete();}final Future<void> closing = bottomSheetKey.currentState.close();if (closing != null) {closing.then(_closed);} else {_closed(null);}}

_removeCurrentBottomSheet方法会执行其内部的_closed方法,_closed方法先想_currentBottomSheet 设置为null,然后调用setState方法,是的Scaffold回调其build方法进行页面的重绘,因为_currentBottomSheet 设置了null,所以页面重绘的时候不会展示BottomSheet,进而关闭了BottomSheet. 下面就来看看Scaffold build方法

3、BottomSheet在Scaffold build方法的构建过程

因为showBottomSheet会调用setState方法,从而回调了Scaffold的build方法,所以在此在看看其build方法:

  @overrideWidget build(BuildContext context) {final List<LayoutId> children = <LayoutId>[];//如果_currentBottomSheet不等于nullif (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {//将_currentBottomSheet和_dismissedBottomSheets集合里的BottomSheets放在Stackfinal Widget stack = Stack(//底部居中展示alignment: Alignment.bottomCenter,children: <Widget>[..._dismissedBottomSheets,if (_currentBottomSheet != null) _currentBottomSheet._widget,],);//将Stack使用LayoutI包裹然后放入到children 集合_addIfNonNull(children,stack,  );return _ScaffoldScope(//省略部分代码child: PrimaryScrollController(child: AnimatedBuilder(//省略部分参数) {return CustomMultiChildLayout(//使用children数据构建页面children: children,//省略部分代码,);}),),),);}}

build方法的逻辑其实跟处理SnackBar的展示逻辑上总体差不多
1、将当前的BottomSheet放在Stack中,另外有一个_dismissedBottomSheets集合,如果该集合不为空的话,也要将集合中的BottomSheet添加到Stack中(常规情况下应该为空,后面会有说明)
2、将Stack调用_addIfNonNull方法包裹在LayoutId中,然后将之添加到children集合
3、最终build方法会使用children集合构成我们最终的UI

最后附上本篇博文的demo代码如下:

class BottomSheetDemo extends StatelessWidget {Widget build(BuildContext context) {return Center(child: RaisedButton(child: const Text('showModalBottomSheet'),onPressed: () {//             showModalBottomSheet<void>(///使用showModalBottomSheet的方式showModalBottomSheet<void>(///使用showBottomSheet的方式context: context,builder: (BuildContext context) {return _createBottomContent(context);},);},),);}Widget _createBottomContent(BuildContext context) {return Container(height: 300,color: Colors.green,child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.min,children: <Widget>[const Text('绿色区域一个Bottom Sheet'),RaisedButton(child: const Text('关闭Bottom Sheet'),onPressed: () => Navigator.pop(context),)],),),);}
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home:Scaffold(body:  BottomSheetDemo (),bottomNavigationBar:BottomNavigationBar(items: const <BottomNavigationBarItem>[BottomNavigationBarItem(icon: Icon(Icons.home),title: Text('Home'),),BottomNavigationBarItem(icon: Icon(Icons.business),title: Text('Business'),),BottomNavigationBarItem(icon: Icon(Icons.school),title: Text('School'),),],currentIndex: 0,) ,),);}
}

Flutter之BottomSheet相关推荐

  1. Flutter 踩坑 在bottomNavigationBar下显示bottomSheet

    原文转自:http://ddrv.cn/a/333884 在我们的应用程序中,我们使用了bottomSheet和bottomNavigationBar. bottomSheet出现在bottomNav ...

  2. Flutter 入门指北(Part 9)之弹窗和提示(SnackBar、BottomSheet、Dialog)

    该文已授权公众号 「码个蛋」,转载请指明出处 前面的小节把常用的一些部件都介绍了,这节介绍下 Flutter 中的一些操作提示.Flutter 中的操作提示主要有这么几种 SnackBar.Botto ...

  3. Flutter Exception降到万分之几的秘密

    1. flutter exception 闲鱼技术团队于2018年上半年率先引入了Flutter技术实现客户端开发,到目前为止成功改造并上线了复杂的商品详情和发布业务.随着flutter比重越来越多, ...

  4. Flutter开发之BottomSheetDialog选择组件-5(44)

    BottomSheetDialog.ModalBottomSheetDialog同样也是需要借助showDialog唤起,就跟它名字一样,这两种dialog是从屏幕下方向上弹出的,不同的是Bottom ...

  5. 【Flutter】底部导航栏实现 ( BottomNavigationBar 底部导航栏 | BottomNavigationBarItem 导航栏条目 | PageView )

    文章目录 一.Scaffold 组件 二.底部导航栏整体架构 三.BottomNavigationBar 底部导航栏 四.BottomNavigationBarItem 导航栏条目 五.PageVie ...

  6. 【Flutter】顶部导航栏实现 ( Scaffold | DefaultTabController | TabBar | Tab | TabBarView )

    文章目录 一.Scaffold 组件 二.实现顶部导航栏 三.DefaultTabController 导航标签控制组件 四.TabBar 导航按钮组件 五.Tab 标签组件 六.TabBarView ...

  7. 【Flutter】StatefulWidget 组件 ( 创建 StatefulWidget 组件 | MaterialApp 组件 | Scaffold 组件 )

    文章目录 一.StatefulWidget 组件 二.创建 StatefulWidget 组件 三.MaterialApp 组件 四.Scaffold 组件 五. 相关资源 一.StatefulWid ...

  8. Unity的Flutter——UIWidgets简介及入门

    介绍 UIWidgets(https://github.com/UnityTech/UIWidgets)是Unity编辑器的一个插件包,可帮助开发人员通过Unity引擎来创建.调试和部署高效的跨平台应 ...

  9. Flutter入门篇(一)

    距离Google发布Flutter已经有很长一段时间了,又是一门新的技术,那么我们到底是学呢还是学呢还是学呢?不要问我,我不知道,鬼特么知道我这辈子还要学习多少东西.其实新技术的出现也意味着,老技术会 ...

  10. 揭秘!如何用Flutter设计一个100%准确的埋点框架?

    阿里妹导读:用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失.闲鱼将业务代码从Native迁移到Flutter上过程中,发现 ...

最新文章

  1. HTML初级知识点总结(1.0)
  2. spring --(12)bean的生命周期
  3. WIN7系统共享访问方式总结
  4. 如何做好iOS应用安全?这有一把行之有效的“三板斧”
  5. 怎样让防火墙跟其他网络设备实现时钟同步
  6. hadoop矩阵乘法源码_使用Hadoop计算共现矩阵
  7. Maven原型创建技巧
  8. 11.SolrJ索引操作
  9. php 下拉菜单多选get,Jquery实现select二级联动多选下拉菜单
  10. 《精通并发与Netty》学习笔记(02 - 服务端程序编写)
  11. 苹果修复已遭在野利用的 iOS 和 macOS 0day
  12. ASP.NET之Application、Session和Cookie的差别
  13. 开关造成的毛刺_解决交易中的毛刺问题,你可以这样做
  14. verilog中generate用法及参数传递(转)
  15. 距离向量算法与链路状态算法(RIP、OSPF)
  16. idea 2018 破解教程
  17. 支付宝当面付扫码支付支付后不回调_【支付宝支付】支付宝手机网站支付流程...
  18. Python 安装pyinstaller失败的解决方法
  19. 计算机主机上有几个按钮,键盘按键有什么功能 电脑键盘上各个按键功能详解...
  20. hdu 3625 Examining the Rooms

热门文章

  1. 设置java heap_JAVA HEAP SPACE解决方法和JVM参数设置
  2. canvas需要gpu_提高HTML5 canvas性能的几种方法(转)
  3. Javascript:js借助jQuery和fileSave将表格存储到world
  4. Cesium:实现漫游飞行
  5. sweetalert2使用教程
  6. dedecms读取多个类别信息
  7. 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。
  8. PCL点云参数估计算法之RANSAC和LMEDS
  9. 物体检测中的mAP含义
  10. P5231 [JSOI2012]玄武密码