本文爲自身的總結與結合其餘文章引用而成,分別爲:android
wangwangli6:Android開發:最全面、最易懂的Android屏幕適配解決方案程序員
jiashuai94:安卓屏幕完美適配方案——獨家祕笈面試
司小三石:android 屏幕適配的總結,適合面試windows
宇寶守護神:ImageView的scaleType的屬性理解bash
秦子帥:Android劉海屏適配方案
(暫未總結劉海屏適配,若有須要請查看這篇文章)服務器
自身的思考&實踐app
因爲Android系統的開放性,任何用戶、開發者、硬件廠商、運營商均可以對Android系統和硬件進行定製,修改爲他們想要的樣子。 那麼這種「碎片化」到達什麼程度呢? ide
以上每個矩形都表明一種機型,且它們屏幕尺寸、屏幕分辨率截然不同。隨着Android設備的增多,設備碎片化、系統碎片化、屏幕尺寸碎片化、屏幕碎片化的程度也在不斷加深。
備註:佈局
當Android系統、屏幕尺寸、屏幕密度出現碎片化的時候,就很容易出現同一元素在不一樣手機上顯示不一樣的問題。試想一下這麼一個場景: 爲4.3寸屏幕準備的UI設計圖,運行在5.0寸的屏幕上,極可能在右側和下側存在大量的空白;而5.0寸的UI設計圖運行到4.3寸的設備上,極可能顯示不下。
爲了保證用戶得到一致的用戶體驗效果,使得某一元素在Android不一樣尺寸、不一樣分辨率的、不一樣系統的手機上具有相同的顯示效果,可以保持界面上的效果一致,咱們須要對各類手機屏幕進行適配!性能
密度類型 | 表明的分辨率(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寸
密度類型 | 表明的分辨率(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 |
追到android源碼,發現系統內部用applyDimension() (路徑:android.util.TypedValue.applyDimension())將全部單位都轉換成px 再處理:
/**
* 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;
}
能夠發現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; 複製代碼
屏幕適配問題的本質是使得佈局、佈局組件在Android不一樣尺寸、不一樣分辨率的手機上具有相同的顯示效果,下面我將分幾個方面來談談如何去適配。
3.1.1 使用密度無關像素指定尺寸
因爲各類屏幕的像素密度都有所不一樣,所以相同數量的像素在不一樣設備上的實際大小也會有所差別,這樣使用像素(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來定義,不一樣的屏幕尺寸能夠定義不一樣的數值,或者是不一樣的語言顯示咱們也能夠定義不一樣的數值,由於翻譯後的長度通常都不會跟中文的一致。
以上幾種方式能夠解決屏幕適配性的問題,可是那些經過伸縮控件來適應各類不一樣屏幕大小的佈局,未必就是提供了最好的用戶體驗。你的應用程序應該不只僅實現了可自適應的佈局,還應該提供一些方案根據屏幕的配置來加載不一樣的佈局,能夠經過配置限定符(configuration qualifiers)來實現。配置限定符容許程序在運行時根據當前設備的配置自動加載合適的資源(好比爲不一樣尺寸屏幕設計不一樣的佈局)。
請注意第二種佈局名稱目錄中的 large 限定符。系統會在屬於較大屏幕(例如 7 英寸或更大的平板電腦)的設備上選擇此佈局。系統會在較小的屏幕上選擇其餘佈局(無限定符)。
也就是說,對於最小寬度大於等於 600 dp 的設備,系統會選擇 layout-sw600dp/main.xml(雙面板)佈局,不然系統就會選擇 layout/main.xml(單面板)佈局。
但 Android 版本低於 3.2 的設備不支持此技術,緣由是這些設備沒法將 sw600dp 識別爲尺寸限定符,所以咱們仍需使用 large 限定符。這樣一來,就會有一個名稱爲 res/layout-large/main.xml 的文件(與 res/layout-sw600dp/main.xml 同樣)。可是沒有太大關係,咱們將立刻學習如何避免此類佈局文件出現的重複。
3.2.3 使用佈局別名
最小寬度限定符僅適用於 Android 3.2 及更高版本。所以,若是咱們仍需使用與較低版本兼容的歸納尺寸範圍(小、正常、大和特大)。例如,若是要將用戶界面設計成在手機上顯示單面板,但在 7 英寸平板電腦、電視和其餘較大的設備上顯示多面板,那麼咱們就須要提供如下文件:
res/layout/main.xml: 單面板佈局 res/layout-large: 多面板佈局 res/layout-sw600dp: 多面板佈局
後兩個文件是相同的,由於其中一個用於和 Android 3.2 設備匹配,而另外一個則是爲使用較低版本 Android 的平板電腦和電視準備的。
要避免平板電腦和電視的文件出現重複(以及由此帶來的維護問題),您可使用別名文件。例如,您能夠定義如下佈局:
res/layout/main.xml,單面板佈局
res/layout/main_twopanes.xml,雙面板佈局
而後添加這兩個文件:
res/values-large/layout.xml:
res/values-sw600dp/layout.xml:
複製代碼
後兩個文件的內容相同,但它們並未實際定義佈局。它們只是將 main 設置成了 main_twopanes 的別名。因爲這些文件包含 large 和 sw600dp 選擇器,所以不管 Android 版本如何,系統都會將這些文件應用到平板電腦和電視上(版本低於 3.2 的平板電腦和電視會匹配 large,版本高於 3.2 的平板電腦和電視則會匹配 sw600dp)。
3.2.4 使用屏幕方向限定符
某些佈局會同時支持橫向模式和縱向模式,但咱們能夠經過調整優化其中大部分佈局的效果。在新聞閱讀器示例應用中,每種屏幕尺寸和屏幕方向下的佈局行爲方式以下所示:
小屏幕,縱向:單面板,帶徽標
小屏幕,橫向:單面板,帶徽標
7 英寸平板電腦,縱向:單面板,帶操做欄
7 英寸平板電腦,橫向:雙面板,寬,帶操做欄
10 英寸平板電腦,縱向:雙面板,窄,帶操做欄
10 英寸平板電腦,橫向:雙面板,寬,帶操做欄
電視,橫向:雙面板,寬,帶操做欄
所以,這些佈局中的每一種都定義在了 res/layout/ 目錄下的某個 XML 文件中。爲了繼續將每一個佈局分配給各類屏幕配置,該應用會使用佈局別名將二者相匹配:
res/layout/onepane.xml:(單面板)
res/layout/onepane_with_bar.xml:(單面板帶操做欄)
複製代碼
res/layout/twopanes.xml:(雙面板,寬佈局)
複製代碼
res/layout/twopanes_narrow.xml:(雙面板,窄佈局)
複製代碼
既然咱們已定義了全部可能的佈局,那就只需使用配置限定符將正確的佈局映射到各類配置便可。
如今只需使用佈局別名技術便可作到這一點:
res/values/layouts.xml:
res/values-sw600dp-land/layouts.xml:
res/values-sw600dp-port/layouts.xml:
res/values-large-land/layouts.xml:
res/values-large-port/layouts.xml:
屏幕密度 | 對應的圖片大小 | 圖片資源目錄 |
---|---|---|
120dip | 36px * 36px | mipmap-ldpi |
160dip(基準) | 48px * 48px | mipmap或者mipmap-mdpi |
240dip(1.5倍) | 72px * 72px | mipmap-hdpi |
320dip (2倍) | 96px * 96px | mipmap-xhdpi |
480dip (3倍) | 144px * 144px | mipmap-xxhdpi |
640dip (4倍) | 192px * 192px | mipmap-xxxhdpi |
3.3.2 普通圖片和圖標
建議安裝官方的密度類型進行切圖便可,但通常咱們只需xxhdpi或xxxhdpi的切圖便可知足咱們的需求;
3.3.3 自動拉伸位圖:Nine-Patch的圖片類型
支持不一樣屏幕大小一般狀況下也意味着,你的圖片資源也須要有自適應的能力。例如,一個按鈕的背景圖片必須可以隨着按鈕大小的改變而改變。 若是你想使用普通的圖片來實現上述功能,你會發現這是難以實現的,由於運行時會均勻地拉伸或壓縮你的圖片。解決方案是使用nine-patch圖片,它是一種被特殊處理過的PNG圖片,你能夠指定哪些區域能夠拉伸而哪些區域不能夠。
3.3.4 動畫、自定義view、shape
可使用代碼進行控制和展現多種視圖,如patch動畫替代幀動畫。
3.3.5 ImageView的ScaleType適配
在代碼中使用Google提供的API對設備的屏幕寬度進行測量,而後按照需求進行設置。
對於當前控件的寬高設置,須要作的操做是首先要獲取到該控件的父控件,使用父控件對當前控件的寬高進行設置操做!
DisplayMetrics metrics = new DisplayMetrics ();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
手機對應的寬高:
Constants.screenHeight= metrics.heightDixels;
Constants.screenWidth= metrics.widthDixels;
RelativeLayout.LayoutParams=new RelativeLayout.LayoutParams();
(int)( Constants.screenHeight*0.5+0.5f);
(int)( Constants.screenWidth *0.5+0.5f);
複製代碼
在上面的兩個計算操做中最後加上0.5f的做用是:進行float強轉到int類型的時候會出現都是精度的問題。當使用Java代碼進行寬高設置的時候,假如出現320.2dp這樣的數據此時直接進行int獲得的值是320;可是假如出現320.7這樣的數據的時候,因爲int的計算規則,會直接強轉爲320,可是從實際出發,這個時候的值取321更爲合適。
因此在計算的最後直接加0.5,這樣一來,320.2+0.5=320.7,進行數據的強轉操做獲得的數據是320,320.7+0.5=321.2,進行數據強轉操做獲得的數據是321,這樣一來獲得的數據就和實際預想的更爲接近!!
本地加載圖片前判斷手機分辨率或像素密度,向服務器請求對應級別圖片。
通過上面的介紹,相信你們對於屏幕的適配都有了必定的瞭解,但實際上咱們並不會徹底去執行上面的所有操做,而是須要根據咱們的項目需求去選擇最合適的方法去適配。例如說你的產品是針對老年人的,你的字體單位是使用sp仍是dp呢?又好比說RelativeLayout和權重能較好的解決適配的問題,但實際上它們消耗更多的性能,如何去衡量性能與適配的度呢? 知識是死的,人是活的,能靈活運用相關知識方顯真本事。