资深架构师:深入聊聊获取屏幕高度这件事( 二 )

其中vivo手机,屏幕高度加状态栏高度大于真实高度(2201 + 84 > 2280) 。本以为差值79是刘海高度,但查看vivo文档后发现,vivo刘海固定27dp(81px),也还是对不上 。。。
一加6最奇怪,有三种设置模式 。使用侧边全屏手势时,手势底部有一个小条,NavigationBar高度变为42 。(2159 + 42 = 2075 + 126 = 2201)也就是说这种模式也属于有导航栏的情况 。

资深架构师:深入聊聊获取屏幕高度这件事

文章插图
 
这时如果你需要获取准确的ScreenHeight,只有通过RealHeight - NavigationBar来实现了 。
所以首先需要判断当前导航栏是否显示,再来决定是否减去NavigationBar高度 。
先看看老牌的判断方法如下:
public boolean isNavigationBarShow(){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {Display display = getWindowManager().getDefaultDisplay();Point size = new Point();Point realSize = new Point();display.getSize(size);display.getRealSize(realSize);return realSize.y!=size.y;} else {boolean menu = ViewConfiguration.get(this).hasPermanentMenuKey();boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);if(menu || back) {return false;}else {return true;}}}此方法通过比较ScreenHeight和RealHeight是否相等来判断 。如果对比上面表中的数据,那只有OPPO Find X可以判断成功 。也有一些方法通过ScreenHeight和RealHeight差值来计算导航栏高度 。显然这些方法已无法再使用 。
所以搜索了一下相关信息,得到了下面的代码:
/*** 是否隐藏了导航键** @param context* @return*/public static boolean isNavBarHide(Context context) {try {String brand = Build.BRAND;// 这里做判断主要是不同的厂商注册的表不一样if (!StringUtils.isNullData(brand) && (Rom.isVivo() || Rom.isOppo())) {return Settings.Secure.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;} else if (!StringUtils.isNullData(brand) && Rom.isNokia()) {//甚至 nokia 不同版本注册的表不一样, key 还不一样 。。。return Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) == 1|| Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;} elsereturn Settings.Global.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;} catch (Exception e) {e.printStackTrace();}return false;}/*** 各个手机厂商注册导航键相关的 key** @return*/public static String getDeviceForceName() {String brand = Build.BRAND;if (StringUtils.isNullData(brand))return "navigationbar_is_min";if (brand.equalsIgnoreCase("HUAWEI") || "HONOR".equals(brand)) {return "navigationbar_is_min";} else if (Rom.isMiui()||Rom.check("XIAOMI")) {return "force_fsg_nav_bar";} else if (Rom.isVivo()) {return "navigation_gesture_on";} else if (Rom.isOppo()) {return "hide_navigationbar_enable";} else if (Rom.check("samsung")) {return "navigationbar_hide_bar_enabled";} else if (brand.equalsIgnoreCase("Nokia")) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {return "navigation_bar_can_hiden";} else {return "swipe_up_to_switch_apps_enabled";}} else {return "navigationbar_is_min";}}可以看到包含了华为、小米、vivo、oppo 、三星甚至诺基亚的判断 。这就是适配的现实状况,不要妄想寻找什么通用方法,老老实实一个个判断吧 。毕竟幺蛾子就是这些厂家搞出来的,厂家魔改教你做人 。
这种方法在上面的测试机中都亲测准确有效 。
不过这个判断方法不够严谨,比如其他品牌手机使用此方法,那么结果都是false 。用这样的结果来计算高度显得不够严谨 。
根据前面提到问题发生的原因是全面屏带来的(7.0及以上) 。所以我们可以先判断是否是全面屏手机(屏幕长宽比例超过1.86以上),然后判断是否显示导航栏,对于不确定的机型,我们还是使用原先的ScreenHeight 。尽量控制影响范围 。
我整理的代码如下(补充了一加、锤子手机判断):
/** * @author weilu **/public class ScreenUtils {private static final String BRAND = Build.BRAND.toLowerCase();public static boolean isXiaomi() {return Build.MANUFACTURER.toLowerCase().equals("xiaomi");}public static boolean isVivo() {return BRAND.contains("vivo");}public static boolean isOppo() {return BRAND.contains("oppo") || BRAND.contains("realme");}public static boolean isHuawei() {return BRAND.contains("huawei") || BRAND.contains("honor");}public static boolean isOneplus(){return BRAND.contains("oneplus");}public static boolean isSamsung(){return BRAND.contains("samsung");}public static boolean isSmartisan(){return BRAND.contains("smartisan");}public static boolean isNokia() {return BRAND.contains("nokia");}public static boolean isgoogle(){return BRAND.contains("google");}public static int getRealScreenHeight(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Display display = wm.getDefaultDisplay();DisplayMetrics dm = new DisplayMetrics();display.getRealMetrics(dm);return dm.heightPixels;}public static int getRealScreenWidth(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Display display = wm.getDefaultDisplay();DisplayMetrics dm = new DisplayMetrics();display.getRealMetrics(dm);return dm.widthPixels;}public static int getScreenHeight(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Display display = wm.getDefaultDisplay();DisplayMetrics dm = new DisplayMetrics();display.getMetrics(dm);return dm.heightPixels;}/*** 判断设备是否显示NavigationBar** @return 其他值 不显示 0显示 -1 未知*/public static int isNavBarHide(Context context) {// 有虚拟键,判断是否显示if (isVivo()) {return vivoNavigationEnabled(context);}if (isOppo()) {return oppoNavigationEnabled(context);}if (isXiaomi()) {return xiaomiNavigationEnabled(context);}if (isHuawei()) {return huaWeiNavigationEnabled(context);}if (isOneplus()) {return oneplusNavigationEnabled(context);}if (isSamsung()) {return samsungNavigationEnabled(context);}if (isSmartisan()) {return smartisanNavigationEnabled(context);}if (isNokia()) {return nokiaNavigationEnabled(context);}if (isGoogle()) {// navigation_mode 三种模式均有导航栏,只是高度不同 。return 0;}return -1;}/*** 判断当前系统是使用导航键还是手势导航操作** @param context* @return 0 表示使用的是虚拟导航键,1 表示使用的是手势导航,默认是0*/public static int vivoNavigationEnabled(Context context) {return Settings.Secure.getInt(context.getContentResolver(), "navigation_gesture_on", 0);}public static int oppoNavigationEnabled(Context context) {return Settings.Secure.getInt(context.getContentResolver(), "hide_navigationbar_enable", 0);}public static int xiaomiNavigationEnabled(Context context) {return Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0);}private static int huaWeiNavigationEnabled(Context context) {return Settings.Global.getInt(context.getContentResolver(), "navigationbar_is_min", 0);}/*** @param context* @return 0虚拟导航键2为手势导航*/private static int oneplusNavigationEnabled(Context context) {int result = Settings.Secure.getInt(context.getContentResolver(), "navigation_mode", 0);if (result == 2) {// 两种手势 0有按钮, 1没有按钮if (Settings.System.getInt(context.getContentResolver(), "buttons_show_on_screen_navkeys", 0) != 0) {return 0;}}return result;}public static int samsungNavigationEnabled(Context context) {return Settings.Global.getInt(context.getContentResolver(), "navigationbar_hide_bar_enabled", 0);}public static int smartisanNavigationEnabled(Context context) {return Settings.Global.getInt(context.getContentResolver(), "navigationbar_trigger_mode", 0);}public static int nokiaNavigationEnabled(Context context) {boolean result = Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) != 0|| Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;if (result) {return 1;} else {return 0;}}public static int getNavigationBarHeight(Context context){int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");if (resourceId > 0) {return context.getResources().getDimensionPixelSize(resourceId);}return 0;}private static boolean isAllScreenDevice(Context context) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {// 7.0放开限制,7.0以下都不为全面屏return false;} else {int realWidth = getRealScreenWidth(context);int realHeight = getRealScreenHeight(context);float width;float height;if (realWidth < realHeight) {width = realWidth;height = realHeight;} else {width = realHeight;height = realWidth;}// Android中默认的最大屏幕纵横比为1.86return height / width >= 1.86f;}}/*** 获取去除导航栏高度的剩余高度(含状态栏)* @param context* @return*/public static int getScreenContentHeight(Context context) {if (isAllScreenDevice(context)) {int result = isNavBarHide(context);if (result == 0) {return getRealScreenHeight(context) - getNavigationBarHeight(context);} else if (result == -1){// 未知return getScreenHeight(context);} else {return getRealScreenHeight(context);}} else {return getScreenHeight(context);}}}


推荐阅读