注: 本文已整理成博客,见: https://blog.csdn.net/u011200604/article/details/84990040
注: 本文最终方案推荐源于JessYanCoding/AndroidAutoSize 的开源库(详见GitHub)
在Android开发中,由于Android碎片化严重,屏幕分辨率千奇百怪,而想要在各种分辨率的设备上显示基本一致的效果,适配成本越来越高。虽然Android官方提供了dp单位来适配,但其在各种奇怪分辨率下表现却不尽如人意.
参见Google官方的屏幕适配文档:Google- screen compatibility overview : https://developer.android.com/guide/practices/screens_support
主流适配方案
目前有如下几种:拉丁吴老师的 : Android 目前最稳定和高效的UI适配方案 中,有如下5种
dp直接适配
宽高限定符适配(在资源文件下生成不同分辨率的资源文件,然后在布局文件中引用对应的 dimens)
UI适配框架: 见 鸿洋大神的适配方案: AndroidAutoLayout (已停止维护,作者推荐使用JessYanCoding的AndroidAutoSize )
smallestWidth适配
今日头条适配方案
下面着重介绍后两种方案
首先看今日头条适配方案
一种极低成本的Android屏幕适配方式 -- -今日头条适配方案
(见今日头条技术团队 5月25日 - 一种极低成本的Android屏幕适配方式: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA)
首先看屏幕尺寸、分辨率、像素密度三者关系,关于dip(即dp),dpi与 ppi,px,sp
屏幕分辨率:在橫纵向上的像素点数。单位:px即1px=1个像素点 一般以纵向像素*横向像素表示,如1920*1080
Dpi:屏幕像素密度,指每英寸上的像素点数,dot per inch的缩写,与屏幕尺寸和屏幕分辨率有关
px:像素,构成图像的最小单位,这个比较简单,无需介绍。
dp/dip: dp和dip是一样的,密度无关像素,Density Independent Pixels的缩写,以160dpi为基准。在160dpi设备 上1dp=1px,在240dpi设备上1dp=1.5px,以此类推
sp:Scale-Independent Pixels,可以根据文字大小首选项进行放缩,常用于设置字体大小,尽量避免使用奇数或小数,因为容易造成精度的丢失
density : 设备的逻辑密度,是dip的缩放因子。以160dpi的屏幕为基线,density=dpi/160。
通常情况下,一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是:
dpi是像素密度,指的是在系统软件上指定的单位尺寸的像素数量,它往往是写在系统出厂配置文件的一个固定值。
另一个叫ppi的参数,这个在手机屏幕中指的也是像素密度,但是这个是物理上的概念,它是客观存在的不会改变。dpi是软件参考了物理像素密度后,人为指定的一个值,这样保证了某一个区间内的物理像素密度在软件上都使用同一个值。这样会有利于我们的UI适配
几部相同分辨率不同尺寸的手机的ppi可能分别是是430,440,450,那么在Android系统中,可能dpi会全部指定为480
drawable |
适用 常规: 360dp * 640dp |
DP |
比例值 |
比例系数 |
dp与px计算图 |
ldpi:低密度屏幕;约为 120dpi。 mdpi:中等密度(传统 HVGA)屏幕;约为 160dpi。 hdpi:高密度屏幕;约为 240dpi。 xhdpi:超高密度屏幕;约为 320dpi。API 级别 8 中新增配置 xxhdpi:超超高密度屏幕;约为 480dpi。API 级别 16 中新增配置 xxxhdpi:超超超高密度屏幕使用(仅限启动器图标,请参阅“支持多个屏幕”中的注释);约为 640dpi。 API 级别 18 中新增配置 |
QVGA (240×320) HVGA (320×480) WVGA (480×800),(480×854) 720P(1280*720) 1080p(1920*1080 ) 4K(3840×2160) |
320dp 320dp 320dp 360dp 360dp 540dp |
3 4 6 8 12 16 |
.075 1.0 1.5 2.0 3.0 4.0 |
ldpi:1dp=0.75px mdpi:1dp=1px hdpi:1dp=1.5px xhdpi:1dp=2px xxhdpi:1dp=3px xxxhdpi:1dp=4px |
六个主要密度之间的缩放比为 3:4:6:8:12:16(忽略 tvdpi 密度)。因此,9x9 (ldpi) 位图相当于 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 位图,依此类推。
传统dp适配方式的缺点
android中的dp在渲染前会将dp转为px,计算公式:
-
px = density * dp;
-
density = dpi / 160;
-
px = dp * (dpi / 160);
这样会存在什么问题呢?
假设我们UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况。
而且上述屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱,没有规律可循,从而导致使用dp适配效果差强人意。
梳理需求
首先来梳理下我们的需求,一般我们设计图都是以固定的尺寸来设计的。比如以分辨率1920px * 1080px来设计,以density为3来标注,也就是屏幕其实是640dp * 360dp。
如果我们想在所有设备上显示完全一致,其实是不现实的,因为屏幕高宽比不是固定的,16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了。
但是通常下,我们只需要以宽或高一个维度去适配,比如我们Feed是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致,尤其不能存在某些设备上显示不全的情况。同时考虑到现在基本都是以dp为单位去做的适配,如果新的方案不支持dp,那么迁移成本也非常高。
因此,总结下大致需求如下:
-
支持以宽或者高一个维度去适配,保持该维度上和设计图一致;
-
支持dp和sp单位,控制迁移成本到最小。
找兼容突破口
从dp和px的转换公式 :px = dp * density
可以看出,如果设计图宽为360dp,想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,我们只能修改 density 的值。
阅读源码,我们可以得知,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得。
先来熟悉下 DisplayMetrics 中和适配相关的几个变量:
-
DisplayMetrics#density 就是上述的density
-
DisplayMetrics#densityDpi 就是上述的dpi
-
DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
那么是不是所有的dp和px的转换都是通过 DisplayMetrics 中相关的值来计算的呢?
首先来看看布局文件中dp的转换,最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换:
不管你在布局文件中填写的是什么单位,最后都会被转化为 px,系统就是通过下面的方法,将你在项目中任何地方填写的单位都转换为 px 的
public static float applyDimension(int unit, float value,DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
再看看图片的decode,BitmapFactory#decodeResourceStream方法:(此处省略,可查看源码)
也是通过 DisplayMetrics 中的值来计算的。
当然还有些其他dp转换的场景,基本都是通过 DisplayMetrics 来计算的,这里不再详述。因此,想要满足上述需求,我们只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可。
最终方案
原理:
屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度
在这个公式中我们要保证 屏幕的总 dp 宽度 和 设计图总宽度 一致,并且在所有分辨率的屏幕上都保持不变,我们需要怎么做呢?屏幕的总 px 宽度 每个设备都不一致,这个值是肯定会变化的,这时今日头条的公式就派上用场了
当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density
这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是一样的,只要 density 根据不同的设备进行实时计算并作出改变,就能保证 设计图总宽度 不变,也就完成了适配
下面假设设计图宽度是360dp,以宽维度来适配。
那么适配后的 density = 设备真实宽(单位px) / 360,接下来只需要把我们计算好的 density 在系统中修改下即可,代码实现如下:
同时在 Activity#onCreate 方法中调用下。代码比较简单,也没有涉及到系统非公开api的调用,因此理论上不会影响app稳定性。
出现的问题:
修改后上线灰度测试了一版,稳定性符合预期,没有收到由此带来的crash,但是收到了很多字体过小的反馈
原因是在上面的适配中,我们忽略了DisplayMetrics#scaledDensity的特殊性,将DisplayMetrics#scaledDensity和DisplayMetrics#density设置为同样的值,从而某些用户在系统中修改了字体大小失效了,但是我们还不能直接用原始的scaledDensity,直接用的话可能导致某些文字超过显示区域,因此我们可以通过计算之前scaledDensity和density的比获得现在的scaledDensity;
但是测试后发现另外一个问题,就是如果在系统设置中切换字体,再返回应用,字体并没有变化。
于是还得监听下字体切换,调用 Application#registerComponentCallbacks 注册下 onConfigurationChanged 监听即可。
(当然以上代码只是以设计图宽360dp去适配的,如果要以高维度适配,可以再扩展下代码即可。)
假设我们设计稿是宽度是 1080px,资源放在 xxhdpi,那么我们宽度转换为 dp 就是 1080 / 3 = 360dp,要在不同设备上宽度都表现为 360dp,那么就需要修改其 density = screenWidthPx / 360,这样就满足了上述条件,而和 density 相关的还有 densityDpi、scaledDensity,我们根据 density 等比修改 densityDpi、scaledDensity 即可。
暴露的问题:
这样可能会导致获取状态栏和导航栏高度有问题,其获取状态栏高度代码为如下所示, 导航栏高度也可以这样
public static int getStatusBarHeight() {
Resources resources = Utils.getApp().getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}
由于使用的是 Application#getResources,这会导致最后计算状态栏高度使用的是修改过后的 density,
解决: Resources.getSystem() 来获取系统的 Resources,果不其然可以获取到正确高度的状态栏高度,代码如下所示:
public static int getStatusBarHeight() {
Resources resources = Resources.getSystem();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}
优点
-
使用成本非常低,操作非常简单,使用该方案后在页面布局时不需要额外的代码和操作,这点可以说完虐其他屏幕适配方案
-
侵入性非常低,该方案和项目完全解耦,在项目布局时不会依赖哪怕一行该方案的代码,而且使用的还是 Android 官方的 API,意味着当你遇到什么问题无法解决,想切换为其他屏幕适配方案时,基本不需要更改之前的代码,整个切换过程几乎在瞬间完成,会少很多麻烦,节约很多时间,试错成本接近于 0
-
可适配三方库的控件和系统的控件(不止是 Activity 和 Fragment,Dialog、Toast 等所有系统控件都可以适配),由于修改的 density 在整个项目中是全局的,所以只要一次修改,项目中的所有地方都会受益
-
不会有任何性能的损耗
缺点
暂时没发现其他什么很明显的缺点,已知的缺点有一个,那就是第三个优点,它既是这个方案的优点也同样是缺点,但是就这一个缺点也是非常致命的
只需要修改一次 density,项目中的所有地方都会自动适配,这个看似解放了双手,减少了很多操作,但是实际上反应了一个缺点,那就是只能一刀切的将整个项目进行适配,但适配范围是不可控的
这样不是很好吗?这样本来是很好的,但是应用到这个方案是就不好了,因为我上面的原理也分析了,这个方案依赖于设计图尺寸,但是项目中的系统控件、三方库控件、等非我们项目自身设计的控件,它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样
当这个适配方案不分类型,将所有控件都强行使用我们项目自身的设计图尺寸进行适配时,这时就会出现问题,当某个系统控件或三方库控件的设计图尺寸和和我们项目自身的设计图尺寸差距非常大时,这个问题就越严重
总结:
介绍这个今日头条的屏幕适配方案并不是说他有多么完美,只是他确实有效而且能帮我们减少很多开发成本
对于很多人说的 DPI 的存在,不就是为了让大屏能显示更多的内容,如果一个大屏手机和小屏手机,显示的内容都相同,那用户买大屏手机又有什么意义呢,我觉得大家对 DPI 的理解是对的,这个观点我也是认同的,Google 设计 DPI 时可能也是这么想的,但是有一点大家没考虑到,Android 的碎片化太严重了
为什么 Android 诞生这么多年,Android 的百分比库层出不穷,按理说他们都违背了上面说的这个理念,但为什么还有这么多人去研究百分比库通过各种方式去实现百分比布局 (谷歌官方也曾出过百分比库)?为什么?很简单,因为需求啊!为什么需求,因为开发成本低啊!为什么今日头条的这个屏幕适配方案现在能这么火,因为他的开发成本是目前所有屏幕适配方案中最低的啊!
DPI 的意义谁又不懂呢?今日头条这种大公司的程序员不懂这个道理吗?今日头条这么大的公司难道不想把每个机型每个版本的设备都适配完美,让自己的 App 体验更好,哪怕付出更大的成本,有些时候想象是美好的,但是这个投入的成本,谁又能承担呢,连今日头条这么大的公司,这么雄厚的资本都没选择投入更大的成本,对每个机型进行更精细化的适配,难道市面上的中小型公司又有这个能力投入这么大的成本吗?
鱼和熊掌不可兼得,DPI 的意义在 Google 的设计理念中是完全正确的,但不是所有公司都能承受这个成本,想必今日头条的程序员,也是因为今日头条 App 的用户量足够多,机型分布足够广,也是被屏幕适配这个问题折磨的不要不要的,才想出这么个不这么完美但是却很有效的方案
附录: 下图为Android手机碎片化,每个矩形代表一种屏幕尺寸
简介 smallestWidth 限定符适配方案
下面要介绍的 smallestWidth 限定符屏幕适配方案
原理也同样是按照百分比缩放布局,理论上也会存在上面所说的 大屏手机和小屏手机显示的内容相同 的问题,选择与否请仔细斟酌
这个方案的的使用方式和我们平时在布局中引用 dimens 无异,核心点在于生成 dimens.xml 文件,但是已经有大神帮我们做了这 一步(一种非常好用的Android屏幕适配)
如果有人还记得上面这种 宽高限定符屏幕适配方案 的话,就可以把 smallestWidth 限定符屏幕适配方案 当成这种方案的升级版,smallestWidth 限定符屏幕适配方案 只是把 dimens.xml 文件中的值从 px 换成了 dp,原理和使用方式都是没变的,这些在上面的文章中都有介绍,下面就直接开始剖析原理,smallestWidth 限定符屏幕适配方案 长这样
原理
其实 smallestWidth 限定符屏幕适配方案 的原理也很简单,开发者先在项目中根据主流屏幕的 最小宽度 (smallestWidth) 生成一系列 values-sw<N>dp 文件夹 (含有 dimens.xml 文件),当把项目运行到设备上时,系统会根据当前设备屏幕的 最小宽度 (smallestWidth) 去匹配对应的 values-sw<N>dp 文件夹,而对应的 values-sw<N>dp 文件夹中的 dimens.xml 文字中的值,又是根据当前设备屏幕的 最小宽度 (smallestWidth) 而定制的,所以一定能适配当前设备
如果系统根据当前设备屏幕的 最小宽度 (smallestWidth) 没找到对应的 values-sw<N>dp 文件夹,则会去寻找与之 最小宽度 (smallestWidth) 相近的 values-sw<N>dp 文件夹,系统只会寻找小于或等于当前设备 最小宽度 (smallestWidth) 的 values-sw<N>dp,这就是优于 宽高限定符屏幕适配方案 的容错率,并且也可以少生成很多 values-sw<N>dp 文件夹,减轻 App 的体积
smallestWidth 翻译为中文的意思就是 最小宽度,那这个 最小宽度 是什么意思呢?
移动设备都是允许屏幕可以旋转的,当屏幕旋转时,屏幕的高宽就会互换,加上 最小 这两个字,是因为这个方案是不区分屏幕方向的,它只会把屏幕的高度和宽度中值最小的一方认为是 最小宽度,这个 最小宽度 是根据屏幕来定的,是固定不变的
如果想让屏幕宽度随着屏幕的旋转而做出改变该怎么办呢?可以再根据 values-w<N>dp (去掉 sw 中的 s) 生成一套资源文件
如果想区分屏幕的方向来做适配该怎么办呢?那就只有再根据 屏幕方向限定符 生成一套资源文件咯,后缀加上 -land 或 -port 即可,像这样,values-sw400dp-land (最小宽度 400 dp 横向),values-sw400dp-port (最小宽度 400 dp 纵向)
我们假设设备的屏幕信息是 1920 * 1080、480 dpi
根据上面的规则我们要在屏幕的高度和宽度中选择值最小的一方作为最小宽度,1080 < 1920,明显 1080 px 就是我们要找的 最小宽度 的值,但 最小宽度 的单位是 dp,所以我们要把 px 转换为 dp
px / density = dp,DPI / 160 = density,所以最终的公式是 px / (DPI / 160) = dp
所以我们得到的 最小宽度 的值是 360 dp (1080 / (480 / 160) = 360),系统会根据这个 最小宽度 帮助我们匹配到 values-sw360dp 文件夹下的 dimens.xml 文件,如果项目中没有 values-sw360dp 这个文件夹,系统才会去匹配相近的 values-sw<N>dp 文件夹
核心dimens.xml 生成原理
dimens.xml 的生成,就要涉及到两个因数,第一个因素是 最小宽度基准值,第二个因素就是您的项目需要适配哪些 最小宽度,通俗理解就是需要生成多少个 values-sw<N>dp 文件夹
小宽度基准值 是什么意思呢?简单理解就是您需要把设备的屏幕宽度分为多少份,假设我们现在把项目的 最小宽度基准值 定为 360,那这个方案就会理解为您想把所有设备的屏幕宽度都分为 360 份
values-sw400dp 指的是当前设备屏幕的 最小宽度 为 400dp (该设备高度大于宽度,则最小宽度就是宽度,所以该设备宽度为 400dp),把屏幕宽度同样分为 360份,这时每份就等于 1.1111dp 了,每个引用都递增 1.1111dp,值最大的 dimens 引用 dp_360 同样刚好覆盖屏幕宽度,为 400dp
这样就能保证不管将项目运行到哪个设备上,只要当前设备能匹配到对应的 values-sw<N>dp 文件夹,那布局中的 dimens 引用就能根据当前屏幕的情况进行缩放,保证能完美适配,如果没有匹配到对应的 values-sw<N>dp 文件夹,也没关系,它会去寻找与之相近的 values-sw<N>dp 文件夹,虽然在这种情况下,布局中的 dimens 引用的值可能有些许误差,但是也能保证最大程度的完成适配
每个 values-sw<N>dp 文件夹其实都会占用一定的 App 体积,values-sw<N>dp 文件夹越多,App 的体积也就会越大要合理分配 values-sw<N>dp,以越少的 values-sw<N>dp 文件夹,覆盖越多的机型 ;
优点
1. 非常稳定,极低概率出现意外
2. 不会有任何性能的损耗
3. 适配范围可*控制,不会影响其他三方库
4. 在插件的配合下,学习成本低
缺点
1. 在布局中引用 dimens 的方式,虽然学习成本低,但是在日常维护修改时较麻烦
2. 侵入性高,如果项目想切换为其他屏幕适配方案,因为每个 Layout 文件中都存在有大量 dimens 的引用,这时修改起来工作量非常巨大,切换成本非常高昂
3. 无法覆盖全部机型,想覆盖更多机型的做法就是生成更多的资源文件,但这样会增加 App 体积,在没有覆盖的机型上还会出现一定的误差,所以有时需要在适配效果和占用空间上做一些抉择
4. 如果想使用 sp,也需要生成一系列的 dimens,导致再次增加 App 的体积
5. 不能自动支持横竖屏切换时的适配,如上文所说,如果想自动支持横竖屏切换时的适配,需要使用 values-w<N>dp 或 屏幕方向限定符 再生成一套资源文件,这样又会再次增加 App 的体积
6. 不能以高度为基准进行适配,考虑到这个方案的名字本身就叫 最小宽度限定符适配方案,所以在使用这个方案之前就应该要知道这个方案只能以宽度为基准进行适配,为什么现在的屏幕适配方案只能以高度或宽度其中的一个作为基准进行适配,请看 这里
smallestWidth 限定符屏幕适配方案 的原理和 今日头条屏幕适配方案 挺像的,今日头条屏幕适配方案 是根据屏幕的宽度或高度动态调整每个设备的 density (每 dp 占当前设备屏幕多少像素),而 smallestWidth 限定符屏幕适配方案 同样是根据屏幕的宽度动态调整每个设备 每份占的 dp 值
上述两个方案对比及新的方案
这两个方案中,并不能以单个标准就能评判出谁一定比谁好,因为它们都有各自的优缺点,都不是完美的,从更客观的角度来看,它们谁都不能成为最好的那个,只有可能明确了它们各自的优缺点,知道在它们的优缺点里什么是我能接受的,什么是我不能接受的,是否能为了某些优点做出某些妥协,从而选择出一个最适合自己项目的屏幕适配方案
对比项目 |
对比对象 A |
对比结果 |
对比对象 B |
适配效果(越高越好) |
今日头条适配方案 |
≈ |
SW 限定符适配方案(在未覆盖的机型上会存在一定的误差) |
稳定性(越高越好) |
今日头条适配方案 |
< |
SW 限定符适配方案 |
灵活性(越高越好) |
今日头条适配方案 |
> |
SW 限定符适配方案 |
扩展性(越高越好) |
今日头条适配方案 |
> |
SW 限定符适配方案 |
侵入性(越低越好) |
今日头条适配方案 |
< |
SW 限定符适配方案 |
使用成本(越低越好) |
今日头条适配方案 |
< |
SW 限定符适配方案 |
维护成本(越低越好) |
今日头条适配方案 |
< |
SW 限定符适配方案 |
性能损耗 |
今日头条适配方案没有性能损耗 |
= |
SW 限定符适配方案没有性能损耗 |
副作用 |
今日头条适配方案会影响一些三方库和系统控件 |
≈ |
SW 限定符适配方案会影响 App 的体积 |
SmallestWidth 限定符适配方案 和 今日头条适配方案 的适配效果其实都是差不多的,我在前面的文章中也通过公式计算过它们的精确度,SmallestWidth 限定符适配方案 运行在未覆盖的机型上虽然也可以适配,但是却会出现一定的误差,所以 今日头条适配方案 的适配精确度确实要比 SmallestWidth 限定符适配方案 略高的,不过只要 SmallestWidth 限定符适配方案 合理的分配资源文件,适配效果的差距应该也不大
SmallestWidth 限定符适配方案 主打的是稳定性,在运行过程中极少会出现安全隐患,适配范围也可控,不会产生其他未知的影响,而 今日头条适配方案 主打的是降低开发成本、提高开发效率,使用上更灵活,也能满足更多的扩展需求,简单一句话概括就是,这两兄弟,一个求稳,一个求快!
AndroidAutoSize
关于今日屏幕适配的方案,参考了今日头条于2018.5月公布的适配方案,以及之前被广泛接受的限定符适配(如sw-600dp,layout-land等)进行优劣对比,探索自身适合的适配方式
JessYanCoding/AndroidAutoSize 于2018.10月底公布其基于今日头条方案优化的适配库 ((源码29k大小,一周2k star,目前5k+))
目前统计 : bintray 每个月的下载数有 15k 以上,已经有几千个项目引入了 AndroidAutoSize
建议是,可以在项目中先使用 今日头条屏幕适配方案,感受下它的使用方式以及适配效果,今日头条屏幕适配方案 的侵入性非常低,如果在使用过程中遇到什么不能解决的问题,马上可以切换为其他的屏幕适配方案,在切换的过程中也花费不了多少工作量,试错成本非常低
但如果你在项目中先使用 SmallestWidth 限定符适配方案,之后在使用的过程中再遇到什么不能解决的问题,这时想切换为其他的屏幕适配方案,这工作量可就大了,每个 Layout 文件都含有大量的 dimens 引用,改起来这工作量得有多大,想想都觉得后怕,这就是侵入性太高导致的最致命的问题
功能介绍
AndroidAutoSize 在使用上非常简单,只需要填写设计图尺寸这一步即可接入项目,但需要注意的是,AndroidAutoSize 有两种类型的布局单位可以选择,一个是 主单位 (dp、sp),一个是 副单位 (pt、in、mm),两种单位面向的应用场景都有不同,也都有各自的优缺点
-
主单位: 使用 dp、sp 为单位进行布局,侵入性最低,会影响其他三方库页面、三方库控件以及系统控件的布局效果,但 AndroidAutoSize 也通过这个特性,使用 ExternalAdaptManager 实现了在不修改三方库源码的情况下适配三方库的功能
-
副单位: 使用 pt、in、mm 为单位进行布局,侵入性高,对老项目的支持比较好,不会影响其他三方库页面、三方库控件以及系统控件的布局效果,可以彻底的屏蔽修改 density 所造成的所有未知和已知问题,但这样 AndroidAutoSize 也就无法对三方库进行适配
大家可以根据自己的应用场景在 主单位 和 副单位 中选择一个作为布局单位,建议想引入老项目并且注重稳定性的人群使用 副单位,只是想试试本框架,随时可能切换为其他屏幕适配方案的人群使用 主单位
其实 AndroidAutoSize 可以同时支持 主单位 和 副单位,但 AndroidAutoSize 可以同时支持 主单位 和 副单位 的目的,只是为了让使用者可以在 主单位 和 副单位 之间灵活切换
基本使用
引入库(29k大小,manifest加入此配置即完成适配)
进阶用法:(activity/fragment都适用)
-
当某个 Activity 的设计图尺寸与在 AndroidManifest 中填写的全局设计图尺寸不同时,可以实现 CustomAdapt 接口扩展适配参数
-
当某个 Activity 想放弃适配,请实现 CancelAdapt 接口
自动运行是如何做到的?
为什么使用者只需要在 AndroidManifest.xml 中填写一下 meta-data 标签,其他什么都不做,AndroidAutoSize 就能自动运行,并在 App 启动时自动解析 AndroidManifest.xml 中填写的设计图尺寸,难道使用了什么 黑科技?
其实这里并没有用到什么 黑科技,原理反而非常简单,只需要声明一个 ContentProvider,在它的 onCreate 方法中启动框架即可,在 App 启动时,系统会在 App 的主进程中自动实例化你声明的这个 ContentProvider,并调用它的 onCreate 方法,执行时机比 Application#onCreate 还靠前,可以做一些初始化的工作
这里需要注意的是,如果你的项目拥有多进程,系统只会在主进程中实例化一个你声明的 ContentProvider,并不会在其他非主进程中实例化 ContentProvider,如果在当前进程中 ContentProvider 没有被实例化,那 ContentProvider#onCreate 就不会被调用,你的初始化代码在当前进程中也就不会执行,这时就需要在 Application#onCreate 中调用下 ContentProvider#query 执行一下查询操作,这时 ContentProvider 就会在当前进程中实例化 (每个进程中只会保证有一个实例),所以应用到框架中就是,如果你需要在多个进程中都进行屏幕适配,那就需要在 Application#onCreate 中调用 AutoSize#initCompatMultiProcess 方法
进阶用法:(activity/fragment都适用)
该适配方案的问题汇总:
https://github.com/JessYanCoding/AndroidAutoSize/issues/13
优缺点上述已经描述,总结下目前接入后遇到的问题:
- 1.以设计图尺寸接入,在过小屏手机上可能会使字体布局等显示较小. 这是此方案的优点也是此方案的缺点,单靠此方案无解,可以配合其他方案,比如像之前专为过小/过大屏专门写一套尺寸
- 2.在download项目,主fragment切换为搜索fragment时,第二次切换后搜索框不变
关于此问题: 见issue解答: 单Activity多fragment适配问题 , 主页面有很多个fragment (核心代码: AutoSize--autoConvertDensity(...)) ; 对于横屏、平板电脑、TV 的适配
fragment生命周期较为复杂,某些未知情况下CancelAdapt会失效,在Google规定下只有 Activity 能修改 DisplayMetrics,
Fragment 里面想修改 density,也必须从它依赖的 Activity 上拿到 DisplayMetrics,
Activity 也是这样做的,看上去 Activity 可以单独设置 density,只不过在这个 Activity setContentView 之前,按照它的需求切换了 density,这个 density 是全局的,不存在什么单独设置这一说
- 3.此适配方案仅针对原生页面,对webview无效, webview有自己单独设置整体size大小的方法
4.DisplayMetrics#density 是公有的,谁都有权限修改,AndroidAutoSize 可以把 DisplayMetrics#density 修改成一个可以完成屏幕适配的值,其他三方库、Android 系统、以及项目成员就可以把 DisplayMetrics#density 修改或恢复成另一个值,这都将导致屏幕适配的失效,特别是在某些定制系统上
* 万能解决方案
在任何情况下本来适配正常的布局突然出现适配失效,适配异常等问题,只要在合适的时机 (在布局绘制到屏幕上之前) 调用 AutoSize#autoConvertDensity() 或 AutoSize#autoConvertDensityOfGlobal 即可解决大部分问题,比如如果从桌面或后台返回 APP 后当前布局出现混乱的问题,在 Activity#onResume() 中调用 AutoSize#autoConvertDensity() 或 AutoSize#autoConvertDensityOfGlobal 即可解决大部分问题,如果是 Dialog、PopupWindow 等控件出现适配失效或适配异常,同样在每次 show() 之前调用 AutoSize#autoConvertDensity() 即可
注: 框架的原理并没有涉及到与 View 相关的操作,只是单纯的修改 density,修改 density 只会影响 dp 转 px 的过程
建议是:
可以在项目中先使用 今日头条屏幕适配方案,感受下它的使用方式以及适配效果,今日头条屏幕适配方案 的侵入性非常低,如果在使用过程中遇到什么不能解决的问题,马上可以切换为其他的屏幕适配方案,在切换的过程中也花费不了多少工作量,试错成本非常低
但如果你在项目中先使用 SmallestWidth 限定符适配方案,之后在使用的过程中再遇到什么不能解决的问题,这时想切换为其他的屏幕适配方案,这工作量可就大了,每个 Layout 文件都含有大量的 dimens 引用,改起来这工作量得有多大,这就是侵入性太高导致的最致命的问题
另: 关于Blankj/AndroidUtilCode 也基于今日头条方案出了一套适配util方案 (仅是util类中一个,非专门项目; 项目目前2万+ star ,Android工具类最受欢迎项目,关于工具类的统一建议参考此项目)
见博客:Android 屏幕适配从未如斯简单 (2018.8.10)
参考:
骚年你的屏幕适配方式该升级了!(二)-smallestWidth 限定符适配方案
Android 屏幕适配从未如斯简单 (BlankJ): https://juejin.im/post/5b6250bee51d451918537021
一种极低成本的Android屏幕适配方式 -- 今日头条技术团队 : https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
Android 目前稳定高效的UI适配方案 -- 拉丁吴 : https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q
Google- screen compatibility overview : https://developer.android.com/guide/practices/screens_support
一种非常好用的Android屏幕适配 : (最小宽限定符) https://www.jianshu.com/p/1302ad5a4b04