从源码看Flutter状态栏高度之谜

63790fa9541e3f17746ddf0e1b410eb6.png

点击上方蓝字关注我,知识会给你力量

d9984591b018fa608b27973de9726725.png

写过Android的开发者都知道,关于状态栏高度的计算,Google改了一版又一版,依然还有很多兼容性问题,那么来到Flutter这边,问题突然变得简单了,通过下面的方法就可以很方便的获取。

MediaQuery.of(context).padding.top

本来很开心的完成任务了,直到有同事反馈这个方法有时获取的状态栏高度为0,但有时候又是对的,很奇怪,那么到底是什么原因,导致我们间歇性获取不到状态栏高度呢,事出反常必有妖,我们来看下这个方法的实现。fb69ad5cfbf472067be5b139928c1de5.png从注释可以看出,这个方法其实并不是返回状态栏的高度,而是返回Flutter渲染页面被遮挡的部分的尺寸,但由于顶部的被遮挡区,通常就是状态栏或者刘海部分,所以这个方法就可以间接来获取状态栏高度了。

注释里有一句话非常关键,是一个重要的伏笔:If you consumed this padding (e.g. by building a widget that envelops or accounts for this padding in its layout in such a way that children areno longer exposed to this padding)。

再来看看MediaQuery这个类。a325f4eb87050118d1b9b630008e53dc.png这个类是个InheritedModel,这就意味着其子树是可以直接获取到这个数据的,它具体赋值的地方,就是这个fromView方法。0be1fecfd2ffb655a3302cb2223ecce7.png调用的方法。dbec2a5bc5f29f6ca65a758756bf133a.png

老版本是MediaQueryData.fromWindow方法,3.7后废弃了。32c93626cb1fc39d2562dd1e691d633a.png

看到这里就很奇怪了,不管怎么说,肯定是可以拿到padding的值的,那么为什么没有呢,我们注意到源码中有一个removePadding。530a556ad22f3373829258e956ecf358.png唯一会将这个值移除的地方,就是这个removePadding,那么它会在哪些场景下调用呢?

注意到前面的注释了吗,If you consumed this padding (e.g. by building a widget that envelops or accounts for this padding in its layout in such a way that children areno longer exposed to this padding)。

所以,难道说是父布局消耗了这个padding?看不太懂,我们写一个简单的测试代码。

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print('外层获取状态栏高度:${MediaQuery.of(context).padding.top}');
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Builder(builder: (context) {
        print('内层获取状态栏高度:${MediaQuery.of(context).padding.top}');
        return const Text('data');
      }),
    );
  }
}

输出结果如下:

I/flutter (11316): 外层获取状态栏高度:32.0
I/flutter (11316): 内层获取状态栏高度:0.0

果然,在Scaffold内部,就拿不到padding了,我们进入Scaffold的源码看看,哪里有消耗这个padding。dc5cfa1e7259f34255af4d6a79998b45.png不出所料,Scaffold在构建的时候,会有很多的removeTopPadding处理,我们来看具体的逻辑。先看关键的,添加body属性的地方。bc3e4baf85945b3cc249d0eeb753fe85.png这里传入的removeTopPadding值,就是根据appBar判断的,原来如此,当我们传入appBar的时候,removeTopPadding就是true了,这时候,body内就拿不到padding了,我们在上面的测试代码中取得AppBar,再执行下代码,输出如下。

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print('外层获取状态栏高度:${MediaQuery.of(context).padding.top}');
    return Scaffold(
      // appBar: AppBar(
      //   backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      //   title: Text(widget.title),
      // ),
      body: Builder(builder: (context) {
        print('内层获取状态栏高度:${MediaQuery.of(context).padding.top}');
        return const Text('data');
      }),
    );
  }
}

输出:

I/flutter (11316): 外层获取状态栏高度:32.0
I/flutter (11316): 内层获取状态栏高度:32.0

果不其然,现在可以获取了。

既然知道了原因,那么如何解决这个问题呢——「随时随地准确的获取状态栏的高度」。其实很简单了,毕竟之前获取不到是因为被手动remove了,所以我们拿它获取的方法,自己重新写一个就好了。看到前面获取padding的处理。da841cc0442a77380bcbb915f568ddc0.png8649b4435fd3d524a7cd2c1229a0a0e7.png所以,其实算法就是:

WidgetsBinding.instance.window.padding.top / WidgetsBinding.instance.window.devicePixelRatio

但新版本WidgetsBinding.instance.window已经被废弃了,所以最新的方法应该是下面的这样。

double height = MediaQueryData.fromView(PlatformDispatcher.instance.views.first).padding.top;

通过这个方式,可以在任意context下拿到当前状态栏的高度,而不用关心padding的影响。至此,我们终于搞清楚了在Flutter中获取状态栏高度的来龙去脉,不得不说,Flutter最大的好处就是源码透明,我们可以很方便的追踪问题的根源,甚至在发现问题时,可以将源码直接copy出来进行修改。

向大家推荐下我的网站 https://www.yuque.com/xuyisheng  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

往期推荐

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下👇

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值