Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >今日头条屏幕适配方案落地研究

今日头条屏幕适配方案落地研究

作者头像
终端研发部
修改于 2019-06-06 02:01:28
修改于 2019-06-06 02:01:28
1.6K0
举报
文章被收录于专栏:终端研发部终端研发部

原文作者:但,我知道,链接: https://www.cnblogs.com/haichao/p/10020893.html

目录

前言

大家好,现在给大家推荐一种极低版本的 Android 屏幕适配方案,就是今日头条适配方案,“极低成本”这四个字正是今日头条的适配文章标题。

众所周知,安卓的屏幕碎片化极其严重,适配一直是从事安卓开发人员十分头疼的事情。前期,由于公司支持的平板款式单一,只需要做几款平板的适配即可,选用了 smalledtWidth(最小宽度)适配,但是这个方案在增加新屏幕时且原 dimens 文件无法很好适配时,就需要增加新屏幕的最小宽度 dimens 文件了,比较麻烦而且会增加项目大小(虽然只是几个文件),而且这种屏幕适配极度依赖设备的屏幕密度,叫density。为了讲解更清楚,这里需要引入几个公式:

px = density * dp dp : 安卓开发人员常常挂在嘴上的长度单位 px : 设计人员眼中的长度单位 density = dpi / 160 因此,px = dp * (dpi/160) dpi : 根据屏幕真实分辨率和尺寸计算得出 举个例子:屏幕分辨率为 1920 * 1080,屏幕尺寸为5寸(屏幕斜边长度cm/0.3937), 则 dpi = √(宽度²+ 高度²)/屏幕尺寸

因此,屏幕密度至关重要,屏幕密度怎么来的?厂商写入一个 system/build.prop 文件,有时还会写错,就我们一款华为平板,获取的屏幕密度是2,但是手工测量并按公式得到实际屏幕密度是1.56。导致我们的适配方案在那款平板就失效了。

本人一直在寻找可以一劳永逸的屏幕适配方案,今日头条是选定基准分辨率,基于设备屏幕分辨率计算出新的屏幕密度进行适配,保证所有设备的显示效果一致,完美避开上面那款设备的问题。推荐给大家。

各平板数据比较

首先,我详细记录了公司主流设备的参数,新方案肯定要对主流设备都能完美适配,这才是入门门槛。

三星N5100-4.1

三星p355c-6.0(基准)

华为-8.0

真实宽度(px)

800

768

1200

真实高度(px)

1280

1024

1852

原始 density

1.33125

1.0

2.0(不准,实际1.56 )

new density

1.04166

-

1.5625

new height(px)

1066

-

1600

可以看到横向是几种设备,竖向是一些参数,其中中英文混杂,这是为什么呢?这是我故意的,中文是设备原始参数,英文是根据今日头条方案原理计算的。因为,今日头条的目的是所有设备的显示效果一致。但是设备的分辨率是不同的,怎么显示一致呢?简单述之,就是缩放,按宽度缩放的。可能有人会有疑问,缩放后的效果图放不下,显示不完整怎么办?

我们看看上面的数据,可以看到按照三星6.0基准进行缩放,效果图在三星4.1这款设备宽度上的显示,是按768乘以new density ,也就是 1.04166 进行放大,不用按计算器了,就是800px,完美适配。那么高度呢,1024 也乘以 new density,发现是1066px,比实际高度像素值 1280px 小,不会出现显示不全的现象。可能有人会问了,这不是多出来了么,会不会留空白啊?对,好问题,所以合格的开发在竖向布局上增加自适应权重,以应对这种情况。当然,横向也需要考虑自适应权重。

同理,可得知效果图在华为8.0设备的宽度像素是 1600px, 也比实际设备宽度 1852px 小,也能显示完全。

为什么看起来更小了?(头条方案跟最小宽度方案比较)

对的,跟原先的比起来,是更小了,包括图片更小,文字更小。这是为什么呢?且听我细细道来... ...

大家都知道,安卓有 mdpi、hdpi、xhdpi后缀的文件,具体使用有 drawable-mdpi、drawable-hdpi,或者mipmap-mdpi、mipmap-hdpi, 又或者 values-mdpi、values-hdpi, 这些都是安卓自带的屏幕适配方案,只是不太好用吗,经常出问题。那么,这些文件都是怎么使用的呢,这又涉及到了屏幕密度这个属性,关联如下:

dpi

屏幕密度

drawable-ldpi

0.75

drawable-mdpi

1(baseline)

drawable-hdpi

1.5

drawable-xhdpi

2

drawable-xxhdpi

3

drawable-xxxdpi

4

  1. 平板A 三星平板5100 的屏幕密度是1.33125,大于mdpi,小于hdpi,向上取整,所以属于hdpi
  2. 平板B 三星平板P355C 的屏幕密度是1,属于mdpi
  3. ldpi:mdpi:hdpi:xhdpi:xxhdpi:xxxdpi = 0.75:1:1.5:2:3:4 = 3:4:6:8:12:16
  4. 上述比值乘以12,就是 36:48:72:144:192,刚好就是icon尺寸
  5. 我们会看到,最小宽度适配方案,values-hdpi 的值是 values-mdpi 的值乘以 0.8
0.8 的参数
  1. 宽高100dp的正方形图片,平板A会显示100px,平板B会乘以1.5,显示成150px,导致偏大
  2. 由于平板B的屏幕密度是 1.33125, 最好 显示成 100* 1.33125
  3. 1.33125/ 1.5 = 0.8875 约为 0.8
sw600dp-dpi
  1. sw : small width,就是最小宽度是600dp,
  2. px -> dp : dp = px / density
  3. 平板A: 800 /1.33125 = 600.93
  4. 平板B: 768/1 = 768 上述两个平板,一个是600dp,一个是768dp,都是大于600dp,平板A使用sw600dp-hdpi,平板B使用sw600dp-mdpi
最后称述

平板A、B 同时显示一个 100px 的图片:

  1. 按最小宽度适配:100 1.5 0.8 = 120 ,图片会显示成 120px
  2. 按今日头条适配: 100 * 1.04166 = 104.166,图片会显示成 104.166 px
  3. 所以今日头条方案显示的图片就更小了。

那么,哪个更好呢?我们再来看看一个极端,显示一个 平板B 的填满宽度的图片, 768px:

  1. 按最小宽度适配:768px 1.5 0.8 = 921.6px ,图片会显示成 921.6px, 远远超出平板A的尺寸,此时开发人员需要手动干预
  2. 按今日头条适配: 768px * 1.04166 = 799.99488,图片可以看成显示成 800 px
  3. 优点很明显,布局更简单

严谨的你,可能会问了,那显示超过768px呢?

不好意思,我们的基准就是 768,不会超过他了。

smallesWidth 方案迁移

我们原项目使用的是 smallestWidth 方案,经试验迁移代价很低,经研究有如下两个方案。

  1. 删除所有适配 smallestWidth 的dimens 文件夹,只保留dp 值是1:1 的 dimens 文件即可;
  2. 不想删除亦可,将所有的 dimens 文件都覆盖成 dp 值是1:1 的 dimens 文件即可

优缺点

优点
  1. 使用成本非常低,操作非常简单,使用该方案无需增加dimens 文件,修改代码,完虐其他屏幕适配方案
  2. 侵入性非常低,切换几乎瞬间完成,试错成本接近为0
  3. 修改的 density 是全局的,一次修改,终生受益。
  4. 不会有任何性能的损耗
  5. 今日头条 大厂保证

缺点

1、 第三方布局库, 未按项目效果图布局,全局修改 density 导致修改第三方布局,造成显示界面问题

2、与 smallestwith 适配方案不兼容,切换回来比较麻烦

issue

一个 Bitmap 的density 问题

在某处,开启今日头条适配方案,全局修改屏幕密度,获取 ImageView 的 Bitmap 的宽高,发现获取的宽高和实际的宽高(布局出来观察)不一致。经查阅源码,发现 Bitmap 也有一个 density, 怀疑未被修改。

随决定,修改 sDefaultDensity 值,查阅代码,发现 sDefaultDensity 是静态私有,于是召唤反射大法

测试 Ok, 收工。

附录(适配核心代码)

  • initAppDensity 方法 Application 调用,记录默认屏幕密度
  • setDefault 和 setOrientation 方法 Activity 调用,设置新的屏幕密度
  • resetAppOrientation 方法,恢复屏幕密度
代码语言:txt
AI代码解释
复制
// * ================================================
    // * 本框架核心原理来自于 <a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">今日头条官方适配方案</a>
    // * <p>
    // * 本框架源码的注释都很详细, 欢迎阅读学习
    // * <p>
    // * 任何方案都不可能完美, 在成本和收益中做出取舍, 选择出最适合自己的方案即可, 在没有更好的方案出来之前, 只有继续忍耐它的不完美, 或者自己作出改变
    // * 既然选择, 就不要抱怨, 感谢 今日头条技术团队 和 张鸿洋 等人对 Android 屏幕适配领域的的贡献
    // * <p>
    // * ================================================
    // */

    private static final int WIDTH = 1;
    private static final int HEIGHT = 2;
    private static final float DEFAULT_WIDTH = 768f; //默认宽度
    private static final float DEFAULT_HEIGHT = 1024f; //默认高度
    private static float appDensity;
    /**
     * 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
     */
    private static float appScaledDensity;
    /**
     * 状态栏高度
     */
    private static int barHeight;
    private static DisplayMetrics appDisplayMetrics;
    private static float densityScale = 1.0f;

    /**
     * application 层调用,存储默认屏幕密度
     *
     * @param application application
     */
    public static void initAppDensity(@NonNull final Application application) {
        //获取application的DisplayMetrics
        appDisplayMetrics = application.getResources().getDisplayMetrics();
        //获取状态栏高度
        barHeight = getStatusBarHeight(application);
        if (appDensity == 0) {
            //初始化的时候赋值
            appDensity = appDisplayMetrics.density;
            appScaledDensity = appDisplayMetrics.scaledDensity;

            //添加字体变化的监听
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    //字体改变后,将appScaledDensity重新赋值
                    if (newConfig != null && newConfig.fontScale > 0) {
                        appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {
                }
            });
        }
    }

    /**
     * 此方法在BaseActivity中做初始化(如果不封装BaseActivity的话,直接用下面那个方法就好了)
     *
     * @param activity activity
     */
    public static void setDefault(Activity activity) {
        setAppOrientation(activity, WIDTH);
    }

    /**
     * 比如页面是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,
     * 再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致
     *
     * @param activity    activity
     * @param orientation WIDTH HEIGHT
     */
    public static void setOrientation(Activity activity, int orientation) {
        setAppOrientation(activity, orientation);
    }

    /**
     * 重设屏幕密度
     *
     * @param activity    activity
     * @param orientation WIDTH 宽,HEIGHT 高
     */
    private static void setAppOrientation(@NonNull Activity activity, int orientation) {

        float targetDensity;

        if (orientation == HEIGHT) {
            targetDensity = (appDisplayMetrics.heightPixels - barHeight) / DEFAULT_HEIGHT;
        } else {
            targetDensity = appDisplayMetrics.widthPixels / DEFAULT_WIDTH;
        }

        float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
        int targetDensityDpi = (int) (160 * targetDensity);
        // 最后在这里将修改过后的值赋给系统参数,只修改Activity的density值
        DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;

        densityScale = appDensity / targetDensity;
        setBitmapDefaultDensity(activityDisplayMetrics.densityDpi);
    }

    /**
     * 重置屏幕密度
     *
     * @param activity activity
     */
    public static void resetAppOrientation(@NonNull Activity activity) {
        DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = appDensity;
        activityDisplayMetrics.scaledDensity = appScaledDensity;
        activityDisplayMetrics.densityDpi = (int) (appDensity * 160);

        densityScale = 1.0f;
        setBitmapDefaultDensity(activityDisplayMetrics.densityDpi);
    }

    /**
     * 获取状态栏高度
     *
     * @param context context
     * @return 状态栏高度
     */
    private static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    /**
     * 设置 Bitmap 的默认屏幕密度
     * 由于 Bitmap 的屏幕密度是读取配置的,导致修改未被启用
     * 所有,放射方式强行修改
     * @param defaultDensity 屏幕密度
     */
    private static void setBitmapDefaultDensity(int defaultDensity) {
        //获取单个变量的值
        Class clazz;
        try {
            clazz = Class.forName("android.graphics.Bitmap");
            Field field = clazz.getDeclaredField("sDefaultDensity");
            field.setAccessible(true);
            field.set(null, defaultDensity);
            field.setAccessible(false);
        } catch (ClassNotFoundException e) {
        } catch (NoSuchFieldException e) {
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 屏幕密度缩放系数
     *
     * @return 屏幕密度缩放系数
     */
    public static float getDensityScale() {
        return densityScale;
    }

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android全面的屏幕适配方案解析(四)__今日头条适配方案
之前三篇把屏幕适配概念梳理了还讲解了列举的其中四种适配方案,还没有看过的童鞋可以先参考这三篇: Android全面的屏幕适配方案解析(一)__屏幕适配概念梳理 Android全面的屏幕适配方案解析(二)__宽高限定符屏幕适配 Android全面的屏幕适配方案解析(三)__sw限定符适配方案 Android全面的屏幕适配方案解析(四)__今日头条适配方案
SoullessCoder
2022/03/23
1.6K0
Android全面的屏幕适配方案解析(四)__今日头条适配方案
骚年你的屏幕适配方式该升级了!-今日头条适配方案
原文地址: https://www.jianshu.com/p/55e0fca23b4f
用户2965681
2018/09/13
7660
如何让一套代码完美适配各种屏幕?
区别于iOS,android设备有不同的分辨率大小以及不同厂商的系统,目前市场的分辨率可以看下相关统计。
CRMEB商城源码
2022/07/19
1.3K0
Android 屏幕适配从未如此简单
一个月前看了今日头条新的屏幕适配方案,对此不禁拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码即可适配,如今最新 1.18.0 版 AndroidUtilCode:https://github.com/Blankj/AndroidUtilCode 已有其适配方案,其相关函数在 ScreenUtils 中,相关 API 如下所示:
用户1269200
2018/08/14
8860
Android 屏幕适配从未如此简单
一种非常简单的Android屏幕适配方案
作为一个Android开发人员,你还在为了适配各种尺寸的屏幕而苦恼吗?你还在为了出现一个新的机型而修改着数不尽的dimens和layout吗?你还在为了UI给的奇葩尺寸的设计图而绞尽奶汁计算距离吗?如果你为了这些事情而苦恼,那么看完这篇文章,希望可以帮你减少开发时间,减缓生命的流逝速度。。。
Android技术干货分享
2019/03/27
6830
一种非常简单的Android屏幕适配方案
Android智能平板应用,界面适配的另一种轻量级方法
各种设备种类和尺寸那么多,基于一种原型设计好的界面,换到另一种设备上去若不适配全乱套了。好在还是有很多方案的,这减少了不少的开发工作量。最流行的就是头条的方案了,使用也超级简单。然而,如果不想引入,还可以简单的一个工具类实现,原理类似于头条的方案。
杨永贞
2022/04/13
8940
Android智能平板应用,界面适配的另一种轻量级方法
骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案
原文地址: https://www.jianshu.com/p/2aded8bb6ede
用户2965681
2018/09/19
9700
骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案
今日头条屏幕适配方案终极版正式发布!前言方案对比AndroidAutoSize总结
我在前面两篇文章中详细介绍了 今日头条适配方案 和 SmallestWidth 限定符适配方案 的原理,并验证了它们的可行性,以及总结了它们各自的优缺点,可以说这两个方案都是目前比较优秀、比较主流的 Android 屏幕适配方案,而且它们都已经拥有了一定的用户基数
用户2965681
2018/11/23
3.6K0
Android适配全面总结(一)----屏幕适配
版权声明:本文为博主原创文章(部分引用他人博文,已加上引用说明),未经博主允许不得转载。https://www.jianshu.com/p/7aa34434ad4d
AWeiLoveAndroid
2018/09/03
2.4K0
Android适配全面总结(一)----屏幕适配
谈谈Android屏幕适配的那些事,我们到底该怎么去选择
每个Android程序员都会遇见一个棘手的问题,那就是手机适配。因为现在出现了许多分辨率的手机,所以我们必须得考虑到各种分辨率的手机适配,这对于程序员来说是一个必须要解决的麻烦。
Android技术干货分享
2019/03/26
1.1K0
谈谈Android屏幕适配的那些事,我们到底该怎么去选择
AndroidAutoSize今日头条适配方案[通俗易懂]
最近公司没有什么事,闲来无聊,就研究一下今日头条的适配方案,不看不知道,真是一看吓一跳,快速上手而且还简单易懂,
全栈程序员站长
2022/08/18
4K0
AndroidAutoSize今日头条适配方案[通俗易懂]
AndroidAutoSize开源库屏幕适配分析[通俗易懂]
https://github.com/JessYanCoding/AndroidAutoSize
全栈程序员站长
2022/09/06
5K0
Android技能树 — 屏幕适配小结
关于屏幕适配,几乎每隔一段时间就会看见有人发出来说XXX方案,实现超级简单的适配方式等等。所以我把我目前了解过的常用的适配方案做个总结,并简单说说原理,从而让大家也初步了解各个方案的实现。(其实很多人都是看见别人写的适配方案,虽然可能实际在使用了,但是却从来没有去了解过这个方案的原理,而且遇到一些简单的坑的时候,因为不知道原理,也无法自己解决。)
青蛙要fly
2018/08/29
9040
Android技能树 — 屏幕适配小结
Android屏幕适配很难嘛?其实也就那么回事
前言 作为一个Android开发人员,你还在为了适配各种尺寸的屏幕而苦恼吗?你还在为了出现一个新的机型而修改着数不尽的dimens和layout吗?你还在为了UI给的奇葩尺寸的设计图而绞尽奶汁计算距离
Android技术干货分享
2019/03/27
3490
Android屏幕适配很难嘛?其实也就那么回事
Android全面的屏幕适配方案解析(三)
之前两篇把屏幕适配概念梳理了还讲解了dp适配方案、宽高限定符适配方案,还没有看过的童鞋可以先参考这两篇: Android全面的屏幕适配方案解析(一) Android全面的屏幕适配方案解析(二)
SoullessCoder
2022/01/20
5430
Android全面的屏幕适配方案解析(三)
Android TV开发总结【适配】
Android 的屏幕适配是指适配不同机顶盒 UI 框架层输出的分辨率和 dpi,而不是适配不同分辨率的电视机(电视机的适配交由机顶盒本身完成,和各个应用无关)
先知先觉
2019/01/21
4.2K0
实用Android 屏幕适配方案分享
真正可用,并且简单易行,可以在多个屏幕大小和屏幕密度上有良好表现的Android 屏幕适配方案,已用在一款成熟互联网应用中,效果还不错。 说起android开发,UI界面的多机型适配,一向是个很重要
xiangzhihong
2018/01/30
1.3K0
实用Android 屏幕适配方案分享
Android 屏幕适配之框架(AndroidAutoSize)(今日头条)适配
https://github.com/JessYanCoding/AndroidAutoSize
全栈程序员站长
2022/09/05
1.7K0
Android 屏幕适配之框架(AndroidAutoSize)(今日头条)适配
提示Android屏幕适配方案分析
Android开发过程中我们常用的尺寸单位有px、dp,还有一种sp一般是用于字体的大小。但是由于px是像素单位,比如我们通常说的手机分辨例如1920*1080都是px的单位。现在Android屏幕分辨率碎片化720x1280、1080x1920、2280x1080,这就造成例如187px会在各个分辨率的机型上都是显示一样大小的,那肯定不是我们想要的效果,所以用px单位我们是难以达到适配效果的,那么为什么用dp可以呢?
用户2356368
2019/05/15
1.2K0
提示Android屏幕适配方案分析
Android开发:最全面、最易懂的Android屏幕适配解决方案
给你带来一种全新、全面而逻辑清晰的Android屏幕适配思路,只要你认真阅读,保证你能解决Android的屏幕适配问题!
Carson.Ho
2019/02/22
3.3K0
推荐阅读
相关推荐
Android全面的屏幕适配方案解析(四)__今日头条适配方案
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档