作者:夏至 欢迎转载,也请保留这段申明,谢谢
以前在做小东西的时候,觉得只要适应了自己的手机就可以了,发现真的给自己挖了很多坑。今天终于好好地研究了一下,这是个痛苦的事情,但是却又真的很有趣。
首先,先理解几个概念。
屏幕尺寸指屏幕的对角线的长度,单位是英寸 : 1 英寸 = 2.54 mm
常见的尺寸有: 2.4 、2.8、3.5 、3.7,4.2、5.0等等
屏幕分辨率是指在横纵向上的像素点数,单位是px ,1px = 1个像素点
屏幕像素密度是指每 英寸上的像素点数,单位是dpi (也叫dp),屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小,分辨率越高,像素密度越大,反之越小。
dip 和 dp 是一个意思,即屏幕密度。至于 dpi 是什么?可以这样理解,假如 1 英寸 里面有160个像素,那么这个屏幕的像素密度就是160dpi。在这种情况下,dp 和 px 换算非常简单,如下图所示:
比如你在一个480x320的手机上布好了局,一看霸道炫酷,但你的单位是用px的,一到了800x480或者更高分辨率的手机的时候,就是东倒西歪的了。所以,我们一般不建议用px做单位。
上图中还有个ldpi 的,不过现在基本没人用这么惨不忍睹的分辨率了,所以就可以不用去管它了。用eclipse的我们经常会在 res 文件夹中看到这样定义的 drawable 文件夹,那么它们究竟是什么意思呢?Google 官方指定按照下列标准进行区分:
在进行开发的时候,我们也需要对应不同的文件夹放入不同的图片:
如果是对缩放不是特别重要的,我们可以把它放在drawable文件夹中,比如.9图等
回到我们的重点,我们今天要讲的就是屏幕的自适应。
为了确保布局的灵活性并适应各种不同的屏幕,我们一般建议使用 wrap_content 内容包裹,和match_parent 填充整个容器,这样就是不是绝对布局,而是可以根据屏幕的大小而自适应,但我们布局那么多,不可能每个都用这两个属性,所以,局限性也很大。
至于 weight 权重这个属性呢,好用是好用,不过一般是在线性布局中,但线性布局有个尴尬的地方就是,如果你的布局很多,那么它的树根就会很长,这个对于 UI 的优化是很不利的,而谷歌一般建议我们的布局层数不要超过10层,所以,如果是相对比较复杂的,都是建议使用 Reativelayout 相对布局。所以,局限也很大。
这个时候,你可能会说,使用 dp 和 sp 啊,dp 是密度,你分辨率不同,用 dp 也能很好的适应。 在网上找到两张图。使用相同的代码在两种不同的机型上运行的效果如下:
很尴尬是不是,都使用了 dp 了,为什么还会出现这样的问题?其实很简单,因为屏幕的多样性,并不是所有的屏幕都是相同的 dp 长度,比如 Nexus One 属于hdpi ,屏幕宽度是320dp,而 Nexus 5属于xxhdpi ,屏幕宽度是360dp。所以,就算你用了dp也一样存在问题。比如,你在320 dp 里,写了一个TextView ,字体大小为24 sp,在两个不同的设备下,大小显而易见的不一样。
这个时候,你是不是很绝望,感觉生无可恋的感觉。分辨率不一样,所以不能用px;屏幕宽度不一样,所以不能用 dp ,还让不让人活了。别急,还是有解决方法的。参考两位大神的神作:
鸿洋大神的:http://blog.csdn.net/lmj623565791/article/details/45460089
一叶飘舟的:http://blog.csdn.net/jdsjlzx/article/details/45891551
基本看了它们的你就会了。我自己只是把我的理解也写一下而已。
当然,在安卓 5.0 之后,安卓也支持百分比的了。这无疑是个好消息。大家可以看到,为什么我们在电脑浏览网页什么的,那些控件大小,总能根据我们屏幕大小的不同而变化,就是人家用了百分比的效果啊,百分比就是占据整个屏幕的比例,这个很容易懂吧。但是它的局限性就是在也在这里,需5.0系统,我自己试了一下,效果真心不错,但是放到4.3的系统,就乱套了。所以,可以适当了解。如果你想更近一步的了解,请参考鸿洋大神的作品:http://blog.csdn.net/lmj623565791/article/details/46695347
好了,言归正传,我们的方案很简单,就是在项目中添加你的分辨率的不同文件夹,如:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
/** * Created by zhy on 15/5/3. */
public class GenerateValueFiles {
private int baseW;
private int baseH;
private String dirStr = "./res";
private final static String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";
/** * {0}-HEIGHT */
private final static String VALUE_TEMPLATE = "values-{0}x{1}";
private static final String SUPPORT_DIMESION = "320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";
private String supportStr = SUPPORT_DIMESION;
public GenerateValueFiles(int baseX, int baseY, String supportStr) {
this.baseW = baseX;
this.baseH = baseY;
if (!this.supportStr.contains(baseX + "," + baseY)) {
this.supportStr += baseX + "," + baseY + ";";
}
this.supportStr += validateInput(supportStr);
System.out.println(supportStr);
File dir = new File(dirStr);
if (!dir.exists()) {
dir.mkdir();
}
System.out.println(dir.getAbsoluteFile());
}
/** * @param supportStr * w,h_...w,h; * @return */
private String validateInput(String supportStr) {
StringBuffer sb = new StringBuffer();
String[] vals = supportStr.split("_");
int w = -1;
int h = -1;
String[] wh;
for (String val : vals) {
try {
if (val == null || val.trim().length() == 0)
continue;
wh = val.split(",");
w = Integer.parseInt(wh[0]);
h = Integer.parseInt(wh[1]);
} catch (Exception e) {
System.out.println("skip invalidate params : w,h = " + val);
continue;
}
sb.append(w + "," + h + ";");
}
return sb.toString();
}
public void generate() {
String[] vals = supportStr.split(";");
for (String val : vals) {
String[] wh = val.split(",");
generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
}
}
private void generateXmlFile(int w, int h) {
StringBuffer sbForWidth = new StringBuffer();
sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sbForWidth.append("<resources>");
float cellw = w * 1.0f / baseW;
System.out.println("width : " + w + "," + baseW + "," + cellw);
for (int i = 1; i < baseW; i++) {
sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
w + ""));
sbForWidth.append("</resources>");
StringBuffer sbForHeight = new StringBuffer();
sbForHeight.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sbForHeight.append("<resources>");
float cellh = h *1.0f/ baseH;
System.out.println("height : "+ h + "," + baseH + "," + cellh);
for (int i = 1; i < baseH; i++) {
sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
change(cellh * i) + ""));
}
sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
h + ""));
sbForHeight.append("</resources>");
File fileDir = new File(dirStr + File.separator
+ VALUE_TEMPLATE.replace("{0}", h + "")//
.replace("{1}", w + ""));
fileDir.mkdir();
File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sbForWidth.toString());
pw.close();
pw = new PrintWriter(new FileOutputStream(layyFile));
pw.print(sbForHeight.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
public static void main(String[] args) {
int baseW = 320;
int baseH = 400;
String addition = "";
try {
if (args.length >= 3) {
baseW = Integer.parseInt(args[0]);
baseH = Integer.parseInt(args[1]);
addition = args[2];
} else if (args.length >= 2) {
baseW = Integer.parseInt(args[0]);
baseH = Integer.parseInt(args[1]);
} else if (args.length >= 1) {
addition = args[0];
}
} catch (NumberFormatException e) {
System.err
.println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
e.printStackTrace();
System.exit(-1);
}
new GenerateValueFiles(baseW, baseH, addition).generate();
}
}
要修改的就一个地方,即基准,比如上面的是以 480 x 320 为基准的。,我们可以改成我们所需要的。当然,你也可以生成 jar 包,然后在命令行下,生成不同的代码:
先把过程打包成jar包,然后输入:
Java -jar xx.jar width height width,height_width,height
什么意思呢,前面两个是基准的意思。后面的是你要额外扩展的包。比如
我要一个1280x720 的基准,再额外扩展 一个 1366x768的包。就可以这样写:
java -jar test.jar 1280x720 1366,768
后面也是要扩展的,这里可以根据不同而选择不同。、
当然你也可以直接你要的基准的包:
但这里也有局限性,看到网上很多屏幕说,如果带有虚拟键的手机,这个方案也不适合了,只能跟着虚拟键来重新适配,当然这里还是适用于很多记性的。这里把常用的两个基准 480x320 、800x480,和1280x720 基准的res包发给大家,当然我建议你自己弄。CSDN 的附件太蛋疼了,用360好了:
160 基准 https://yunpan.cn/cPFiutXhwhdpB 访问密码 1bc7
240 基准 https://yunpan.cn/cPFiFXihU7ytz 访问密码 29df
320 基准 https://yunpan.cn/cPFixBsSQqrrX 访问密码 91da
如果消失了,请联系我。
这里我们要 讲讲安卓TV 跟手机的不同。在手机上,我们可以以一个480x320的基准来设计屏幕大小,因为手机撑死的就5.0寸,所以,这个基准还是非常不错的。但是安卓TV不一样,都是平板类的,而且是比较大的液晶屏。所以,我们的基准就是要变一下。要用1280x720 的为基准,当然,这个只是个人理解。有错误也欢迎指出。