Android 屏幕適配方案

轉自https://mp.weixin.qq.com/s/v_aauFjx-f91WrpCAaNMVQ

 

爲什麼要適配

由於Android系統的開放性,任何用戶、開發者、硬件廠商、運營商都可以對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不同尺寸、不同分辨率的、不同系統的手機上具備相同的顯示效果,能夠保持界面上的效果一致,我們需要對各種手機屏幕進行適配!

基本概念

1、像素(px):

  • 含義:通常所說的像素,就是CCD/CMOS上光電感應元件的數量,一個感光元件經過感光,光電信號轉換,A/D轉換等步驟以後,在輸出的照片上就形成一個點,我們如果把影像放大數倍,會發現這些連續色調其實是由許多色彩相近的小方點所組成,這些小方點就是構成影像的最小單位“像素”(Pixel)。簡而言之,像素就是手機屏幕的最小構成單元

  • 單位:px(pixel),1px = 1像素點 一般情況下UI設計師的設計圖會以px作爲統一的計量單位。

2、分辨率:

  • 含義:手機在橫向、縱向上的像素點數總和 一般描述成 寬*高 ,即橫向像素點個數 * 縱向像素點個數(如1080 x 1920)。

  • 單位:px(pixel),1px = 1像素點

3、屏幕尺寸(in):

  • 含義:手機對角線的物理尺寸

  • 單位 英寸(inch),一英寸大約2.54cm 常見的尺寸有4.7寸、5寸、5.5寸、6寸

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

  • 含義:每英寸的像素點數。 例如每英寸內有160個像素點,則其像素密度爲160dpi。

  • 單位:dpi(dots per inch)

  • 計算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)

  • 標準屏幕像素密度(mdpi): 每英寸長度上還有160個像素點(160dpi),即稱爲標準屏幕像素密度(mdpi)。

屏幕尺寸、分辨率、像素密度三者關係

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

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 再處理:

   /**
     * 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 關於佈局組件的適配:

  • 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限定符
    很多應用會在較大的屏幕上實施“雙面板”模式,即在一個面板上顯示項目列表,而在另一面板上顯示對應內容。平板電腦和電視的屏幕已經大到可以同時容納這兩個面板了,但手機屏幕就需要分別顯示。因此,我們可以使用以下文件以便實施這些佈局:

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

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

也就是說,對於最小寬度大於等於 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:(雙面板,窄佈局)

既然我們已定義了所有可能的佈局,那就只需使用配置限定符將正確的佈局映射到各種配置即可。

現在只需使用佈局別名技術即可做到這一點:

  • 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 圖標
    建議按官方標準準備好各個圖標;

  • 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(後部/尾部/底部)

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和權重能較好的解決適配的問題,但實際上它們消耗更多的性能,如何去衡量性能與適配的度呢? 知識是死的,人是活的,能靈活運用相關知識方顯真本事。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章