Android屏幕适配终极方案-原理篇_第1张图片 image.png

当你看到这张图时,你觉得你会怎么适配Android各种机型?

前言

随着Android系统的不断更新,碎片化越来越严重。 Android 碎片化主要表现在 Android 品牌和机型众多,Android 版本众多和 Android 设备的尺寸和分辨率众多。


Android屏幕适配终极方案-原理篇_第2张图片

上图每一个方框代表一种 Android 设备的屏幕,颜色越深,这种尺寸的屏幕也就越多。也就是说,Android 开发者理论上需要适配上图中的屏幕。

适配方案

面对众多的分辨率,网络上各大开发者总是各显神通,我们谈谈曾经或者当前在用的适配方案吧:

  1. 官方方案:
  • 密度无关像素 (dp):密度无关像素等于 160 dpi 屏幕上的一个物理像素,这是 系统为“中”密度屏幕假设的基线密度。在运行时,系统 根据使用中屏幕的实际密度按需要以透明方式处理 dp 单位的任何缩放 。dp 单位转换为屏幕像素很简单: px = dp * (dpi / 160)。 例如,在 240 dpi 屏幕上,1 dp 等于 1.5 物理像素。在定义应用的 UI 时应始终使用 dp 单位 ,以确保在不同密度的屏幕上正常显示 UI。


    Android屏幕适配终极方案-原理篇_第3张图片 image.png
  • 使用尺寸和密度特定资源:使用不同限定符创建资源,如


  • android-percent-support百分比支持库与ConstraintLayout:这两个就不细说。
  1. dimens:通过脚本把既定分辨率按比例生成dimen供引用
Android屏幕适配终极方案-原理篇_第4张图片

3.非官方百分比布局库:目前比较好的是鸿洋的增加版官方百分比库
4.还是鸿洋的AutoLayout
...etc

问题及想法

适配方案远远不止上述这些。
虽然适配方案较多,其中不乏能解决适配问题的,但是仍然不能满足我们的需求。
Why?
不管是官方的百分比库还是增加版百分比库,或是AutoLayout或是其他,基本都需使用别的ViewGroup代替原有,通用性和灵活性打了折;并且部分库由于重写onMeasure方法,在性能上可能有所影响。这些都不是我们想要的。
那我们能不能在不生成dimens的情况,但是又能像dp一样自动完成适配呢?
刚好前段时间写了这篇Android-禁用系统字体缩放 ,其中重写了onConfigurationChanged和getResources方法来重置字体倍率,同时也阅读了TypedValue类的源码,那时就出现了这个想法,我能不能利用他们呢。

然鹅一直找不到切入点,有一天有位朋友让我认真阅读下TypedValue.applyDimension()。

于是这套适配方案出来了。截至发稿前,我也发现网上有相同的适配方案了,虽然如此我还是决定把我的发出来。

经过两个多月,多个项目的反复测试与实践,Github项目终于出来。
如果你不太关注原理,请点击这里Android屏幕适配终极方案-实战篇

原理

换算原理

先来看看TypedValue.applyDimension()源码:

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

该方法把dp、sp、pt、in、mm这5个单位的值换算成px单位,如果我们能在换算时做处理,那我们不就可以不需要改动原有代码就可以达到适配的目的?
细心查看会发现pt、in、mm这三个不常用的单位换算规则均与水平dpi metrics.xdpi有关,因此如果我们能利用起来,因此这三个单位是我们适配的理想单位。

Android屏幕适配终极方案-原理篇_第5张图片 image.png

以上图为例,750px宽的设计稿在720的屏幕上想要完美重现UI,等于设计稿等比缩放到0.96倍,由此换算公式为:

倍率 = 屏幕宽度 / 设计稿宽度

以in换算px的公式为例,把metrics.xdpi替换成上公式的倍率可得
px = value * 倍率

因此我们把设计稿上的值即可换算成当前屏幕对应值。
综上所述,in的metrics.xdpi = 屏幕宽度 / 设计稿宽度,
pt亦可转换为metrics.xdpi = 屏幕宽度 / 设计稿宽度 * 72
mm亦可转换为metrics.xdpi = 屏幕宽度 / 设计稿宽度 * 25.4

替换

不了解ResourcesManager的话建议先自行了解Android资源访问机制
已知不管哪种方法getResources()实际返回的均是mMainThread.getTopLevelResource()。
因此我们在Application修改DisplayMetrics的xdpi:

float xdpi = 0f;DisplayMetrics dm = getMetrics(context.getResources());    switch (unit) {        case TypedValue.COMPLEX_UNIT_PT:            xdpi = dm.widthPixels / DESIGN_WIDTH * 72;            break;        case TypedValue.COMPLEX_UNIT_IN:            xdpi = dm.widthPixels / DESIGN_WIDTH;            break;       case TypedValue.COMPLEX_UNIT_MM:            xdpi = dm.widthPixels / DESIGN_WIDTH * 25.4f;            break;    }dm.xdpi = xdpi;

同时由于旋转屏幕时会重新new DisplayMetrics(),因此我们还需重写onConfigurationChanged重写修改DisplayMetrics。

布局

由于我们已经在全局更换了TypedValue的换算px的规则,因此,我们可以直接在布局上使用。以上面的设计图为例,代码可以这么写:

<?xml version="1.0" encoding="utf-8"?>                                                                
<?xml version="1.0" encoding="utf-8"?>            

app在测量布局的时候就会直接换算px。

适配效果

描述 华为荣耀8 魅族MX3 魅族MX2
分辨率 1920*1080 1800*1080 1280*800
宽高比 16:9 15:9 16:10
效果图 Android屏幕适配终极方案-原理篇_第6张图片 荣耀8 Android屏幕适配终极方案-原理篇_第7张图片 mx3 Android屏幕适配终极方案-原理篇_第8张图片 mx2

总结

到此该方案基本结束。
经过2个多月及多个项目的测试,发现在部分情况下(除横竖屏切换)DisplayMetrics会重新new对象,并且小米在Android5.1.1及xposed框架该方案会失效。查明后发现由于系统被修改,所引用的Resources对象和getResources对象并非同一个。以上两个问题在Github项目中已经处理。

本方案问题:
横屏状态下,华为显示/隐藏导航栏后,UI并没有以最新的宽度重新适配,暂时查到view在宽高定值时onMeasure(int widthMeasureSpec, int heightMeasureSpec)测量得到的宽高不变。目前暂无有效解决方案。

本方案虽然能有效解决适配上的问题,但并非万能方案,推荐配合官方推荐方案一起使用。

参考文档

  1. 官方文档-支持多种屏幕
  2. 对于开发者来说,屏幕碎片化并不算个事儿
  3. Android 屏幕适配方案
  4. Android AutoLayout全新的适配方式 堪称适配终结者
  5. Android 增强版百分比布局库 为了适配而扩展
  6. Android资源访问机制

更多相关文章

  1. Android screenOrientation 屏幕方向的设定与控制
  2. android ScrollView 充满屏幕
  3. android 拖动条改变屏幕亮度
  4. android屏幕分辨率适配
  5. 去掉android的屏幕上的title bar
  6. 详解Android中的屏幕方向
  7. android屏幕截图
  8. Android实现动态改变屏幕方向(Landscape & Portrait)
  9. (mac)Android Studio安装以及Fetching android sdk component in

随机推荐

  1. Gradle for Android(安卓)第六篇( 测试)
  2. ios, android 应用程序不允许锁屏
  3. Android(安卓)一次启动多个Activity (Tas
  4. Android十八章:EventBus3.0使用
  5. Android动画详解之Android动画属性和实现
  6. android JNI utils/Log.h 找不到 解决方
  7. [疑难杂症] Android(安卓)WebView 无法打
  8. 常见android应用
  9. Android(安卓)Sqlite数据库升级时注意事
  10. Android电话信息相关API