Android屏幕适配

Posted by 阿呆 on 2019-01-29

屏幕适配的两个问题

一是适配的效率问题

二是保证UI在不同尺寸和分辨率的手机上的一致性

几个概念

屏幕尺寸、屏幕分辨率、屏幕像素密度

dp、dip、dpi、sp、px ,它们之间的关系是什么?

mdpi、hdpi、xdpi、xxdpi ,如何计算和区分?

屏幕尺寸

指的对角线的长度,单位是英寸,1英寸 = 2.54 cm

常见的屏幕尺寸有 2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0 等

屏幕分辨率

指的在横纵向上的像素点数,单位px,一般是 纵向横向* ,如 1960 * 1080

屏幕像素密度

每英寸上的像素点数,单位 dpi ,即 dot per inch 的缩写。

屏幕像素密度与屏幕分辨率有关。
$$
dpi = \frac {\sqrt(1080^2+1980^2)}{5}
$$

dp(dip)、dpi、sp、px

px我们比较熟悉,大多数情况下,比如 UI 设计 、 Android 原生 API 都会以 px 作为统一的计量单位(比如获取屏幕宽高)

dp和dip是一回事。都是 Density Independent Pixels 的缩写,即密度无关像素

在Android中,以 160 dpi (160dot/inch)为基准, 1dp=1px

我们来推理一下,如果密度是 320dpi ,则 1dp = 2px

sp,即 scale-independent pixels ,与 dp 类似,是设置字体的御用单位

mdpi、hdpi、xdpi、xxdpi

它们用来修饰Android中的drawable文件夹及values文件夹,用来区分不同像素密度下的图片和dimen值

名称 像素密度范围
mdpi 120dpi~160dpi
hdpi 160dpi~240dpi
xhdpi 240dpi~320dpi
xxhdpi 320dpi~480dpi
xxxhdpi 480dpi~640dpi

在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面,下面以图标设计为例进行介绍

在设计图标时,对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。例如**,一个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px**,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。

屏幕密度 图标尺寸
mdpi 48x48px
hdpi 72x72px
xhdpi 96x96px
xxhdpi 144x144px
xxxhdpi 192x192px

解决方案

主要从两个方面来解决

1.支持各种屏幕尺寸

2.支持各种屏幕密度

支持各种屏幕尺寸

使用 wrap_content、match_parent、weight

使用 wrap_content 和 match_parent 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需地空间或展开以填满可用空间。此方法可让布局正确适应各种屏幕尺寸和屏幕方向

尤其注意weight的计算,android:layout_weight 的真实含义:如果View设置了该属性并且有效,那么该View的宽度等于原有宽度(android:layout_width)加上剩余空间(减去所有子View的width)的占比

使用相对布局,禁用绝对布局(AbsoluteLayout)

学习下ContraintLayout,应该是目前最好用的layout了

使用限定符

限定符又分为尺寸限定符和最小宽度限定符

使用尺寸限定符

灵活布局只能带给我们这些优势了,虽然这些布局可以拉伸组建内外的空间以适应各种屏幕,但它们不一定能为每种屏幕都提供最佳用户体验。

例如,好很多应用会在较大的屏幕上实施双面板模式,即在一个面板上显示项目列表,而在另一个面板上显示对应内容,而在手机上则要分开显示。

1
2
res/layout/main.xml
res/layout-large/main.xml

使用最小宽度限定符

在Android 3.2 以上,可以使用最小宽度限定符(3.2之前无法识别)

1
2
res/layout/main.xml
res/layout-sw600dp/main.xml //对于最小宽度大于等于600dp的设备,采用该布局

使用布局别名

为了解决3.2以下不识别最小宽度限定符的问题

使用屏幕方向限定符

支持各种屏幕密度

使用非密度制约像素

请使用 dp or sp

除了上面这个,我们下面还要来讨论一种常见的问题:

假如我们以Nexus5作为书写代码时查看效果的测试机型,Nexus5的总宽度为360dp,我们现在需要在水平方向上放置两个按钮,一个是150dp左对齐,另一个是200dp右对齐;中间留有10dp间隔

但如果在 Nexus S 或者 Nexus One 运行呢?可以看到两个按钮发生了重叠。为什么我们用了dp,可是还是发生了这样的情况呢?

你听我慢慢道来

虽然说dp可以去除不同像素密度的问题,使得1dp在不同像素密度上面的显示效果相同,但是由于Android屏幕设备的多样性,并不是所有的屏幕的宽度都是相同的dp长度

当然,我们尽量使用 match_parent 和 wrap_content ,尽可能少的用 dp 来指定控件的具体长宽,再结合上权重,大部分的情况我们都可以做到适配

但是除了这个方法,我们还有没有其它的更彻底的解决方案呢?接下来我们用另一个思路来思考这个问题。

因为分辨率(像素密度?)不一样,所以不能用px;因为屏幕宽度不一样,所以要小心地用dp,那么我们可不可以用另外一种方法来同一单位,不管分辨率是多大,真实尺寸多大,屏幕宽度用一个固定的值得单位来统计呢?

答案是当然可以!下面这个方案来自Android Day Day Up 一群的 blue-深圳

方案

我们假设手机屏幕的宽度都是320某单位,那么我们将一个屏幕宽度的总像素平均分成320份,每一份对应具体的像素就可以了。

具体如何来实现呢?我们先来看一下下面的代码

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
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 = "<dimen name=\"x{0}\">{1}px</dimen>\n";
private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sb.append("<resources>");
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("</resources>");

StringBuffer sb2 = new StringBuffer();
sb2.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
sb2.append("<resources>");
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("</resources>");

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;
}
}

REF

Android屏幕适配全攻略(最权威的官方适配指导)

鸿洋Android适配方案