从源码的角度理解Px/Dp/Sp

Px、Dp和Sp是什么?

其实它们就是个度量单位,

Px表示像素点, 1px = 1像素

Dp和Sp,都表示和屏幕密度无关,不过后者只适用于字体大小。

举个例子,假设:

获取layout的宽度,返回的int数据代表像素值:

int layout_width = contxt.getResource().getDimension(R.dimen.layout_width);

对应dimens.xml文件中的数据为:

42dp

跟着源码来看是如何获取dp对应的像素值:

    /**
     * Retrieve a dimensional for a particular resource ID.  Unit 
     * conversions are based on the current {@link DisplayMetrics} associated
     * with the resources.
     * 
     * @param id The desired resource identifier, as generated by the aapt
     *           tool. This integer encodes the package, type, and resource
     *           entry. The value 0 is an invalid identifier.
     * 
     * @return Resource dimension value multiplied by the appropriate 
     * metric.
     * 
     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
     *
     * @see #getDimensionPixelOffset
     * @see #getDimensionPixelSize
     */
    public float getDimension(int id) throws NotFoundException {
        synchronized (mAccessLock) {
            TypedValue value = mTmpValue;
            if (value == null) {
                mTmpValue = value = new TypedValue();
            }
            getValue(id, value, true);
            if (value.type == TypedValue.TYPE_DIMENSION) {
                return TypedValue.complexToDimension(value.data, mMetrics);
            }
            throw new NotFoundException(
                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
                    + Integer.toHexString(value.type) + " is not valid");
        }
    }

​ 根据英文注释,这个方法,根据id值,返回对应的dimens数据。其中TypeValue是资源数据容器,主要用于Resource类中,主要成员变量有typestringdatadensity等等。

​ 在getValue方法中,由id值来初始化value,这是个jni方法,具体涉及到aapt的资源打包,此处不深入了。有兴趣的话,可以研究下老罗的这篇文章:

Android应用程序资源的编译和打包过程分析

​ getValue完事之后,对value的type进行判断,只有Dimension类型的数据才能调用下一步的方法,毕竟也不能保证误操作getDimension()方法中会不会传进一个字符id。

​ complexToDimension方法最后会调用到applyDimension方法,

public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

​ 其中,DisplayMetrics也是一个重要的数据容器,记录了重要的显示数据,比如屏幕宽度、高度、密度等。unit数据是从先前的TypeValue的data数据中获取,对了,再重申一遍,我们要返回的是像素值

​ 1.如果unit是PX,直接返回数值;
2.如果是DP,则将数值乘以屏幕像素密度。dp也叫dip(density independent pixel)直译为密度无关的像素。我们猜测如果使用了这个单位,在不同屏幕密度的设备上显示的长度就会是相同的。1dp在mdpi设备下等于1px,在hdpi设备下等于1.5px,同样的在xxhdpi设备下1dp=3px。因此推断出,metrics.density = px值/dp值。
不过设备的密度值是死的,它是一个比例因子,density = 像素值/160, mdpi的像素值为160dpi,因此它的density = 1,所以在mdpi下1dp = 1 px。

​ 因此上述例子中,在mdpi下最终获取的int layout_width = 42,hdpi下是63,以此类推。

​ 3.如果是SP,这个单位和字体大小有关,scaledDensity是系统字体比例因子,在魅族手机的简易模式中,该数值大于1,因此所有应用的字体都看起来很大。

​ 如果你不希望字体大小跟着scaledDensity走怎么办呢?
可以试着setTextSize传入:
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,size, metrics),
unit改为PX就好了,跟着像素走,缺点也是显而易见的,那就是不同屏幕密度,大小也会不一样,只能算一个规避简易模式这个大坑的不得已的处理手段。

​ 剩下的几个单位,PT/IN/ MM,基本用不到,暂时不追踪,等用到的时候再追。哈哈哈。

你可能感兴趣的:(从源码的角度理解Px/Dp/Sp)