Android 屏幕适配
作者:林子木
转载请附带链接:http://blog.csdn.net/wolinxuebin/article/details/54288798
导读
这篇文章洋洋洒洒,讲了很多,主要的内容如下:
第二章,介绍了dpi、px、dp以及ppi的概念。
第三章,主要介绍了Android的实际dpi(ppi)和我们平时接触的dpi的区别。
第四章,介绍了Android查找资源的原理。
第五章,重点介绍了dpi及像素这两个资源的优选过程。
第六章,讲解了讲解了几种适配方案,以及如快速适配。
第七章,讲解了几个比较实用的工具。如果你只想知道如何做,就从第五章开始看。
一、写在前面
屏幕适配是一个很基础的问题,但是又都多少人真正的了解其中的一些细节呢?
这篇文章适合遇到以下问题的你:
- 1、如何处理带虚拟键的手机
- 2、同样的实现方案在同样的分辨率下,不同的手机表现出不同的效果
- 3、为什么我的适配方案在三星手机上表现好好的,却在Nexus 5x,Nexus 6, Nexus 6p等手机上表现出不同的效果
- 4、为什么刚刚适配好了一个机型,在接着适配其他的时候,原来的适配的机型出现了问题
- 5、思考过,如何才能简化适配的过程,不想时间浪费在适配上
注:本文只针对Android手机竖屏的情况下,横屏仅供参考
二、再度理解DPI、PX、DP及PPI
看到这个标题很多人不屑一顾,但是如果没有真正准确的把握这三者的准确定义,对之后的理解还是存在一点问题,所以在此继续说明下:
- PX: 是取自pixel,像素,这个就不解释了——也就是我们熟知的像素
- PPI: 是pixels per inch的缩写,表示一个Inch包含多少像素;
- DPI: 是Dots Per Inch的缩写,指代每英寸点数,160DPI的屏幕就表示一个Inch包含160个Dot。在Android中,直接可以理解为表示一个Inch包含多少像素,也即PPI,想了解更多可以参看这里;DPI的值越高,就代表在一个物理Inch内,像素越多,也就是屏幕更加清晰,——谷歌官网将其叫为:屏幕密度
- DP (DIP): Density-Independent pixel, 中文为”密度无关像素”,谷歌原话为”在定义 UI 布局时应使用的虚拟像素单位,用于以密度无关方式表示布局维度 或位置”。——可以理解为一种新型的像素,称为虚拟像素
2.1 DPI,PX,DP三者的转换关系
经过上面的分析,是不是很明确了呢?
哈哈,如果你很明确了的话,那你就是已经很熟悉上的的定义了,所以接下来还得说道说道他们三者之间的关系。
谷歌定义:在dpi为160的手机上,1dp = 1px。
因此,在240dpi的手机上,1dp就是1.5px。通用公式可以表示为:
2.2 如何获取手机的DPI值
那么有人会问dpi如何知道?如何知道一个手机的dpi?
可以采用命令:adb shell dumpsys window displays | head -3
将会显示如下的结果:
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
Display: mDisplayId=0
init=480x800 240dpi cur=480x800 app=480x800 rng=480x442-800x762
由上面的结果我们可以看到,该手机的dpi为240
**注:**init可以理解为手机本身的屏幕分辨率,cur表示当前分辨率,app中我们将会看到除去虚拟键的分辨率。
2.3 深入理解PPI
对于求知欲比较强的我,当然不限于adb命令的结果,还是想知道如何得出的,那么且听下面的分析:
之前说dpi就是ppi,那么我们就看看ppi是如何得出的。
注: pxw 、 pxh 、screenSize分别表示横向分辨率、纵向分辨率以及屏幕此处
那么这个公式是如何得出来的呢?
简书的*产品经理马忠信老师的博客《(全解析)屏幕尺寸,分辨率,像素,PPI之间到底什么关系?》中有这么一段话:
什么是屏幕像素密度?
屏幕像素密度,即每英寸屏幕所拥有的像素数,英文简称PPI。在读到这个每英寸屏幕时,我曾经深深的疑惑,这个每英寸是不是每平方英寸的简称呢?事实证明,我还是太年轻,这个英寸跟之前手机屏幕的尺寸一样,也是对角线的长度。所以,我们可以这么理解屏幕像素密度,即在一个对角线长度为1英寸的正方形内所拥有的像素数。
那么果真如此吗?
如果ppi一个对角线长度为1英寸的正方形内所拥有的像素数。那么我们分别用
w、h
表示屏幕的宽高,那么屏幕的面积为
w×h
,而对角线为一英寸的正方形的边长为
2√2
,因而面积为
12
,那么就有
w×h÷12=2wh
个小正方形。
那么我们将得到如下的公式:
但是,w和h为未知数,我们该怎么得到呢?别忘了,一个像素点是一个正方形,所以,横纵的像素比正好是w与h的比值,所以:
此外,
所以,
将公式(4)(5)带入到(1)中,并经过化简处理,得到如下公式:
完全不是那回事!
所以他给出的解释完全不正确。
所以我们再次看看最初始的公式:
由于针对某个具体的屏幕,宽高比是固定的,所以上面的分析的[2-5]式子都是正确的,我们假定屏幕的宽高比为 β :
接下来,我们将公式(7)稍微做下变换,就可以得到如下公式:
同样,我们也可以得到:
所以这就回到最开始的定义,ppi为一英寸上的像素点。那么一平方英寸上的像素点,其实是 ppi2 。
就以我的老机型I9100为例,屏幕尺寸为4.3寸,分辨率为480*800,根据公式(7)那么计算结果约为217ppi。
采用直尺测量,屏幕的宽为5.6cm(2.20in),高为9.28cm(3.65in),所以
所以上两式的计算结果,可以验证上面的推论。
2.4 DPI和PPI真正关系
之前说的,Android上dpi可以等价于ppi,难道你们发现没,我们计算的结果是217ppi(官方说明也是),但是我们adb命令的结果是240dpi,所以这里还是存在一定的偏差,这是为什么呢?
那么先让我们看看这个经典的图,关于这幅图,谷歌中文版给出的定义为:
该图说明为, Android 如何将实际尺寸和密度粗略地对应到通用的尺寸和密度(数据并不精确)
也就是说,我们之前计算出来的ppi就等于图中位于下面的那个图中的“Actual density”,而谷歌将实际dpi(Actual density),也即ppi,映射到相应的通用dpi(Genralized dpi)
图 2.1 Android 如何将实际尺寸和密度粗略地对应到通用的尺寸和密度
下表为当前通用的dpi说明:
通用dpi名 | 相应的值 |
---|---|
ldpi | 120dpi |
mdpi | 160dpi |
tvdpi | 213dpi |
hdpi | 240dpi |
xhdpi | 320dpi |
420dpi(属于xxdhpi) | 420dpi |
xxhdpi | 480dpi |
560dpi(属于xxxdpi) | 560dpi |
xxxdpi | 640dpi |
- 注:560dpi代表手机为Nexus 6,6p,在API 21(5.0)才引入,420dpi代表手机为Nexus 5x, 三星A9100,在API 23(6.0)才引入。
2.5 引入dp的意义
最主要的的还是dp——密度无关像素的概念的引出,再次附上谷歌的官方说的的图。
图 2.2 不支持不同密度的示例应用在低、中、高密度屏幕上的显示情况
图 2.3 良好支持不同密度(密度独立)的示例应用在低、中、高密度屏幕上的显示情况。
图2.2由于为采用了不支持不同密度的显示,所以不同手机,显示效果差异很大,而2.3采用dp作为单位,所以规避密度,做到在不同的手机又大致相同的显示效果。
2.6 本章小结
那么让我们来整理下上面的内容,手机处在一个实际dpi(也即ppi),谷歌为了降低Android适配的工作量,将实际dpi根据所在的区间,映射到相应的通用dpi(如ldpi,mdpi,hdpi等),如图1.1所示。
此外,本章还讲解了px、dp、dpi三者之间的关系,并介绍了谷歌引入dp单位的真正目的。
三、为什么要适配
经过上一章的介绍,我们大致了解了我们平时所使用的dp单位的意义。也知道,这是谷歌为了减少适配的工作量,而提出的一个新的概念。是否心中有如此疑问:
- 既然google定义了新的单位dp来减少适配的工作量了,那么为什么我们还需要进行屏幕适配?
- 为什么不一步到位,直接写一套方案,对所有的机型都适用呢?
3.1 为什么要适配
接下来将由子木将其中的原委给各位看官一一道来。
图3.1 经典的Android屏幕差异化标准图
图3.2 友盟2016年7月android设备分辨率
上面图3.1说明,Android屏幕尺寸差异巨大;图3.2,列举了截止2016年7月android设备分辨率分布(占比 > 1%);这时,我们一般会得出一个结论——Android屏幕碎屏化严重。
但是,碎片化严重,和我们有什么关系?我们用的是google定义的dp。
对的,我们是用了dp这个单位,但是经过第二章的讲解,我们知道,dp代表密度无关像素,而且也知道到它的公式:
那么我们举一个最简单的例子,两个标准的配置:
手机名称 | 手机分辨率 | dpi |
---|---|---|
手机A | 480x800px | 240dpi |
手机B | 720X1280px | 320dpi |
那么,将手机的宽高dp表示,可以得到下表:
手机名称 | 宽X高(dp) |
---|---|
手机A | 320x533 |
手机B | 320X640 |
是不是发现了什么?
如果一个view宽高分别设置为320dp, 533dp,那么它可以占满手机A的全部空间屏幕空间,却不能铺满手机B的屏幕。(注:状态栏有一定的高度,所以不需要533dp就能占满,这里将问题简化,忽略状态栏的存在。)
所以,采用dp并没有完全解决适配的问题。
那么采用dp单位,能为我减少多少工作量呢?请看下面这张表:
通用dpi名 | 相应的值 | 经典屏幕分辨率(px) | 屏幕dp值 |
---|---|---|---|
ldpi | 120dpi | 240x320 | 320x427 |
mdpi | 160dpi | 320x480 | 320x480 |
hdpi | 240dpi | 480x800 | 320x533 |
xhdpi | 320dpi | 720x1280 | 360x640 |
420dpi(属于xxdhpi) | 420dpi | 1080x1920 | 411x731 |
xxhdpi | 480dpi | 1080x1920 | 360x640 |
560dpi(属于xxxdpi) | 560dpi | 1440x2560 | 411x731 |
xxxdpi | 640dpi | 1440x2560 | 360x640 |
从上面这个表中可以看到,如果我们根据xhdpi密度情况下,做出一个布局方案,那么就可以将此方案适用至xhdpi、xxhdpi、xxxdpi三个屏幕密度,再次查看图3.2,其中1280x720,1920x1080,2560x1440,1776x1080,1812x1080,1184x720(后三者,有存在虚拟键的情况下,而形成的分辨率)就立马完成了61.8%的手机展示。而ldpi,mdpi,hdpi这三者的宽度都一致,只要在纵向稍做处理,就完成了大致90%工作量了。
那么为什么说是90%的工作量呢?别忘了还有很多特殊的机型,如果适配,适配到什么地步,这个就和你的项目要求有关。最低的要求是,在xhdpi上给出一套方案,然后看下ldpi,mdpi,hdpi及主流的分辨率上效果,然后稍作修改,就直接完成了。
3.2 总结
本章通过简单的例子,解答了“为什么需要适配”这个问题。并通过数据讲解了,对于不同的项目要求,适配的工作量是不同的。
四、Android如何查找最佳资源
谷歌Developer的“提供资源”篇中的表二,给出了当前Android支持的资源的配置限定符名称(19种)。
如果对Android的资源查找的规律有所疑惑的同学可以查看“Android 如何查找最佳匹配资源”篇。
以上两者都需要翻墙,是不是立马就一脸懵逼了?
那就子木为你代劳吧,全篇幅的复制有点不大现实,所以子木将内容进行了精简处理,罗列如下:
如果要介绍资源配置限定符的话,那么还是必须介绍下Android到底有哪些资源:
图4.1 Android项目示例文件目录
res/ 目录下存在如下表中罗列的一些目录:
表4.1 项目 res/ 目录内支持的资源目录
目录 | 资源类型 |
---|---|
animator/ | 用于定义属性动画的 XML 文件。 |
anim/ | 用不补间动画及属性动画的xml |
color/ | 用于定义颜色状态列表的 XML 文件 |
drawable/ | 存放图片资源 |
mipmap/ | 用于存放启动icon |
layout/ | 用于存放布局文件 |
menu/ | 用于定义应用菜单 |
raw/ | 要以原始形式保存的任意文件。 |
values/ | 存放: arrays.xml colors.xml dimens.xml strings.xml styles.xml |
xml/ | 可以在运行时通过调用 Resources.getXML()读取的任意 XML 文件。 |
接下来的表,将大致罗列 Android的19中资源配置限定符:
表4.2 资源配置限定符
优先级 | 配置 | 限定符值 | 说明 |
---|---|---|---|
1 | MCC 和 MNC | 示例:mcc310 -mnc004 | 移动国家代码 (MCC) SIM 卡中的移动网络代码 (MNC) |
2 | 语言和区域 | en、zh-rCN等 | 语言 |
3 | 布局方向 | ldrtl、ldltr | 表示布局从“左”到“右”, 或相反(如阿拉伯) |
4 | smallestWidth | swdp 示例:sw320dp | sw320dp表示屏幕的宽高 中较小的一个大于320dp |
5 | 可用宽度 | wdp 示例:w720dp | 最小可用屏幕宽度,大于N |
6 | 可用高度 | hdp 示例:h720dp | 最小可用屏幕宽度,大于N |
7 | 屏幕尺寸 | small normal large xlarge | 表示手机屏幕的物理大小,不推荐使用 |
8 | 屏幕纵横比 | long、notlong | long表示宽屏,notlong表示非宽 |
9 | 圆形屏幕 | round、notround | 表示是否为圆形屏幕,23新增 |
10 | 屏幕方向 | port、land | port表示垂直,land表示横向 |
11 | UI 模式 | car、desk television appliance watch | 不介绍了 |
12 | 夜间模式 | night、notnight | |
13 | 屏幕像素密度 | ldpi、hdpi等 | |
14 | 触摸屏类型 | notouch、finger | notouch表示没有触摸屏 |
15 | 键盘可用性 | keysexposed keyshidden keyssoft | |
16 | 主要文本输入法 | nokeys、qwerty 12key | 分别表示,没有,标准,12键盘输入 |
17 | 导航键可用性 | navexposed navhidden | 表示是否隐藏 |
18 | 主要非触 摸导航方法 | .. | |
19 | API 级别 | v3等 |
注:精简官方表格得出,其中第一行的优先级由子木自行标注,为了更好的说明。
图4.2 Android 如何查找最佳匹配资源的流程图
大致了解了上面的资源的信息,接下来,让我们了解下,Android的是如何找到自己最佳资源的。
举一个例子:
资源目录如下:
>
drawable/
drawable-en/
drawable-fr-rCA/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/
设备信息如下:
语言区域 = en-GB
屏幕方向 = port
屏幕像素密度 = hdpi
触摸屏类型 = notouch
主要文本输入法 = 12key
根据表4.2 我们可以知道,上述几个资源的优先级分别如下:
语言区域 = en-GB —— 优先级 2
屏幕方向 = port —— 优先级 10
屏幕像素密度 = hdpi —— 优先级 13
触摸屏类型 = notouch —— 优先级 14
主要文本输入法 = 12key —— 优先级 16
1) 那么第一步直接排除和配置相冲突的资源:
>
drawable/
drawable-en/
drawable-fr-rCA/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/
注:
- 屏幕密度不会被排除,也就是存在多种dpi不会再低于不被排除
2) 选择列表(表 4.2)中(下一个)优先级最高的限定符。
3) 是否有资源目录包括此限定符?
- 若无,请返回到第 2 步,看看下一个限定符。(在该示例中,除非达到语言限定符,否则答案始终为“否”。)
- 若有,请继续执行第 4 步。
4) 淘汰不含此限定符的资源目录。在该示例中,系统会淘汰所有不含语言限定符的目录。
drawable/
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-notouch-12key/
5) 返回并重复第 2 步、第 3 步和第 4 步,直到只剩下一个目录为止。。在此示例中,屏幕方向是下一个判断是否匹配的限定符。因此,未指定屏幕方向的资源被淘汰:
>
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
剩下的目录是 drawable-en-port。
五 详解屏幕像素密度及分辨率限定符的资源优选
通过第四章,我们大致了解了,Android如何获取最佳的资源的。那么接下来让我们仔细分析下和我们屏幕适配最相关的两个资源限定符——屏幕像素密度和分辨率。
>
values/
values-ldpi/
values-mdpi/
values-hdpi/
values-xhdpi/
values-xxhdpi/
values-xxhdpi-1776x1080/
values-xxhdpi-1920x1080/
values-xxxhdpi/
上面的这种资源目录,大家应该很熟悉吧?
恩,子木在这一章,就只要讲这配置属性的优选。
关于屏幕像素密度及分辨率的最佳选项,谷歌官方的说明并没有给出一个十分确切的说法。当时子木也是非常的痛苦,最后是通过阅读罗升阳老师的《Android应用程序资源的查找过程分析》博客,才定位到资源获取的源代码,代码是在
android/platform/frameworks/base/libs/androidfw/ResourceTypes.cpp
可以通过点击这个超链接去访问(需要翻墙):ResourceTypes.cpp。
我们最需要关注的是getEntry()这个函数,这是一个关于如何选择资源的函数,大致过程和第四章描述的一致,其中最核心的两个调用是match()和isBetterThan()这两个函数。
- match 用于判断当前的资源是否符合系统的要求
- isBetterThan 用于找出最佳的那个资源
接下来我们将通过阅读以上的源码,来分析屏幕像素密度及分辨率两个资源的优选。
5.1 屏幕像素密度
在这一小节将用dpi代替屏幕像素密度这个概念。
在match()函数没有关于dpi的任何描述,这说明说明?
也就是,不会因为你的系统是属于xhdpi,而直接将其他标定为其他dpi的资源,直接排除。
在isBetterThan()中有如下的代码:
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
//...
if (screenType || o.screenType) {
if (density != o.density) {
// Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified.
const int thisDensity = density ? density : int(ResTable_config::DENSITY_MEDIUM);
const int otherDensity = o.density ? o.density : int(ResTable_config::DENSITY_MEDIUM);
// We always prefer DENSITY_ANY over scaling a density bucket.
if (thisDensity == ResTable_config::DENSITY_ANY) {
return true;
} else if (otherDensity == ResTable_config::DENSITY_ANY) {
return false;
}
int requestedDensity = requested->density;
if (requested->density == 0 ||
requested->density == ResTable_config::DENSITY_ANY) {
requestedDensity = ResTable_config::DENSITY_MEDIUM;
}
// DENSITY_ANY is now dealt with. We should look to
// pick a density bucket and potentially scale it.
// Any density is potentially useful
// because the system will scale it. Scaling down
// is generally better than scaling up.
int h = thisDensity;
int l = otherDensity;
bool bImBigger = true;
if (l > h) {
int t = h;
h = l;
l = t;
bImBigger = false;
}
if (requestedDensity >= h) {
// requested value higher than both l and h, give h
return bImBigger;
}
if (l >= requestedDensity) {
// requested value lower than both l and h, give l
return !bImBigger;
}
// saying that scaling down is 2x better than up
if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) {
return !bImBigger;
} else {
return bImBigger;
}
}
if ((touchscreen != o.touchscreen) && requested->touchscreen) {
return (touchscreen);
}
}
//...
}
让我们来总结下,这段代码,可以用简单的公式表示如下:
- request < =Low < High -> 选择 Low
- request > = High < Low -> 选择 High
- Low < request < High; -> 选择
注:因为源码中对low和high的顺序进行过调整,所以最后return返回的注释表示的具体会选择low还是high
对于第三中条件,还是不够直观?那么就看看下面这张图:
注:R表示系统的dpi值,H和L分别表示对比的两者中大的及相对小的dpi值。
不知道各位是否已经很直观了?什么?还不直观?那么再来看看下面这张表吧:
- 注:纵坐标表示设备本身的dpi,横坐标表示含有的相应dpi的资源,表中的数字表示选取的优先级。
这张图可能就是这篇文章的精华了。可能会颠覆你原先的一些观念,具体如下:
- 基本上,有些dpi情况下,如果不存在相应的资源,不会第一时间去找默认的,而是寻找附近的dpi,如xhdpi设备,在不存在xhdpi资源,会优先寻找xxdpi资源,而不是默认。
- 默认dpi的优先级比mdpi小,也就是如果配置了mdpi,这是很需要注意的。
5.2 分辨率
至于分辨率资源,比较坑的是,谷歌官方未给出相关的说明,但是在源码中我们可以找到相应的说明,优先级在表4.2中的18及19之间。
在ResourceTypes.cpp的match()函数中有相关代码如下:
if (screenSize != 0) {
if (screenWidth != 0 && screenWidth > settings.screenWidth) {
return false;
}
if (screenHeight != 0 && screenHeight > settings.screenHeight) {
return false;
}
}
在ResourceTypes.cpp的isBetterThan()函数中有相关代码如下:
if (screenSize || o.screenSize) {
// "Better" is based on the sum of the difference between both
// width and height from the requested dimensions. We are
// assuming the invalid configs (with smaller sizes) have
// already been filtered. Note that if a particular dimension
// is unspecified, we will end up with a large value (the
// difference between 0 and the requested dimension), which is
// good since we will prefer a config that has specified a
// size value.
int myDelta = 0, otherDelta = 0;
if (requested->screenWidth) {
myDelta += requested->screenWidth - screenWidth;
otherDelta += requested->screenWidth - o.screenWidth;
}
if (requested->screenHeight) {
myDelta += requested->screenHeight - screenHeight;
otherDelta += requested->screenHeight - o.screenHeight;
}
if (myDelta != otherDelta) {
return myDelta < otherDelta;
}
}
可以总结为如下两条:
- 1.是否符合: 一旦资源的宽或者高大于手机的宽或者高,直接不匹配。
- 最优选择规则 :det = 资源的宽+高 – 手机的宽+高
,det 小的为优选。
有个坑点大家必须注意:如果适配xxhpi带虚拟键盘的手机如1776x1080,给出了如下目录结构:
\res\
values
values-xxhdpi
values-xxhdpi-1176x1080
但是如果在这种情况下,如果标准的1920x1080分辨率,会优先采用values-xxhdpi-1176x1080下的资源,而不是values-xxhdpi下的资源,所以需要再新建values-xxhdpi-1920x1080目录。
5.3 本章小结
在这一章,主要通过介绍了Android的屏幕分辨率及dpi资源的优选规则。
六、适配实战
逼逼了这么久,有点对不住大家了。在这一章,子木将讲解如何适配,如何快速适配,以及最新的一些适配方案选择。以及哪些可以提高适配的效率的工具。
6.1 如何适配
当前的适配方案大致有如下几种:
- 1、采用values-xhdpi样式
- 2、采用values-1920x1080
- 3、采用values-swdp
- 4、自定义方式
6.1.1 方案1和2:
大家最熟悉的是values-xhdpi及values-1920x1080这两种模式;前者存在不能适配全部的机型;后者存在同样的分辨率有相同的dpi的情况,所以也不能完全适配。所以大部分情况下1、2两种方式是同时搭配使用。
但是存在一个比较棘手的问题,是存在560dpi(api 21引入)及420dpi(api 23引入),如果一些老的项目采用较低的编译api将不能定义values-420dpi这样的资源定义。接下来可以配合方案3来解决这个问题。
6.1.2 方案3
values-swdp、values-wdp、values-hdp这三种情况类似,是通过限定屏幕的转换为dp后的宽高来实现适配的,也是一种比较常见的方案,但是限定的面还是比较广,存在很大问题,也是需要配合其他方式使用。
配合方案3可以处理方案1处在的问题,也即采用values-sw411dp来暂时替代values-420dpi,而且values-sw411dp同样会匹配420及560dpi的情况,需要重点注意下。
6.1.2 方案4
那么既然google采用了新的密度无关的单位dp来降低设备适配的难度,为什么我们不自己定义一种方式呢?那如何定义呢?
具体思路如下:
- 首先,我们的需求是什么呢?就是想要在所有的机型上,展示的效果都是一样的。也就是说一个button在480x800手机上,宽度是半个屏幕,那么在1920x1080手机上也要占半个屏幕。
- 接着,既然明确了自己的需求,而且分辨率也是能准确的定义一个屏幕,我们是需求是两个屏幕上的比例是一致的,就引出了我们的单位——x。xN表示在x轴的占了N%的长度。
- 再,百分比可能粒度还是太大了,而且UE给出的都是dp、或者px,每次都需要自己计算不是很方便。那么我们就学谷歌,定位一款基本的设备——480x800(也可以是1920x1080),我们就认为所有的设备都是480x800,这个时候我们的xN就表示%frac{N}{480}%个px了。
- 最后我们需要做的就是,设置valus-WxH的资源文件,然后定义相应的转换关系,如下图。
是不是感觉似曾相似,没错,这就是鸿洋大神的《 Android 屏幕适配方案》,想了解更多,就查看大神的原文。
6.2 如何快速适配
这一小节,主要是针对方案1来说,其他方案也是同样适用。
我们先看张表格:
那我们如何根据这张表格实现快速适配呢?
具体的步骤可以分为如下几步:
step1: 当前我们UE给出的设计稿基本是基于xxhdpi(或xhdpi),所以我们根据设计稿实现相关的UI,同样的设计稿使用xhdpi、xxhdpi、xxxhdpi正常分辨率的手机。
step2: 根据上述表格的的“相对xhdpi的比例关系”,将xhdpi的下的相应的值乘以相关的系数,写入到相应的dpi目录下(这个可是使用shell或者python写个自动化脚本,可以实现自动化的适配)
step3: 然后将各个dpi下的特殊分辨率下的展示效果进行调整
通过以上的步骤,可以将适配时间压缩至少一半以上。
- 注:特殊分辨率最多的是除去虚拟键的分辨率,虚拟键的高度三星大部分是48dp,华为部分荣耀机型是36dp的高度,所以在xxdpi情况下,出现了1776x1080和1812x1080两种分辨率。
七、适配神兵利器
这里主要提三个常用的,莫喷哈。
7.1 虚拟机
可以Genymotion,也可以使用Android官方的x86架构的(arm的有些启动实在太慢了)。
以Genymotion为例,如下图,可以快速更改分辨率、dpi以及是否含有虚拟键。
7.2 屏幕截图
1. adb shell /system/bin/screencap -p /sdcard/screenshot.png
2.adb pull /sdcard/screenshot.png ~/Downloads/screenshot.png
采用以上两个命令,可以将屏幕截图,高清的保存到手机上。
7.3 PS
我们用的没那么高端,仅仅使用了PS了测量像素的功能,如果有其他工具,也是可以采用的。
有时候我们的UI的一些值,是需要反复调整,才能达到相应的效果。比如在发现UE给的设计稿在特殊分辨率上显示效果不佳,需要调整的情况,这时候有可能需要调整多次。
这个时候,我们可以使用7.2的屏幕截图,然后配合ps直接测量出当前位置距离目标位置需要调整多少px,那么再转换为相应的dp值,然后就可以一步到位了。
总结
之前写过一篇,也达到了2300的访问量,但是写的自己都看不下去了,所以就讲只有自己的一篇的部门分享的ppt再次进行整理,才写出了这篇博客,希望能对大家有所帮助。
子木再次谢过大家了。
参考文献:
[1]: Android Developer的多屏幕支持; https://developer.android.com/guide/practices/screens_support.html?hl=zh-cn
[2]:liangfei;详解Android开发中常用的 DPI / DP / SP; http://www.jianshu.com/p/913943d25829
[3]:Alex Bigman; PPI vs. DPI: what’s the difference?; https://99designs.com/blog/tips/ppi-vs-dpi-whats-the-difference/
[4]:产品经理马忠信; (全解析)屏幕尺寸,分辨率,像素,PPI之间到底什么关系?; http://www.jianshu.com/p/c3387bcc4f6e
[5]:Android Developer的利用资源提供最佳设备兼容性; https://developer.android.com/guide/topics/resources/providing-resources.html#Compatibility
[6]: Android Developer的最佳选择; https://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
[7]: 罗升阳; Android应用程序资源的查找过程分析; http://blog.csdn.net/luoshengyang/article/details/8806798
[8]:Android源码; https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/ResourceTypes.cpp
[9]: 鸿洋; Android 屏幕适配方案; http://blog.csdn.net/lmj623565791/article/details/45460089