Flutter之drawer详细分析(你要的操作都有)

栏目: ASP.NET · 发布时间: 5年前

内容简介:我们先来看看简单的然后运行一下项目: 如下图所示可以看到,根据我们对

我们先来看看简单的 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'),
  );
}


复制代码

然后运行一下项目: 如下图所示

Flutter之drawer详细分析(你要的操作都有)
Flutter之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 => 一个名为"设置"的点击项 然后我们热部署一下

Flutter之drawer详细分析(你要的操作都有)
Oh,emmm....还是很丑的一个 drawer

嘢!上面那坨灰色的东西是怎么肥事!不急不急,我们慢慢来分析

3 . 解决Drawer灰色头部

因为加了一个 DrawerHeader ,所以,我们需要看看 DrawerHeader 里面是什么原因导致添加灰色的地方 DrawerHeader 源码:

Flutter之drawer详细分析(你要的操作都有)

可以看到: 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('设置'),
            )
          ],
        ),
      );
复制代码
Flutter之drawer详细分析(你要的操作都有)
确实,跟 ListView 有关,这是什么原因导致 ListView 加上一个 statusBarHeight 大小的内边距呢?我们可以继续找 ListView

的源码

Flutter之drawer详细分析(你要的操作都有)
可以直接点击 ListView 的构造方法,跳转到455行可看到 1.当 ListView 的属性 padding 为空时,获取 MediaQueryData

的信息

2.因为 ListView 的滚动方向默认为垂直,会使用 mediaQueryVerticalPadding

3. sliver 添加一层 MediaQuery ,这个表明 sliver 的子部件会使用该 MediaQuery 的值,根据判断,子部件会使用 mediaQueryHorizontalPadding ,而上面的两个复制:

mediaQueryHorizontalPadding =>将原有的 MediaQuery 的padding复制为 topbottom 都为0,该值会被子部件使用,所以可以知道,DrawerHeader使用了该值,导致statusBarHeader为0 mediaQueryVerticalPadding =>将原有的 MediaQuery 的padding复制为 leftright 都为0

所以,我们只要不让 ListViewpadding 属性为空就可以了,这里我传入一个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('设置'),
            )
          ],
        ),
      );
复制代码
Flutter之drawer详细分析(你要的操作都有)

ok,我们成功解决了Drawer灰色头部

4. 定制Drawer的滑出大小

我们来看看 drawer 的源码, 其实看源码并不是一件痛苦的事,我们一般直接跳到build方法就好

Flutter之drawer详细分析(你要的操作都有)

可以看到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('设置'),
            )
          ],
        ),
      );
复制代码
Flutter之drawer详细分析(你要的操作都有)
可以看到,我们成功的修改了 Drawer

弹出的大小

5.监听Drawer的弹出和关闭

监听 Drawer 这里官方给我们埋了一个坑 监听我们以 Tab 为例,Flutter会给我我们一个 XXXController 部件,而 Drawer 会不会也会有个 DrawerController 呢?

Flutter之drawer详细分析(你要的操作都有)
可以看到,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
  }
复制代码

我们来运行一下吧

Flutter之drawer详细分析(你要的操作都有)
当我点击 AppBar 中左边的按钮是发现,弹出了一个蒙版, Drawer

并没有弹出来,这是怎么回事?别急,我们开启一下布局边界

Flutter之drawer详细分析(你要的操作都有)

点击Toggle Debug Paint按钮

Flutter之drawer详细分析(你要的操作都有)

会发现,你的布局左边有一条矩形,这个是什么,我们在左边矩形区域拖动一下看看

Flutter之drawer详细分析(你要的操作都有)
诶!我们的 Drawer 出现了,这是什么回事?为什么要拖动两遍才出现,神奇了? 别急,这一切都可以分析 我们先来看看 Scaffold 是怎么定义 DrawerScaffold

源码

Flutter之drawer详细分析(你要的操作都有)

该代码比较简单: 1.先判断 drawer 是否为空,若不为空添加 drawer

  1. _addIfNonNull 该方法从命名可以看出若不为空添加到children里面

  2. 这里被添加了一个 DrawerController ,可知道Flutter写死了一个DrawerController(这个真的很郁闷,还不把 callback 放出来给用户) 由此可以点击 _drawerOpendCallback 看看做了什么操作 _drawerOpendCallback 部分代码:

    Flutter之drawer详细分析(你要的操作都有)
    这里将值给了 _drawerOpened ,用于
    Flutter之drawer详细分析(你要的操作都有)
    给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

Flutter之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 的打开了,完美!

目前遇到上面的定制问题,本篇文章会继续更新,请持续关注! 如果这篇文章对你有所帮助,希望能讨个赞,谢谢!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

网络空间导论

网络空间导论

李良荣、方师师 / 复旦大学出版社 / 2018-6-1 / 38

在互联网蓬勃发展的今天,新闻传播学科该往何处去?在长达半个多世纪的深入研究后,李良荣教授及其团队给出了答案:从“小新闻”走向“大传播”,并撰写了这本开创性的教材:《网络空间导论》。 在本书中,互联网被定义为“空间”——持续演进的数字化现实。为了深刻把握网络空间的内涵,本书提供了六个维度的观察:技术应用、组织架构、政治经济、媒介文化、网络素养、安全治理,并以大胆且富有建设性的观点重新定义了新闻......一起来看看 《网络空间导论》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具