全面屏虚拟按键高度适配

3,489 阅读3分钟

需求场景:ScrollView中需要一个定高的recyclerView,其高度为屏幕高度,本以为一个简单的需求,调试了半天.

最初的高度获取

    public static int getScreenHeight(Context context) {
        final Resources resources = context.getResources();
        final DisplayMetrics dm = resources.getDisplayMetrics();
        return dm.heightPixels;
    }

测试结果(PS:测试机有限,欢迎更多人加入测试,发现未知的问题)

手机类型 getScreenHeight() getRealHeight() 状态栏 虚拟按键
Mi8虚拟按键模式 2120 2340 110 130
Mi8手势模式 2120 2340 110 130
华为Mate20虚拟按键模式 2094 2244 81 114
华为Mate20手势模式 2163 2244 81 114
vivo-z20虚拟按键模式 2154 2280 84 126
vivo-z20手势模式 2280 2280 84 126

由测试可以看出,getScreenHeight()获取的高度在各平台是不统一的,原因也各异:

  • 理论上 getScreenHeight()获取的是可用高度,即屏幕整体高度减去占用的状态栏和虚拟按键.
  • 小米不区分是否使用了虚拟按键.
  • 华为,手势模式下正常,但虚拟按键模式高度有偏差.
  • vivo,获取的高度多了状态栏高度.

而获取真实高度,状态栏高度,虚拟按键高度,可以获取一致的值,因此最终方案采用 真实高度- 状态栏高度- 虚拟按键高度来获取真实的可用高度(PS:由于现今绝大部分项目都不使用ActionBar,所以未考虑ActionBar的影响).

真实的可用高度获取(真实全屏高度- 状态栏高度- 虚拟按键高)

  • 获取真实高度
    public static int getRealHeight(Context context) {
        Display display = getDisplay(context);
        if (display == null) {
            return 0;
        }
        DisplayMetrics dm = new DisplayMetrics();
        display.getRealMetrics(dm);
        return dm.heightPixels;
    }
  • 状态栏高度
 public static int getStatusHeight() {
       int height = 0;
       int resourceId = Latte.getApplicationContext().getResources().getIdentifier("status_bar_height", "dimen", "android");
       if (resourceId > 0) {
           height = Latte.getApplicationContext().getResources().getDimensionPixelSize(resourceId);
       }
       return height;
   }
  • 虚拟按键是否使用
    1. 检测是否是全面屏模式,如果是,不需考虑虚拟键
    2. 检测虚拟按键是否隐藏了
    /**
     * 非全面屏下 虚拟按键是否打开
     *
     * @param activity activity
     * @return 虚拟按键是否打开
     */
    private static boolean isNavigationBarShown(Activity activity) {
        //虚拟键的view,为空或者不可见时是隐藏状态
        View view = activity.findViewById(android.R.id.navigationBarBackground);
        if (view == null) {
            return false;
        }
        int visible = view.getVisibility();
        if (visible == View.GONE || visible == View.INVISIBLE) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 全面屏(是否开启全面屏开关 0 关闭  1 开启)
     *
     * @param context activity
     * @return 是否是前面屏
     */
    private static boolean navigationGestureEnabled(Context context) {
        int val = Settings.Global.getInt(context.getContentResolver(), getDeviceInfo(), 0);
        return val != 0;
    }

  • 虚拟按键高度
    private static int getNavigationBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
          result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
  • 可用高度 有了上面这些信息,即可获取真实的可用区域高度: getScreenHeightReal(context) - getStatusHeight() - getNavigationBarHeightIfRoom(context)

工具类路径

相关知识(Display&&DisplayMetrics)

DisplayMetrics: 提供屏幕的通用信息,如显示大小,分辨率和字体
Display:提供逻辑显示区域大小、密度的相关信息

本次用的的方法
  • context.getResources().getDisplayMetrics(): 包含可视区域的屏幕信息的DisplayMetrics;
  • activity.getWindowManager().getDefaultDisplay(): 获取当前屏幕信息的 Display;
  • display.getMetrics(DisplayMetrics outMetrics): 将dispay的可视区信息填入outMetics;
  • display.getRealMetrics(DisplayMetrics outMetrics) :将dispay的真实区域(即完整屏幕区域)信息填入outMetics;