Android 虚拟导航键适配

最近项目里需要适配虚拟导航键,以及获取导航键的高度,来适配界面布局的高度。

判断虚拟导航键是否存在

不得不说,国内由于不同手机厂商对系统做了不同的修改,对系统界面底部的NavigationBar处理方式也就各不相同,有些手机系统有NavigationBar,有些手机没有,还有则是在设置增加开关,让用户选择是否启用NavigationBar。因此,对弈APP开发者来说,完美适配虚拟导航键也是一件比较有挑战性的事。

首先,我们来看看android源码有没有提供公共API来判断当前系统是否存在NavigationBar。

分析源码

通过查阅Android源码,我们发现在WindowManagerService.java下面有一个方法是hasNavigationBar:

   @Overridepublic boolean hasNavigationBar() {return mPolicy.hasNavigationBar();}

但是,WindowManagerService是系统服务,我们无法直接调用这个方法。那我继续看这个方法的具体实现。
mPolicy是什么呢?看源码:final WindowManagerPolicy mPolicy;,WindowManagerPolicy只是一个接口,具体的实现是在哪里呢?
它的实现类是PhoneWindowManager,所以最终是调到了PhoneWindowManager的hasNavigationBar()

 // Use this instead of checking config_showNavigationBar so that it can be consistently// overridden by qemu.hw.mainkeys in the emulator.@Overridepublic boolean hasNavigationBar() {return mHasNavigationBar;}

再看看PhoneWindowManager中给mHasNavigationBar赋值的地方在哪里:

public void setInitialDisplaySize(Display display, int width, int height, int density) {......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;}......
}

从上面代码可以看到mHasNavigationBar的值的设定是由两处决定的:

1.首先从系统的资源文件中取设定值config_showNavigationBar, 这个值的设定的文件路径是frameworks/base/core/res/res/values/config.xml

  <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be  autodetected from the Configuration. -->  <bool name="config_showNavigationBar">false</bool>
  1. 我们可以从"qemu.hw.mainkeys" 处着手, 这个值可能会覆盖上面获取到的mHasNavigartionBar的值。 如果“qemu.hw.mainkeys”获取的值不为空的话,不管值是true还是false,都要依据后面的情况来设定。
    所以上面的两处设定共同决定了NavigationBar的显示与隐藏。

实现判断NavigationBar 的方法

网络上流传的大多数的方法 都是获取

rs.getIdentifier("config_showNavigationBar", "bool", "android");hasNavigationBar = rs.getBoolean(id);

根据hasNavigationBar的状态来确定是否有虚拟键盘。

下面这个方法把网络上的方法合成一下基本如此 。

    //判断是否存在NavigationBarpublic static boolean hasNavigationBar(Context context) {boolean hasNavigationBar = false;Resources rs = context.getResources();int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");if (id > 0) {hasNavigationBar = rs.getBoolean(id);}try {//反射获取SystemProperties类,并调用它的get方法Class systemPropertiesClass = Class.forName("android.os.SystemProperties");Method m = systemPropertiesClass.getMethod("get", String.class);String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");if ("1".equals(navBarOverride)) {hasNavigationBar = false;} else if ("0".equals(navBarOverride)) {hasNavigationBar = true;}} catch (Exception e) {e.printStackTrace();}return hasNavigationBar;}

从原理上讲到此为止了吧,在我的小米手机和锤子手机上实体物理键, 以及Android 8, 9 的虚拟机上跑了一下,美滋滋, 感觉没事了,世界和平了。然而现实并不是如此。

自从设计拿了个华为全面屏和Redmi7 , 发现这个方法没用了。 瞬间感觉世界黯淡了。

全面屏手机虚拟导航键的开关

由于全面屏手机都没有底部的Home,Back等实体按键,因此,大多数全面屏手机都是支持虚拟导航键,即通过上面的方法hasNavigationBar获取的返回值都是true。

而在国内的全面屏手机中,大多数如果是全面屏,底部NavigationBar会占用一些屏幕空间, 一直显示出来, 这就失去了全面屏的意义, 用户体验并不好。

现在很多手机 例如华为P20 ,30 ,小米手机、VIVO x20 ,X20 Plus 就会再系统进入以及设置中增加了是否启用NavigationBar的开关, 以及手势的滑动 是否显示。

例如VIVO 开关在设置-> 导航键

当隐藏虚拟导航键时,用户可以通过底部上滑的手势实现导航键同样的功能,非常便利。
感觉这种设计貌似是苹果先带的头吧 。

那么是不是有什么可以判断呢? 必须有了, 例如VIVO 就是在Setting 中这个开关的值, 可以在系统setting.xml
中找到该属性。 看一下兼容代码:

vivo 适配代码

    private static final String NAVIGATION_GESTURE = "navigation_gesture_on";private static final int NAVIGATION_GESTURE_OFF = 0;/*** 获取vivo手机设置中的"navigation_gesture_on"值,判断当前系统是使用导航键还是手势导航操作* @param context app Context* @return false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false*/public static boolean vivoNavigationGestureEnabled(Context context) {int val = Settings.Secure.getInt(context.getContentResolver(), NAVIGATION_GESTURE, NAVIGATION_GESTURE_OFF);return val != NAVIGATION_GESTURE_OFF;}

完了以后 那么总结出来方法就是

    //vivoNavigationGestureEnabled()从设置中取不到值的话,返回false,因此也不会影响在其他手机上的判断boolean hasNavigationBar = hasNavigationBar(this) && !vivoNavigationGestureEnabled(this);

网上又找了个 MIUI 的适配方法。

 private static final String XIAOMI_FULLSCREEN_GESTURE = "force_fsg_nav_bar";public static boolean xiaomiNavigationGestureEnabled(Context context) {int val = Settings.Global.getInt(context.getContentResolver(), XIAOMI_FULLSCREEN_GESTURE, 0);return val != 0;
}

这里在放一个获取常见系统类型的判断类。 挺好使 ,

/**
系统信息以及系统类型判断。
*/
public class OSInfo {public enum OSType {OS_TYPE_OTHER(0),OS_TYPE_EMUI(1),OS_TYPE_MIUI(2),OS_TYPE_FLYME(3),OS_TYPE_COLOR(4),OS_TYPE_FUNTOUCH(5);private final int value;OSType(int value) {this.value = value;}}/** SharedPreferences标识 */public static final String OS_SP = "com_github_xubo_statusbarutils_os_sp";/** MIUI标识(小米) */private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name";/** EMUI标识(华为) */private static final String KEY_VERSION_EMUI = "ro.build.version.emui";/** Flyme标识(魅族) */public static final String KEY_VERSION_FLYME = "ro.meizu.setupwizard.flyme";/** color标识(oppo) */private static final String KEY_VERSION_COLOR = "ro.build.version.opporom";/** color标识(funtouch) */private static final String KEY_VERSION_FUNTOUCH = "ro.vivo.os.version";public static OSType getRomType(Context context) {SharedPreferences sharedPreferences = context.getSharedPreferences(OS_SP, Context.MODE_PRIVATE);int osTypeValue = sharedPreferences.getInt("os_type", -1);if (osTypeValue == -1) {String display = Build.DISPLAY;if (display.toUpperCase().contains("FLYME")) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_FLYME.value).commit();return OSType.OS_TYPE_FLYME;} else {if (!TextUtils.isEmpty(getProp(KEY_VERSION_MIUI))) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_MIUI.value).commit();return OSType.OS_TYPE_MIUI;} else if (!TextUtils.isEmpty(getProp(KEY_VERSION_EMUI))) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_EMUI.value).commit();return OSType.OS_TYPE_EMUI;} else if (!TextUtils.isEmpty(getProp(KEY_VERSION_FLYME))) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_FLYME.value).commit();return OSType.OS_TYPE_FLYME;} else if (!TextUtils.isEmpty(getProp(KEY_VERSION_COLOR))) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_COLOR.value).commit();return OSType.OS_TYPE_COLOR;} else if (!TextUtils.isEmpty(getProp(KEY_VERSION_FUNTOUCH))) {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_FUNTOUCH.value).commit();return OSType.OS_TYPE_FUNTOUCH;} else {sharedPreferences.edit().putInt("os_type", OSType.OS_TYPE_OTHER.value).commit();return OSType.OS_TYPE_OTHER;}}} else {OSType osType;switch (osTypeValue) {case 0:osType = OSType.OS_TYPE_OTHER;break;case 1:osType = OSType.OS_TYPE_EMUI;break;case 2:osType = OSType.OS_TYPE_MIUI;break;case 3:osType = OSType.OS_TYPE_FLYME;break;case 4:osType = OSType.OS_TYPE_COLOR;break;case 5:osType = OSType.OS_TYPE_FUNTOUCH;break;default:osType = OSType.OS_TYPE_OTHER;break;}return osType;}}public static String getProp(String name) {String line;BufferedReader input = null;try {Process p = Runtime.getRuntime().exec("getprop " + name);input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);line = input.readLine();input.close();} catch (IOException ex) {return null;} finally {if (input != null) {try {input.close();} catch (IOException e) {e.printStackTrace();}}}return line;}
}

先这吧 ,如果碰上别的系统适配的方法可以拿出来,一起分享下。

Android 虚拟导航键适配相关推荐

  1. Android 虚拟导航键 遮盖布局

    Android 虚拟导航键 遮盖布局 参考文章如下: - https://www.cnblogs.com/lanlengran/p/6415946.html 隐藏底部虚拟按键 - https://bl ...

  2. 关于android 7.0全面屏,底部虚拟导航键 适配问题

    上图为没适配之前 // 在setContentView之后,适配顶部状态栏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCE ...

  3. android功能导航布局,Android全面屏虚拟导航栏适配

    手机正朝着全面屏的方向演进,与此同时也给开发者带来了很多适配上的新问题,虚拟导航栏就是其中一个.最近在糗百的项目中,就有相关的适配问题,我查阅了目前关于虚拟导航栏适配的相关文章,基本上在全面屏手机里都 ...

  4. android 窗口导航,Android全面屏虚拟导航栏适配

    手机正朝着全面屏的方向演进,与此同时也给开发者带来了很多适配上的新问题,虚拟导航栏就是其中一个.最近在糗百的项目中,就有相关的适配问题,我查阅了目前关于虚拟导航栏适配的相关文章,基本上在全面屏手机里都 ...

  5. Android 获取屏幕高度,虚拟导航键检测

    本篇文章主要总结一下在全面屏上获取高度的问题. 获取屏幕高度 一般 Android 上获取设备的高度都是通过 DefaultDisplay 的方式来获取的如下: public int getScree ...

  6. Android获取虚拟导航键的高度

    自从有了全面屏,就有了虚拟按键,我们该如何获取 屏幕的真实高度 以及 虚拟键的高度 呢? 之前我们使用的都是下面的方法,但有一个问题就是,在全面屏中,它获取到的高度是不包含下面导航键的高度的: pub ...

  7. Android学习整理 - 状态栏和虚拟导航键透明效果

    状态栏和虚拟导航键 4.4上半透明,5.0以上可以全透明 先上效果 4.4 半透明效果 5.0及以上 全透明效果 上代码 MainActivity代码 public class MainActivit ...

  8. Android 华为虚拟导航栏适配

    Android 华为虚拟导航栏适配 在写界面的时候 然后发现在界面最底下的几行文字 正好被虚拟导航栏遮挡住了,不滑动还看不到底下的文字,所以想隐去这些导航栏. 采用下面的代码将DecorView中的属 ...

  9. 安卓判断虚拟导航键是否显示

    在全面屏手机之前,我们对虚拟导航栏的判断就有很多种方法, // 判断系统是否写入了关于定义虚拟导航栏的高度相关变量.//如果高度大于0,则表示该手机有虚拟导航栏Resources res = acti ...

最新文章

  1. 为何 NLP 领域难以出现“独角兽”?
  2. jzoj3360-[NOI2013模拟]苹果树【树上莫队,LCA】
  3. C++类模板实例化条件
  4. Handler post用法整理
  5. c++ 打印条码_金蝶盘点机PDA仓库条码管理之——外购入库扫码开单操作
  6. 云服务器重启后网站打不开及FTP连不上的原因及解决方法
  7. JavaScript继承详解
  8. Unity移动平台相关(一)
  9. Vue.js基础知识点总结
  10. matlab检验数据异方差,求教!怀特异方差检验方法在matlab中的实现,以及广义最........
  11. 【STM32】HAL库——ADC
  12. LDO的基础特性——热关断
  13. c# 获取照片的经纬度和时间
  14. html 圆环实现多种颜色,Echart饼图实现(圆环图)+状态颜色控制
  15. oracle无法加载库单元,PLS-00907: 无法加载库单元 是什么错误啊??
  16. [运放滤波器]4_积分微分电路
  17. 银监会计算机专业考试,)(2015国家公务员考试银监会计算机专业考试分析
  18. 网络ioctl实践3:设置网卡的mac、ip、子网掩码、广播地址
  19. python中哈希表和set的使用
  20. 如果你看见这个舞女是顺时针转,说明你用的是右脑;耶鲁大学耗时5年的研究成果。左脑?右脑?

热门文章

  1. 一件事,做得快,还是好?
  2. UI设计师如何自学?
  3. 一起来学SpringBoot | 第八篇:通用Mapper与分页插件的集成
  4. git克隆代码报错fatal: unable to access...
  5. 经过近期互联网裁员潮,运营岗是失业群体重灾区
  6. 讲解人工智能在现代科技中的应用和未来发展趋势
  7. 连接物理和数字世界,以数据驱动企业持续增长
  8. 一级建造师资格考试合格标准
  9. C51数字电子日历/时钟设计
  10. Linux的解析与如何学习