Android屏幕适配

Android的碎片化严重,为了适应更多的机型往往需要进行屏幕适配,这里介绍一些屏幕适配的基础知识,并且介绍几种屏幕适配的方法。

基础知识

屏幕尺寸

屏幕尺寸指的是屏幕的对角线的长度,单位为英寸,1英寸=2.54厘米,我们通常说的4.3,5.0寸即是指的这个。

屏幕分辨率

屏幕分辨率指的是在横纵轴上的像素点数,单位是px,1px即为1个像素,我们通常说的720x1080就是指的这个,即横轴上的像素点为720,纵轴上的像素点为1080

屏幕像素密度

屏幕像素密度指的是在每英寸上的像素点数,即dot per inch,单位为dpi,屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

dp、dip、dpi、sp、px

  • dp和dip dp和dip是一个意思,都是Density Independent Pixels的缩写,即密度无关像素
  • dpi指的是像素密度,比如我们通常所说的160dpi,指的就是一英寸里面有160个像素。
  • px指的是像素,在像素密度为160dpi时,1dp = 1px ,在像素密度为320dpi时,1dp = 2px,以此类推。一般Android原生的API返回的值为px,比如获取屏幕宽高。
  • sp是scale-independent pixels的缩写,与dp类似,但是可以根据文字大小首选项进行缩放,是设置字体的一般使用的单位。

mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi

mdpi、hdpi、xdpi、xxdpi用来修饰Android中的drawable文件夹及values文件夹,用来区分不同像素密度下的图片和dimen值。
Google官方指定按照屏幕像素密度的大小区间来区分:

  • mdpi:120dpi~160dpi
  • hdpi:160dpi~240dpi
  • xhdpi:240dpi~320dpi
  • xxhdpi:320dpi~480dpi
  • xxxhdpi:480dpi~640dpi

几种屏幕适配的方法

这里介绍的方法只是比较常用的几种方法,由于Android碎片化太过于严重,有些机型上可能还存在问题,可能需要一些特殊的适配。

百分比方法

所谓的百分比方法,即是选定一个分辨率为基准,然后通过工具计算相应的其他分辨率的像素。在使用前,我们需要为需要适配的不同分辨率创建不同的values文件夹,这里列举其中两个分辨率,如下所示:
不同的values文件夹
比如以480*320的分辨率为基准,那么将任何分辨率的宽度都分为320份,取值为x1-x320,将任何分辨率的高度都分为480份,取值为y1-y480,如下所示:
按照基准分

那么对于800*480的宽度为:
对应分比率

即按照屏幕像素密度对比基准进行尺寸上的缩放。有了这些数值之后,就可以按照百分比的方法进行适配了,比如我们需要一个按钮的宽度为屏幕宽度的50%,就可以将宽度设置为x160,即x320的一半。对于这么多的分比率,我们可以使用工具类进行生成,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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();
}
}

将上面的java代码以java的方式直接运行,便可以生成相关的文件。参考博客Android 屏幕适配方案 - Hongyang - 博客频道 - CSDN.NET

Google提供的百分比支持类

使用

Google官方提供了一个Android-percent-support库,基本可以解决上述问题。

这个库提供了:

两种布局供大家使用:
PercentRelativeLayout、PercentFrameLayout,通过名字就可以看出,这是继承自FrameLayout和RelativeLayout两个容器类;

支持的属性有:

layout_widthPercent、layout_heightPercent、 layout_marginPercent、layout_marginLeftPercent、 layout_marginTopPercent、layout_marginRightPercent、 layout_marginBottomPercent、layout_marginStartPercent、layout_marginEndPercent。

可以看到支持宽高,以及margin。

也就是说,只要在开发过程中使用PercentRelativeLayout、PercentFrameLayout替换FrameLayout、RelativeLayout即可。使用这个库只需要在gradle中添加如下语句:

1
compile 'com.android.support:percent:22.2.0'

对于LinearLayout可以参考鸿洋大神自定义的PercentLinearLayout

Android AutoLayout全新的适配方式

参考这篇文章AndroidAutoLayout