Android全面屏适配指南 您所在的位置:网站首页 全面屏手机屏幕分辨率 Android全面屏适配指南

Android全面屏适配指南

2024-05-11 08:44| 来源: 网络整理| 查看: 265

何为全面屏

全面屏是手机业界对于超高屏占比手机设计的一个宽泛的定义。从字面上解释就是,手机的正面全部都是屏幕,四个边框位置都是采用无边框设计,追求接近100%的屏占比。但受限于目前的技术,还不能做到手机正面屏占比100%的手机。现在业内所说的全面屏手机是指真实屏占比可以达到80%以上,拥有超窄边框设计的手机。

全面屏手机屏幕的宽高比例比较特殊,不再是以前的16:9。比如三星的Galaxy S8屏幕分辨率是:2960×1440,对应的屏幕比例为:18.5:9。VIVO X20手机屏幕分辨率是2160x1080,对应的屏幕比例:18:9。对于这种奇葩的屏幕比例,APP开发者该如何去优化自己的应用,才能在这些手机上显示的更加完美呢?

下面,从以下两个方面来探究APP完美适配全面屏手机的方法:

更大的屏幕高宽比例虚拟导航键(NavigationBar)屏幕高宽比例

由于全面屏手机的高宽比比之前大,如果不适配的话,Android默认为最大的宽高比是1.86(即16:9),小于全面屏手机的宽高比,因此在全面屏手机上打开没有适配全面屏的App时,上下就会显示空白空间。

针对此问题,Android官方提供了适配方案,即提高App所支持的最大屏幕纵横比,实现起来也比较简单,在AndroidManifest.xml中做如下配置即可:

代码语言:javascript复制

其中,ratio_float表示宽高比,为浮点数,官方建议为2.1或更大。例如,好医生APP针对华为的全面屏的ratio_float设置为2.3.4。

代码语言:javascript复制

另外,如果只是针对某个Activity,可以直接在AndroidManifest中针对Activity标签添加android:resizeableActivity = “true”,但是此设置只针对Activity生效,且增加了此属性该activity也会支持分屏显示。

当然,max_aspect值也支持在Java代码中动态地设置。例如:

代码语言:javascript复制public void setMaxAspect() { ApplicationInfo applicationInfo = null; try { applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } if(applicationInfo == null){ throw new IllegalArgumentException(" get application info = null, has no meta data! "); } applicationInfo.metaData.putString("android.max_aspect", "2.1"); }

除了设置max_aspect外,为了适配全面屏,还需要在布局上进行一些优化,即能用百分比布局的尽量不要用dp。

先看一下dp的定义:Density-independent pixel (dp)独立像素密度。标准是160dpi,即1dp对应1个pixel,计算公式为:px = dp * (dpi / 160)。

即屏幕密度越大,1dp对应 的像素点越多。上面的公式中有个dpi,dpi为DPI是Dots Per Inch(每英寸所打印的点数),也就是当设备的dpi为160的时候1px=1dp;

使用dp来布局虽然非常方便,但是dp并不能够解决所有的适配问题。例如,为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。因此,从API 23开始,Google提供了百分比布局方案,在Android中使用百分比布局,需要在build.gradle中添加如下依赖:

代码语言:javascript复制compile 'com.android.support:percent:23.0.1'

当然,除了百分比布局外,官方建议使用的是ConstraintLayout,ConstraintLayout布局具有如下的一些优点:

可以极大地减少布局的嵌套,提升界面渲染性能; 可以使用可视化的方式来编写Android布局文件,非常方便; 跟上面介绍的几种布局对比,可以更方便地实现百分比布局,适配全面屏也毫无压力;

虚拟导航键适配

适配虚拟导航键是适配全面屏的重要内容,由于不同手机厂商对系统做了不同的修改,因此对系统界面底部的NavigationBar处理方式也就各不相同。例如,有些手机系统有NavigationBar,有些手机没有,还有则是在设置增加开关,让用户选择是否启用NavigationBar。 好在Android系统提供了相关的方法,可以在WindowManagerService.java源码中找到hasNavigationBar方法,该方法就是用来判断是否存在NavigationBar。

代码语言:javascript复制@Override public boolean hasNavigationBar() { return mPolicy.hasNavigationBar(); }

但是,WindowManagerService是系统服务,开发者是无法直接调用这个方法的。可以继续看PhoneWindowManager,PhoneWindowManager提供了一个hasNavigationBar(),源码如下:

代码语言:javascript复制@Override public boolean hasNavigationBar() { return mHasNavigationBar; }

再看看给PhoneWindowManager的mHasNavigationBar赋值的地方。

代码语言:javascript复制public void setInitialDisplaySize(Display display, int width, int height, int density) { ... mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); if ("1".equals(navBarOverride)) { mHasNavigationBar = false; } else if ("0".equals(navBarOverride)) { mHasNavigationBar = true; } ... }

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

从系统的资源文件中取设定值config_showNavigationBar; 系统获取“qemu.hw.mainkeys”的值,这个值可能会覆盖上面获取到的mHasNavigationBar的值; 自定义NavigationBar判断方法 既然系统没有提供直接的方法来判断NavigationBar是否存在,我们可以仿照PhoneWindowManager给mHasNavigationBar赋值的方法,自己去实现一个判断NavigationBar的方法。

代码语言:javascript复制 public 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; }

当然,网上也提供了很多其他的方式,例如,通过通过WindowManagerGlobal获取windowManagerService,然后通过反射拿到IWindowManager。

代码语言:javascript复制public static boolean deviceHasNavigationBar() { boolean haveNav = false; try { Class windowManagerGlobalClass = Class.forName("android.view.WindowManagerGlobal"); Method getWmServiceMethod = windowManagerGlobalClass.getDeclaredMethod("getWindowManagerService"); getWmServiceMethod.setAccessible(true); Object iWindowManager = getWmServiceMethod.invoke(null); Class iWindowManagerClass = iWindowManager.getClass(); Method hasNavBarMethod = iWindowManagerClass.getDeclaredMethod("hasNavigationBar"); hasNavBarMethod.setAccessible(true); haveNav = (Boolean) hasNavBarMethod.invoke(iWindowManager); } catch (Exception e) { e.printStackTrace(); } return haveNav; }虚拟导航键开关

现在很多的手机没有底部实体的Home键和Back键,为了支持虚拟导航键,大部分手机都提供了虚拟的导航键,开发者可以通过上面的方法hasNavigationBar获取手机是否支持虚拟导航键。当然,也可以在【设置】面板中来手动打开或关闭虚拟导航键,并且部分手机还支持使用手势来开启和关闭导航键。

在这里插入图片描述在这里插入图片描述

那么,对于开发者来说,怎么知道是否开启了虚拟导航键呢,又如何进行适配呢?

代码语言:javascript复制private static final String NAVIGATION_GESTURE = "navigation_gesture_on"; private static final int NAVIGATION_GESTURE_OFF = 0; /** * @return false 判断是否使用虚拟导航键,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; }

判断当前系统是否存在并开启了NavigationBar,就要结合上面给出的两个方法一起判断才准确。

代码语言:javascript复制 boolean hasNavigationBar = hasNavigationBar(this) && !vivoNavigationGestureEnabled(this);配置虚拟导航键属性

对于大多数视频播放类的应用,在播放视频的时候,肯定希望能够隐藏NavigationBar和StatusBar。对于这种需求,在Android 4.1以上的系统里也有很好的支持,google官方给出下面的Example:

代码语言:javascript复制View decorView = getWindow().getDecorView(); // Hide both the navigation bar and the status bar. // SYSTEM_UI_FLAG_FULLSCREEN is only available on Android 4.1 and higher, but as // a general rule, you should design your app to hide the status bar whenever you // hide the navigation bar. int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; decorView.setSystemUiVisibility(uiOptions);

但是这么做也是有缺陷的,Google共给出了5个注意事项:

使用这种设置flag的方式虽然暂时隐藏了NavigationBar,但是用户触摸屏幕的任何地方flags将会被清除,也就是说你的设置,在用户触摸屏幕后会失效;一但你设置的flags被清除后,如果你再想隐藏Navigation Bar,需要重新设置,这个需要设置监听事件;在不同的地方设置UI标签是有所区别的。如果你在activity的onCreate()方法中隐藏系统栏,当用户按下home键系统栏就会重新显示。当用户再重新打开activity的时候,onCreate()不会被调用,所以系统栏还会保持可见。如果你想让在不同activity之间切换时,系统UI保持不变,你需要在onReasume()与onWindowFocusChaned()里设定UI标签;setSystemUiVisibility()仅仅在被调用的View显示的时候才会生效;当从View导航到别的地方时,用setSystemUiVisibility()设置的标签会被清除。

显然,View.SYSTEM_UI_FLAG_HIDE_NAVIGATION和View.SYSTEM_UI_FLAG_FULLSCREEN这个两个属性使用起来根本无法满足我们需要在应用中隐藏NavigationBar的需求。不过,好在Android4.4版本提供了沉浸式全屏的概念。沉浸式全屏的应用在Android4.4的手机上会自动全屏显示,并不会出现恼人的虚拟键问题。

并且,Android 4.4 中提供了View.SYSTEM_UI_FLAG_IMMERSIVE和View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY标签, 需要和View.SYSTEM_UI_FLAG_HIDE_NAVIGATION、View.SYSTEM_UI_FLAG_FULLSCREEN一起使用, 才能实现沉浸模式。

基于此,我们可以自己封装一个虚拟按键栏的显示隐藏逻辑。

代码语言:javascript复制public void showBar(){ int uiOptions = getWindow().getDecorView().getSystemUiVisibility(); int newUiOptions = uiOptions; boolean isImmersiveModeEnabled = ((uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) == uiOptions); if (isImmersiveModeEnabled) { Log.i(TAG, "Turning immersive mode mode off. "); //先取 非 后再 与, 把对应位置的1 置成0,原本为0的还是0 if (Build.VERSION.SDK_INT >= 14) { newUiOptions &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } if (Build.VERSION.SDK_INT >= 16) { newUiOptions &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; } if (Build.VERSION.SDK_INT >= 18) { newUiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } getWindow().getDecorView().setSystemUiVisibility(newUiOptions); } } public void hideBar() { // The UI options currently enabled are represented by a bitfield. // getSystemUiVisibility() gives us that bitfield. int uiOptions = getWindow().getDecorView().getSystemUiVisibility(); int newUiOptions = uiOptions; boolean isImmersiveModeEnabled = ((uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) == uiOptions); if (!isImmersiveModeEnabled) { Log.i(TAG, "Turning immersive mode mode on. "); if (Build.VERSION.SDK_INT >= 14) { newUiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } if (Build.VERSION.SDK_INT >= 16) { newUiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN; } if (Build.VERSION.SDK_INT >= 18) { newUiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } getWindow().getDecorView().setSystemUiVisibility(newUiOptions); } }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有