一、UI设计稿尺寸
iPhone设计尺寸参考:https://uiiiuiii.com/screen/ios.htm
在说屏幕适配之前,先提一下UI设计稿的尺寸问题。我们布局时的的尺寸都是通过UI设计图获得,那么UI设计师是以什么屏幕尺寸为标准来设计的就至关重要了。
UI设计稿为Iphone 8 plus(1242px * 2208px)
一些公司IOS和Android共用一套设计稿,假设设计师以IPhone的标准设计,对IOS来说就很友好,但是对Android来说就很坑了。因为Iphone的Retina屏的像素对Android设备来说不是标准的尺寸。
假设UI设计师以iPhone 8 plus的尺寸为标准来设计,iPhone 8 plus的设计尺寸为1242px * 2208px(这里有个坑,iPhone8plus的分辨率为1080 * 1920,和设计稿不一样,需要注意下),在zeplin转化成xxhdpi为414dp*736dp。
现在问题来了,Android从哪找一部手机符合这个尺寸呢?
参考某些手机尺寸信息,发现Nexus6p的分辨率非常接近。
Nexus 6p的分辨率为1440px * 2560px,这是xxxhdpi的尺寸,按照官网的说法,这个屏幕的缩放因子应该为4,可是实际测试scale=3.5。
1440/3.5=411,2560/3.5=731。非常接近设计稿的尺寸!
但是这样是不够精确的,而且还要对Android其他尺寸的设备做适配,如:1080 * 1920,720 * 1280,因此最好是IOS和Android各出一套UI图
UI设计稿尺寸为1080*1920
如果UI以1080 * 1920的尺寸设计,那么就很好了,因为这个尺寸是自带适配属性的。而且国内的大部分手机都是1080的分辨率。
为什么说1080 * 1920是自带适配属性?因为如果你的布局文件用dp做单位,那么下面三种尺寸是完全适配的,因为他们的dp尺寸完全一样。
screenSize = 1440 * 2560 , screenDensity = 4, dp = 360 * 640
screenSize = 1080 * 1920 , screenDensity = 3, dp = 360 * 640
screenSize = 720 * 1280 , screenDensity = 2, dp = 360 * 640
如果出现其他奇葩的屏幕宽度,比如1120、980这种,那么就需要做对应的适配了。
二、屏幕缩放因子
屏幕尺寸计算的关键
Android系统是怎样计算缩放因子的呢?
看看Display.getMetrics()
方法做了什么,该方法最终调用下面的方法
private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
Configuration configuration, int width, int height) {
//logicalDensityDpi是屏幕固有的dpi
outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
//假设logicalDensityDpi=480,那么outMetrics.density=3,这里DENSITY_DEFAULT_SCALE=1/160=0.00625
outMetrics.density = outMetrics.noncompatDensity =
logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
//下面的x和y方向的dpi是通过dpi的公式计算出来的
outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
width = configuration != null && configuration.appBounds != null
? configuration.appBounds.width() : width;
height = configuration != null && configuration.appBounds != null
? configuration.appBounds.height() : height;
outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
compatInfo.applyToDisplayMetrics(outMetrics);
}
}
屏幕的固有dpi也可以在DisplayMetrics类里面找到
/**
* The device's stable density.
* <p>
* This value is constant at run time and may not reflect the current
* display density. To obtain the current density for a specific display,
* use {@link #densityDpi}.
*/
public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
因此屏幕的缩放因子是厂商固定好的。而且可能和你根据屏幕尺寸计算的结果不一致。
真机测试
测试机Honor7X
下面是商家给出的手机参数
根据商家给出的尺寸计算dpi = Sqrt(1080^2 + 2160^2)/5.93 = 407.243
在真机上将DisplayMetrics对象打印出来可以看到
屏幕的固有dpi和实际屏幕尺寸计算的dpi差很多,但是Android设备的缩放因子计算却是以固有dpi为准:
缩放因子 = 固有dpi * DisplayMetrics.DENSITY_DEFAULT_SCALE
缩放因子决定屏幕的dp尺寸,而屏幕适配用到的也是dp尺寸。
三、屏幕适配
这里只考虑以宽度为基准适配。适配最终的效果是在不同尺寸的设备上显示相同的UI效果,因此最终显示在各个设备上的尺寸是一个百分比尺寸。
举个例子,假设设计稿为1080px宽度(360dp),Nexus6p手机的宽度为1440px(411dp),那么在1080手机上的1dp,放到1440手机上是多少呢?很简单
width = 1 * 411 / 360 = 1 * (1440 / 3.5) / (1080 / 3) = 1.14 dp
1.生成百分比尺寸
我们在开发时屏幕上的尺寸一般以dp和sp为单位,Android已经帮我们做了一部分的自适应。但是不同屏幕dp的尺寸还是有较大区别。如果以屏幕为基础的百分比尺寸为单位那就肯定不会有问题了。
以生成xxdpi和xdpi文件为例。
以宽度为计算标准,计算百分比尺寸需要几个参数:
1、你现在使用UI设计稿的手机的屏幕宽度。比如我用的Nexus 6p,那么我的宽度为1440。
2、你现在使用UI设计稿的手机的屏幕缩放因子。Nexus 6p为3.5
3、适配手机屏幕的宽度,xxhdpi取1080,xhdpi取720。
4、适配手机屏幕的缩放因子。xxhdpi取3.0,xhdpi取2.0。
xxhdpi的百分比尺寸为:
UI_SCREEN_WIDTH = 1440
UI_SCREEN_SCALE = 3.5
XXHDPI_SCREEN_WIDTH = 1080
XXHDPI_SCREEN_SCALE = 3.0
percentSize = uiSize * ( XXHDPI_SCREEN_WIDTH / XXHDPI_SCREEN_SCALE ) / ( UI_SCREEN_WIDTH / UI_SCREEN_SCALE )
xhdpi的百分比尺寸为:
UI_SCREEN_WIDTH = 1440
UI_SCREEN_SCALE = 3.5
XHDPI_SCREEN_WIDTH = 720
XHDPI_SCREEN_SCALE = 2.0
percentSize = uiSize * ( XHDPI_SCREEN_WIDTH / XHDPI_SCREEN_SCALE ) / ( UI_SCREEN_WIDTH / UI_SCREEN_SCALE)
uiSize就是你使用设计稿的dp尺寸,得出的percentSize就是相对应屏幕的百分比dp尺寸。
上面只适配了1080和720两种宽度,还需要适配哪些宽度呢?
看看友盟的设备统计:https://compass.umeng.com/#/equipment?_k=8snfbu
2.建立适配文件夹
根据自己的适配需求来建立对应的适配文件
适配文件的创建可以参考:Android资源文件说明
继续以Nexus6p为例子,设计稿的尺寸是411dp的,假设我想适配1440(360dp)、1080(360dp)、720(360dp)的标准屏幕,那么我需要建立如下几个文件:
res/values-xhdpi/dimens.xml
res/values-xxhdpi/dimens.xml
res/values-sw411dp-xxxhdpi/dimens.xml
res/values-xxxhdpi/dimens.xml
然后将算好的百分比尺寸填写到这几个文件中,在项目布局文件中直接引用就可以了。
注意,这里411dp的尺寸文件是原始的设计稿尺寸,其他xdpi、xxdpi、xxxdpi都是加了百分比的尺寸。
百分比尺寸文件手写是不可能的,因此我们借助一些其他手段自动生成。
3.新项目dimens文件生成
如果我们的项目刚开始,那么最好先准备好这些文件。
生成工具:dimens文件生成脚本
下面是我生成的三种dpi的dimens.xml文件:
4.老项目dimens文件生成和修改
如果你像我一样项目写到一半发现有适配的问题,那么可以用下面的解决方法。下面可以用到上一步生成的固定dp和sp尺寸文件。
没有适配的项目存在一个默认的res/value/dimens.xml文件,这里的尺寸是默认尺寸,我的项目默认尺寸是针对xxxhdpi屏幕的。还缺少xxhdpi和xhdpi屏幕的dimens.xml文件,xxxhdpi的dimens.xml文件最好也创建一份。
可能想到方法是将现有的dimens文件copy两份,然后修改里面的值为对应的百分比屏幕尺寸。如果现有的dimens文件内容非常多,那么这个工作量将会非常大。下面我们编写脚本自动完成这个过程:
上面三个transfer文件是根据values/dimens.xml文件生成。
自动化脚本:
https://github.com/xionghaoo/Android-screen-adaptation/blob/master/dimen_transfer.py
dimen文件的问题解决了,我们已经写在布局文件中的dp和sp怎么办?下面给出自动替换脚本:
自动化脚本:
https://github.com/xionghaoo/Android-screen-adaptation/blob/master/dimen_modify.py
例如:脚本自动将layout文件夹下面的xml文件中的10dp替换成@dimen/x10dp
注意:程序是根据字符串搜索替换,如果替换出现问题将无法恢复,建议在替换之前先做好备份
上述脚本会自动替换已经写好数值的dp和sp值:
但是TextView的默认字体大小是14sp,也是没有经过适配的,上述脚本也不会自动替换。这时候我们可以在style文件的基础主题里面修改。
到此为止,屏幕适配就完成了。另外还有在代码中写死的尺寸就需要自己手动修改了,不过我相信这种尺寸不会太多。
其他
Android的适配以我目前的认知来看并不能做到覆盖所有机型,只能根据自身的需求适配主流机型或一些特定的机型。
另外有一种修改系统固有缩放因子的方案,这种方案会修改系统组件的UI样式,如果有用到系统控件的项目不推荐使用。