聊聊获取屏幕高度这件事

heightPixels = (Integer) Display.class.getMethod(“getRawHeight”).invoke(display);

} catch (Exception e) {

}

复制代码

现在不会还有人适配4.4甚至5.0一下的机子了吧,不会吧不会吧。。。所以历史的包袱可以去掉了。

在这里插入图片描述

上面方法名都是getScreenHeight,可是这个高度范围到底和你需要的是否一致。这个需要开发时注意,我的习惯是ScreenHeight指应用显示的高度,不包括导航栏(非全屏下),RealHeight来指包含导航栏和状态栏的高度(getRealMetrics)。

PS:以前也使用过AndroidUtilCode这个工具库,里面将前者方法名定义为getAppScreenHeight,后者为getScreenHeight。也是很直观的方法。

下文中我会以自己的习惯,使用ScreenHeightRealHeight来代表两者。

我印象中华为手机很早就使用了虚拟导航键,如下图(图片来源):

华为手机

比较特别的是,当时华为的导航栏还可以显示隐藏,注意图中左下角的箭头。点击可以隐藏,上滑可以显示。即使这样,使用getScreenHeight也可以准确获取高度,隐藏了ScreenHeight就等于RealHeight

上述的这一切在“全面屏”时代没有到来之前,没有什么问题。

2.立足当下


小米MIX的发布开启了全面屏时代(16年底),以前的手机都是16:9的,记得雷布斯在发布会上说过,他们费了很大的力气说服了谷歌去除了16:9的限制(从Android 7.0开始) MIX 2发布会

MIX 2发布会

全面屏手机是真的香,不过随之也带来适配问题。首当其冲的就是刘海屏,各家有各自的获取刘海区域大小的方法。主要原因还是国内竞争的激烈,各家为了抢占市场,先于谷歌定制了自己的方案。这一点让人想起了万恶的动态权限适配。。。

其实在刘海屏之下,还隐藏一个导航栏的显示问题,也就是本篇的重点。全面屏追求更多的显示区域,随之带来了手势操作。在手势操作模式下,导航栏是隐藏状态。

本想着可以和上面提到的华为一样,隐藏获取的就是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 | 否 |

ScreenHeight一栏中括号内表示显示导航栏时获取的屏幕高度。

大致的规律总结如下:

  • 在有刘海的手机上,ScreenHeight不包含状态栏高度。

  • 小米手机在隐藏显示导航栏时,ScreenHeight不变,且不包含导航栏高度。

其中vivo手机最奇怪,屏幕高度加状态栏高度大于真实高度(2201 + 84 > 2280)。本以为差值79是刘海高度,但查看vivo文档后发现,vivo刘海固定27dp(81px),也还是对不上。。。

这时如果你需要获取准确的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;

}

}

}

复制代码

此方法通过比较ScreenHeightRealHeight是否相等来判断。如果对比上面表中的数据,那只有OPPO Find X可以判断成功。也有一些方法通过ScreenHeightRealHeight差值来计算导航栏高度。显然这些方法已无法再使用。

所以搜索了一下相关信息,得到了下面的代码:

/**

  • 是否隐藏了导航键

  • @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”);

}

public static boolean isHuawei() {

return BRAND.contains(“huawei”) || BRAND.contains(“honor”);

}

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 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 1 不显示 0显示 2 未知

*/

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 (isSamsung()) {

return samsungNavigationEnabled(context);

}

if (isSmartisan()) {

return smartisanNavigationEnabled(context);

}

if (isNokia()) {

return nokiaNavigationEnabled(context);

}

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);

}

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;

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

[外链图片转存中…(img-AXd7eydv-1712026498281)]

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值