Android判断虚拟按键(导航栏)显示与否、高度以及获取屏幕实际高度
最近发现了一个Bug:网络异常时弹出SnackBar提示检查网络。
但是当有导航栏存在时,SnackBar会出现在导航栏的下面,被导航栏覆盖掉,导致不能点击。这段代码如下所示:
final ViewGroup viewGroup = (ViewGroup) findViewById(android.R.id.content).getRootView();
snackBar = Snackbar.make(viewGroup, "网络不可用,请检查网络设置", Snackbar.LENGTH_LONG);
snackBar.getView().setBackgroundResource(R.color.colorPrimary);
snackBar.setActionTextColor(Color.WHITE);
snackBar.setAction("现在去", new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(Settings.ACTION_SETTINGS));
}
}).show();
getRootView()
就是DecorView了。所以其原因也就不难理解了。 但是在我研究途中,走了不少弯路。我想了这个方法:
判断NavigationBar存在与否;若存在,获取其高度,给SnackBar设置bottomMargin。
1 走的弯路¶
1.1 判断Navigation存在与否¶
这个只能根据Android系统编译时生成的文件来判断,如果ROM支持动态设置的话,那就不行了。所以,还是有缺陷的。
原理的代码来自PhoneWindowManager#setInitialDisplaySize
:
boolean mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
// Allow a system property to override this. Used by the emulator.
// See also hasNavigationBar().
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
mHasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
mHasNavigationBar = true;
}
frameworks/base/core/res/res/values/config.xml
里面config_showNavigationBar
是否是true
,然后在根据手机目录system/build.prop
里面qemu.hw.mainkeys
的值来判断。 上面这段代码是不能直接运行的,首先config_showNavigationBar
的值不能直接获取,需要先获取其resId然后在获取其值。
int resourceId = resources.getIdentifier("config_showNavigationBar","bool", "android");
boolean mHasNavigationBar = resources.getBoolean(resourceId);
题外话:按照同样的原理,将上面的config_showNavigationBar
换成status_bar_height
就可以获取状态栏的高度。
获取statusbar的高度可以使用这个方法
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
其次,SystemProperties
的API在普通应用也是获取不到的。但是SystemProperties
中的值可以简单的理解为记录在system/build.prop
中这个文件中。我们可以通过Runtime.exec读取该文件,获取qemu.hw.mainkeys
属性的值。
目前,博主在阅读滴滴的开源项目VirtualAPK时,学会了不用通过读system/build.prop
就可以获取系统变量的方法了。原理是Fake SystemProperties文件。
1.2 获取NavigationBar的高度¶
这步也是和上面config_showNavigationBar
获取一样:
int resourceId = resources.getIdentifier("navigation_bar_height","dimen", "android");
int navigationBarHeight = resources.getDimensionPixelSize(resourceId);
1.3 给SnackBar设置bottomMargin¶
((ViewGroup.MarginLayoutParams) snackBar.getView().getLayoutParams()).bottomMargin = navigationBarHeight;
1.4 弯路总结¶
实际上,这个方法还不是完美的。因为这个是根据Android系统编译时生成的文件来判断,如果ROM支持动态设置的话,那就不行了。
2 由Bug启发的获取屏幕实际高度的方法¶
当然最后我才意识到,可以通过对比屏幕可用高度以及DecorView的高度是否一样来判断NavigationBar是否显示出来了。
我们可以看到DecorView处于最底层,其上面有statusbar、actionbar、content以及navigationbar。屏幕可用高度是不会包括navigationbar的。
所以我们可以判断DecorView的高度与可用屏幕高度是否相等来判断是否有导航栏,因为导航栏是不会计算到可用屏幕高度中的。
具体判断NavigationBar是否存在的代码如下
private void test() {
getWindow().getDecorView().post(mRunnable);
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
int decorViewHeight = getWindow().getDecorView().getHeight();
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int useableScreenHeight = dm.heightPixels;
boolean hasNavigation = decorViewHeight != useableScreenHeight;
}
};
因此,可以得出两个小技巧:
- 获取NavigationBar的高度
在上面的代码中,我们可以用DecorView的高度减去屏幕可用高度,若为0表示当前NavigationBar不占用高度;若不为0,则就是NavigationBar的高度 - 获取整个屏幕的高度
DecorView的高度即为整个屏幕(包括NavigationBar)的高度
当然,还有一种更简单的方法getRealSize
,不过需要API Level 17或以上才能使用
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getRealSize(size);