Android全面的屏幕适配方案解析(四)__今日头条适配方案

之前三篇把屏幕适配概念梳理了还讲解了列举的其中四种适配方案,还没有看过的童鞋可以先参考这三篇:
Android全面的屏幕适配方案解析(一)__屏幕适配概念梳理
Android全面的屏幕适配方案解析(二)__宽高限定符屏幕适配
Android全面的屏幕适配方案解析(三)__sw限定符适配方案
Android全面的屏幕适配方案解析(四)__今日头条适配方案

下面列举常用的适配方案:

  • dp适配方案
  • 宽高限定符适配方案
  • AndroidAutoLayout适配方案
  • sw限定符适配方案
  • 今日头条适配方案
  • AndroidAutoSize适配方案

这里还是有必要重申一下,有些过时的适配方案这里还讲解啊,只能说每种适配方案都会有各自的优缺点,从最原始的适配方案讲起,才能更好的理解为啥会衍生出各种适配方案,话不多说,下面继续讲解。

今日头条适配方案

今日头条适配方案原理解析

今日头条适配方案原理在于通过公式density = 设备真实宽度(单位px)/设计图总宽度(单位dp),在确保设计图总宽度(单位dp)一定时,通过修改density值,确保所有不同尺寸分辨率设备计算出的真实宽度值正好是屏幕宽度,这样就能达到适配所有设备的目的啦。

举个例子:比如UI设计稿总宽度为360dp,这里有两台不同尺寸分辨率的设备:

设备1:分辨率1080x1920,dpi为480,正常情况下计算density=dpi/160=480/160=3,此时屏幕总宽度dp=px/density=1080/3=360;

设备2:分辨率1440x2560,dpi为560,正常情况下计算desity=dpi/160=560/160=3.5,此时屏幕总宽度dp=px/density=1440/3.5=411;

正常情况下density 在每个设备上都是固定的,那要是我们想确保设计稿总宽度360不变,再来看看density值:

设备1:分辨率1080x1920,dpi为480,计算density = 设备真实宽度(单位px)/设计图总宽度(单位dp) = 1080/360 = 3,此时屏幕总宽度dp=px/density=1080/3=360;

设备2:分辨率1440x2560,dpi为560,计算density = 设备真实宽度(单位px)/设计图总宽度(单位dp) = 1440/360 = 4,此时屏幕总宽度dp=px/density=1440/4=360;

通过对比可以看出,修改density值,确实是能确保不同分辨率设备总宽度值始终是360dp,这样就能保证UI在不同的设备上显示效果是一致的。

今日头条适配方案推导过程

通过上面例子我们知道需要修改density值,那下面我们就来看看怎么修改系统的density值:
首先我们得知道无论在布局文件中填写的是什么样单位的值,比如10dp、10sp、10px等等,最后都会被系统转换成px,这个时候有童鞋就问了,你怎么知道啊?当然是通过布局文件中dp转换源码知道的啦。

通过源码可以知道,dp转换最终都是调用系统工具类Typedvalue类中的applyDimension方法进行转换的:

      /**
     * @param unit 要转换的单位
     * @param value 单位对应的值
     * @param metrics 显示指标
     */
    public static float applyDimension(int unit, float value,DisplayMetrics metrics){
        switch (unit) {
        case COMPLEX_UNIT_PX://单位为px
            return value;
        case COMPLEX_UNIT_DIP://单位为dp
            return value * metrics.density;
        case COMPLEX_UNIT_SP://单位为sp
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT://单位为pt
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN://单位为in
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM://单位为mm
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

系统就是通过上面的方法,将你在项目中任何地方填写的单位都转换为px,在applyDimension方法中有个参数DisplayMetrics,我们来看看这个参数的含义:

 @param metrics Current display metrics to use in the conversion -- supplies display density and scaling information.

这句话的意思是转换中使用的当前显示指标,提供显示密度和缩放信息。通过applyDimension方法中dp转换:

public static float applyDimension(int unit, float value,DisplayMetrics metrics){
        switch (unit) {
        ......
        case COMPLEX_UNIT_DIP://单位为dp
            return value * metrics.density;
        ......
    }

可以发现有我们需要的density值,通过Ctrl键追踪位置发现这个值是系统工具类DisplayMetrics类的成员变量,如图所示:

而DisplayMetrics实例可以通过系统资源文件Resources类中的getDisplayMetrics方法获得,系统资源文件Resouces也可以通过Activity或者Application的Context获得:

DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();

然后就可以通过DisplayMetrics实例,修改density值啦,这里需要注意有个scaledDensity变量的影响,这个变量是字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值,防止用户调节了系统字体。

今日头条适配的最终方案:

这里是以设计图总宽度360dp来适配,接下来只需要把我们计算好的 density 在系统中修改下即可,代码实现如下:

    /**
     * 今日头条适配方案
     *
     * @param activity
     * @param application
     */
    public static void setCustomDensity(Activity activity, final Application application) {
        //通过资源文件getResources类获取DisplayMetrics
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            //保存之前density值
            sNoncompatDensity = appDisplayMetrics.density;
            //保存之前scaledDensity值,scaledDensity为字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
            sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            //监听设备系统字体切换
            application.registerComponentCallbacks(new ComponentCallbacks() {

                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        //调节系统字体大小后改变的值
                        sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                public void onLowMemory() {

                }
            });
        }

        //获取以设计图总宽度360dp下的density值
        float targetDensity = appDisplayMetrics.widthPixels / 360;
        //通过计算之前scaledDensity和density的比获得scaledDensity值
        float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
        //获取以设计图总宽度360dp下的dpi值
        int targetDensityDpi = (int) (160 * targetDensity);
        //设置系统density值
        appDisplayMetrics.density = targetDensity;
        //设置系统scaledDensity值
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        //设置系统densityDpi值
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //获取当前activity的DisplayMetrics
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        //设置当前activity的density值
        activityDisplayMetrics.density = targetDensity;
        //设置当前activity的scaledDensity值
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        //设置当前activity的densityDpi值
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

上面代码是今日头条的最终方案,为了方便阅读,我只是基于个人理解做了标注解析,不对的地方请大佬们指正。

今日头条适配步骤:

下面来看验证一下这种适配方案的可行性。

1、布局文件,这里沿用之前的布局,只有一张宽高为150x150(dp)的图片,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

2、这里将今日头条适配方案封装成了工具类,如下所示:

public class CustomDensityUtil {
    private static float sNoncompatDensity;
    private static float sNoncompatScaledDensity;

    /**
     * 今日头条适配方案
     *
     * @param activity
     * @param application
     */
    public static void setCustomDensity(Activity activity, final Application application) {
        //通过资源文件getResources类获取DisplayMetrics
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            //保存之前density值
            sNoncompatDensity = appDisplayMetrics.density;
            //保存之前scaledDensity值,scaledDensity为字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
            sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            //监听设备系统字体切换
            application.registerComponentCallbacks(new ComponentCallbacks() {

                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        //调节系统字体大小后改变的值
                        sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                public void onLowMemory() {

                }
            });
        }

        //获取以设计图总宽度360dp下的density值
        float targetDensity = appDisplayMetrics.widthPixels / 360;
        //通过计算之前scaledDensity和density的比获得scaledDensity值
        float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
        //获取以设计图总宽度360dp下的dpi值
        int targetDensityDpi = (int) (160 * targetDensity);
        //设置系统density值
        appDisplayMetrics.density = targetDensity;
        //设置系统scaledDensity值
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        //设置系统densityDpi值
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //获取当前activity的DisplayMetrics
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        //设置当前activity的density值
        activityDisplayMetrics.density = targetDensity;
        //设置当前activity的scaledDensity值
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        //设置当前activity的densityDpi值
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }
}

3、在Activity中的onCreate方法中调用,如下所示:

CustomDensityUtil.setCustomDensity(MainActivity.this, getApplication());

到这里就完成了,是不是挺简单的呢,下面来看看在分辨率:480x800、720x1280、1080x1920的测试显示效果图:

再来看一下没有使用今日头条适配方案之前不同手机的测试对比效果:

根据适配前后的对比效果还是挺明显的。

今日头条适配方案优点

1、侵入性很低,而且没有涉及私有API,该方案与项目完全解耦,今日头条大厂在使用,稳定性有保证。

2、使用成本非常低,操作简单方便。

3、接入没有任何的性能损耗,使用的都是系统API。

今日头条适配方案缺点

1、只需要修改一次 density,项目中的所有地方都会自动适配,这个看似解放了双手,减少了很多操作,但是实际上反映了一个缺点,那就是只能一刀切的将整个项目进行适配,但适配范围是不可控的。比如项目中使用了第三方库控件等不是我们项目自身设计的控件,这时就会出现和我们项目自身的设计图尺寸差距非常大的问题。

2、使用过程中需要进行registerComponentCallbacks监听内容文字的大小改变情况,解决退出应用修改文字大小后,会出现文字大小不改变的情况。

AndroidAutoSize适配方案

所谓的AndroidAutoSize适配方案其实就是今日头条适配方案的升级版,是基于今日头条适配方案进行拓展的开源库,该库在很大程度上解决了今日头条适配方案的缺点,使用方式也比较简单,只需要填写设计图尺寸这一步即可接入项目,还支持对Activity、Fragment进行取消适配,灵活性会更强。

下面还是根据实例来讲解集成过程:

1、根目录build.gradle添加:

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2、添加该适配框架依赖

 implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'

3、在项目中的AndroidManifest配置文件注明设计稿的尺寸,这里测试以360x640为例:

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

如果只是想使用AndroidAutoSize适配方案的基础功能,AndroidAutoSize框架的使用方法在这里就结束了,只需要上面这一步,即可帮助你以最简单的方式接入AndroidAutoSize适配框架,我这里只做适配方案有效性演示,需要拓展需求的请参考下面Github地址有详细介绍。

4、测试布局文件非常的简单,只设置了图片,为了突出跟今日头条适配方案测试结果不同,这里设置图片宽高为120x120(dp),测试布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

我们来看看这个实例在不同手机的测试对比效果图:

再来看一下没有使用AndroidAutoSize适配方案之前不同手机的测试对比效果:

根据适配前后的对比效果还是挺明显的,AndroidAutoSize适配方案是根据今日头条屏幕适配方案官方公布的 30 行不到的代码,经过不断的优化和扩展,发展成了现在拥有将近20个类文件,上千行代码的全面性屏幕适配框架,在迭代的过程中完善和优化了很多功能,相比今日头条屏幕适配方案官方公布的原始代码,AndroidAutoSize适配方案更加稳定、更加易用、更加强大,感兴趣的可以去阅读源码,注释非常详细,这里就不做过多的介绍啦,点击进入源码地址

以上六种适配方案就全部讲解完了,适配框架因为原作者已经讲解的很清楚了,我这里就只讲解了基本用法,其它几种都尽可能讲解的详细一点,我这里讲解并没有说哪种方案更好或者更坏,每个项目的需求都不一样,适合的才是最好的。欢迎关注公众号【龙旋】能获取最新更新内容哦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容