《屏幕适配: Android屏幕适配简单且十分轻量级的解决方案(今日头条)》

问题

通常在写布局的时候,我们用相对布局、权重比来解决因为屏幕尺寸大小不一带来的控件摆放问题,而大控件的宽高度量使用dp、文字大小使用sp,通常情况下dp和sp是一样的、例如10dp和10sp在屏幕上显示效果一样,只不过当系统修改文字大小时,使用sp标量的控件都跟随系统发生变化。不论是dp还是sp,最终要在页面上渲染出来前都会被转成像素单位px。
1、哪为什么一开始不使用像素作为控件的宽高的单位呢?
2、dp和px换算关系呢?
3、同等dp在不同设备上能保证控件大小一致吗?

解决这些疑问前先来复习一下Android屏幕的几个概念。

概念

屏幕分辨率:1920*1080标识高上有1920个像素点、宽度上有1080个像素点

1080x1920

屏幕尺寸 屏幕对角线的长度(单位inch)

屏幕尺寸

屏幕像素密度dpi 计算公式

计算公式

dip为160,则刚好 1dp = 1px。

接下来就可以解决上诉提出的三个问题

1、dp和px换算关系
px = dp * (dip / 160)
安卓中定义了一个系数density
density = (dip / 160)
px = density * dp

2、假设屏幕大小相同,若屏幕分别率越高,那么在相同的区域内就得放下更多的像素点、意味着屏幕密度越大,像素点就得越小,反之,像素点就越大,密度越小,像素点就越大;所以同样画一段长度100px的线段在高分辨率下,看上去就比在低分辨率屏幕下短,所以在写布局时不会采取像素来度量控件宽高。

3、最后一个问题 “同等dp在不同设备上能保证控件大小一致吗?”
理想状态状态下是可以基本保持一致。但是安卓手机碎片化严重,有很多奇怪尺寸出现。通过对比,可以明显发现同样的相同的dp华为设备上UI显示比较粗大
由于屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱


我现在手上有两台pad设备,屏幕分辨率1920*1200,一台是华为C5pad,另一台是三星pad。三星是9寸多,华为是8寸多。

image.png
image.png

今日头条对此给出分析和解决方案,造成这样问题是UI设计假设按宽度360dp这一尺寸设计的,而有的设备实际上宽度比360dp还大,有的比360小。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况。

梳理需求
首先来梳理下我们的需求,一般我们设计图都是以固定的尺寸来设计的。比如以分辨率1920px * 1080px来设计,以density为3来标注,也就是屏幕其实是640dp * 360dp。如果我们想在所有设备上显示完全一致,其实是不现实的,因为屏幕高宽比不是固定的,16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了。但是通常下,我们只需要以宽或高一个维度去适配,比如我们Feed是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致,尤其不能存在某些设备上显示不全的情况。同时考虑到现在基本都是以dp为单位去做的适配,如果新的方案不支持dp,那么迁移成本也非常高。

因此,总结下大致需求如下:

支持以宽或者高一个维度去适配,保持该维度上和设计图一致;

支持dp和sp单位,控制迁移成本到最小。

解决突破口

从dp和px的转换公式 :px = dp * density

可以看出,如果设计图宽为360dp,想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,我们只能修改 density 的值

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

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

DisplayMetrics#density 就是上述的density

DisplayMetrics#densityDpi 就是上述的dpi

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

下面给出代码,只有一个方法,对dp进行修改。

//三星pad宽度1280dp (是dp,是不px) ,SCREEN_WIDTH_DP根据不同的设计图修改,手机一般是360
private final static int SCREEN_WIDTH_DP = 1280;
        private static float sNoncompatDensity;
    private static float sNoncompatScaleDensity;
    public static void setCusomDensity(final Activity activity,final Application application){
        final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0){
            sNoncompatDensity = appDisplayMetrics.density;
            sNoncompatScaleDensity = appDisplayMetrics.scaledDensity;
             //监听系统改变字体的大小
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig !=null &&newConfig.fontScale>0){
                        sNoncompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                        Log.e("tag","sNoncompatScaleDensity:"+sNoncompatScaleDensity);
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }

        //根据参考的适配宽度 计算新的Density、ScaleDensity、DensityDpi
        float targetDensity = (float) appDisplayMetrics.widthPixels/SCREEN_WIDTH_DP;
        float targetScaleDensity = (float)targetDensity*(sNoncompatScaleDensity/sNoncompatDensity);
        int targetDensityDpi = (int) (160*targetDensity);
        
        //修改全局的
        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //修改当前activity
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

在要在UI布局渲染前调用即可

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ScreanAdapterUtils.setCusomDensity(this,MyAplication.getApplication());
        setContentView(R.layout.activity_main);
}

今日头天适配方案https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA

问题

但是这个方法会导致一些比较大的对话框出现暂时问题,对话框不能全名展示。需要从新计算对话宽的宽度和高度,当然一些小对话的的展示不受影响。
直接给出代码

public static void dialogAdapter(WindowManager windowManager, Dialog dialog,float heightScale,float widthScale){

        Point point = new Point();

        //获得代表当前window属性的对象
        Window window = dialog.getWindow();
        WindowManager.LayoutParams params = window.getAttributes();


        //获取window的宽高信息
        Display display = windowManager.getDefaultDisplay();
        display.getSize(point);

        // 将设置后的大小赋值给window的宽高
        if (widthScale!=0){
            params.width = (int) (point.x * widthScale);
        }

        if (heightScale!=0){
            params.height = (int) (point.y * heightScale);
        }

        //设置属性
        window.setAttributes(params);
    }

在UI渲染之后调用


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

推荐阅读更多精彩内容