当您的Android应用即将发布的时候,如果你想让更多的用户去使用你的应用,摆在工程师面前的一个重要问题就是如何让你的应用能在各种各样的终端上运行,这里的各种各样首当其冲的就是不同的屏幕分辨率和尺寸。
屏幕适配主要从图片和距离(文字)进行下手。从以往的方式适配方式中,开发者可能会考虑各种各样的分辨率,比如480*800、1280*800、1920*1080等,为此在资源文件夹里面创建了一大堆子文件夹,那么有什么更好的方式吗?首先我们看下genymotion模拟其中一些流行的镜像的屏幕参数信息:![](http://www.2cto.com/uploadfile/2016/0730/20160730111232275.png)
![](http://www.2cto.com/uploadfile/2016/0730/20160730111233420.png)
上面举了两个例子,其实看了好几个发现大部分的屏幕虽然分辨率各种各样,但是dpi最多的就三种:160dpi、240dpi、320dpi 所以为了屏幕适配,我们应该仅仅对dpi做适配即可,这里我们分别对160dpi、240dpi、320dpi进行适配即可满足市场上大部分的需求,以上是图片的解决方案。 同时,光看dpi也不行,也要看对应的屏幕最小宽度,这个最小宽度是和dp对应的,用下列代码既可:
1
2
3
|
Configuration config = getResources().getConfiguration();
int
smallestScreenWidth = config.smallestScreenWidthDp;
L.i(
'smallest width : '
+ smallestScreenWidth);
|
这个获取出来的值,我们需要建立对应的values-sw{smallestWidth}dp文件夹进行适配,以上是文字和距离尺寸的适配。 下面将具体的距离说明,文中的例子原先是在1280*800的160dpi的屏幕上开发的,这个在这里被叫做base size,我们对这个项目的代码进行适配。
在android3.2以前,所有的资源文件都有相应的xhdpi,hdpi,mdpi,ldpi四种文件来对应,android3.2以后,为了提供更精准的对布局文件的控制,可以通过为资源文件(res目录下文件)增加后缀来指定该文件夹里的xml布局文件或color.xml,string.xml是为哪种大小的屏幕使用。
第一种后缀:swdp,如layout-sw600dp, values-sw600dp
这里的sw代表smallwidth的意思,当你所有屏幕的最小宽度都大于600dp时,屏幕就会自动到带sw600dp后缀的资源文件里去寻找相关资源文件,这里的最小宽度是指屏幕宽高的较小值,每个屏幕都是固定的,不会随着屏幕横向纵向改变而改变。
第二种后缀wdp 如layout-w600dp, values-w600dp
带这样后缀的资源文件的资源文件制定了屏幕宽度的大于Ndp的情况下使用该资源文件,但它和swdp不同的是,当屏幕横向纵向切换时,屏幕的宽度是变化的,以变化后的宽度来与N相比,看是否使用此资源文件下的资源。
第三种后缀hdp 如layout-h600dp, values-h600dp
这个后缀的使用方式和wdp一样,随着屏幕横纵向的变化,屏幕高度也会变化,根据变化后的高度值来判断是否使用hdp ,但这种方式很少使用,因为屏幕在纵向上通常能够滚动导致长度变化,不像宽度那样基本固定,因为这个方法灵活性不是很好,google官方文档建议尽量少使用这种方式。
最小宽度 swdp例如:sw320dp、sw600dp、sw720dp等
屏幕的基本尺寸,是指最短的可用屏幕区域。具体的说,设备的最小宽度是屏幕可用的宽度和高度中最短的那个(也可以把它看做是屏幕的最小可能的宽度)。这样就可以使用这个限定符来确保应用程序至少有dp的宽度可用于UI界面,而不管屏幕的当前方向。
例如,如果布局在任何时候都需要至少600dp的最小屏幕尺寸,那么就能够使用这个限定符,在res/layout-sw600dp/目录中创建布局资源。系统只会在可用屏幕的尺寸至少是600dp的时候才会使用这些资源,而不管600dp是否是被用户认知的高度或宽度。最小宽度是设备的固定屏幕尺寸特征,当屏幕的方向发生改变时,设备的最小宽度不改变。
设备的最小宽度需要考虑屏幕的装饰和系统UI的占用。例如,如果设备有一些固定的UI元素要沿着最小宽度的轴向,占用一定的屏幕空间,那么系统声明的最小宽度要比实际的屏幕尺寸要小,因为被系统占用的像素部分对用户应用程序的UI无效。因此,这个值应该是应用程序布局所需要的最小的实际尺寸(通常,这个值是布局支持的最小宽度,而不管屏幕的当前方向)。
以下是可以使用的通用屏幕尺寸的一些值:
1.320,针对以下屏幕配置的设备:
240x320ldpi(QVGA手持设备)
320x480mdpi(手持设备)
480x800hdpi(高分辨率手持设备)
2.480,针对480x800mdpi的屏幕(平板或手持设备)
3.600,针对600x1024mdip的屏幕(7英寸平板)
4.720,针对720x1280mdip的屏幕(10英寸平板)
当应用程序提供了多个带有不同值的最小宽度限定符资源目录时,系统会使用最接近(不超出)设备最小宽度的那个资源。
这个限定符被添加在API级别13中。
还要看android:requiresSmallestWidthDp属性,它声明了与你的应用程序兼容的最小的最小宽度,并且smallestScreenWidthDp配置字段会持有这个设备最小宽度的值。
文字和尺寸的适配
我们这里需要将代码跑在一个1920*1200分辨率320dpi的平板上,发现所有的字体都变大了,看似1920*1200的分辨率比之前的1280*800要大一大圈,但是因为dpi也高,所以导致字体变大。
运行上面的获取smallestScreenWidth的代码后,发现值为600。(base size的平板电脑这个值是800)
首先在values文件夹中建立一个dimens.xml文件
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111233453.png)
继续在res中建立和values文件夹同级别的两个文件夹values-sw600dp-land和values-sw800dp-land,为了适应更多的屏幕,也加入了values-sw480dp-land (后缀是land是因为例子的项目是平板)
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111233387.png)
随后我们一个个的把原来写的layout文件找出来,找出里面原来写死的“数字”,比如宽度和字体大小之类的,一般来说单位是dp或者sp,将这些数字全部在values/dimens.xml中定义一个变量同时写回layout文件中对应的数字的地方,这里举个例子:
原来的代码是这样的:
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111233901.png)
现在代码成了这样:
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111233826.png)
dimens.xml中添加的内容:
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111233552.png)
然后你将values-sw600dp-land的里面的dimens.xml分别乘以0.75来获得:(因为600/800等于0.75)
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111234899.png)
values-sw480dp-land里面的dimens.xml分别乘以0.6来获得(因为480/800等于0.6)
![\](http://www.2cto.com/uploadfile/2016/0730/20160730111234361.png)
values-sw800dp-land保持和values里面的一样,因为它是base size。
这样子以后我们再运行代码到1920*1200分辨率320dpi的平板上,发现这个时候字体还有空间宽高都和原来的base size的一模一样了,就像是原封不动的跑在base size平板上的感觉!
那么这个时候问题来了:那么多的layout文件夹本身每个创建一个dimen变量就够累了,然后还要分别拷贝一份同时手动计算乘以0.6或者0.75来获得新的值,拿计算器一个个的计算,多大的工作量啊,况且以后要是要来个sw1028、sw320呢?再次算一次? 这里提供一个方法: 在代码里面新加一个带main的java类,事实上它是自动的帮你在原来的values/dimens.xml的基础上自动的帮你把每个dimen乘以你所需要的乘积然后将结果写回对应的dimens.xml里面,这大大的减轻了工作量,以后每次修改dimens.xml只要这样子即可: 在values/dimens.xml里面添加或修改变量 跑一边上述java类自动的生成别的dimens.xml 这里给出这个工具java类的代码:
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
|
public
class
DimenTool {
public
static
void
gen() {
File file =
new
File(
'./app/src/main/res/values/dimens.xml'
);
BufferedReader reader =
null
;
StringBuilder sw480 =
new
StringBuilder();
StringBuilder sw600 =
new
StringBuilder();
StringBuilder sw720 =
new
StringBuilder();
StringBuilder sw800 =
new
StringBuilder();
StringBuilder w820 =
new
StringBuilder();
try
{
System.out.println(
'生成不同分辨率:'
);
reader =
new
BufferedReader(
new
FileReader(file));
String tempString;
int
line =
1
;
// 一次读入一行,直到读入null为文件结束
while
((tempString = reader.readLine()) !=
null
) {
if
(tempString.contains(
''
)) {
//tempString = tempString.replaceAll(' ', '');
String start = tempString.substring(
0
, tempString.indexOf(
'>'
) +
1
);
String end = tempString.substring(tempString.lastIndexOf(
'<'
) -
2
);
int
num = Integer.valueOf(tempString.substring(tempString.indexOf(
'>'
) +
1
, tempString.indexOf(
''
) -
2
));
sw480.append(start).append((
int
) Math.round(num *
0.6
)).append(end).append(
''
);
sw600.append(start).append((
int
) Math.round(num *
0.75
)).append(end).append(
''
);
sw720.append(start).append((
int
) Math.round(num *
0.9
)).append(end).append(
''
);
sw800.append(tempString).append(
''
);
w820.append(tempString).append(
''
);
}
else
{
sw480.append(tempString).append(
''
);
sw600.append(tempString).append(
''
);
sw720.append(tempString).append(
''
);
sw800.append(tempString).append(
''
);
w820.append(tempString).append(
''
);
}
line++;
}
reader.close();
System.out.println(
'{C}<!-- sw480 -->'
);
System.out.println(sw480);
System.out.println(
'{C}<!-- sw600 -->'
);
System.out.println(sw600);
System.out.println(
'{C}<!-- sw720 -->'
);
System.out.println(sw720);
System.out.println(
'{C}<!-- sw800 -->'
);
System.out.println(sw800);
String sw480file =
'./app/src/main/res/values-sw480dp-land/dimens.xml'
;
String sw600file =
'./app/src/main/res/values-sw600dp-land/dimens.xml'
;
String sw720file =
'./app/src/main/res/values-sw720dp-land/dimens.xml'
;
String sw800file =
'./app/src/main/res/values-sw800dp-land/dimens.xml'
;
String w820file =
'./app/src/main/res/values-w820dp/dimens.xml'
;
writeFile(sw480file, sw480.toString());
writeFile(sw600file, sw600.toString());
writeFile(sw720file, sw720.toString());
writeFile(sw800file, sw800.toString());
writeFile(w820file, w820.toString());
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
if
(reader !=
null
) {
try
{
reader.close();
}
catch
(IOException e1) {
e1.printStackTrace();
}
}
}
}
public
static
void
writeFile(String file, String text) {
PrintWriter out =
null
;
try
{
out =
new
PrintWriter(
new
BufferedWriter(
new
FileWriter(file)));
out.println(text);
}
catch
(IOException e) {
e.printStackTrace();
}
out.close();
}
public
static
void
main(String[] args) {
gen();
}
}
|
图片的适配
文字和空间宽度适配后,大家可能发现部分的ImageView或者ImageButton部分还有些变大或者变小,有的变模糊了,这里需要美工提供多套图片,大家请看这张图:
![](http://www.2cto.com/uploadfile/2016/0730/20160730111234770.jpg)
这里说明了开发时应该图片以160dpi为基准,同时提供不同dpi的基于baseline的图片的放大或者缩小版本。那么每套图片放什么地方呢?
![](http://www.2cto.com/uploadfile/2016/0730/20160730111234155.png)
以上是Google官方给出的分类标准( 虽然Android也支持低像素密度 (LDPI) 的屏幕,但无需为此费神,系统会自动将 HDPI 尺寸的图标缩小到 1/2 进行匹配。) 所以对于我们的例子中1920*1200分辨率320dpi的平板,我们应该让美工制作放大2x的版本的图片,同时将图片放到drawable-xhdpi文件夹中,原来的图片放到drawable-mdpi文件夹中。这里需要注意一下,对于drawable你可能会写很多的比如shape或者selector的xml形式的drawable,他们本身不是图片而是一个xml文件,但是他们都会去引用真实的drawable图片,对于这种xml最好是要放到无dpi影响的drawable文件夹中(无后缀) 这样子以来,我们再把代码跑到1920*1200分辨率320dpi的平板上,发现图片部分也OK了,适配完毕。 这里还需要提醒的一点,并不是每个地方的图片都需要提供多套图片这种方案来解决,因为这种方案会带来使apk的size变大的副作用。所以大家可以根据实际需求,若可以通过上一节的方式来修改imageView的尺寸大小来解决(而非用wrap_content来指定layout_width和layout_height)的话,就不需要用多套图片的方式。
应用启动图标的适配
对于高分辨率低dpi的设备,我们经常会发现在launcher中我们的应用的启动icon被拉伸的模糊了,严重影响了门面的形象。 这里我们也通过提供多套icon的方式来解决,下面列表给出了不同屏幕密度中推荐的icon的size大小
![](http://www.2cto.com/uploadfile/2016/0730/20160730111234935.png)
在Android4.2以上的版本中,提供了对mipmaps的支持,说简单点就是他能对bitmap进行缩放的时候减少一些性能的耗损。如果你用Andorid Studio开发Android程序会发现Android Studio自动帮你创建了几个mipmaps文件夹,你可将应用的启动图标放到不同的mipmaps文件夹中而不是上述的drawable文件夹中,比如:
![](http://www.2cto.com/uploadfile/2016/0730/20160730111234512.png)
这里你至少要提供一个xxxhdpi类型的启动图标,因为Android会帮你自动缩小图标到对应的别的分辨率上(放大是会变模糊的),这样子可以节省些apk size。