Android屏幕适配—最易懂的今日头条适配方案解析

屏幕参数基础概念

硬件/物理参数
物理属性
名称定义单位
尺寸对角线的长度inch,约等于2.54厘米
分辨率屏幕方向上的像素个数pixel,个
像素密度单位英寸上的像素个数ppi,point per inch

其中:


此处应该是height^2 + width^2 但是CSDN不支持此类型的maekdown我就不改了)

日常中我们一般听到或者用到的就是屏幕的尺寸(XX手机6.5寸大屏blabla),指的就是手机屏幕对角线的长度

其次是分辨率,1080P(1920x1080)、2k(2560x1440)、4k(4096×2160),其中的数字指的是屏幕两个方向上的像素个数(为什么不说是垂直水平两个方向?因为你能竖屏看,也能横屏看 😃

软件概念
软件概念
名称定义
px像素点
dp即dip,像素密度
sp字体的单位,同dp,但是可以根据系统的设置来改变
dpi每单位英寸的像素数,也叫做屏幕密度
density密度限定符,表示当前屏幕的密度水平

其中关系如下:

为什么会出现屏幕不适配的问题

Android的画面编码,控件的宽高等属性通常使用的单位都是dp

举个例子:屏幕分辨率为:1920*1080,屏幕尺寸为5寸的话,那么dpi为440。

假设我们UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况。

而且上述屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱,没有规律可循,从而导致使用dp适配效果差强人意。

今日头条的屏幕适配方案

今日头条技术团队给出了一个比较完善的解决方案:

因为android中的dp在渲染前会将dp转为px,

所以从dp和px的转换公式入手:

在这里插入图片描述

而density又是通过dpi而来

在这里插入图片描述

px是最终的显示效果,dp是我们按照设计图纸进行布局的单位,所以我们就拿density/dpi开刀

通过阅读源码,我们可以得知,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources.getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得。

先来熟悉下 DisplayMetrics 中和适配相关的几个变量:

  • DisplayMetrics.density就是上述的density
  • DisplayMetrics.densityDpi 就是上述的dpi
  • DisplayMetrics.scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值

动手

获取当前的Denisty
DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
计算目标Denisty
// 以设计DP为360为例,density = px / dp
final float targetDensity = appDisplayMetrics.widthPixels / 360.0;
//计算对应比例大小的字体大小,一般scaledDensity与density相同,防止用户在系统设置调整字体大小的情况
final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density);
//dpi = density * 160
final int targetDensityDpi = (int)(targetDensity * 160);
加上设置成为完全体
    private static void setCustomDensity(Application application, Activity activity) {
        final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
    
        // 以设计DP为360为例,获得目标Density
        final float targetDensity = appDisplayMetrics.widthPixels / 360.0;
        final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density);
        final int targetDensityDpi = (int)(targetDensity * 160);

        //设置application的Density
        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaledDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //设置activity的Density
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

一般的应用场景就是在BaseActivity的onCreate方法中调用setCustomDensity(),即可达成全局适配

目前看来是没问题了,但是还有一个场景:如果用户在运行期间在系统设置例修改了字体,再返回了app,此时app无感知,所以字体没有变化,所以我们还应该监听一下字体变化,同时调整ScaledDensity

究极体

    private static float aNoncompatDensity;
    private static float aNoncompatScaledDensity;

    private static void setCustomDensity(Application application, Activity activity) {

        final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (aNoncompatDensity == 0) {
            aNoncompatDensity = appDisplayMetrics.density;
            aNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(@NonNull Configuration newConfig) {
                    if (newConfig.fontScale > 0) {
                        aNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }


        final float targetDensity = appDisplayMetrics.widthPixels / 360.0;
        final float targetScaledDensity = targetDensity * (aNoncompatScaledDensity/ aNoncompatDensity);
        final int targetDensityDpi = (int)(targetDensity * 160);

        //设置application的Density
        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaledDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //设置activity的Density
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

贫穷的我并没有多台设备可以测试适配效果,所以引用一下今日头条技术团队的测试截图:

在这里插入图片描述
在这里插入图片描述

这种适配方式侵入性极低,效果稳定,短短几十行代码就可以完成适配问题,简直不要太简单

挖个坑,有空在谈谈其他几种屏幕适配方法以及之间的优缺点比较

参考:《今日头条:一种极低成本的Android屏幕适配方式》

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值