安卓开发的同学们都知道,在项目中经常需要获取屏幕的宽高来做UI适配。经常用的一个方法是:
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
通常来讲,使用这种方法可以获取到正确的宽高值。但是在最近的一个项目中,app设置为全屏。物理宽高为1024x600的情况下,使用这个方法获取到的分辨率竟然是1024x552。这就诡异了,通过在网上查找资料发现,自安卓4.4之后,使用这个方法获取宽高是不完备的,原因是4.4后有了虚拟导航栏这个选项。但是app中并没有定义虚拟导航栏啊,别着急,继续往下找,发现除了app定义之外,还有一个系统选项可以定义这个导航栏。也就是qemu.hw.mainkeys。需要注意的是,这个选项为0时表示有导航栏,为1则反之。
问题找到了,怎么解决呢?
- 很明显的方案就是如果想继续沿用getMetrics方法而又不需要虚拟导航栏,那么就把qemu.hw.mainkeys设置为1,或者直接去掉。有些同学表示,也许我并没有ROM权限,无法修改系统参数,那怎么办呢?这个时候我们就需要方法2了。
- 我们可以使用接口getRealMetrics()来代替,这个方法不会将虚拟导航栏的高度计算进去。
延伸一下,具体的原理在哪里呢?答案就在phoneWindowManager的getNonDecorDisplayHeight方法,这其中有几行代码这样写的
if( displayId == DEFAULT_DISPLAY && mHasNavigationBar ) {
if(!mNavigationBarCanMove || fullWidth < fullHeight) {
return fullHeight - getNavigationBarHeight(rotation, uiMode);
}
}
显而易见,是fullHeight - getNavigationBarHeight(rotation, uiMode);这句捣的鬼,但是想进入这里,还有俩关需要闯。
第一关:在displayId是DEFAULT_DISPLAY的条件下,需要mHasNavigationBar成立, 检查逻辑是: 首先R.bool.config_showNavigationBar,其次是qemu.hw.mainkeys, 而qemu.hw.mainkeys如果定义为0可覆盖config_showNavigationBar的配置。
第二关:mNavigationBarCanMove不成立或者fullWidth < fullHeight, fullWidth < fullHeight比较好理解,即宽度小于高度时,系统会认为虚拟导航栏在垂直底部,会减去这部分。那么前者是什么意思呢,别急,看下代码就知道了:
// Allow the navigation bar to move on non-square small devices (phones).
mNavigationBarCanMove = width != height && shortSizeDp < 600;
从注释来看,也就是说当针对宽高不等,且较小的边小于600dp的情况下,导航栏允许移动到较短边的侧边。
具体可参考getNonDecorDisplayWidth中的:
2744 if (mNavigationBarCanMove && fullWidth > fullHeight) {
2745 return fullWidth - getNavigationBarWidth(rotation, uiMode);
2746 }
可看到这部分代码逻辑恰好与getNonDecorDisplayHeight相反,也就是验证了上边说的结论。
好了,这部分的解释就到此为止。