Android屏幕适配-终结者
文章目录
Android屏幕适配专题
前言
屏幕适配问题一直在开发中存在,没有一种完美的解决方案。Android 的碎片化很严重。
下面这张图片所显示的内容足以充分说明当今Android系统碎片化问题的严重性,因为该图片中的每一个矩形都代表着一种Android设备。
而随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深。而我们今天要探讨的,则是对我们开发影响比较大的——屏幕的碎片化。
下面这张图是Android屏幕尺寸的示意图,在这张图里面,蓝色矩形的大小代表不同尺寸,颜色深浅则代表所占百分比的大小。
而与之相对应的,则是下面这张图。这张图显示了IOS设备所需要进行适配的屏幕尺寸和占比。
如何解决
- Smallest Width适配
- DisplayMetrics.densityDpi属性修改
1. Smallest Width适配
什么是Smallest Width适配
smallestWidth适配,或者叫sw限定符适配。指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。
举个例子,小米5的dpi是480,横向像素是1080px,根据px=dp(dpi/160),横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的资源文件。如果找不到,系统就会去向下寻找,下面的图就会找到 value-sw320dp的文件夹。
这套方案是最接近完美的方案。 首先,从开发效率上,它不逊色于任意一种方案。
根据固定的放缩比例,我们基本可以按照UI设计的尺寸不假思索的填写对应的dimens引用。
我们还有以375个像素宽度的设计稿为例(iOS 设计稿),在values-sw375dp文件夹下的diemns文件应该怎么编写呢?
这个文件夹下,意味着手机的最小宽度的dp值是375,直接按着 1:1的比例写就好,那么接下来的事情就很简单了,假如设计稿上出现了一个20dp*20dp的TextView,那么,我们就可以不假思索的在layout文件中写下对应的尺寸。
|
|
values-sw375dp 目录下的 dimens.xml
|
|
那么设计稿为 375 个像素宽度,那么有没有办法直接可以生成其他限定符文件夹呢?
Smallest Width终极解决方案 screen-plugin
使用方法
添加 jcenter()仓库,在项目的根 build.gradle 中添加
|
|
在要使用插件的的子项目的 build.gradle 中添加
|
|
如果 auto 设置为 true ,则每次 build 项目的时候自动生成 values-sw[]dp 文件
如果 auto 设置为 false,则可以通过命令行,来生成文件.
|
|
也可以在 gradle命令的 窗口中 点击 dimensCovert 的 task.
自动生成的sw 文件
生成规则:只会生成 dp 后缀的属性值,根据 values 目录下的 dimens.xml,生成具体的文件。 values 目录下的 dimens.xml
|
|
生成的目标文件,values-sw320dp 目录下的 dimens.xml 文件
|
|
包体积
因为是按着一对一的方式进行生成,所以对于最后生成的 apk 来说,只是增加了几 k的大小,完全不必担心会增加包体积。
2. DisplayMetrics.densityDpi 属性修改
发现过程
通过阅读源码,我们可以得知,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) 来进行转换:
|
|
这里用到的DisplayMetrics正是从Resources中获得的。
再看看图片的decode,BitmapFactory#decodeResourceStream方法:
|
|
PhoneWindow的getDimension方法
|
|
当然还有些其他dp转换的场景,基本都是通过 DisplayMetrics 来计算的,这里不再详述。因此,想要满足上述需求,我们只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可。
这个方案侵入性很低,而且也没有涉及私有API,是个极不错的方案,我暂时也想不到强行修改density是否会有其他影响,既然有今日头条的大厂在用,稳定性应当是有保证的。
根据我的实践,这套方案对任何项目来说都是完美的,因为修改了系统的density值之后,整个布局的实际尺寸都会发生改变,如果想要在老项目文件中使用,那么可以把DisplayMetrics#density设置成原来你项目中的设计图的宽度。因此,如果你是在维护或者改造老项目,直接使用这套方案就可以了。
DisplayMetrics.densityDpi 终极解决方案 screen-helper
在项目的根 build.gradle 中添加 jcenter 仓库
然后在子项目中的 build.gradle 文件中添加
|
|
使用,在每个Activity 重写getResources()方法。
|
|
如果是悬浮窗适配,因为 inflate 用到的 context 是 application 级别的,所以需要在自定义的 Application 中重写 getResource。
|
|
类型
- ScreenHelper.WIDTH_DP 以 dp 来适配,在 xml 中使用 dp 单位
- ScreenHelper.WIDTH_PT 以 pt 来适配,在 xml 中使用 pt 单位
- ScreenHelper.HEIGHT_PT 以 pt 来适配,在 xml 中使用 pt 单位
版本变化
- 1.0.2 :优化传递 ScreenMode 的参数传递,去除不必要的 log
- 1.0.1 :优化 Resources.getSystem() 变量获取,由于此方法是 synchronized ,如果频繁调用会影响性能
- 1.0.0 :正式发版
源码地址
https://github.com/bugyun/ScreenHelper
如果你喜欢我的文章,可以关注我的掘金、公众号、博客、简书或者Github!
简书: https://www.jianshu.com/u/a2591ab8eed2
GitHub: https://github.com/bugyun
Blog: https://ruoyun.vip
欢迎关注微信公众号
文章作者 若云
上次更新 2019-09-06