大家好,我是本期栏目编辑小友,现在为大家讲解基于Android的全屏幕适配方案问题。
一.现状
由于安卓系统碎片化严重,屏幕适配一直是开发中令人头疼的问题。面对市场上各种屏幕尺寸和分辨率,安卓基于dp和res目录名的适配方案已经不能满足一次性写入全屏适配的需求。为了达到最好的视觉效果,在开发过程中总是需要更多的资源去适应。一些开发者也给出了自己的一些解决方案。首先,我们来分析一些常见解决方案的现状:
官方适应计划
dp .它是dp Android开发中的一个独特单元。与px不同,dp是基于每英寸屏幕像素的单位。在低密度屏幕上,可能1dp=1px,但在高密度屏幕上,可能1dp=4px。在编写xml布局时,如果控件的长度和宽度由dp指定,则可以确保在各种大小和分辨率的屏幕下,控件的绝对大小大致相同。也就是说,我们实际看到的控件的大小,无论是在pad下,还是大屏或小屏的手机上,几乎是一样的:
资源目录名。上图显示,尽管dp确保不同屏幕中控件的绝对大小一致。这样做的好处是,在大小差不多的屏幕中,无论分辨率有多大,都不会影响布局;但是,当屏幕尺寸相差很大时,似乎只有保证控件的绝对尺寸才存在一些问题。在res目录下,您可以向每个资源目录添加后缀,如'-1920x1080 ',以适应不同的屏幕。具体规则可以在官网文档中找到。这样可以为不同的屏幕提供不同的布局,甚至可以为pad和手机提供两种完全不同的布局风格。但是通常设计师不会为不同的屏幕提供不同的设计图。他们的需求只是不同屏幕上控件的相对大小是一样的,所以dp不能满足这个要求,重新适应各种屏幕有点麻烦,修改起来也比较麻烦。通常,我们需要的适应是这样的:
百分比布局支持库。否,但在API级别26.0.0-beta 1中已弃用。
约束布局.百分比库被否决后推荐的布局似乎有点复杂。
玩家适应方案。大部分玩家的适配目的很明确,目的是保证不同屏幕上控件的相对大小相同,看起来也一样。以大神玩家的两个改编方案为例:
选择一。写一个脚本把长度转换成每个分辨率的长度,缺点是很难覆盖市面上所有的分辨率。
选择二。自动布局支持库。库的思路很好:和设计图对比,用px写布局不影响预览;在绘图阶段,将对应设计图的px数值计算转换为当前屏幕下的自适应尺寸;为了简化访问,每个Layout在膨胀时会自动转换为相应的AutoLayout,因此不需要在所有xml中进行更改。但是,该库也存在以下问题:
可扩展性差。对于每个视图组,编写相应的AutoLayout进行扩展,对于每个需要适配的视图的每个属性,编写代码进行适配和扩展;
在测量阶段,进行数值计算。性能消耗,这对于不在LayoutParams中的属性是不合理的。例如,如果您转换“文本视图”的“文本大小”,并在“测量”中设置“文本大小”,那么播放器在代码中动态设置的“文本大小”将无效,因为每次在“确定”上自动布局都会重置和覆盖它。
有许多问题,作者不再坚持。
第二,观念
对于尺寸差异较大的屏幕,不宜采用相同的设计方案,否则大屏的优势不会得到充分体现,官方的改编方案似乎也表达了这个意思。但是在实际的设计开发中,对于一个普通的App来说,很少有项目有意愿和精力去设计开发一套适合每个屏幕的设计方案来适应。
通常一个简单的适配要求是:如果设计宽度为200,设计上标注的控件长度为3,那么控件的长度就相当于总宽度的3/200,那么我们希望控件的长度在任何屏幕尺寸上都是屏幕宽度的3/200。
我个人认为AutoLayout的设计思路很优秀,但是在以LayoutParams和属性为切入点的测量过程中的转换计算方案存在效率和可扩展性等问题。那么,安卓计算长度的收敛在哪里?安卓计算长度的时候可以转换吗?如果安卓计算长度时可以转换,那么就不需要一系列冗余。
的计算以及适配,一切问题就都迎刃而解了。经过一番寻觅,发现系统进行长度计算的收口为 TypedValue中的applyDimension函数,传入单位与value将其计算为对应的px数值。
可以看见换算方法非常简单,而DisplayMetrics的 所有属性都是public的,不用反射就能修改;
pt的原意是长度单位磅,根据当前屏幕与设计图尺寸将metrics.xdpi 进行修改就可以实现将pt这个单位重定义成我们所需要的相对长度单位,使修改之后计算出的1pt实际对应的px/屏幕宽度px=1px/设计图宽度px。
而这个DisplayMetrics从哪来?从源码中可以看出一般为mContext.getResources().getDisplayMetrics(),这个mContext即为所在AcTIvity;
横竖屏切换等ConfiguraTIon的变化会导致DisplayMetrics 的 重新计算还原;
px,dp与sp都是平时常用的单位,而pt,in与mm几乎没有看见过,从这些不常见的单位下手正好可以不影响其他常用的单位。
基于以上几点,便有了以下方案。
三、方案本适配方案的目标是:完全按照设计图上标注的尺寸来编写页面,所编写的页面在所有大小与分辨率的屏幕上都表现一致,即控件在所有屏幕上相对于整个屏幕的相对大小都一致(看起来只是将设计图等比缩放至屏幕宽度大小)。
核心。使用冷门的pt作为长度单位,按照上述想法将其重定义为与屏幕大小相关的相对单位,不会对dp等常用单位的使用造成影响。
绘制。编写xml时 完全对照设计稿上的尺寸来编写,只不过单位换为pt。假如设计图宽度为200,一个控件在设计图上标注的长度为3,只需要在初始化时定义宽度为200,绘制该控件时长度写为3pt,那么在任何大小的屏幕上该控件所表现的长度都为屏幕宽度的3/200。如果需要在代码中动态转换成px的话,使用 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, value, metrics)。
预览。实时预览时绘制页面是很重要的一个环节。以1334x750的设计图为例,为了实现于正常绘制时一样的预览功能,创建一个长为1334磅,宽为750磅的设备作为预览,经换算约为21.5英寸((sqrt(1334^2+750^2))/72)。 预览时选择这个设备即可。
代码处理。在acTIvityonCreate时修改DisplayMetrics即可,推荐写在基类或AcTIvityLifecycleCallbacks中,参考github demo。
Point size = new Point(); activity.getWindowManager().getDefaultDisplay().getSize(size); context.getResources().getDisplayMetrics().xdpi = size.x / designWidth * 72f;
这样绘制出来的页面就跟设计图几乎完全一样,无论大小屏上看起来就只是将设计图缩放之后的结果。
适配前(左图API19 400x800, 右图API24 1440x2560):
适配后(左图API19 400x800, 右图API24 1440x2560):
虽然方案比较简单,但是为了方便使用也整理成了一个library,代码及demo见github
一、现状由于Android碎片化严重,屏幕适配一直是开发中较为头疼的问题。面对市面上五花八门的屏幕大小与分辨率, Android基于dp与res目录名称来适配的方案已无法满足一次编写全屏幕适配的需求,为了 达到最优的视觉效果,开发过程中总是需要花费较多资源进行适配。也有开发者给出了一些自己的解决方案。首先来分析一下一些常见的解决方案的现状:
官方适配方案
dp。dp是Android开发中特有的一个单位。与px不同,dp是基于屏幕像素密度的一种单位。在 密度低的屏幕上或许1dp=1px,但在密度高的屏幕上可能1dp=4px。编写布局xml时,如果一个控件的长宽都使用dp来指定,那么能确保该控件在各种大小与分辨率的屏幕下的绝对大小都大致相当。也就是说无论在pad下还是大小屏手机下,我们实际看到的该控件的大小是差不多的:
资源目录名。上图可见虽然使用dp确保了控件在不同屏幕中的绝对大小一致。这样的好处在于,在大小相近的屏幕中, 无论分辨率多大都不会对布局造成影响;但是当屏幕大小相差较大时,仅保证控件的 绝对大小看起来就有些问题了。在res目录下可以给 各资源目录都加上例如‘-1920x1080’等后缀来适配不同的 屏幕,具体规则可见官网文档。这样可以针对不同的屏幕提供不同的布局,甚至针对pad与手机提供两套完全不同的布局样式。但是通常情况下,设计师并不会对不同屏幕提供不同的设计图,他们的需求仅仅是不同屏幕下控件对屏幕的相对大小一致,所以dp并不能满足这一点,而对各种屏幕适配一遍又显得略为繁琐,并且修改也较为麻烦。通常我们需要的适配是这样的:
百分比布局支持库。没有使用过,但是deprecated in API level 26.0.0-beta1。
ConstraintLayout。百分比支持库deprecated之后推荐使用的布局,看起来似乎略复杂。
玩家适配方案。广大玩家的适配目的很明确,目的就是要确保控件在不同屏幕的相对大小一致,看起来一毛一样的。 以一位大神玩家的两种适配方案为例:
方案一。 编写脚本将长度转换成各分辨率下的长度,缺点是难以覆盖市面上的所有分辨率。
方案二。AutoLayout支持库。该库的想法非常好:对照设计图,使用px编写布局,不影响预览;绘制阶段将对应设计图的px数值计算转换为当前屏幕下适配的大小;为简化接入,inflate时自动将 各Layout转换为对应的AutoLayout,从而不需要在所有的xml中更改。但是同时该库也存在以下等问题:
扩展性较差。对于每一种ViewGroup都要对应编写对应的AutoLayout进行扩展,对于各View的每个需要适配的属性都要编写代码进行适配扩展;
在onMeasure阶段进行数值计算。消耗性能,并且这对于非LayoutParams中的属性存在较多不合理之处。比如在onMeasure时对TextView的 textSize进行换算并setTextSize,那么 玩家在代码中动态设置的textSize都会失效,因为在每次onMesasure时都会重新被 AutoLayout重新设置覆盖。
issue较多并且作者已不再维护。
二、想法对于大小差异较大的屏幕,本不该使用同一套设计方案,否则大屏的优势没有完全体现出来,从官方的适配方案也似乎是表达了这个意思。但是在实际设计与开发中,对于一个普通的App,很少有项目有意愿有精力来对各屏幕来分别设计与开发一套设计方案来适配。
通常的一个简单的适配需求是:假如设计图宽度为200,一个控件在设计图上标注的长度为3,那么该控件长度相当于总宽度的3/200,那么我们希望在任何大小的屏幕上该控件所表现的长度都为屏幕宽度的3/200。
个人觉得AutoLayout的设计思想非常优秀,但是将LayoutParams与 属性作为切入口在mesure过程中进行 转换计算的方案存在效率与扩展性等方面的问题。那么Android计算长度的收口在哪里,能不能在Android计算长度时进行换算呢?如果能在Android计算长度时进行换算,那么就不需要一系列多余的计算以及适配,一切问题就都迎刃而解了。
经过一番寻觅,发现系统进行长度计算的收口为 TypedValue中的applyDimension函数,传入单位与value将其计算为对应的px数值。
可以看见换算方法非常简单,而DisplayMetrics的 所有属性都是public的,不用反射就能修改;
pt的原意是长度单位磅,根据当前屏幕与设计图尺寸将metrics.xdpi 进行修改就可以实现将pt这个单位重定义成我们所需要的相对长度单位,使修改之后计算出的1pt实际对应的px/屏幕宽度px=1px/设计图宽度px。
而这个DisplayMetrics从哪来?从源码中可以看出一般为mContext.getResources().getDisplayMetrics(),这个mContext即为所在AcTIvity;
横竖屏切换等ConfiguraTIon的变化会导致DisplayMetrics 的 重新计算还原;
px,dp与sp都是平时常用的单位,而pt,in与mm几乎没有看见过,从这些不常见的单位下手正好可以不影响其他常用的单位。
基于以上几点,便有了以下方案。
三、方案本适配方案的目标是:完全按照设计图上标注的尺寸来编写页面,所编写的页面在所有大小与分辨率的屏幕上都表现一致,即控件在所有屏幕上相对于整个屏幕的相对大小都一致(看起来只是将设计图等比缩放至屏幕宽度大小)。
核心。使用冷门的pt作为长度单位,按照上述想法将其重定义为与屏幕大小相关的相对单位,不会对dp等常用单位的使用造成影响。
绘制。编写xml时 完全对照设计稿上的尺寸来编写,只不过单位换为pt。假如设计图宽度为200,一个控件在设计图上标注的长度为3,只需要在初始化时定义宽度为200,绘制该控件时长度写为3pt,那么在任何大小的屏幕上该控件所表现的长度都为屏幕宽度的3/200。如果需要在代码中动态转换成px的话,使用 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, value, metrics)。
预览。实时预览时绘制页面是很重要的一个环节。以1334x750的设计图为例,为了实现于正常绘制时一样的预览功能,创建一个长为1334磅,宽为750磅的设备作为预览,经换算约为21.5英寸((sqrt(1334^2+750^2))/72)。 预览时选择这个设备即可。
代码处理。在acTIvityonCreate时修改DisplayMetrics即可,推荐写在基类或AcTIvityLifecycleCallbacks中,参考github demo。
Point size = new Point(); activity.getWindowManager().getDefaultDisplay().getSize(size); context.getResources().getDisplayMetrics().xdpi = size.x / designWidth * 72f;
这样绘制出来的页面就跟设计图几乎完全一样,无论大小屏上看起来就只是将设计图缩放之后的结果。
适配前(左图API19 400x800, 右图API24 1440x2560):
适配后(左图API19 400x800, 右图API24 1440x2560):
虽然方案比较简单,但是为了方便使用也整理成了一个library,代码及demo见github
技术专区 周立功的AWorks哲学思想 Shader的诞生 十大贴图常用贴图介绍 Unity中Shader的三种类别 如何区分 Linux 监控多台主机的技巧分享 针对SWO Trace使用的教程,对MSP432 开发