1、基礎(chǔ)概念
屏幕尺寸
- 含義:指的屏幕對(duì)角線的物理長(zhǎng)度,單位一般采用英寸(1英寸≈2.53cm)
目前市面常見的有5.0、5.5、5.7等尺寸。
屏幕分辨率
- 含義:屏幕縱橫向的像素?cái)?shù)量,一般描述成屏幕的"寬x高”=AxB
- 例子:1080x1920,即寬度方向上有1080個(gè)像素點(diǎn),在高度方向上有1920個(gè)像素點(diǎn)
- Android手機(jī)常見的分辨率:320x480、480x800、720x1280、1080x1920等等
屏幕像素密度
- 含義:每英寸長(zhǎng)度包含的像素點(diǎn)數(shù)
- 單位:dpi(dots per ich)
- 例子:假設(shè)設(shè)備內(nèi)每英寸有160個(gè)像素,那么該設(shè)備的屏幕像素密度=160dpi,這個(gè)160dpi在安卓中,也被當(dāng)做一個(gè)基準(zhǔn)的屏幕像素密度,此情況下1dp=1px,我們常用的px和dp互相轉(zhuǎn),用到的一個(gè)安卓api中的邏輯密度density在160dpi的時(shí)候,density=1,同理如果是320dpi,density=2;以此類推。
- desity = 當(dāng)前屏幕像素密度/160
- **dp = px/desity **
/**
* px轉(zhuǎn)dp
*
* @param context
* @param pxValue
* @return
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
-
安卓常用的像素密度
image.png
dp 介紹
- 含義:density-independent pixel,叫dp或dip,與終端上的實(shí)際物理像素點(diǎn)無關(guān)。
- 例子:場(chǎng)景:假如同樣都是畫一條長(zhǎng)度是屏幕一半的線,如果使用px作為計(jì)量單位,那么在240dpi手機(jī)上設(shè)置應(yīng)為240px;在160dpi的手機(jī)上應(yīng)設(shè)置為160px,二者設(shè)置就不同了;如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長(zhǎng)度。
sp 介紹
- 含義:scale-independent pixels,與dp類似,但是可以根據(jù)文字大小首選項(xiàng)進(jìn)行放縮,是設(shè)置字體大小的御用單位。
- sp 需要注意的事項(xiàng)
1、當(dāng)修改系統(tǒng)字體大小時(shí),字體大小以dp為單位時(shí),大小不變;
-
2、當(dāng)修改系統(tǒng)字體大小時(shí),字體大小以sp為單位時(shí),大小跟隨變化;
image.png- 為何會(huì)有這種差異性。
參考文章關(guān)于設(shè)置文字大小,最終會(huì)調(diào)用下面這個(gè)方法:
- 為何會(huì)有這種差異性。
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP://------->>dp 基于 density
return value * metrics.density;
case COMPLEX_UNIT_SP://------->>sp 基于 scaledDensity
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;
}
從代碼上看 dp 和 sp的差別就是 density 和 scaleDensity的區(qū)別,關(guān)于這兩個(gè)字段在源碼中可以看到注釋scaledDensity會(huì)收到用戶配置的影響,而density是基于屏幕dpi的,不會(huì)受到用戶配置影響。
這也就是為什么設(shè)置sp單位時(shí)字體大小會(huì)受到用戶配置系統(tǒng)字體的影響.
關(guān)于字體大小到底什么時(shí)候用dp 什么時(shí)候用sp
- 使用sp會(huì)鎖著系統(tǒng)文字的變化而呈現(xiàn)正相關(guān)變化,這個(gè)設(shè)計(jì)是符合安卓最初設(shè)計(jì)思想的,比如一些眼睛不好的人,可能就會(huì)把字體放大來看,如果app上的文字沒有跟著變大,這可能就會(huì)影響用戶體驗(yàn)。
- 簡(jiǎn)單測(cè)試了下騰訊系的很多app多數(shù)頁(yè)面都不會(huì)跟隨系統(tǒng)字體大小變化,知乎會(huì)受到系統(tǒng)字體的影響。
- 其中使用apktool簡(jiǎn)單查看了下qq的一些布局文件和dimens,發(fā)現(xiàn)字體大小,dp、sp都有引用。
- 還有就是如果使用sp 寫的話,一些注意事項(xiàng)。
- 首先布局高度盡量不要寫死,可以通過設(shè)置padding或者margin來控制高度,避免文字變大時(shí)候?qū)е碌恼故静蝗默F(xiàn)象。
- 相對(duì)來說對(duì)于這種適配,相對(duì)布局體驗(yàn)會(huì)更好一些,可以定位控件之間的相對(duì)關(guān)系,避免遮擋問題。
貼幾張圖:(系統(tǒng)設(shè)置文字變大)
比如下面的父控件限死高度的,導(dǎo)致展示不全、文字過大自動(dòng)換行、沒有設(shè)置相關(guān)關(guān)系導(dǎo)致的重疊。
整體來說需要根據(jù)實(shí)際需求具體調(diào)整,盡量在保證業(yè)務(wù)邏輯正常的情況下,調(diào)優(yōu)展示效果。

屏幕尺寸大小、分辨率、像素密度之間的關(guān)系:
<span id="jump1">像素密度計(jì)算方法</span>
實(shí)際像素密度算法

計(jì)算結(jié)果不一致?
- 問題:為什么按照上面算法獲取的dpi值,和api方法獲取的dpi不一致?
比如我的手機(jī)360n5,在開發(fā)時(shí)我使用DisplayMetrics獲取手機(jī)的densityDpi,這個(gè)densityDpi的大小為480,但是我在官網(wǎng)看見的卻是PPI為401。他的分辨率為1080*1920,屏幕大小為5.5inch,按照上面的算法計(jì)算的話,大約就是401,因此可以肯定sdk上獲取的dpi應(yīng)該不是手機(jī)的真實(shí)dpi,那么android的dpi的計(jì)算方式又是怎樣的? - dpi 和 ppi到底有何區(qū)別
DPI(dots per inch)和 PPI(pixels per inch)這兩個(gè)措辭的差別,表面上看來只在于是在談「dot」還是「pixel」。
* 但實(shí)際上 dot 可以指半調(diào)印刷的墨點(diǎn),可以指噴墨打印的墨點(diǎn),可以指掃描儀的采樣點(diǎn),可以指數(shù)字圖像的最小單位(即 pixel),可以指屏幕的物理像素,可以指操作系統(tǒng)的抽象像素……在不同的語境下可以指不同的概念。
* 而 pixel 也可以指數(shù)字圖像的數(shù)據(jù) pixel,可以指屏幕物理像素,也可以指代操作系統(tǒng)的抽象像素……在不同語境下的意義也不同。
* 兩者經(jīng)?;煊茫躁P(guān)于dpi和ppi的區(qū)別,在不同的用途上面,意思也不同。
結(jié)論:在安卓機(jī)上面,我們可以理解ppi就是安卓機(jī)的真實(shí)像素密度,而dpi則是系統(tǒng)的一個(gè)內(nèi)置的值,它接近真實(shí)值,但并不是真實(shí)的像素密度,它的存在就是為了換算dp、sp的 。
- 安卓的dpi進(jìn)一步解釋
- Android系統(tǒng)目錄 /system/build.prop 里面有一行 ro.sf.lcd_density=480,這個(gè)就是dpi,這個(gè)值是可以變的,改變后通過sdk api拿到的dpi值就變化了,所以覺得dpi是個(gè)定義值,同一款設(shè)備,最大分辨率已經(jīng)定了,而dpi其實(shí)并不是一定的,但是這個(gè)dpi決定了我們dp、sp和px的換算關(guān)系,這點(diǎn)很重要,也是屏幕適配的關(guān)鍵。其實(shí),每部安卓手機(jī)屏幕都有一個(gè)初始的固定密度,這些數(shù)值是120、160、240、320、480,這些就是android為不同設(shè)備設(shè)定的系統(tǒng)密度。 得到實(shí)際密度以后,一般會(huì)選擇一個(gè)最近的密度作為系統(tǒng)密度,系統(tǒng)密度是出廠預(yù)置的,如440dpi的系統(tǒng)密度就是和它最接近的480dpi;如果是330dpi的設(shè)備,它的系統(tǒng)密度就是320dpi。但是,現(xiàn)在很多手機(jī)不一定會(huì)選擇這些值作為系統(tǒng)密度,而是選擇實(shí)際的dpi作為系統(tǒng)密度,這就導(dǎo)致了很多手機(jī)的dpi也不是在這些值內(nèi)。例如小米Note這樣的xxhdpi的設(shè)備他的系統(tǒng)密度并不是480,而是它的實(shí)際密度440。
- dp到px的轉(zhuǎn)換公式:px = dp * (dpi / 160)
2、適配方案
布局適配
多套布局
針對(duì)不同的屏幕尺寸,如果差異過大的話,就需要考慮多套布局的方式來處理
- 首先需要針對(duì)不同屏幕寫布局
- 然后需要使用尺寸限定符
-
尺寸限定符相關(guān)簡(jiǎn)要介紹:
-
限定符示例:
image.png 如圖示上面同名文件夾后面標(biāo)注都屬于限定符
限定符使用方法:只需要用橫線加限定符的方式即可使用,xx-限定符。
- 常見限定符:
限定符(mdpi,tvdpi,hdpi)可以幫助我們判斷屏幕密度 限定符(land,port)可以幫助我們區(qū)分屏幕橫豎屏狀態(tài) 限定符(en,fr…)可以幫助我們語言和地區(qū) 限定符(v3,v4…)可以幫助我們區(qū)分安卓版本 等等具體參考文章:http://blog.csdn.net/wzy_1988/article/details/52932875
- 關(guān)于屏幕適配我們需要重點(diǎn)關(guān)注的限定符
- sw(n)dp:(最窄邊限制符,不受屏幕方向的影響)
屏幕最小尺寸限定符:就是屏幕可用區(qū)域的最小尺寸,是指屏幕可用高度或?qū)挾鹊淖钚≈?,例?如果你的布局在運(yùn)行時(shí)需要的屏幕最窄邊是600dp,則你可以利用這個(gè)限定符創(chuàng)建布局資源目錄res/layout-sw600dp.只有當(dāng)屏幕的最小寬度或最小高度大于等于600dp時(shí),系統(tǒng)才會(huì)使用這些布局文件或者資源文件 - w(n)dp:(屏幕最小寬度限定符,受到屏幕方向的影響)
指定資源使用時(shí)需要的最小寬度.當(dāng)屏幕方向發(fā)生變化時(shí),系統(tǒng)會(huì)調(diào)整這個(gè)值,使其始終為你UI顯示的寬度.
- sw(n)dp:(最窄邊限制符,不受屏幕方向的影響)
- 怎么計(jì)算限定符應(yīng)該是多少dp
- 計(jì)算方法 dp = px/(當(dāng)前屏幕像素密度/160 )
- 范例:
- 密度:480 dp / xxhdpi / 3.0x 屏幕分辨率:1080 x 1920 px 屏幕尺寸:2.8" x 5.0" / 5.7 英寸
- 1080px /(480/160) = 360dp,所以對(duì)應(yīng)文件夾后綴應(yīng)該是xxx-w360dp
-
-
定義布局時(shí)候注意事項(xiàng)
- 多使用相對(duì)關(guān)系定義控件之間的關(guān)聯(lián)
- 使用"wrap_content"、"match_parent"和"weight“來控制視圖組件的寬度和高度
- 布局中引用的dp sp一定使用引用關(guān)系,便于后期可能存在的針對(duì)性修改。
一般提前生成一套dimens文件,例如0-400dp,0-30sp,方便使用。
dp sp 適配
dp sp能夠讓同一數(shù)值在不同的分辨率展示出大致相同的尺寸大小。但是當(dāng)設(shè)備差異較大的時(shí)候,就無能為力了。適配的問題還需要我們自己去做,一般是生成帶標(biāo)識(shí)符的多套dimens文件。
- 適配方案一:dp方法:{"320","360", "384", "400", "411", "533", "640", "720", "768", "820"};
- 缺點(diǎn):如果沒有默認(rèn)的dimens.xml,那就黃昏依斜陽(yáng)了,還好有提供。
- 優(yōu)點(diǎn):沒有枚舉全部的item,可以省一些apk空間;有字體sp的適配。
- 適配方案二:px百分比方法
- 優(yōu)點(diǎn):針對(duì)性適配效果更精確,體驗(yàn)更好。
- 缺點(diǎn):屏幕px的種類遠(yuǎn)多于dp的種類,文件數(shù)量多;程序for循環(huán)枚舉item項(xiàng),有部分用不上的px項(xiàng);width和height都適配了,給控件寫width尺寸時(shí)要用dimens_x.xml里面的變量值,寫height尺寸時(shí)需要用dimens_y.xml里面的變量值。
圖片適配
使用點(diǎn)九圖
- 9patch圖片的作用就是在圖片拉伸的時(shí)候保證其不會(huì)失真,讓圖片在指定的位置拉伸和在指定的位置顯示內(nèi)容,這樣圖片的邊邊角角就不會(huì)出現(xiàn)失真了。
范例:
image.png
具體參考:點(diǎn)九圖的制作
多套圖片
- 傳統(tǒng)方式切多套圖片,放在對(duì)應(yīng)標(biāo)識(shí)符的文件夾里面(缺點(diǎn):會(huì)導(dǎo)致apk包過大)
- 只放一套xhdpi的圖(Android會(huì)根據(jù)屏幕密度自動(dòng)選擇對(duì)應(yīng)的資源文件進(jìn)行渲染加載)
比如說,SDK檢測(cè)到你手機(jī)的分辨率是320x480(dpi=160),會(huì)優(yōu)先到drawable-mdpi文件夾下找對(duì)應(yīng)的圖片資源;但假設(shè)你只在xhpdi文件夾下有對(duì)應(yīng)的圖片資源文件(mdpi文件夾是空的),那么SDK會(huì)去xhpdi文件夾找到相應(yīng)的圖片資源文件,然后將原有大像素的圖片自動(dòng)縮放成小像素的圖片,于是大像素的圖片照樣可以在小像素分辨率的手機(jī)上正常顯示。
具體請(qǐng)看http://blog.csdn.net/xiebudong/article/details/37040263
所以理論上來說只需要提供一種分辨率規(guī)格的圖片資源就可以了。
一般選擇xhdpi的,這一套可以適配市面上大多數(shù)手機(jī),向下縮放,向上擴(kuò)展表現(xiàn)都不會(huì)有太大問題。
注意事項(xiàng):
- 通過apktool簡(jiǎn)單分析幾個(gè)知名的app,看看別人到底使用了幾套。
- 微信基本都是xxh為主了
- 拼多多還是xh xxh都有
- 隨著安卓設(shè)備的發(fā)展,分辨率越來越高,可能后期xxh更為合適
- 可以同時(shí)放入xxh的圖標(biāo),避免后期加入的麻煩。
- 為了防止包體過大,打包時(shí)候可以臨時(shí)刪除xxh文件夾
- 也可以推廣期一套圖片資源,用戶主動(dòng)升級(jí)時(shí)候可以升級(jí)攜帶xxh資源,體驗(yàn)可能更好一些。
代碼動(dòng)態(tài)適配
某些布局要求嚴(yán)格寬高比的地方,可以考慮代碼的動(dòng)態(tài)設(shè)置寬高;或者為了保持美觀又防止過高的一些pop、dialog也需要代碼動(dòng)態(tài)按照當(dāng)前屏幕比例動(dòng)態(tài)設(shè)置寬高。
- 范例
view.getLayoutParams().height = Utils.getRealHeight(mContext, 720, 300);
/**
* 獲取控件準(zhǔn)確的高度(針對(duì)滿屏的情況)
*
* @param context
* @param width 寬度(可以是相對(duì)值,僅僅用來計(jì)算寬高比例)
* @param height 高度(可以是相對(duì)值,僅僅用來計(jì)算寬高比例)
* @return 真正的高度
*/
public static int getRealHeight(Context context, int width, int height) {
//寬高比
float aspectRatio = (float) width / (float) height;
return (int) (Utils.getScreenWidth(context) / aspectRatio);
}
/**
* 根據(jù)當(dāng)前寬度基準(zhǔn)算真實(shí)高度
*
* @param context
* @param height 寬度基準(zhǔn)下的高度
* @param totalWidth 寬度基準(zhǔn)
* @return
*/
public static int getRealHeightWithBenchmark(Context context, float height, float totalWidth) {
return (int) ((Utils.getScreenWidth(context) / totalWidth) * height);
}
- 范例2:(針對(duì)自適應(yīng)的一些布局,防止過高情況)
//布局渲染完成后回調(diào)設(shè)置,防止獲取不到寬高度
ViewTreeObserver vto = mRecyclerView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mRecyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (mRecyclerView.getHeight() > Utils.getScreenHeight(mContext) / 2) {
mRecyclerView.getLayoutParams().height = Utils.getScreenHeight(mContext) / 2;
}
}
});
3、實(shí)際適配過程
參考文章:http://blog.csdn.net/wangwangli6/article/details/63258270



