最近 Android 11 系统兼容性测试的时候,发现界面适配异常(标题栏沉浸式部分),已经无法通过网上流行的,通过反射方法获取状态栏的高度了。
于是翻了一下以前公共库里的代码,发现是使用如下代码获取状态栏高度的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * 获取状态栏高度 * */ int statusBarH = 0; try { Class<?> clazz = Class.forName("com.android.internal.R$dimen"); Object object = clazz.newInstance(); int height = Integer.parseInt(clazz.getField("status_bar_height").get(object).toString()); statusBarH = getResources().getDimensionPixelSize(height); } catch (Exception e) { e.printStackTrace(); } return statusBarH; |
其实从 Android 9 开始,就已经对通过反射调用非公开 API 的方式进行警告了。
Android 11以前的系统,只是给出警告, Android 11直接抛出了调用异常。
于是搜索一下,发现网上已经更改成如下方法了:
1 2 3 4 5 6 7 8 |
int statusBarH = 0; //获取status_bar_height资源的ID int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { //根据资源ID获取响应的尺寸值 statusBarH = getResources().getDimensionPixelSize(resourceId); } return statusBarH; |
仔细观察两个方法,会发现,其实两者都是获取了系统里的状态栏使用的某个资源的高度信息,然后作为状态栏的高度信息。
另外,网上流传的另一段代码
1 2 3 |
Rect frame = new Rect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; |
不能解决小屏模式下的显示问题,在小屏模式下得到的偏移并不是正确的状态栏高度。
这样就引发一个问题,那就是如果非官方的系统UI,比如小米,华为等自定义的UI,不使用这个资源文件,或者根本就没有这个资源文件,那么获取到的高度信息不就是不正确的了吗?
比如下图的 Asus EeePad Transformer Prime TF201
另外,如果系统可以动态隐藏状态栏或者根本就没有状态栏(比如某些Wi-Fi平板),或者切换到小屏模式,如下:
注意,在小屏幕模式下,状态栏的高度是不存在的,并且用户可以还原到正常屏幕状态,这个状态下,系统状态栏的高度是动态变化的。但是我们通过上面的代码获取到的高度却是固定的。
其实,从 Android 4.4 开始,系统已经提供了 View.setOnApplyWindowInsetsListener 来动态监听系统状态栏的高度变化。也可以在 View 中重写 onApplyWindowInsets 来实现相同逻辑。
可以参考如下代码:
1 2 3 4 5 6 7 8 |
ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { final int statusBarH = insets.getSystemWindowInsetTop(); setupImBarView(statusBarH); return insets; } }); |
注意,这个函数会被多次调用,并不是只会调用一次。
另外,需要注意的问题在于,有时候我们注册了监听器之后,系统并没有调用通知我们。这个现象的原因在于系统在分发消息过程中,其中的某个View 通过调用 WindowInsets.consumeSystemWindowInsets 拦截了分发流程导致的。
解决这个问题,可以通过调用 View.requestApplyInsets 要求系统分发消息给指定的 View 即可。