点击上方“程序员大咖”,选择“置顶公众号”
关键时刻,第一时间送达!
一、现状
由于Android碎片化严重,屏幕适配一直是开发中较为头疼的问题。面对市面上五花八门的屏幕大小与分辨率,Android基于dp与res目录名称来适配的方案已无法满足一次编写全屏幕适配的需求,为了达到最优的视觉效果,开发过程中总是需要花费较多资源进行适配。也有开发者给出了一些自己的解决方案。首先来分析一下一些常见的解决方案的现状:
1.官方适配方案
2.玩家适配方案
广大玩家的适配目的很明确,目的就是要确保控件在不同屏幕的相对大小一致,看起来一毛一样的。以一位大神玩家的两种适配方案为例:
扩展性较差。对于每一种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数值。
public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f); }
return 0; }
可以看见换算方法非常简单,而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