聊聊获取屏幕高度这件事
Posted 唯鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊获取屏幕高度这件事相关的知识,希望对你有一定的参考价值。
问题的起因是我发现 PopupWindow
弹出位置不正确时发现的。其实早在两年多前,我就发现我手上的小米MIX2s 获取屏幕高度不正确,后面参考V2EX 的这篇帖子处理了。最近又一次做到类似功能,发现小米、vivo都出现了问题。所以有了今天的内容。
1.回顾过去
说起获取屏幕高度,不知道你是如何理解这个高度范围的?是以应用显示区域高度作为屏幕高度还是手机屏幕的高度。
那么我们先看一下平时使用获取高度的方法:
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;
//或
public static int getScreenHeight(Context context)
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
wm.getDefaultDisplay().getSize(point);
return point.y;
// 或
public static int getScreenHeight(Context context)
return context.getResources().getDisplayMetrics().heightPixels;
// 貌似还有更多的方法
以上三种效果一致,只是写法略有不同。
当然你或许使用的是这种:
public static int getScreenHeight(Context context)
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
display.getRealMetrics(dm);
else
display.getMetrics(dm);
return dm.heightPixels;
// 其他几种写法大同小异
...
这个方法判断了系统大于等于android 4.2时,使用getRealMetrics
(getRealSize
)来获取屏幕高度。那么这里发生了什么,为什么会这样?
其实在Andoird 4.0时,引入了虚拟导航键,如果你继续使用getMetrics
之类的方式,获取的高度是去除了导航栏的高度的。
当时因为在4.0和4.2之间还没有的getRealMetrics
这个方法,所以甚至需要添加下面的适配代码:
try
heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
catch (Exception e)
现在不会还有人适配4.4甚至5.0一下的机子了吧,不会吧不会吧。。。所以历史的包袱可以去掉了。
上面方法名都是getScreenHeight
,可是这个高度范围到底和你需要的是否一致。这个需要开发时注意,我的习惯是ScreenHeight指应用显示的高度,不包括导航栏(非全屏下),RealHeight来指包含导航栏和状态栏的高度(getRealMetrics
)。
PS:以前也使用过AndroidUtilCode这个工具库,里面将前者方法名定义为getAppScreenHeight
,后者为getScreenHeight
。也是很直观的方法。
下文中我会以自己的习惯,使用ScreenHeight
和RealHeight
来代表两者。
我印象中华为手机很早就使用了虚拟导航键,如下图(图片来源):
比较特别的是,当时华为的导航栏还可以显示隐藏,注意图中左下角的箭头。点击可以隐藏,上滑可以显示。即使这样,使用getScreenHeight
也可以准确获取高度,隐藏了ScreenHeight
就等于RealHeight
。
上述的这一切在“全面屏”时代没有到来之前,没有什么问题。
2.立足当下
小米MIX的发布开启了全面屏时代(16年底),以前的手机都是16:9的,记得雷布斯在发布会上说过,他们费了很大的力气说服了谷歌去除了16:9的限制(从Android 7.0开始)
全面屏手机是真的香,不过随之也带来适配问题。首当其冲的就是刘海屏,各家有各自的获取刘海区域大小的方法。主要原因还是国内竞争的激烈,各家为了抢占市场,先于谷歌定制了自己的方案。这一点让人想起了万恶的动态权限适配。。。
其实在刘海屏之下,还隐藏一个导航栏的显示问题,也就是本篇的重点。全面屏追求更多的显示区域,随之带来了手势操作。在手势操作模式下,导航栏是隐藏状态。
本想着可以和上面提到的华为一样,隐藏获取的就是RealHeight
,显示就是减去导航栏高度的ScreenHeight
。然而现实并不是这样,下表是我收集的一些全面屏手机各高度的数据。
机型 | 系统 | ScreenHeight | RealHeight | NavigationBar | StatusBar | 是否有刘海 |
---|---|---|---|---|---|---|
vivo Z3x | Funtouch OS_10(Android 10) | 2201(2075) | 2280 | 126 | 84 | 是 |
Xiaomi MIX 2s | MIUI 12(Android 10) | 2030(2030) | 2160 | 130 | 76 | 否 |
Redmi Note 8Pro | MIUI 11.0.3(Android 10) | 2134(2134) | 2340 | 130 | 76 | 是 |
Redmi K30 5G | MIUI 12.0.3(Android 10) | 2175(2175) | 2400 | 130 | 95 | 是 |
Honor 10 Lite | EMUI 10(Android 10) | 2259(2139) | 2340 | 120 | 81 | 是 |
华为畅享 20 | EMUI 10.1.1(Android 10) | 1552(1472) | 1600 | 80 | 48 | 是 |
OPPO Find X | ColorOS 7.1(Android 10) | 2340(2208) | 2340 | 132 | 96 | 否 |
OnePlus 6 | H2OS 10.0.8(Android 10) | 2201(2159,2075) | 2280 | 126(42) | 80 | 否 |
ScreenHeight
一栏中括号内表示显示导航栏时获取的屏幕高度。
大致的规律总结如下:
- 在有刘海的手机上,ScreenHeight不包含状态栏高度。
- 小米手机在隐藏显示导航栏时,ScreenHeight不变,且不包含导航栏高度。
其中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;
else
return 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 2;
/**
* 判断当前系统是使用导航键还是手势导航操作
*
* @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;
以上是关于聊聊获取屏幕高度这件事的主要内容,如果未能解决你的问题,请参考以下文章