众所周知,Android机型尺寸五花八门,于是屏幕适配被成为我们Android开发很重要的一部分,Android屏幕适配的时候大家或多或少都会遇到各种问题,所以这也是面试一家新公司,面试官基本会问的一个问题,因为在我的角度看,不同公司不同项目它的屏幕适配的方法都可能不一样的。下面让我来分享我在网上看的各种屏幕适配资料再加上自己的实践得出的心得和经验。
屏幕尺寸指的是屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米,比如说iPhone7是4.7寸,iPhone7Plus是5.5寸。(勿吐槽我,因为Android机太多了,说某个品牌大家不一定能马上反应过来)
屏幕分辨率是指在横纵方向上的像素点数,单位是px,1px = 1个像素点。一般的说法是纵向像素*横向像素,例如,1920*1080。大家可以理解成纵向有1920个点,横向1080个点。
屏幕像素密度匙指每英寸上的像素点数,单位是dpi,是dpi。在单一变化条件下,屏幕尺寸越小,分辨率越高,像素密度越大,反之越小。
px这个是像素的单位,px均为整数。
dip、dp是同一个意思,是Density Independent Pixels的缩写,即密度无关像素。是Android特有的单位,规定以160dpi为基准,即1dp对应1个pixel,计算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp对应 的像素点越多。
sp我们大多数用在设置字体的大小上,sp尽量不要用小数,可能在适配的过程中导致精度丢失。
名称 | 像素密度范围 | 比例 |
---|---|---|
mdpi | 120dpi~160dpi | 2 |
hdpi | 160dpi~240dpi | 3 |
xhdpi | 240dpi~320dpi | 4 |
xxhdpi | 320dpi~480dpi | 6 |
xxxhdpi | 480dpi~640dpi | 8 |
问题来了,我们是不是要在每一种dpi都出一套图片资源,按照上述是一种处理方法,但是是不是觉得这样的工作量很大,不说我们,你的美工MM会不会帮你弄都是一个问题,还有你各个目录都放一套图片资源,到时你打包的apk就变得很大。那么有没有什么好的方法既能保证屏幕适配,又可以最小占用设计资源,同时最好又只使用一套dpi的图片资源呢?
好啦,假设我们只放一套图片资源在hdpi的文件夹下,然后我们的测试机的dpi是跑xhdpi文件夹的,系统发现xhdpi没有图片资源,然后就跑去了我们放图片资源的hdpi的文件夹读取图片,同时根据比例放大图片,此时我们的图片就可能被放大导致不清晰了。所以我们需要美工MM提供一套你需要支持的最大dpi的图片,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。
上面所说我们提供一套图片资源就好了,然而我们应该放在哪个文件夹呢?我在某些统计SDK平台看到,xhdpi是目前最普遍的手机对应的像素密度,所以我们就需要一套放xhdpi的图片资源就好了,虽然现在手机的发展,xxhdpi和xxxhdpi越来越多了,但是我们暂时就维护好xhdpi一个资源文件夹就好了。
公司的设计MM都会按照一种size来设计、切图、标记。然而我公司的MM就用iPhone6plus的大小来设计的,有公司会要求设计MM做一套iOS的一套Android的,好羡慕啊~。iPhone6plus是5.5英寸,分辨率是1920*1080,那么他的dpi计算就是,dpi = √(1920*1920+1080*1080)/5.5 ≈ 401,再根据上面的dpi表,这套图我们是要放在xxhdpi,大家可能觉得我刚刚说应该首选放在xhdpi,然后这里就说自己公司的图就放在xxhdpi,这是因为不同的公司它的习惯不同,我们根据实际情况进行处理。
可能上面的介绍你已经知道了解了。那么我接着来说一下如何适配。
我们首先用一个例子来引出:
wrap_content和dip我们在布局的时候经常用到,那么在布局中他们的效果是怎么的呢?
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
上面的效果,我是在420dpi的测试机效果图,首先我们的图片是180*180px的size,对于420dpi的测试机,系统首先会对应的xxhdpi寻找资源,(见xxhdpi文件夹效果图),找到之后显示180px的尺寸;第二种情况,如果在对应的资源文件夹下找不到,就在最接近的资源文件下找,然后我们只放在hdpi下,系统就只能读取hdpi下的图片,那么屏幕显示360px(xxhdpi:hdpi = 2:1)。
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<ImageView
android:layout_width="100dip"
android:layout_height="100dip"
android:src="@drawable/ic_launcher" />
上面的效果,我是用1920*1080,420dpi的测试机,在wrap_content情况,180px,就根据像素显示;在100dip的情况,我们需要换算一下,px = 100x(420/160)= 262.5px。
上面的三种情况,总结起来就是1.图片应该放哪里?不同手机怎么适配资源?3.自适应尺寸跟固定尺寸的效果区别。
上面好像都是说图片放哪里的问题,接着我们要说的是尺寸适配问题。其实说dip可以去除不同像素密度的问题,使得1dip在不同像素密度上面的显示的效果大致相同,但是由于Android屏幕设备的多样式,比如说,Nexus S和Nexus One属于hdpi,屏幕宽度是320dp,而Nexus 5属于xxhdpi,屏幕宽度是360dp,Galaxy Nexus属于xhdpi,屏幕宽度是384dp,Nexus 6 属于xxxhdpi,屏幕宽度是410dp。所以说,光Google自己一家的产品就已经有这么多的标准,而且屏幕宽度和像素密度没有任何关联关系,即使我们使用dp,在320dp宽度的设备和410dp的设备上,还是会有90dp的差别。我们写代码的时候尽量使用match_parent和wrap_content,尽可能少用dip指定某些控件的长宽,再结合权重,大部分情况我们是可以做到适配的了。
但是在我面试的时候,面试总喜欢刁难一下你,转几个弯再问你问题的。
我接着介绍四种布局屏幕适配的方法。
values-XXX*XXX(dp数据)
我们创建我们需要适配的分辨率,在对应的valus下设置我们需要的dimen,单位用dp。我们尽量参考某些统计SDK再配合自己应用最常用的几款机型做对应的适配。
values-sw600dp
这里的sw代表smallwidth的意思,当你所有屏幕的最小宽度都大于600dp时,屏幕就会自动到带sw600dp后缀的资源文件里去寻找相关资源文件,这里的最小宽度是指屏幕宽高的较小值,每个屏幕都是固定的,不会随着屏幕横向纵向改变而改变。
values-XXX*XXX(px数据)
下面的方案来自Android Day Day Up 一群的【blue-深圳】,谢谢大神。
因为分辨率不一样,所以不能用px;因为屏幕宽度不一样,所以要小心的用dp,那么我们可不可以用另外一种方法来统一单位,不管分辨率是多大,屏幕宽度用一个固定的值的单位来统计呢?
答案是:当然可以。
我们假设手机屏幕的宽度都是320某单位,那么我们将一个屏幕宽度的总像素数平均分成320份,每一份对应具体的像素就可以了。
具体如何来实现呢?我们看下面的代码.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
public class MakeXml {
private final static String rootPath = "C:\\Users\\Administrator\\Desktop\\layoutroot\\values-{0}x{1}\\";
private final static float dw = 320f;
private final static float dh = 480f;
private final static String WTemplate = "{1}px \n";
private final static String HTemplate = "{1}px \n";
public static void main(String[] args) {
makeString(320, 480);
makeString(480,800);
makeString(480, 854);
makeString(540, 960);
makeString(600, 1024);
makeString(720, 1184);
makeString(720, 1196);
makeString(720, 1280);
makeString(768, 1024);
makeString(800, 1280);
makeString(1080, 1812);
makeString(1080, 1920);
makeString(1440, 2560);
}
public static void makeString(int w, int h) {
StringBuffer sb = new StringBuffer();
sb.append("\n");
sb.append("" );
float cellw = w / dw;
for (int i = 1; i < 320; i++) {
sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sb.append(WTemplate.replace("{0}", "320").replace("{1}", w + ""));
sb.append("");
StringBuffer sb2 = new StringBuffer();
sb2.append("\n");
sb2.append("" );
float cellh = h / dh;
for (int i = 1; i < 480; i++) {
sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
change(cellh * i) + ""));
}
sb2.append(HTemplate.replace("{0}", "480").replace("{1}", h + ""));
sb2.append("");
String path = rootPath.replace("{0}", h + "").replace("{1}", w + "");
File rootFile = new File(path);
if (!rootFile.exists()) {
rootFile.mkdirs();
}
File layxFile = new File(path + "lay_x.xml");
File layyFile = new File(path + "lay_y.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sb.toString());
pw.close();
pw = new PrintWriter(new FileOutputStream(layyFile));
pw.print(sb2.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
}
代码应该很好懂,我们将一个屏幕宽度分为320份,高度480份,然后按照实际像素对每一个单位进行复制,放在对应values-widthxheight文件夹下面的lax.xml和lay.xml里面,这样就可以统一所有你想要的分辨率的单位了,下面是生成的一个320*480分辨率的文件,因为宽高分割之后总分数和像素数相同,所以x1就是1px,以此类推
<resources><dimen name="x1">1.0pxdimen>
<dimen name="x2">2.0pxdimen>
<dimen name="x3">3.0pxdimen>
<dimen name="x4">4.0pxdimen>
<dimen name="x5">5.0pxdimen>
<dimen name="x6">6.0pxdimen>
<dimen name="x7">7.0pxdimen>
<dimen name="x8">8.0pxdimen>
<dimen name="x9">9.0pxdimen>
<dimen name="x10">10.0pxdimen>
...省略好多行
<dimen name="x300">300.0pxdimen>
<dimen name="x301">301.0pxdimen>
<dimen name="x302">302.0pxdimen>
<dimen name="x303">303.0pxdimen>
<dimen name="x304">304.0pxdimen>
<dimen name="x305">305.0pxdimen>
<dimen name="x306">306.0pxdimen>
<dimen name="x307">307.0pxdimen>
<dimen name="x308">308.0pxdimen>
<dimen name="x309">309.0pxdimen>
<dimen name="x310">310.0pxdimen>
<dimen name="x311">311.0pxdimen>
<dimen name="x312">312.0pxdimen>
<dimen name="x313">313.0pxdimen>
<dimen name="x314">314.0pxdimen>
<dimen name="x315">315.0pxdimen>
<dimen name="x316">316.0pxdimen>
<dimen name="x317">317.0pxdimen>
<dimen name="x318">318.0pxdimen>
<dimen name="x319">319.0pxdimen>
<dimen name="x320">320pxdimen>
resources>
那么1080*1960分辨率下是什么样子呢?我们可以看下,由于1080和320是3.37倍的关系,所以x1=3.37px。
<resources><dimen name="x1">3.37pxdimen>
<dimen name="x2">6.75pxdimen>
<dimen name="x3">10.12pxdimen>
<dimen name="x4">13.5pxdimen>
<dimen name="x5">16.87pxdimen>
<dimen name="x6">20.25pxdimen>
<dimen name="x7">23.62pxdimen>
<dimen name="x8">27.0pxdimen>
<dimen name="x9">30.37pxdimen>
<dimen name="x10">33.75pxdimen>
...省略好多行
<dimen name="x300">1012.5pxdimen>
<dimen name="x301">1015.87pxdimen>
<dimen name="x302">1019.25pxdimen>
<dimen name="x303">1022.62pxdimen>
<dimen name="x304">1026.0pxdimen>
<dimen name="x305">1029.37pxdimen>
<dimen name="x306">1032.75pxdimen>
<dimen name="x307">1036.12pxdimen>
<dimen name="x308">1039.5pxdimen>
<dimen name="x309">1042.87pxdimen>
<dimen name="x310">1046.25pxdimen>
<dimen name="x311">1049.62pxdimen>
<dimen name="x312">1053.0pxdimen>
<dimen name="x313">1056.37pxdimen>
<dimen name="x314">1059.75pxdimen>
<dimen name="x315">1063.12pxdimen>
<dimen name="x316">1066.5pxdimen>
<dimen name="x317">1069.87pxdimen>
<dimen name="x318">1073.25pxdimen>
<dimen name="x319">1076.62pxdimen>
<dimen name="x320">1080pxdimen>
resources>
无论在什么分辨率下,x320都是代表屏幕宽度,y480都是代表屏幕高度。
那么,我们应该如何使用呢?
首先,我们要把生成的所有values文件夹放到res目录下,当设计师把UI高清设计图给你之后,你就可以根据设计图上的尺寸,以某一个分辨率的机型为基础,找到对应像素数的单位,然后设置给控件即可。
但是,还是有个问题,这是因为由于在生成的values文件夹里,没有对应的分辨率,其实一开始是报错的,因为默认的values没有对应dimen,所以我只能在默认values里面也创建对应文件,但是里面的数据却不好处理,因为不知道分辨率,我只好默认为x1=1dp保证尽量兼容。这也是这个解决方案的几个弊端,对于没有生成对应分辨率文件的手机,会使用默认values文件夹,如果默认文件夹没有,就会出现问题。
所以说,这个方案虽然是一劳永逸,但是由于实际上还是使用的px作为长度的度量单位,所以多少和google的要求有所背离,不好说以后会不会出现什么不可预测的问题。其次,如果要使用这个方案,你必须尽可能多的包含所有的分辨率,因为这个是使用这个方案的基础,如果有分辨率缺少,会造成显示效果很差,甚至出错的风险,而这又势必会增加软件包的大小和维护的难度,所以大家自己斟酌,择优使用。
4. 百分比布局
对于网站来说他们就是使用百分比布局的,所以我们发现他们并没有出现任何的屏幕适配问题,写iOS的同学也是使用百分比布局,我们的Google已经支持百分比的方式布局了。具体的我们来参考一下【鸿洋】:http://blog.csdn.net/lmj623565791/article/details/46695347;
总结:上述的几种方法,在不同公司会用不同的方法,但是我们一定对上面的方法了解,到时候面试的时候我们可以用最快的语速把你知道的全部表达出来,让面试官觉得你对屏幕适配这块很熟悉,由于Android屏幕的多样式,我们是无法做到绝对的完美的,我们需要的是根据我们的需求,开发周期等方面选择一个适合我们的一种适配方式。