Android 屏幕適配方案

前言

本文爲自身的總結與結合其餘文章引用而成,分別爲:android

1、爲何要適配

因爲Android系統的開放性,任何用戶、開發者、硬件廠商、運營商均可以對Android系統和硬件進行定製,修改爲他們想要的樣子。 那麼這種「碎片化」到達什麼程度呢?  ide

Android 碎片化

以上每個矩形都表明一種機型,且它們屏幕尺寸、屏幕分辨率截然不同。隨着Android設備的增多,設備碎片化、系統碎片化、屏幕尺寸碎片化、屏幕碎片化的程度也在不斷加深。
備註:佈局

  1. Android系統碎片化:基於Google原生系統,小米定製的MIUI、魅族定製的flyme、華爲定製的EMUI等等;
  2. Android機型屏幕尺寸碎片化:5寸、5.5寸、6寸等等;
  3. Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920等;

當Android系統、屏幕尺寸、屏幕密度出現碎片化的時候,就很容易出現同一元素在不一樣手機上顯示不一樣的問題。試想一下這麼一個場景: 爲4.3寸屏幕準備的UI設計圖,運行在5.0寸的屏幕上,極可能在右側和下側存在大量的空白;而5.0寸的UI設計圖運行到4.3寸的設備上,極可能顯示不下。
爲了保證用戶得到一致的用戶體驗效果,使得某一元素在Android不一樣尺寸、不一樣分辨率的、不一樣系統的手機上具有相同的顯示效果,可以保持界面上的效果一致,咱們須要對各類手機屏幕進行適配!性能

2、基本概念

一、像素(px):

  • 含義:一般所說的像素,就是CCD/CMOS上光電感應元件的數量,一個感光元件通過感光,光電信號轉換,A/D轉換等步驟之後,在輸出的照片上就造成一個點,咱們若是把影像放大數倍,會發現這些連續色調實際上是由許多色彩相近的小方點所組成,這些小方點就是構成影像的最小單位「像素」(Pixel)。簡而言之,像素就是手機屏幕的最小構成單元
  • 單位:px(pixel),1px = 1像素點 通常狀況下UI設計師的設計圖會以px做爲統一的計量單位。

二、分辨率:

  • 含義:手機在橫向、縱向上的像素點數總和 通常描述成 寬*高 ,即橫向像素點個數 * 縱向像素點個數(如1080 x 1920)。
  • 單位:px(pixel),1px = 1像素點

三、屏幕尺寸(in):

  • 含義:手機對角線的物理尺寸
  • 單位 英寸(inch),一英寸大約2.54cm 常見的尺寸有4.7寸、5寸、5.5寸、6寸

四、屏幕像素密度(dpi):

  • 含義:每英寸的像素點數。 例如每英寸內有160個像素點,則其像素密度爲160dpi。
  • 單位:dpi(dots per inch)
  • 計算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
  • 標準屏幕像素密度(mdpi): 每英寸長度上還有160個像素點(160dpi),即稱爲標準屏幕像素密度(mdpi)。
密度類型 表明的分辨率(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高,屏幕大小是以寸爲單位,那麼三者的關係是:

image

假設一部手機的分辨率是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

六、獨立比例像素(sp):

  • 含義:scale-independent pixel,叫sp或sip
  • 單位:sp,字體大小專用單位 Android開發時用此單位設置文字大小,可根據字體大小首選項進行縮放; 推薦使用12sp、14sp、18sp、22sp做爲字體大小,不推薦使用奇數和小數,容易形成精度丟失,12sp如下字體過小。

七、sp 與 dp 的區別:

  • dp只跟屏幕的像素密度有關;
  • sp和dp很相似但惟一的區別是,Android系統容許用戶自定義文字尺寸大小(小、正常、大、超大等等),當文字尺寸是「正常」時1sp=1dp=0.00625英寸,而當文字尺寸是「大」或「超大」時,1sp>1dp=0.00625英寸。相似咱們在windows裏調整字體尺寸之後的效果——窗口大小不變,只有文字大小改變。

追到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; 複製代碼

3、適配方案

屏幕適配問題的本質是使得佈局、佈局組件在Android不一樣尺寸、不一樣分辨率的手機上具有相同的顯示效果,下面我將分幾個方面來談談如何去適配。

3.1 關於佈局組件的適配:

  • 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來定義,不一樣的屏幕尺寸能夠定義不一樣的數值,或者是不一樣的語言顯示咱們也能夠定義不一樣的數值,由於翻譯後的長度通常都不會跟中文的一致。

3.2 關於佈局的適配:

以上幾種方式能夠解決屏幕適配性的問題,可是那些經過伸縮控件來適應各類不一樣屏幕大小的佈局,未必就是提供了最好的用戶體驗。你的應用程序應該不只僅實現了可自適應的佈局,還應該提供一些方案根據屏幕的配置來加載不一樣的佈局,能夠經過配置限定符(configuration qualifiers)來實現。配置限定符容許程序在運行時根據當前設備的配置自動加載合適的資源(好比爲不一樣尺寸屏幕設計不一樣的佈局)。

  • 3.2.1 使用Size限定符
    不少應用會在較大的屏幕上實施「雙面板」模式,即在一個面板上顯示項目列表,而在另外一面板上顯示對應內容。平板電腦和電視的屏幕已經大到能夠同時容納這兩個面板了,但手機屏幕就須要分別顯示。所以,咱們可使用如下文件以便實施這些佈局:

單面板 res/layout/main.xml .jpg
1446102583971944.jpg

雙面板佈局 res/layout-large/main.xml.jpg
1446102728277991.jpg

請注意第二種佈局名稱目錄中的 large 限定符。系統會在屬於較大屏幕(例如 7 英寸或更大的平板電腦)的設備上選擇此佈局。系統會在較小的屏幕上選擇其餘佈局(無限定符)。

  • 3.2.2 最小寬度限定符
    使用Size限定符有一個問題會讓不少程序員感到頭疼,large究竟是指多大呢?不少應用程序都但願可以更自由地爲不一樣屏幕設備加載不一樣的佈局,無論它們是否是被系統認定爲」large」。這就是Android爲何在3.2之後引入了」Smallest-width」限定符。
    最小寬度限定符可以讓您經過指定某個最小寬度(以 dp 爲單位)來定位屏幕。例如,標準 7 英寸平板電腦的最小寬度爲 600 dp,所以若是您要在此類屏幕上的用戶界面中使用雙面板(但在較小的屏幕上只顯示列表),您可使用上文中所述的單面板和雙面板這兩種佈局,但您應使用 sw600dp 指明雙面板佈局僅適用於最小寬度爲 600 dp 的屏幕,而不是使用 large 尺寸限定符。

單面板 res/layout/main.xml.jpg
1446102760481409.jpg

雙面板 res/layout-sw600dp/main.xml.jpg
1446102807853454.jpg

也就是說,對於最小寬度大於等於 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:

QQ截圖20151029151423.jpg
1446102885594589.jpg

res/values-sw600dp/layout.xml:
複製代碼

QQ截圖20151029151436.jpg
1446102894526452.jpg

後兩個文件的內容相同,但它們並未實際定義佈局。它們只是將 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:(單面板)

QQ截圖20151029151534.jpg
1446102945879746.jpg

res/layout/onepane_with_bar.xml:(單面板帶操做欄)
複製代碼

QQ截圖20151029151558.jpg
1446102982969502.jpg

res/layout/twopanes.xml:(雙面板,寬佈局)
複製代碼

QQ截圖20151029151653.jpg
1446103034115365.jpg

res/layout/twopanes_narrow.xml:(雙面板,窄佈局)
複製代碼

QQ截圖20151029151701.jpg
1446103047709192.jpg

既然咱們已定義了全部可能的佈局,那就只需使用配置限定符將正確的佈局映射到各類配置便可。

如今只需使用佈局別名技術便可作到這一點:

res/values/layouts.xml:

QQ截圖20151029151847.jpg
1446103188926681.jpg

res/values-sw600dp-land/layouts.xml:

QQ截圖20151029151853.jpg
1446103198972935.jpg

res/values-sw600dp-port/layouts.xml:

QQ截圖20151029151904.jpg
1446103206749499.jpg

res/values-large-land/layouts.xml:

QQ截圖20151029151914.jpg
1446103214277258.jpg

res/values-large-port/layouts.xml:

QQ截圖20151029151920.jpg
1446103221453853.jpg

  • 3.2.5 多套layout適配
    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:

3.3 關於圖片的適配:

  • 3.3.1 LOGO 圖標
    建議按官方標準準備好各個圖標;
屏幕密度 對應的圖片大小 圖片資源目錄
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適配

  1. android:scaleType=「center」 保持原圖的大小,顯示在ImageView的中心。當原圖的size大於ImageView的size時,多出來的部分被截掉。
  2. android:scaleType=「center_inside」 以原圖正常顯示爲目的,若是原圖大小大於ImageView的size,就按照比例縮小原圖的寬高,居中顯示在ImageView中。若是原圖size小於ImageView的size,則不作處理居中顯示圖片。
  3. android:scaleType=「center_crop」 以原圖填滿ImageView爲目的,若是原圖size大於ImageView的size,則與center_inside同樣,按比例縮小,居中顯示在ImageView上。若是原圖size小於ImageView的size,則按比例拉昇原圖的寬和高,填充ImageView居中顯示。
  4. android:scaleType=「matrix」 不改變原圖的大小,從ImageView的左上角開始繪製,超出部分作剪切處理。
  5. androd:scaleType=「fit_xy」 把圖片按照指定的大小在ImageView中顯示,拉伸顯示圖片,不保持原比例,填滿ImageView.
  6. android:scaleType=「fit_start」 把原圖按照比例放大縮小到ImageView的高度,顯示在ImageView的start(前部/上部)。
  7. android:sacleType=「fit_center」 把原圖按照比例放大縮小到ImageView的高度,顯示在ImageView的center(中部/居中顯示)。
  8. android:scaleType=「fit_end」 把原圖按照比例放大縮小到ImageView的高度,顯示在ImageVIew的end(後部/尾部/底部)
    image.png

3.4 關於代碼適配:

在代碼中使用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,這樣一來獲得的數據就和實際預想的更爲接近!!

3.5 關於接口配合:

本地加載圖片前判斷手機分辨率或像素密度,向服務器請求對應級別圖片。


總結

通過上面的介紹,相信你們對於屏幕的適配都有了必定的瞭解,但實際上咱們並不會徹底去執行上面的所有操做,而是須要根據咱們的項目需求去選擇最合適的方法去適配。例如說你的產品是針對老年人的,你的字體單位是使用sp仍是dp呢?又好比說RelativeLayout和權重能較好的解決適配的問題,但實際上它們消耗更多的性能,如何去衡量性能與適配的度呢? 知識是死的,人是活的,能靈活運用相關知識方顯真本事。

相關文章
相關標籤/搜索