1. 程式人生 > >Android 螢幕適配方案

Android 螢幕適配方案

前言

本文為自身的總結與結合其他文章引用而成,分別為:

  • wangwangli6:

Android開發:最全面、最易懂的Android螢幕適配解決方案

https://blog.csdn.net/wangwangli6/article/details/63258270

  • jiashuai94:

安卓螢幕完美適配方案——獨家祕笈

https://blog.csdn.net/jiashuai94/article/details/77639511

  • 司小三石:

android 螢幕適配的總結,適合面試

https://blog.csdn.net/lanxingfeifei/article/details/52161833

  • 宇寶守護神:

ImageView的scaleType的屬性理解

https://blog.csdn.net/qq_34902522/article/details/76682293

  • 秦子帥:

Android劉海屏適配方案

https://www.jianshu.com/p/62c6625db7ab

(暫未總結劉海屏適配,如有需要請檢視這篇文章)

  • 自身的思考&實踐

為什麼要適配

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