Flutter 获取状态栏高度等于0,为啥?
前言
MediaQuery写过Flutter应用的小伙伴大家应该都不陌生,通过他可以获取设备上的一些属性,比如屏幕宽高、横竖屏、状态栏高度等信息。
MediaQuery
为了彻底弄清这个问题,我翻了MediaQuery
的源码,最后发现跟人家一点关系都没有。
MediaQuery.of(context).padding.top = 0 ?
,在有的页面可以正常获取,有的页面就是0,这是为啥呢?
我们先来看下MediaQuery
这个类,他继承了InheritedWidget
数据共享类,这个类里的数据可以从根布局一直向下传递给他的子节点。
class MediaQuery extends InheritedWidget {
/// Creates a widget that provides [MediaQueryData] to its descendants.
///
/// The [data] and [child] arguments must not be null.
const MediaQuery({
Key? key,
required this.data,
required Widget child,
}) : assert(child != null),
assert(data != null),
super(key: key, child: child);
接下来我们看下padding
这个参数,直接看注释,注释中说明,这个参数是系统遮挡用户界面的部分,也就是说,状态栏其实不属于用户交互的区域,因为他被系统UI遮挡了,所以这里我们可以通过这个参数间接的获取状态栏,比如iOS上的刘海屏的刘海高度也属于这个数据,并且在下面有一句有意思的话,如果父布局使用这个数据,那么子布局就不要暴露这个数据了,嗯?这啥意思,难道我获取不到的组件的父布局把这个数据重置为0了吗?
接着往下我们继续看下这个高度是如何设置的,我们可以看如下代码,在
fromWindow
这个方法里winddow窗口直接设置了padding
的四个边距,那么只要根布局使用了 MediaQuery
这个类,在子布局中是一定能拿到这个数据的,如果拿不到,那一定是拿不到节点的父布局在搞事情了。
核心代码:
@override
@override
Widget build(BuildContext context) {
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
if (!kReleaseMode) {
data = data.copyWith(platformBrightness: debugBrightnessOverride);
}
return MediaQuery(
data: data,
child: widget.child,
);
}
MediaQueryData.fromWindow(ui.SingletonFlutterWindow window)
: size = window.physicalSize / window.devicePixelRatio,
/// 略
padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
/// 略
EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio)
: left = padding.left / devicePixelRatio,
top = padding.top / devicePixelRatio,
right = padding.right / devicePixelRatio,
bottom = padding.bottom / devicePixelRatio;
接着往下看,我是在什么情况下拿不到数据的呢,看下方关键代码,我这里创建了一个基类,body
内容动态获取,那么请问在body
里的widget是否能获取状态栏高度呢,答案是能,也不能。
@override
Widget build(BuildContext context) {
print('stateHeight:${MediaQuery.of(context).padding.top}');
return Scaffold(
appBar: AppBar(title: Text("appBar"),),
body: _buildBody(),
);
}
Widget _buildBody() {
return widget.widget;
}
what? 啥情况,能是不加appBar
属性就能获取,不能是加了appBar
就=0,为啥有这种操作?还记得上面的官方注释吗,意思是说父布局如果用了padding
这个属性,那么这个属性对于子布局就不在暴露了,因为这个属性并不是专门的获取状态栏高度的方法,是获取遮挡屏幕宽高的方法,那么子布局对于屏幕而言就是就没有遮挡了,自然就应该返回0,所以真相就只有一个,Scaffold
一定用了状态栏高度这个属性,并且Scaffold
对这个数据进行了归0操作,并且跟是否设置AppBar
还有关系。
下面我们翻翻Scaffold
的源码看看,
果然用到了这个属性,并且通过
primary
这个字段控制,默认为true
,所以我们的AppBar
才会默认把状态栏高度算在了里面,其实就是appBar
的最大高度就是加了一个这个属性而已,我们找到了父组件确实在使用这个属性,那么他是在哪里把这个属性归0了呢,我们知道我们子组件是属于Scaffold
的body
属性,那么我们接着看下body这个属性做了啥,
我们看到在
build
方法中body
套了一个壳,并且我们看到这么一个属性
removeTopPadding: widget.appBar != null,
字面意思好像就是如果AppBar
不为空,这个字段设置true
, 那我们接着看下这个方法里_addIfNonNull()
究竟是什么,
这里面的根节点就是
MediaQuery
,并且通过removePadding
这个构造从新对padding进行了赋值,看下这个方法,
到这里终于真相大白了,就是
Scaffold
内部搞的鬼。不过这也正是说明了这个属性并不是真正的状态栏高度,他只是系统遮挡当前组件的边距而已。 只是当这个组件被屏幕遮挡了,这个数据才会有效,毕竟Flutter
原始画风就是黑底的全屏显示。
所以通过上方代码我们可以得到,真正的获取状态栏高度方法是:
double stateHeight = WidgetsBinding.instance!.window.padding.top / WidgetsBinding.instance!.window.devicePixelRatio;
// 简写
double stateHeight = MediaQueryData.fromWindow(window).padding.top;
小结
以前我们可能只是知道这个属性就是状态栏高度的意思,通过本次总结,我们重新认识了这个属性官方究竟是表达了什么意思,只是当组件直接在屏幕上显示的时候恰好状态栏遮挡了我们的显示区域,所有我们可以获取到这个属性。好了,本篇文章就总结到这里,希望对你有所帮助,如有问题,欢迎评论指正~
转载自:https://juejin.cn/post/7091929196234014757