美文网首页
从源码的角度理解Px/Dp/Sp

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

作者: hmz | 来源:发表于2016-05-11 20:18 被阅读85次

    Px、Dp和Sp是什么?

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

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

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

    举个例子,假设:

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

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

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

    <dimen name="layout_width">42dp</dimen>
    

    跟着源码来看是如何获取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

          本文链接:https://www.haomeiwen.com/subject/raxjlttx.html