骚年你的屏幕适配方式该升级了!-今日头条适配方案

原文地址: www.jianshu.com/p/55e0fca23…android

如下是 骚年你的屏幕适配方式该升级了! 系列文章,欢迎转发以及分享:git

扫描或点击如下二维码,加入技术交流 QQ 群 455850365程序员

前言

这个月在 Android 技术圈中 屏幕适配 这个词曝光率挺高的,为何这么说呢?由于这个月陆续有多个大佬发布了屏幕适配相关的文章,公布了本身承认的屏幕适配方案github

上上个星期 Blankj 老师发表了一篇力挺今日头条屏幕适配方案的 文章,提出了不少优化的方案,并开源了相关源码bash

上个星期 拉丁吴 老师在 鸿神 的公众号上发布了一篇 文章,详细描述了市面上主流的几种屏幕适配方案,并发布了他的 smallestWidth 限定符适配方案和相关源码 (其实早就发布了),文章写的很好,建议你们去看看并发

其实你们最关注的不是市面上有多少种屏幕适配方案,而是本身的项目该选择哪一种屏幕适配方案,能够看出两位老师最终选择的屏幕适配方案都是不一样的app

我下面就来分析分析,我做为一个才接触这两个屏幕适配方案的吃瓜群众,我是怎么来验证这两种屏幕适配方案是否可行,以及怎样根据它们的优缺点来选择一个最适合本身项目的屏幕适配方案框架

这是我推荐给你们的屏幕适配框架,原本想放到最后做为福利的,惧怕你们看不到,因此就将连接放到这里,提早送给你们布局

Github : 您的 Star 是我坚持的动力 ✊post

浅谈适配方案

拉丁吴 老师的文章中谈到了两个比较经典的屏幕适配方案,在我印象中十分深入,我想大多数兄弟都用过,在个人开发生涯里也是有很长一段时间都在用这两种屏幕适配方案

第一种就是宽高限定符适配,什么是宽高限定符适配呢

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440
复制代码

就是这种,在资源文件下生成不一样分辨率的资源文件,而后在布局文件中引用对应的 dimens,你们必定还有印象

第二种就是 鸿神AndroidAutoLayout

这两种方案都已经逐渐退出了历史的舞台,为何想必你们都知道,不知道的建议看看 拉丁吴 老师的文章,因此这两种方案我在文章中就不在阐述了,主要讲讲如今最主流的两种屏幕适配方案,今日头条适配方案smallestWidth 限定符适配方案

建议你们不清楚这两个方案的先看看这两篇文章,才清楚我在讲什么,后面我要讲解它们的原理,以及验证这两种方案是否真的可行,最后对他们进行深刻对比,对于他们的一些缺点给予对应的解决方案,绝对干货

今日头条屏幕适配方案

原理

上面已经告知,不了解这两个方案的先看看上面的两篇文章,因此这里我就假设你们已经看了上面的文章或者以前就了解过这两个方案,因此在本文中我就再也不阐述 DPIDensity 以及一些比较基础的知识点,上面的文章已经阐述的够清楚了

今日头条屏幕适配方案的核心原理在于,根据如下公式算出 density

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

density 的意思就是 1 dp 占当前设备多少像素

为何要算出 density,这和屏幕适配有什么关系呢?

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;
    }
复制代码

你们都知道,无论你在布局文件中填写的是什么单位,最后都会被转化为 px,系统就是经过上面的方法,将你在项目中任何地方填写的单位都转换为 px

因此咱们经常使用的 pxdp 的公式 dp = px / density,就是根据上面的方法得来的,density 在公式的运算中扮演着相当重要的一步

要看懂下面的内容,还得明白,今日头条的适配方式,今日头条适配方案默认项目中只能以高或宽中的一个做为基准,进行适配,为何不像 AndroidAutoLayout 同样,高以高为基准,宽以宽为基准,同时进行适配呢

这就引出了一个如今比较棘手的问题,大部分市面上的 Android 设备的屏幕高宽比都不一致,特别是如今大量全面屏的问世,这个问题更加严重,不一样厂商推出的全面屏手机的屏幕高宽比均可能不一致

这时咱们只以高或宽其中的一个做为基准进行适配,就会有效的避免布局在高宽比不一致的屏幕上出现变形的问题

明白这个后,我再来讲说 densitydensity 在每一个设备上都是固定的,DPI / 160 = density屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度

  • 设备 1,屏幕宽度为 1080px480DPI,屏幕总 dp 宽度为 1080 / (480 / 160) = 360dp

  • 设备 2,屏幕宽度为 1440560DPI,屏幕总 dp 宽度为 1440 / (560 / 160) = 411dp

能够看到屏幕的总 dp 宽度在不一样的设备上是会变化的,可是咱们在布局中填写的 dp 值倒是固定不变的

这会致使什么呢?假设咱们布局中有一个 View 的宽度为 100dp,在设备 1 中 该 View 的宽度占整个屏幕宽度的 27.8% (100 / 360 = 0.278)

但在设备 2 中该 View 的宽度就只能占整个屏幕宽度的 24.3% (100 / 411 = 0.243),能够看到这个 View 在像素越高的屏幕上,dp 值虽然没变,可是与屏幕的实际比例却发生了较大的变化,因此肉眼的观看效果,会愈来愈小,这就致使了传统的填写 dp 的屏幕适配方式产生了较大的偏差

这时咱们要想完美适配,那就必须保证这个 View 在任何分辨率的屏幕上,与屏幕的比例都是相同的

这时咱们该怎么作呢?改变每一个 Viewdp 值?不现实,在每一个设备上都要经过代码动态计算 Viewdp 值,工做量太大

若是每一个 Viewdp 值是固定不变的,那咱们只要保证每一个设备的屏幕总 dp 宽度不变,就能保证每一个 View 在全部分辨率的屏幕上与屏幕的比例都保持不变,从而完成等比例适配,而且这个屏幕总 dp 宽度若是还能保证和设计图的宽度一致的话,那咱们在布局时就能够直接按照设计图上的尺寸填写 dp

屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度

在这个公式中咱们要保证 屏幕的总 dp 宽度设计图总宽度 一致,而且在全部分辨率的屏幕上都保持不变,咱们须要怎么作呢?屏幕的总 px 宽度 每一个设备都不一致,这个值是确定会变化的,这时今日头条的公式就派上用场了

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是同样的,只要 density 根据不一样的设备进行实时计算并做出改变,就能保证 设计图总宽度 不变,也就完成了适配

验证方案可行性

上面已经把原理分析的很清楚了,不少文章只是一笔带过这个公式,公式虽然很简单但咱们仍是想晓得这是怎么来的,因此我就反向推理了一遍,若是仍是看不懂,那我只能说我尽力了,原理讲完了,那咱们再来现场验证一下这个方案是否可行?

假设设计图总宽度为 375 dp,一个 View 在这个设计图上的尺寸是 50dp * 50dp,这个 View 的宽度占整个设计图宽度的 13.3% (50 / 375 = 0.133),那咱们就来验证下在使用今日头条屏幕适配方案的状况下,这个 View 与屏幕宽度的比例在分辨率不一样的设备上是否还能保持和设计图中的比例一致

验证设备 1

屏幕总宽度为 1080 px,根据今日头条的的公式求出 density1080 / 375 = 2.88 (density)

这个 50dp * 50dpView,系统最后会将高宽都换算成 px50dp * 2.88 = 144 px (根据公式 dp * density = px)

144 / 1080 = 0.133View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),因此完成了等比例缩放

某些设备总宽度为 1080 px,可是 DPI 可能不一样,是否会对今日头条适配方案产生影响?其实这个方案根本没有根据 DPI 求出 density,是根据本身的公式求出的 density,因此这对今日头条的方案没有影响

上面只能肯定在全部屏幕总宽度为 1080 px 的设备上能完成等比例适配,那咱们再来试试其余分辨率的设备

验证设备 2

屏幕总宽度为 1440 px,根据今日头条的的公式求出 density1440 / 375 = 3.84 (density)

这个 50dp * 50dpView,系统最后会将高宽都换算成 px50dp * 3.84 = 192 px (根据公式 dp * density = px)

192 / 1440 = 0.133View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),因此也完成了等比例缩放

两个不一样分辨率的设备都完成了等比例缩放,证实今日头条屏幕适配方案在不一样分辨率的设备上都是有效的,若是你们还心存疑虑,能够再试试其余分辨率的设备,其实到最后得出的比例不会有任何误差, 都是 0.133

优势

  1. 使用成本很是低,操做很是简单,使用该方案后在页面布局时不须要额外的代码和操做,这点能够说完虐其余屏幕适配方案

  2. 侵入性很是低,该方案和项目彻底解耦,在项目布局时不会依赖哪怕一行该方案的代码,并且使用的仍是 Android 官方的 API,意味着当你遇到什么问题没法解决,想切换为其余屏幕适配方案时,基本不须要更改以前的代码,整个切换过程几乎在瞬间完成,会少不少麻烦,节约不少时间,试错成本接近于 0

  3. 可适配三方库的控件和系统的控件(不止是是 ActivityFragmentDialogToast 等全部系统控件均可以适配),因为修改的 density 在整个项目中是全局的,因此只要一次修改,项目中的全部地方都会受益

  4. 不会有任何性能的损耗

缺点

暂时没发现其余什么很明显的缺点,已知的缺点有一个,那就是第三个优势,它既是这个方案的优势也一样是缺点,可是就这一个缺点也是很是致命的

只须要修改一次 density,项目中的全部地方都会自动适配,这个看似解放了双手,减小了不少操做,可是实际上反应了一个缺点,那就是只能一刀切的将整个项目进行适配,但适配范围是不可控的

这样不是很好吗?这样原本是很好的,可是应用到这个方案是就很差了,由于我上面的原理也分析了,这个方案依赖于设计图尺寸,可是项目中的系统控件、三方库控件、等非咱们项目自身设计的控件,它们的设计图尺寸并不会和咱们项目自身的设计图尺寸同样

当这个适配方案不分类型,将全部控件都强行使用咱们项目自身的设计图尺寸进行适配时,这时就会出现问题,当某个系统控件或三方库控件的设计图尺寸和和咱们项目自身的设计图尺寸差距很是大时,这个问题就越严重

举个栗子

假设一个三方库的 View,做者在设计时,把它设计为 100dp * 100dp,设计图的最大宽度为 1000dp,这个 View 在设计图中的比例是 100 / 1000 = 0.1,意思是这个 View 的宽度在设计图中占整个宽度的 10%,若是咱们要完成等比例适配,那这个三方库 View 在全部的设备上与屏幕的总宽度的比例,都必须保持在 10%

这时在一个使用今日头条屏幕适配方案的项目上,设置的设计图最大宽度若是是 1000dp,那这个三方库 View,与项目自身均可以完美的适配,但当咱们项目自身的设计图最大宽度不是 1000dp,是 500dp 时,100 / 500 = 0.2,能够看到,比例发生了较大的变化,从 10% 上升为 20%,明显这个三方库 View 高于做者的预期,比以前更大了

这就是两个设计图尺寸不一致致使的很是严重的问题,当两个设计图尺寸差距越大,那适配的效果也就天差万别了

解决方案

方案 1

调整设计图尺寸,由于三方库多是远程依赖的,没法修改源码,也就没法让三方库来适应咱们项目的设计图尺寸,因此只有咱们自身做出修改,去适应三方库的设计图尺寸,咱们将项目自身的设计图尺寸修改成这个三方库的设计图尺寸,就能完成项目自身和三方库的适配

这时项目的设计图尺寸修改了,因此项目布局文件中的 dp 值,也应该按照修改的设计图尺寸,按比例增减,保持与以前设计图中的比例不变

可是若是为了适配一个三方库修改整个项目的设计图尺寸,是很是不值得的,因此这个方案支持以 Activity 为单位修改设计图尺寸,至关于每一个 Activity 均可以自定义设计图尺寸,由于有些 Activity 不会使用三方库 View,也就不须要自定义尺寸,因此每一个 Activity 都有控制权的话,这也是最灵活的

但这也有个问题,当一个 Activity 使用了多个设计图尺寸不同的三方库 View,就会一样出现上面的问题,这也就只有把设计图改成与几个三方库比较折中的尺寸,才能勉强缓解这个问题

方案 2

第二个方案是最简单的,也是按 Activity 为单位,取消当前 Activity 的适配效果,改用其余的适配方案

使用中的问题

有些文章中提到了今日头条屏幕适配方案能够将设计图尺寸填写成以 px 为单位的宽度和高度,这样咱们在布局文件中,也就能直接填写设计图上标注的 px 值,省掉了将 px 换算为 dp 的时间 (大部分公司的设计图都只标注 px 值),并且照样能完美适配

可是我建议你们千万不要这样作,仍是老老实实的以 dp 为单位填写 dp 值,为何呢?

直接填写 px 虽然刚开始布局的时候很爽,可是这个坑就已经埋上了,会让你后面很爽,有哪些坑?

第一个坑

这样无疑于使项目强耦合于这个方案,当你遇到没法解决的问题想切换为其余屏幕适配方案的时候,layout 文件里曾经填写的 px 值都会做为 dp

好比你的设计图实际宽度为 1080px,你不换算为 360dp (1080 / 3 = 360),却直接将 1080px 做为这个方案的设计图尺寸,那你在 layout 文件中,填写的也都是设计图上标注的 px 值,可是单位倒是 dp

一个在设计图上 300px * 300pxView,你能够直接在 layout 文件中填写为 300dp,而因为这个方案能够动态改变 density 的缘由仍是能够作到等比例适配,很是爽!

但你不要忘了,这样你就强耦合于这个方案了,由于当你不使用这个方案时,density 是不可变的!

举个栗子

使用这个方案时,在屏幕宽度为 1080px 的设备上,将设计图宽度直接填写为 1080,根据今日头条公式

当前设备屏幕总宽度 / 设计图总宽度 = density

这时得出 density 为 1 (1080 / 1080 = 1),因此你在 layout 文件中你填写的 300dp 最后转换为 px 也是 300px (300dp * 1 = 300px 根据公式 dp * density = px)

在这个方案的帮助下很是完美,和设计图如出一辙完成了适配

但当你不使用这个方案时,density 的换算公式就变为官方的 DPI / 160 = density, 在这个屏幕宽度为 1080px480dpi 的设备上,density 就固定为 3 (480 / 160 = 3)

这时再来看看你以前在 layout 文件中填写的 dp,换算成 px900 px (300dp * 3 = 900px 根据公式 dp * density = px)

本来在在设计图上为 300pxView,这时却达到了惊人的 900px,3倍的差距,恭喜你,你已经强耦合于这个方案了,你要不全部 layout 文件都改一遍,要不继续使用这个方案

第二个坑

第二个坑其实就是刚刚在上面说的今日头条适配方案的缺点,当某个系统控件或三方库控件的设计图尺寸和和咱们项目自身的设计图尺寸差距很是大时,这个问题就越严重

你若是直接填写以 px 为设计图的尺寸,这不用想,确定和全部的三方库以及系统控件的设计图尺寸都不同,并且差距都很是之大,至少两三倍的差距,这时你在当前页面弹个 Toast 就能够明显看到,比以前小不少,能够说是天差万别,用其余三方库 View,也是同样的,会小不少

由于你以 px 为单位填写设计图尺寸,人家却用的 dp,差距能不大吗,你若是老老实实用 dp,哪怕三方库的设计图尺寸和你项目自身的设计图尺寸不同,那也差距不大,小到必定程度,基本都不用调整,能够忽略不计,并且不少三方库的设计图尺寸其实也都是那几个大众尺寸,很大可能和你项目自身的设计图尺寸同样

总结

能够看到我讲的很是详细,能够说比今日头条官方以及任何博客写的都清楚,从原理到优缺点再到解决方案包罗万象,由于篇幅有限,若是我还想把 smallestWidth 限定符适配方案写的这么详细,那估计这篇文章得有一万字了

因此我把此次的屏幕适配文章归位一个系列,一共分为三篇,第一篇详细的讲 今日头条屏幕适配方案,第二篇详细的讲 smallestWidth 限定符适配方案,第三篇详细讲两个方案的深刻对比以及如何选择,并发布我根据 今日头条屏幕适配方案 优化的屏幕适配框架 AndroidAutoSize

今日头条屏幕适配方案 官方公布的核心源码只有 30 行不到,但我这个框架的源码有 1500 行以上,在保留原有特性的状况下增长了很多功能和特性,功能增长了很多,可是使用上却变简单了

<manifest>
    <application>            
        <meta-data android:name="design_width_in_dp" android:value="360"/>
        <meta-data android:name="design_height_in_dp" android:value="640"/>           
     </application>           
</manifest>
复制代码

只要这一步填写了设计图的高宽以 dp 为单位,你什么都不作,框架就开始适配了

你们能够提早看看我是怎么封装和优化的,我后面的第三篇文章会给出这个框架的原理分析,敬请期待


关于你们的评论以及关注的问题,我在这里统一回复一下:

感谢,你们的关注和回复,我介绍这个今日头条的屏幕适配方案并非说他有多么完美,只是他确实有效并且能帮咱们减小不少开发成本

对于不少人说的 DPI 的存在,不就是为了让大屏能显示更多的内容,若是一个大屏手机和小屏手机,显示的内容都相同,那用户买大屏手机又有什么意义呢,我以为你们对 DPI 的理解是对的,这个观点我也是认同的,Google 设计 DPI 时可能也是这么想的,可是有一点你们没考虑到,Android 的碎片化太严重了

为何 Android 诞生这么多年,Android 的百分比库层出不穷,按理说他们都违背了上面说的这个理念,但为何还有这么多人去研究百分比库经过各类方式去实现百分比布局 (谷歌官方也曾出过百分比库)?为何?很简单,由于需求啊!为何需求,由于开发成本低啊!为何今日头条的这个屏幕适配方案如今能这么火,由于他的开发成本是目前全部屏幕适配方案中最低的啊!

DPI 的意义谁又不懂呢?难道就你懂,今日头条这种大公司的程序员不懂这个道理吗?今日头条这么大的公司难道不想把每一个机型每一个版本的设备都适配完美,让本身的 App 体验更好,哪怕付出更大的成本,有些时候想象是美好的,可是这个投入的成本,谁又能承担呢,连今日头条这么大的公司,这么雄厚的资本都没选择投入更大的成本,对每一个机型进行更精细化的适配,难道市面上的中小型公司又有这个能力投入这么大的成本吗?

鱼和熊掌不可兼得,DPI 的意义在 Google 的设计理念中是彻底正确的,但不是全部公司都能承受这个成本,想必今日头条的程序员,也是由于今日头条 App 的用户量足够多,机型分布足够广,也是被屏幕适配这个问题折磨的不要不要的,才想出这么个不这么完美可是却颇有效的方案

公众号

扫码关注个人公众号 JessYan,一块儿学习进步,若是框架有更新,我也会在公众号上第一时间通知你们


如下是 骚年你的屏幕适配方式该升级了! 系列文章,欢迎转发以及分享:


Hello 我叫 JessYan,若是您喜欢个人文章,能够在如下平台关注我

-- The end