一、為什么要適配
由于Android系統的開放性,任何用戶、開發者、硬件廠商、運營商都可以對Android系統和硬件進行定制,修改成他們想要的樣子。 那么這種“碎片化”到達什么程度呢?
以上每一個矩形都代表一種機型,且它們屏幕尺寸、屏幕分辨率大相徑庭。隨著Android設備的增多,設備碎片化、系統碎片化、屏幕尺寸碎片化、屏幕碎片化的程度也在不斷加深。
備注:
- Android系統碎片化:基于Google原生系統,小米定制的MIUI、魅族定制的flyme、華為定制的EMUI等等;
- Android機型屏幕尺寸碎片化:5寸、5.5寸、6寸等等;
- Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920等;
當Android系統、屏幕尺寸、屏幕密度出現碎片化的時候,就很容易出現同一元素在不同手機上顯示不同的問題。試想一下這么一個場景: 為4.3寸屏幕準備的UI設計圖,運行在5.0寸的屏幕上,很可能在右側和下側存在大量的空白;而5.0寸的UI設計圖運行到4.3寸的設備上,很可能顯示不下。
為了保證用戶獲得一致的用戶體驗效果,使得某一元素在Android不同尺寸、不同分辨率的、不同系統的手機上具備相同的顯示效果,能夠保持界面上的效果一致,我們需要對各種手機屏幕進行適配!
二、基本概念
1、像素(px):
含義:通常所說的像素,就是CCD/CMOS上光電感應元件的數量,一個感光元件經過感光,光電信號轉換,A/D轉換等步驟以后,在輸出的照片上就形成一個點,我們如果把影像放大數倍,會發現這些連續色調其實是由許多色彩相近的小方點所組成,這些小方點就是構成影像的最小單位“像素”(Pixel)。簡而言之,像素就是手機屏幕的最小構成單元。
單位:px(pixel),1px = 1像素點 一般情況下UI設計師的設計圖會以px/dp作為統一的計量單位。
2、分辨率:
含義:手機在橫向、縱向上的像素點數總和 一般描述成 寬*高 ,即橫向像素點個數 * 縱向像素點個數(如1080 x 1920)。
單位:px(pixel),1px = 1像素點
3、屏幕尺寸(inch):
含義:手機對角線的物理尺寸
單位 英寸(inch),一英寸大約2.54cm 常見的尺寸有4.7寸、5寸、5.5寸、6寸
4、屏幕像素密度(dpi):
含義:每英寸長所占的像素點數。 例如每英寸內有160個像素點,則其像素密度為160dpi。
單位:dpi(dots per inch)
計算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
標準屏幕像素密度(mdpi): 每英寸長度上還有160個像素點(160dpi),即稱為標準屏幕像素密度(mdpi)。
至于為什么標準像素密度要設置為160?
簡單說就是為了可以讓像素取整。 下面是具體解釋
實際開發當中,我們經常需要對這幾個尺寸進行相互轉換(比如先在某個分辨率下完成設計,然后縮放到其他尺寸微調后輸出),一般按照 dpi 之間的比例即 2:1.5:1:0.75 來給界面中的元素來進行尺寸定義。
也就是說如果以 160 dpi 作為基準的話,只要尺寸的 DP 是 4 的公倍數,XHDPI 下乘以 2,HDPI 下乘以 1.5,LDPI 下乘以 0.75 即可滿足所有尺寸下都是整數 pixel 。
但假設以 240 dpi 作為標準,那需要 DP 是 3 的公倍數,XHDPI 下乘以 1.333,MDPI 下乘以 0.666 ,LDPI 下除以 2
而以 LDPI 和 XHDPI 為基準就更復雜了,所以選擇 160 dpi
密度類型 | 代表的分辨率(PX) | 屏幕像素密度(DPI) |
---|---|---|
低密度(ldpi) | 240 x 320 | 120 |
中密度(mdpi) | 320 x 480 | 160 |
高密度(hdpi) | 480 x 800 | 240 |
超高密度(xhdpi) | 720 x 1280 | 320 |
超超高密度(xxhdpi) | 1080 x 1920 | 480 |
屏幕尺寸、分辨率、像素密度三者關系
一部手機的分辨率是寬x高,屏幕大小是以寸為單位,那么三者的關系是:
假設一部手機的分辨率是1080x1920(px),屏幕大小是5寸
5、密度無關像素(dp):
含義:density-independent pixel,叫dp或dip,與終端上的實際物理像素點無關
單位:dp,可以保證在不同屏幕像素密度的設備上顯示相同的效果,是安卓特有的長度單位。
場景例子:假如同樣都是畫一條長度是屏幕一半的線,如果使用px作為計量單位,那么在480x800分辨率手機上設置應為240px;在320x480的手機上應設置為160px,二者設置就不同了;如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長度。
dp與px的轉換:1dp = (dpi / 160 ) * 1px;
密度類型 | 代表的分辨率(PX) | 屏幕密度(DPI) | 換算 |
---|---|---|---|
低密度(ldpi) | 240 x 320 | 120 | 1dp = 0.75px |
中密度(mdpi) | 320 x 480 | 160 | 1dp=1px |
高密度(hdpi) | 480 x 800 | 240 | 1dp=1.5px |
超高密度(xhdpi) | 720 x 1280 | 320 | 1dp=2px |
超超高密度(xxhdpi) | 1080 x 1920 | 480 | 1dp=3px |
6、獨立比例像素(sp):
含義:scale-independent pixel,叫sp或sip
單位:sp,字體大小專用單位 Android開發時用此單位設置文字大小,可根據字體大小首選項進行縮放; 推薦使用12sp、14sp、18sp、22sp作為字體大小,不推薦使用奇數和小數,容易造成精度丟失,12sp以下字體太小。
7、sp 與 dp 的區別:
dp只跟屏幕的像素密度有關;
sp和dp很類似但唯一的區別是,Android系統允許用戶自定義文字尺寸大小(小、正常、大、超大等等),當文字尺寸是“正常”時1sp=1dp=0.00625英寸,而當文字尺寸是“大”或“超大”時,1sp>1dp=0.00625英寸。類似我們在windows里調整字體尺寸以后的效果——窗口大小不變,只有文字大小改變。
追到android源碼,發現系統內部用applyDimension() (路徑:android.util.TypedValue.applyDimension())將所有單位都轉換成px 再處理:
/*
*
* @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;
}
?
可以發現dp和sp的區別在于density和scaledDensity兩個值上;
?
/**
* The logical density of the display. This is a scaling factor for the
* Density Independent Pixel unit, where one DIP is one pixel on an
* approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
* providing the baseline of the system's display. Thus on a 160dpi screen
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
* increased (probably to 1.5).
*
* @see #DENSITY_DEFAULT
*/
public float density;
?
/**
* A scaling factor for fonts displayed on the display. This is the same
* as {@link #density}, except that it may be adjusted in smaller
* increments at runtime based on a user preference for the font size.
*/
public float scaledDensity;
8.區分dpi、dp、dip、px、density、分辨率
dpi:dots per inch , 直接來說就是一英寸多少個像素點。常見取值 120,160,240。我一般稱作像素密度,簡稱密度
density:可以理解為像素密度,手機屏幕dpi與標準mdpi(160dpi)的比值,比如160dpi的手機,density=1,440dpi的手機,density=2.75
dp/dip:density-independent pixels 密度無關像素,就是Google工程師們給android定義的一個長度單位,使用這種長度單位,可以使界面元素在不同密度(dpi)的屏幕上保持近似的物理尺寸大小
px:pixel 像素,屏幕上顯示元素的最小單位,比如屏幕分辨率1920*1080 px
分辨率:屏幕寬高的像素 比如:1920*1080
9.px、dpi、density各單位的計算
Metrics
在android里面,獲取一個窗口的metrics,里面有這么幾個值
metrics.densityDpi;
metrics.density;
densityDpi : 就是我們常說的dpi,一英寸占多數像素。
density:密度(density=dpi/160) 常見取值1.0 1.5 。
計算dpi
假設一部手機屏幕為4英寸,分辨率為800*480,計算dpi
dpi是指每英寸長所占的像素數量
4英寸是指屏幕對角線長度為4寸
分辨率高800px、寬480px
那么根據勾股定理可以求得對角線的像素數量
(800平方+480平方)開根號 = 屏幕對角線像素數量
屏幕對角線像素數量 / 4 = dpi
800平方 + 480平方 = 870400
870400開方 = 932.952
932.952 / 4 = 233 (dpi)
也就是說這部手機的dpi是233,接近hdpi
dp與px換算
為什么要知道這個呢? 因為針對不同的手機dp與px的換算大小是不一樣的,我們有必要知道是什么決定了他們的大小
換算公式如下:
1 = dpi/160
(dp) (px)
1是指1dp
160是標準密度,也就是mdpi
所以我們能看出來,當dpi為160的時候 1dp=1px,dpi為240(hdpi)時,1dp=1.5px
我們再將這個公式變一下,我們知道density=(dpi/160),是不是和上邊的公式很像,這個公式就可以變為
density*dp = px
可以理解為dp乘上密度就是px
三、適配方案
屏幕適配問題的本質是使得布局、布局組件在Android不同尺寸、不同分辨率的手機上具備相同的顯示效果,下面我將分幾個方面來談談如何去適配。
3.1 關于布局組件的適配:
3.1.1 避免使用像素(px)指定尺寸
由于各種屏幕的像素密度都有所不同,因此相同數量的像素在不同設備上的實際大小也會有所差異,這樣使用像素(px)定義布局尺寸就會產生問題。 因此,請務必使用密度無關像素 dp 或獨立比例像素 sp 單位指定尺寸。
備注:在生產過程中,廠家不會完全按照屏幕密度標準去生產Android設備,會在Google的標準周圍浮動變化,或是偏離Google的屏幕密度標準比較大,再加上理論計算(開方)造成的誤差,實際上使用dp作為單位是不能完完全全的完成適配操作。
3.1.2 使用相對布局或線性布局,不要使用絕對布局
對于線性布局(Linearlayout)、相對布局(RelativeLayout)、幀布局(FrameLayout)、絕對布局(AbsoluteLayout)以及新增的加強版幀布局(CoordinatorLayout)需要根據需求進行選擇,沒有絕對而言。 ? 但因為RelativeLayout講究的是相對位置,即使屏幕的大小改變,視圖之前的相對位置都不會變化,與屏幕大小無關,靈活性很強,而LinearLayout法準確地控制子視圖之間的位置關系,只能簡單的一個挨著一個地排列,所以,對于屏幕適配來說,使用相對布局(RelativeLayout)將會是更好的解決方案,至于絕對布局由于適配性極差,所以極少使用。
3.1.3 使用wrap_content、match_parent、權重
使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬編碼的尺寸,系統會自動計算相應的數值,視圖就會相應地使用自身所需的空間或填滿可用空間,讓布局正確適應各種屏幕尺寸和屏幕方向,組件的權重比同理。
3.1.4 使用minWidth、minHeight、lines等屬性
很多時候我們顯示的數據都是由后臺返回的,再由我們加工處理后去適配我們的組件,這些數據的長度我們是無法確定的,而正常情況下我們構思的布局都僅是適用于理想的情況下,為了保證界面的對齊、數據顯示完整等等的原因,我們需要在構思布局時增加對組件最小寬高度、行數等屬性的設置,確保在特殊的數據下不會破壞我們的整體布局。
3.1.5 使用dimens
組件的長寬我們可以通過dimens來定義,不同的屏幕尺寸可以定義不同的數值,或者是不同的語言顯示我們也可以定義不同的數值,因為翻譯后的長度一般都不會跟中文的一致。
3.2 成熟的適配框架
3.2.1 生成多套Dimens適配
最常用效果最好的適配方案
3.2.2 針對density轉換px適配(今日頭條的適配方案)
最便捷的適配方案
AndroidAutoSize