Android获取导航栏/状态栏/键盘的高度和状态

最近 Android 11 系统兼容性测试的时候,发现界面适配异常(标题栏沉浸式部分),已经无法通过网上流行的,通过反射方法获取状态栏的高度了。

于是翻了一下以前公共库里的代码,发现是使用如下代码获取状态栏高度的:

/** 
* 获取状态栏高度
* */  
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直接抛出了调用异常。

于是搜索一下,发现网上已经更改成如下方法了:

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;

仔细观察两个方法,会发现,其实两者都是获取了系统里的状态栏使用的某个资源的高度信息,然后作为状态栏的高度信息

另外,网上流传的另一段代码

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 来实现相同逻辑。

可以参考如下代码:

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 即可。

参考链接


发布者

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注