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

问题

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

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

概念

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

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

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

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

屏幕像素密度dpi 计算公式

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

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的值非常乱

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

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

《《屏幕适配: Android屏幕适配简单且十分轻量级的解决方案(今日头条)》》 image.png
《《屏幕适配: Android屏幕适配简单且十分轻量级的解决方案(今日头条)》》 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渲染之后调用

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

    原文作者:Oneminute
    原文地址: https://www.jianshu.com/p/256dcbdeba11
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞