Android APP全面屏适配技术要点
全面屏的概念
为什么先要解释一下全面屏,因为这个词在现在来讲就是一个伪命题。全面屏字面意思就是手机的正面全部都是屏幕,100%的屏占比。但是现在推出所谓“全面屏”手机的厂商没有一个能达到全面的。
那么下面来说一下Android开发领域对全面屏的理解和定义吧。
一般手机的屏幕纵横比为16:9,如1080x1920、1440x2560等,其比值为1.77,在全面屏手机出现之前,Android中默认的最大屏幕纵横比(maximum aspect ratio)为1.86,即能够兼容16:9的屏幕。
一些手机厂商为了追求更大的屏幕空间以及更极致的用户体验,于是提高了屏幕纵横比,17:9、19:10、18:9、18.5:9的手机开始进入市场,这些手机的屏幕纵横比大大超过了1.86,这些手机被称为全面屏手机。
为何需要适配
我们将targetSdkVersion的值改为小于等于23,运行程序,我们会发现屏幕底部出现一个黑条。
如何适配
targetSdkVersion<=23,更大的屏幕纵横比
在Galaxy S8发布之后,Android官方提供了适配方案,即提高App所支持的最大屏幕纵横比,实现很简单,在AndroidManifest.xml中可做如下配置:
<meta-data android:name="android.max_aspect" android:value="ratio_float"/>
其中ratio_float为浮点数,官方建议为2.1或更大,因为18.5:9=2.055555555……,如果日后出现纵横比更大的手机,此值将会更大。
<meta-data android:name="android.max_aspect" android:value="2.1" />
max_aspect值也可以在Java代码中动态地设置,通过下面的方法即可实现:
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 "); } applicationInfo.metaData.putString("android.max_aspect", "2.1"); }
如果targetSdkVersion的值的值大于23,那么应该不用设置max_aspect即可。
查看适配之后的截图:
https://android-developers.googleblog.com/2017/03/update-your-app-to-take-advantage-of.html
图片资源适配
我们看一下启动页,在16:9屏幕中适配的图片,到了18:9的屏幕中就会被拉伸了。
16:9屏幕中显示 | 18:9屏幕中显示 |
---|---|
| |
解决这个问题无非就是两种方法,换图片或者是换布局
换图片
不能依赖单一厂商的解决方案,只能从Android系统属性出发。考虑到目前大部分全面屏手机只是在高度上拉长,且大多为6.0英寸左右,像素密度对比xxhdpi并没有多大区别,那我们可以在项目中增加一组资源drawable-xxhdpi-2160x1080 、drawable-long 这样解决图片的拉伸问题,当然这样的方法肯定是不太好的,会增加app的容量。这里就不演示了。
优化布局
当然最好的方法还是用相对布局采用XML的方式,或者.9图的解决方案。
我总结的就是少量多切,尽量减少尺寸对布局的影响。比如这里,使用正方形的切图,让他居中显示,无论屏幕纵横比如何,都不会拉伸这个图片,拉伸的只是背景而已。
<ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/bz002"/>
适配前 | 适配后 |
---|---|
| |
全面屏高度问题适配
首先解释一下window,decorview,rootview这几个概念
Window官方文档:Window
public abstract class Window. Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
翻译一下:每一个 Activity 都持有一个 Window 对象,但是 Window 是一个抽象类,这里 Android 为 Window 提供了唯一的实现类 PhoneWindow。也就是说 Activity 中的 window 实例就是一个 PhoneWindow 对象。
但是 PhoneWindow 终究是 Window,它并不具备多少 View 相关的能力。不过 PhoneWindow 中持有一个 Android 中非常重要的一个 View 对象 DecorView.
现在的关系就很明确了,每一个 Activity 持有一个 PhoneWindow 的对象,而一个 PhoneWindow 对象持有一个 DecorView 的实例,所以 Activity 中 View 相关的操作其实大都是通过 DecorView 来完成。
DecorView就可以理解为手机的内屏,就是那块玻璃,可以发光的屏幕。
这里通过代码,打印出我们页面中的高度的各项数据
int decorviewHeight = decorView.getHeight(); int screenHeight = FullScreenManager.getScreenHeight(); int nativeBarHeight = FullScreenManager.getNativeBarHeight(); int contentViewHeight = rootView.getHeight(); int navigationBarHeight1 = FullScreenManager.getNavigationBarHeight(); Log.d("shijiacheng","======================================="); Log.d("shijiacheng","DecorView height: " + decorviewHeight + " px"); Log.d("shijiacheng","Screen height: " + screenHeight + " px"); Log.d("shijiacheng","NativeBar height: " + nativeBarHeight + " px"); Log.d("shijiacheng","ContentView height: " + contentViewHeight + " px"); Log.d("shijiacheng","NavigationBar height: " + navigationBarHeight + " px"); Log.d("shijiacheng","---------------------------------------");
获取decorView的高度
final View decorView = getWindow().getDecorView(); int decorviewHeight = decorView.getHeight();
获得屏幕高度
/** * 获得屏幕高度 * @return */ public static int getScreenHeight() { Resources resource = AppContext.getInstance().getResources(); DisplayMetrics displayMetrics = resource.getDisplayMetrics(); return displayMetrics.heightPixels; }
获取状态栏的高度
/** * 获取状态栏的高度 * * @return */ public static int getNativeBarHeight() { Resources resource = AppContext.getInstance().getResources(); int result = 0; int resourceId = resource.getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = resource.getDimensionPixelSize(resourceId); } return result; }
获取contentView的高度
LinearLayout contentView = findViewById(R.id.root); int contentViewHeight = contentView.getHeight();
获取NavigationBar的高度
public static int getNavigationBarHeight() { Resources resources = AppContext.getInstance().getResources(); int resourceId = resources.getIdentifier("navigation_bar_height","dimen", "android"); int height = resources.getDimensionPixelSize(resourceId); return height; }
为了更加直观的展示各个数据,这里我们使用布局的方式将各个数据展示出来,布局代码比较简单,这里就不展示了。
先展示一下正常的屏幕高度的各项数据
10-08 09:52:03.636 23818-23818/? D/shijiacheng: ========================= 10-08 09:52:03.637 23818-23818/? D/shijiacheng: DecorView height: 1280 px 10-08 09:52:03.637 23818-23818/? D/shijiacheng: Screen height: 1280 px 10-08 09:52:03.637 23818-23818/? D/shijiacheng: NativeBar height: 50 px 10-08 09:52:03.637 23818-23818/? D/shijiacheng: ContentView height: 1230 px 10-08 09:52:03.637 23818-23818/? D/shijiacheng: NavigationBar height: 96 px 10-08 09:52:03.637 23818-23818/? D/shijiacheng: -------------------------
DecorView = Screen height = NativeBar height + ContentView height
看一下小米mix全面屏的情况
2018-10-08 09:54:15.640 /? D/shijiacheng: ========================= 2018-10-08 09:54:15.640 /? D/shijiacheng: DecorView height: 2160 px 2018-10-08 09:54:15.641 /? D/shijiacheng: RootView height: 2094 px 2018-10-08 09:54:15.641 /? D/shijiacheng: Screen height: 2030 px 2018-10-08 09:54:15.641 /? D/shijiacheng: NativeBar height: 66 px 2018-10-08 09:54:15.641 /? D/shijiacheng: ContentView height: 2094 px 2018-10-08 09:54:15.641 /? D/shijiacheng: NavigationBar height: 130 px 2018-10-08 09:54:15.641 /? D/shijiacheng: -------------------------
问题出现了,可以发现contentView的高度比screen屏幕的高度还要大,不禁要怀疑,我们的获取屏幕高度的方法在全面屏下计算错误了。
问题1:获取屏幕高度方法计算不准确
我们一直都是使用如下方法进行屏幕高度测量的:
public static int getScreenHeight() { Resources resource = AppContext.getInstance().getResources(); DisplayMetrics displayMetrics = resource.getDisplayMetrics(); return displayMetrics.heightPixels; }
但是这个方法却是一个十分古老的方法,没有与时俱进,虽然说在普通屏幕上这种方法没有问题,但是在全面屏手机上来说,这种方法就不灵了。
下面我们就来研究一下获取屏幕尺寸的方法的演进。
获取屏幕宽高
获取屏幕的宽高是我们开发中经常遇到的问题,而且相信大家都已经非常熟悉,最常用的为以下两种:
public static int getScreenHeight1(Activity activity) { return activity.getWindowManager().getDefaultDisplay().getHeight(); } public static int getScreenHeight2(Activity activity) { DisplayMetrics displayMetrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.heightPixels; }
其实以上两种方式是一样的,只不过第二种是把信息封装到 DesplayMetrics中,再从DesplayMetrics得到数据。
在 Android 3.2(Api 13) 之后又提供了如下的一个方法,将数据封装到Point中,然后返回宽度高度信息。
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) public static int getScreenHeight3(Activity activity) { Point point = new Point(); activity.getWindowManager().getDefaultDisplay().getSize(point); return point.y; }
在 Android 4.2(Api17) 之后提供了如下方法,与第三种类似也是将数据封装到Point中,然后返回款高度信息。
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static int getScreenHeight4(Activity activity) { Point realSize = new Point(); activity.getWindowManager().getDefaultDisplay().getRealSize(realSize); return realSize.y; }
其实getRealSize这个方法在Android Api15的时候就已经加入了,不过是被隐藏了,通过查阅源码我们可以看到。
Android Api15 Display.java源码中getRealSize()方法被标记为@hide
因此,我们可以重写获取高度的方法,适配所有机型,所有系统。
适配所有屏幕的获取屏幕尺寸的方法
public static int[] getScreenSize(Context context) { int[] size = new int[2]; WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display d = w.getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); d.getMetrics(metrics); // since SDK_INT = 1; int widthPixels = metrics.widthPixels; int heightPixels = metrics.heightPixels; // includes window decorations (statusbar bar/menu bar) if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) try { widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d); heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d); } catch (Exception ignored) { } // includes window decorations (statusbar bar/menu bar) if (Build.VERSION.SDK_INT >= 17) try { Point realSize = new Point(); Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize); widthPixels = realSize.x; heightPixels = realSize.y; } catch (Exception ignored) { } size[0] = widthPixels; size[1] = heightPixels; return size; }
使用新的获取高度的方法,重新运行程序,运行结果已经正常显示了。
2018-10-08 13:19:32.389 /? D/shijiacheng: ========================== 2018-10-08 13:19:32.390 /? D/shijiacheng: DecorView height: 2160 px 2018-10-08 13:19:32.390 /? D/shijiacheng: Screen height: 2160 px 2018-10-08 13:19:32.390 /? D/shijiacheng: NativeBar height: 66 px 2018-10-08 13:19:32.390 /? D/shijiacheng: ContentView height: 2094 px 2018-10-08 13:19:32.390 /? D/shijiacheng: NavigationBar height: 130 px 2018-10-08 13:19:32.390 /? D/shijiacheng: --------------------------
问题2:小米mix切为经典导航键模式下的计算问题
我们在MIUI设置中将全面屏导航样式修改为“经典导航键”样式。
重新运行程序,运行结果如下:
可以发现又出问题了,DecorView = Screen height > NativeBar height + ContentView height
这里不难发现,Screen height将底部虚拟导航栏的高度也算进里面了。
很多情况下,我们都用如下方法获取导航栏的高度:
public static int getNavigationBarHeight() { Resources resources = AppContext.getInstance().getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); int height = resources.getDimensionPixelSize(resourceId); return height; }
这种方法得到的导航栏的高度数值是没问题的,但是在全面屏的手机上,即使隐藏了导航栏,也是可以获取到导航栏的高度的。通过上面的logcat日志可以看到,即使没有导航栏,导航栏的高度的计算也是有值的。
适配小米mix虚拟导航栏
小米mix的机型中,我们可以“force_fsg_nav_bar”来判断小米手机是否开启了全面屏手势。
public static int getHeightOfNavigationBar(Context context) { //如果小米手机开启了全面屏手势隐藏了导航栏则返回 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0) != 0) { return 0; } } int realHeight = getScreenSize(context)[1]; Display d = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); DisplayMetrics displayMetrics = new DisplayMetrics(); d.getMetrics(displayMetrics); int displayHeight = displayMetrics.heightPixels; return realHeight - displayHeight; }
因此可以通过这个方法来判断是否显示了底部导航栏,并且可以计算导航栏的高度。
int navigationBarHeight = FullScreenManager.getHeightOfNavigationBar(MainActivity.this); if (navigationBarHeight > 0){ container_navigationview.setVisibility(View.VISIBLE); }else { container_navigationview.setVisibility(View.GONE); }
正常的显示效果如下:
有虚拟导航栏 | 没有虚拟导航栏 |
---|---|
| |
没有虚拟导航栏Log
2018-10-08 13:19:32.389 /? D/shijiacheng: ========================== 2018-10-08 13:19:32.390 /? D/shijiacheng: DecorView height: 2160 px 2018-10-08 13:19:32.390 /? D/shijiacheng: Screen height: 2160 px 2018-10-08 13:19:32.390 /? D/shijiacheng: NativeBar height: 66 px 2018-10-08 13:19:32.390 /? D/shijiacheng: ContentView height: 2094 px 2018-10-08 13:19:32.390 /? D/shijiacheng: NavigationBar height: 0 px 2018-10-08 13:19:32.390 /? D/shijiacheng: --------------------------
有虚拟导航栏Log
2018-10-08 13:38:03.229 /? D/shijiacheng: ========================== 2018-10-08 13:38:03.230 /? D/shijiacheng: DecorView height: 2160 px 2018-10-08 13:38:03.230 /? D/shijiacheng: Screen height: 2160 px 2018-10-08 13:38:03.230 /? D/shijiacheng: NativeBar height: 66 px 2018-10-08 13:38:03.230 /? D/shijiacheng: ContentView height: 1964 px 2018-10-08 13:38:03.230 /? D/shijiacheng: NavigationBar height: 130 px 2018-10-08 13:38:03.230 /? D/shijiacheng: --------------------------
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
WebRTC基础实践 - 1. WebRTC简介
WebRTC 是一个开源的实时通信项目, 主要目标是对Web/原生App平台上的语音、视频、以及数据传输等实时通讯提供支持。 WebRTC 主要包括以下 JavaScript API(点击链接可查看相关demo)。 getUserMedia(): 获取用户设备的音频和视频. MediaRecorder: 录制音频和视频. RTCPeerConnection: 流式传输两个客户端之间的音频与视频. RTCDataChannel: 在两个客户端之间传输数据流. WebRTC的平台支持情况 目前, PC版和Android版的 Firefox、Opera 和 Chrome 浏览器都支持WebRTC。 此外、iOS和Android的一些原生App也支持WebRTC。 译者注: 国内使用量巨大的360浏览器、搜狗浏览器兼容性基本和Chrome一致。当然, 推荐使用最新的版本(当前时间: 2018年6月28日)。 信令(signaling) WebRTC 通过 RTCPeerConnection 在浏览器之间进行流数据传输, 但还需要一种机制, 来协调通信以及发送控制指令, 这个过程就叫做信令控制....
- 下一篇
iOS 设计模式-外观模式
1.外观模式简介 外观模式(Facade)在开发过程中的运用频率非常高,尤其是在现阶段各种第三方SDK充斥在我们的周边,而这些SDK很大概率会使用外观模式。通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节。当然,在我们的开发过程中,外观模式也是我们封装API的常用手段,例如网络模块、ImageLoader模块等。 2.外观模式定义 外观模式又叫门面模式。外观模式为子系统中一组不同的接口提供统一的接口。外观模式定义了上层接口,通过降低复杂度和隐藏子系统间的通信及依存关系,让子系统更易于使用。 3.外观模式的使用场景 3.1 为一个复杂子系统提供一个简单接口。子系统往往因为不断演化而变得越来越复杂,甚至可能被替换。大多数模式使用时都会产生更多、更小的类,在这使子系统更具可重用性的同时也更容易对子系统进行定制、修改,这种易变性使得隐藏子系统的具体实现变得尤为重要。Facade可以提供一个简单统一的接口,对外隐藏子系统的具体实现、隔离变化。 3.2 当你需要构建一个层次结构的子系统时,使用Facade模式定义了子系统中每层的入口点。...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- 2048小游戏-低调大师作品
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Red5直播服务器,属于Java语言的直播服务器
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7