我們在開發(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);
本篇文章僅個人理解,如有偏差,歡迎各位指正。另外貼出我參考的兩篇大神的博客: