Android單位換算

我們在開發(fā)過程中,一般使用xml進行界面布局的繪制,在設(shè)置界面布局寬高方面,Android提供了多種計量單位如dip,dp,px等,特別是dp,是一種根據(jù)屏幕尺寸按比例設(shè)置長度的單位。這為多屏幕開發(fā)提供了極大的便利。

但是,如果使用代碼進行寬高設(shè)置,只能傳入px單位的值,如果需要使用dp,需要將其轉(zhuǎn)換成px,再傳入值,這就涉及到了單位的換算。

1.單位的計算方法

(1)dp

dp指Dots Per Inch,意思是每英寸像素點數(shù)。dp與像素密度有關(guān),什么是像素密度呢,舉個例子,有一手機,它的屏幕物理尺寸寬高為1.5英寸 * 2.0英寸,屏幕分辨率為320*480。那么它的像素密度為320/1.5=160dpi或者480/2.0=160dpi,160dpi即為其像素密度。橫向和縱向的這個值都是相同的,原因是大部分手機屏幕使用正方形的像素點。

同一物理尺寸的設(shè)備的像素密度可能不同,例如同為4英寸手機,有320480分辨率或者800480分辨率。

安卓計算dp不直接通過像素密度,而是通過密度系數(shù)。在設(shè)備中,如果其像素密度為160dpi,1px=1dp,計算設(shè)備的像素密度與該標準像素密度獲得的比例,稱該比例為密度系數(shù)。

密度系數(shù) = 設(shè)備像素密度 / 160

Android系統(tǒng)定義了四種像素密度: 低(120dpi)、中(160dpi)、高(240dpi)、超高(320dpi),分別對應(yīng)的密度系數(shù)為0.75、1、1.5、2。

假如有一個80dp寬的圖片,那么它在320dpi像素密度的設(shè)備上,其寬度為80 * 2=160px。

在不同設(shè)備上,使用dp時,你會發(fā)現(xiàn)其顯示表現(xiàn)得差不多。

(2)px

即像素(Pixels),在Android設(shè)備里面,不建議使用px,因為其在不同設(shè)備的表現(xiàn)會差異很大。

(3)dip

設(shè)備無關(guān)像素,即Density Independent Pixels。該單位與dp是一個意思,以前的開發(fā)使用dip,后臺安卓改名為dp,此單位與dp的唯一區(qū)別是該單位僅支持三種像素密度:低(120dpi)、中(160dpi)、高(240dpi)。分別對應(yīng)的密度系數(shù)為0.75、1、1.5。

(4)sp

與縮放無關(guān)的抽象像素(Scale-independent Pixel)。

sp和dp很類似但唯一的區(qū)別是,Android系統(tǒng)允許用戶自定義文字尺寸大小(小、正常、大、超大等等),當文字尺寸是“正常”時1sp=1dp=0.00625英寸,而當文字尺寸是“大”或“超大”時,1sp>1dp=0.00625英寸。

類似我們在windows里調(diào)整字體尺寸以后的效果——窗口大小不變,只有文字大小改變。

最佳實踐,文字的尺寸一律用sp單位,非文字的尺寸一律使用dp單位。

偶爾需要使用px單位,例如需要在屏幕上畫一條細的分隔線。

2.單位計算的實現(xiàn)

參考以下代碼:

/**
 *  類名:DisplayUtil
 *  作用:單位換算工具
 *  說明:此處加0.5f是為了實現(xiàn)四舍五入。例如4.1和4.9,如果轉(zhuǎn)換為整型都為4,4.1 + 0.5 = 4.6轉(zhuǎn)換為整型為4,4.9 + 0.5 = 5.4轉(zhuǎn)換為整型為5
 */
     
public class DisplayUtil {    
    /**  
     * 將px值轉(zhuǎn)換為dip或dp值,保證尺寸大小不變  
     *   
     * @param pxValue  
     * @param scale DisplayMetrics類中屬性density
     * @return  
     */    
    public static int px2dip(float pxValue, float scale) {    
        return (int) (pxValue / scale + 0.5f);    
    }    
    
    /**  
     * 將dip或dp值轉(zhuǎn)換為px值,保證尺寸大小不變  
     *   
     * @param dipValue  
     * @param scale DisplayMetrics類中屬性density
     * @return  
     */    
    public static int dip2px(float dipValue, float scale) {    
        return (int) (dipValue * scale + 0.5f);    
    }    
    
    /**  
     * 將px值轉(zhuǎn)換為sp值,保證文字大小不變  
     *   
     * @param pxValue  
     * @param fontScale DisplayMetrics類中屬性scaledDensity
     * @return  
     */    
    public static int px2sp(float pxValue, float fontScale) {    
        return (int) (pxValue / fontScale + 0.5f);    
    }    
    
    /**  
     * 將sp值轉(zhuǎn)換為px值,保證文字大小不變  
     *   
     * @param spValue  
     * @param fontScale DisplayMetrics類中屬性scaledDensity
     * @return  
     */    
    public static int sp2px(float spValue, float fontScale) {    
        return (int) (spValue * fontScale + 0.5f);    
    }
    
    /**
     *  獲取設(shè)備屏幕密度系數(shù)方法一-用于dp與px轉(zhuǎn)換
     * @param context 組件上下文
     * @return 當前設(shè)備屏幕密度系數(shù)
     */
    public static float getScreenDensity(Context context) {
        try {
            DisplayMetrics dm = new DisplayMetrics();
    
            ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
                    .getMetrics(dm);
            return dm.density;
        } catch (Exception e) {
            return DisplayMetrics.DENSITY_DEFAULT;
        }
    }
}

/**
     *  獲取設(shè)備屏幕密度系數(shù)方法二-用于dp與px轉(zhuǎn)換
     * @param context 組件上下文
     * @return 當前設(shè)備屏幕密度系數(shù)
     */
    public static float getScreenDensity1(Context context) {
      return context.getResources().getDisplayMetrics().density;
    }

    /**
     *  獲取設(shè)備屏幕密度系數(shù)方法三-用于sp與px轉(zhuǎn)換
     * @param context 組件上下文
     * @return 當前設(shè)備屏幕密度系數(shù)
     */
    public static float getScreenDensity2(Context context) {
        try {
            DisplayMetrics dm = new DisplayMetrics();
    
            ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
                    .getMetrics(dm);
            return dm.scaledDensity;
        } catch (Exception e) {
            return DisplayMetrics.DENSITY_DEFAULT;
        }
    }
}

/**
     *  獲取設(shè)備屏幕密度系數(shù)方法四-用于sp與px轉(zhuǎn)換
     * @param context 組件上下文
     * @return 當前設(shè)備屏幕密度系數(shù)
     */
    public static float getScreenDensity3(Context context) {
      return context.getResources().getDisplayMetrics().scaledDensity;
    }
}       

參考文檔https://blog.csdn.net/qfanmingyiq/article/details/53018320

3.獲取尺寸的另外一種形式-讀取dimen.xml

事實上,除了上述通過代碼去獲取dp對應(yīng)的px值,還有另外一種獲取方法,那就是將dp值定義在dimen.xml中,然后通過可以通過三種方法獲取獲取尺寸值

1.getResource().getDimension(dimensionId)

此方法返回dimen.xml中對應(yīng)dimensionId的尺寸值,如果該尺寸不是px值,那么它將先把尺寸值轉(zhuǎn)換為px,再該值返回來,該方法返回的是一個float值。

2.getResource().getDimensionSize(dimensionId)

同第一種,但是該方法返回的是int值,并且會將小數(shù)部分四舍五入。

3.getResource().getDimensionOffset(dimensionId)

同第一種,返回的是int值,但是小數(shù)部分不會進行四舍五入,即僅僅進行float強制轉(zhuǎn)換為int。
很明顯,使用該種方式獲取尺寸會更好一些,但是由于在dimen.xml定義的值都是靜態(tài)值,因此靈活性稍微差些,如果尺寸值會經(jīng)常變化,那么應(yīng)該使用代碼進行單位。尺寸值較為固定,那么讀取dimen.xml的值即可。

4.TypedValue類的使用;

我們在設(shè)置字體大小時,一般通過setTextSize(float value),該方法默認使用sp作為傳入字體的單位,setTextSize(float value)方法的源碼如下:

/**
     * Set the default text size to the given value, interpreted as "scaled
     * pixel" units.  This size is adjusted based on the current density and
     * user font size preference.
     *
     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
     *
     * @param size The scaled pixel size.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    @android.view.RemotableViewMethod
    public void setTextSize(float size) {
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }

從源碼可以看出,setTextSize(float value)內(nèi)部實際上是調(diào)用其重載的方法setTextSize(int unit, float value)。其中unit代表傳入值使用的單位。而setTextSize(float value)方法,傳入的單位為TypedValue.COMPLEX_UNIT_SP,其代表的意思即是sp單位。但是這個TypedValue.COMPLEX_UNIT_SP僅僅是一個int的靜態(tài)常量,只能作為一個標志,那其內(nèi)部到底怎樣進行單位轉(zhuǎn)換的呢?
我們可以繼續(xù)深入查看setTextSize(int unit, float value)方法的源碼:

/**
     * Set the default text size to a given unit and value. See {@link
     * TypedValue} for the possible dimension units.
     *
     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
     *
     * @param unit The desired dimension unit.
     * @param size The desired size in the given units.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    public void setTextSize(int unit, float size) {
        if (!isAutoSizeEnabled()) {
            setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
        }
    }

setTextSize(int unit, float value)方法內(nèi)部依舊很簡單,大致意思是如果沒有設(shè)置字體大小,那么就調(diào)用setTextSizeInternal(int unit, float size, boolean shouldRequestLayout);
方法。那么,我們繼續(xù)看setTextSizeInternal(int unit, float size, boolean shouldRequestLayout);
這個方法:

private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
        Context c = getContext();
        Resources r;

        if (c == null) {
            r = Resources.getSystem();
        } else {
            r = c.getResources();
        }

        setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
                shouldRequestLayout);
}

可以看出,該方法獲取了TextView的Context上下文,然后通過context的getResource()獲取Resource類的實例,最后獲取Resource類實例中的DisplayMestrics,并其作為參數(shù)與unit和size一同傳入,TypedValue.applyDimension(int unit, int size, DisplayMetrics metrics)方法中。看到這里,是否會覺得有點熟悉?沒錯,這跟我們上面討論的DisplayUtils類中獲取密度系數(shù)的方法二或四原理是一樣的。那么,TypedValue.applyDimension(int unit, int size, DisplayMetrics metrics)究竟做了什么呢?我們看看該方法是怎么寫的:

/**
     * Converts an unpacked complex data value holding a dimension to its final floating 
     * point value. The two parameters <var>unit</var> and <var>value</var>
     * are as in {@link #TYPE_DIMENSION}.
     *  
     * @param unit The unit to convert from.
     * @param value The value to apply the unit to.
     * @param metrics Current display metrics to use in the conversion -- 
     *                supplies display density and scaling information.
     * 
     * @return The complex floating point value multiplied by the appropriate 
     * metrics depending on its unit. 
     */
    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;
    }

這樣就十分明顯了,實際上TextView字體大小單位的轉(zhuǎn)換原理與上面的DisplayUtils類是一樣的,都是通過密度系數(shù)進行單位換算。不同的是,除了sp、dp與px之間的轉(zhuǎn)換, TypedValue類提供了更多單位的換算方法。
但是,從代碼中可以看出,返回的結(jié)果為float類型,并沒有對運算的結(jié)果進行四舍五入。
既然TypedValue.applyDimension(int unit, float value,
DisplayMetrics metrics)可以進行單位換算,那么,我們就可以將其運用到開發(fā)中了。例如,dp轉(zhuǎn)換為px:

float pxSize = TypedValue.applyDimension(Typed.COPLEX_UNIT_DIP, 
15f, context.getResource().getDisplayMetrics());

如果要轉(zhuǎn)換為int并進行四舍五入,那么轉(zhuǎn)換為int之前,就要將換算結(jié)果加上0.5:

float pxSize = TypedValue.applyDimension(Typed.COPLEX_UNIT_DIP, 
15f, context.getResource().getDisplayMetrics());
int pxSizeInt = (int)(pxSize  + 0.5f);

本篇文章僅個人理解,如有偏差,歡迎各位指正。另外貼出我參考的兩篇大神的博客:

  1. px、dp和sp,這些單位有什么區(qū)別?
  2. 在安卓代碼中dp 和 sp 換算px
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容