首先看下谷歌官方 2016 年 8 月 1 日发布的报告:
Android 平台版本的相对数量设备的相关数据:
可以看出 4.1 版本及以上占有量达到 96%,所以适配优先考虑 4.1 以上的版本适配问题。
特定屏幕配置的设备的数据,屏幕配置由屏幕尺寸和密度定义:
从统计数据可以看出,hdpi、xhdpi 和 xxhdpi 的占有率达到 95%。
详细统计数据看这里:
根据官方的统计数据进行适配是入门第一步。
屏幕尺寸: 屏幕尺寸指屏幕的对角线的长度,单位是英寸,1 英寸 = 2.54 厘米,例如常见的 Android 手机尺寸,5.0,5.5 寸。
屏幕分辨率: 屏幕分辨率是指在横纵方向上的像素点数,单位是 px,1px=1 个像素点。一般以纵向像素 * 横向像素,如 1960*1080。
屏幕像素密度: 屏幕像素密度是指每英寸上的像素点数,单位是 dpi,即 "dot per inch" 的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
px: 前面的分辨率就是用的像素为单位,大多数情况下,比如 UI 设计、Android 原生 API 都会以 px 作为统一的计量单位,像是获取屏幕宽高等。
(dpi / 160)。
说明:如果 A 设备的参数为 480×320,160dpi,B 设置的参数为 800×480,240dpi。我们要画出一条和屏幕宽度一样长的直线,如果使用 px 作为单位,必须在 A 设备上设置为 320px,在 B 设备上设置 480px。但是如果我们使用 dp 作为单位,由于以 160dpi 为基准,1dp=1px,所以 A 设备上设置为 320dp 就等于屏幕宽度(320px),在 B 设备上设置为 320dp 就等于 320×(240/160)=480px,即 B 设备的屏幕宽度。这样,使用 dp 作为单位就可以实现简单的屏幕适配。这知识一种巧合,也有 B 设备的像素密度不是这样刚刚好的,就需要我们运用别的屏幕适配技术。这个能解决不同屏幕分辨率问题,但不能解决屏幕物理尺寸问题。
**sp:**Scale-Independent Pixels 的缩写,可以根据文字大小首选项自动进行缩放。Google 推荐我们使用 12sp 以上的大小,通常可以使用 12sp,14sp,18sp,22sp,最好不要使用奇数和小数。
mdpi、hdpi、xhdpi、xxhdpi 用来修饰 Android 中的 drawable 文件夹及 values 文件夹,用来区分不同像素密度下的图片和 dimen 值。
在设计图标时,对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。例如,一个启动图标的尺寸为 48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XHDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。
(1) 使用 dip(Density Independent Pixels 的缩写,即密度无关像素)
由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素定义布局尺寸就会产生问题。因此,请务必使用 dp 或 sp 单位指定尺寸。dp 是一种非密度制约像素,其尺寸与 160 dpi 像素的实际尺寸相同。sp 也是一种基本单位,但它可根据用户的偏好文字大小进行调整(即尺度独立性像素),因此我们应将该测量单位用于定义文字大小。
注意:
虽然说 dp 可以去除不同像素密度的问题,使得 1dp 在不同像素密度上面的显示效果相同,但是还是由于 Android 屏幕设备的多样性,如果使用 dp 来作为度量单位,并不是所有的屏幕的宽度都是相同的 dp 长度,比如说,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,尽可能少的用 dp 来指定控件的具体长宽,再结合上权重,大部分的情况我们都是可以做到适配的。
(2)提供备用位图
由于 Android 可在具有各种屏幕密度的设备上运行,因此我们提供的位图资源应始终可以满足各类普遍密度范围的要求:低密度、中等密度、高密度以及超高密度。这将有助于我们的图片在所有屏幕密度上都能得到出色的质量和效果。
要生成这些图片,我们应先提取矢量格式的原始资源,然后根据以下尺寸范围针对各密度生成相应的图片。
xxxhdpi:4.0
xxhdpi:3.0
xhdpi:2.0
hdpi:1.5
mdpi:1.0(最低要求)
ldpi:0.75
也就是说,如果我们为 xhdpi 设备生成了 200x200 px 尺寸的图片,就应该使用同一资源为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75 尺寸的图片。
然后,将生成的图片文件放在 res/ 下的相应子目录中 (mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片。
这样一来,只要我们引用 @drawable/id,系统都能根据相应屏幕的 dpi 选取合适的位图。
但是还有个问题需要注意下,如果是. 9 图或者是不需要多个分辨率的图片,就放在 drawable 文件夹即可,对应分辨率的图片要正确的放在合适的文件夹,否则会造成图片拉伸等问题。
(1)使用 wrap_content、match_parent、weight
要确保布局的灵活性并适应各种尺寸的屏幕,应使用 "wrap_content" 和 "match_parent" 控制某些视图组件的宽度和高度。
使用 "wrap_content",系统就会将视图的宽度或高度设置成所需的最小尺寸以适应视图中的内容,而 "match_parent"(在低于 API 级别 8 的级别中称为 "fill_parent")则会展开组件以匹配其父视图的尺寸。
如果使用 "wrap_content" 和 "match_parent" 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需的空间或展开以填满可用空间。此方法可让布局正确适应各种屏幕尺寸和屏幕方向。
weight 是线性布局的一个独特的属性,我们可以使用这个属性来按照比例对界面进行分配,完成一些特殊的需求。
我们在布局里面设置为线性布局,横向排列,然后放置两个宽度为 0dp 的按钮,分别设置 weight 为 1 和 2,在效果图中,我们可以看到两个按钮按照 1:2 的宽度比例正常排列了,这也是我们经常使用到的场景,这时候很好理解。
假如我们的宽度不是 0dp(wrap_content 和 0dp 的效果相同),则是 match_parent 呢?
在这种情况下,占比和上面正好相反(2:1),这是怎么回事呢?说到这里,我们就不得不提一下 weight 的计算方法了。
android:layout_weight 的真实含义是: 如果 View 设置了该属性并且有效,那么该 View 的宽度等于原有宽度 (android:layout_width) 加上剩余空间的占比。
从这个角度我们来解释一下上面的现象。在上面的代码中,我们设置每个 Button 的宽度都是 match_parent,假设屏幕宽度为 L,那么每个 Button 的宽度也应该都为 L,剩余宽度就等于 L-(L+L)= -L。
Button1 的 weight=1,剩余宽度占比为 1/(1+2)= 1/3,所以最终宽度为 L+1/3*(-L)=2/3L,Button2 的计算类似,最终宽度为 L+2/3(-L)=1/3L。
垂直方向也一样。
(2)使用相对布局,禁用绝对布局
在开发中,我们大部分时候使用的都是线性布局、相对布局和帧布局,绝对布局由于适配性极差,所以极少使用。
由于各种布局的特点不一样,所以不能说哪个布局好用,到底应该使用什么布局只能根据实际需求来确定。我们可以使用 LinearLayout 的嵌套实例并结合 "wrap_content" 和 "match_parent",以便构建相当复杂的布局。不过,我们无法通过 LinearLayout 精确控制子视图的特殊关系;系统会将 LinearLayout 中的视图直接并排列出。
如果我们需要将子视图排列出各种效果而不是一条直线,通常更合适的解决方法是使用 RelativeLayout,这样就可以根据各组件之间的特殊关系指定布局了。例如,我们可以将某个子视图对齐到屏幕左侧,同时将另一个视图对齐到屏幕右侧。
(3)使用自动拉伸位图
支持各种屏幕尺寸通常意味着您的图片资源还必须能适应各种尺寸。例如,无论要应用到什么形状的按钮上,按钮背景都必须能适应。
如果在可以更改尺寸的组件上使用了简单的图片,您很快就会发现显示效果多少有些不太理想,因为系统会在运行时平均地拉伸或收缩您的图片。解决方法为使用自动拉伸位图,这是一种格式特殊的 PNG 文件,其中会指明可以拉伸以及不可以拉伸的区域。
.9 的制作,实际上就是在原图片上添加 1px 的边界,然后按照我们的需求,把对应的位置设置成黑色线,系统就会根据我们的实际需求进行拉伸。
(4) 文字和尺寸的适配
我们这里需要将代码跑在一个 1920*1200 分辨率 320dpi 的平板上,发现所有的字体都变大了,看似 1920*1200 的分辨率比之前的 1280*800 要大一大圈,但是因为 dpi 也高,所以导致字体变大。
运行上面的获取 smallestScreenWidth 的代码后,发现值为 600。(base size 的平板电脑这个值是 800)
首先在 values 文件夹中建立一个 dimens.xml 文件:
继续在 res 中建立和 values 文件夹同级别的两个文件夹 values-sw600dp-land 和 values-sw800dp-land,为了适应更多的屏幕,也加入了 values-sw480dp-land (后缀是 land 是因为例子的项目是平板)
随后我们一个个的把原来写的 layout 文件找出来,找出里面原来写死的 "数字",比如宽度和字体大小之类的,一般来说单位是 dp 或者 sp,将这些数字全部在 values/dimens.xml 中定义一个变量同时写回 layout 文件中对应的数字的地方。
然后你将 values-sw600dp-land 的里面的 dimens.xml 分别乘以 0.75 来获得:(因为 600/800 等于 0.75)
values-sw800dp-land 保持和 values 里面的一样,因为它是 base size。
这样子以后我们再运行代码到 1920*1200 分辨率 320dpi 的平板上,发现这个时候字体还有空间宽高都和原来的 base size 的一模一样了,就像是原封不动的跑在 base size 平板上的感觉!
自动生成 dimens 工具类:
- 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("</dimen>")) {
- //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("</dimen>") - 2));
- sw480.append(start).append((int) Math.round(num * 0.6)).append(end).append("\n");
- sw600.append(start).append((int) Math.round(num * 0.75)).append(end).append("\n");
- sw720.append(start).append((int) Math.round(num * 0.9)).append(end).append("\n");
- sw800.append(tempString).append("\n");
- w820.append(tempString).append("\n");
- } else {
- sw480.append(tempString).append("\n");
- sw600.append(tempString).append("\n");
- sw720.append(tempString).append("\n");
- sw800.append(tempString).append("\n");
- w820.append(tempString).append("\n");
- }
- line++;
- }
- reader.close();
- System.out.println("<!-- sw480 -->");
- System.out.println(sw480);
- System.out.println("<!-- sw600 -->");
- System.out.println(sw600);
- System.out.println("<!-- sw720 -->");
- System.out.println(sw720);
- System.out.println("<!-- 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();
- }
- }
下一篇文章将给出谷歌官方支持百分比的方式布局的开源库:。
来源: