屏幕適配最佳實踐

基本概念

屏幕尺寸

手機屏幕對角線的物理尺寸。單位英寸(inch),一英寸大約 2.54cm。常見的手機屏幕尺寸有 4.7 英寸、5.0英寸、5.5 英寸、6.0 英寸等。

屏幕尺寸示例圖

像素(px)

像素(英語:Picture Element),Pixel 的縮寫。液晶屏顯示圖像,放大來看是一個個小點組成的,這些小點就是像素點。

像素點

分辨率

分辨率(英語:Image resolution),又稱解析度、解像度,可以從顯示分辨率與圖像分辨率兩個方向來分類。

在 Android 設備中指的是顯示分辨率,即屏幕分辨率。也就是屏幕所能顯示的像素有多少,比如:手機分辨率 1920 x 1080。

在圖片中指是圖像分辨率,則是單位英寸中所包含的像素點數。比如:圖片分辨率 600 x 400。

不同分辨率的圖像的差別

每英寸點數(DPI)

每英寸點數(英語:Dots Per Inch,縮寫:DPI)是一個量度單位,用於點陣數碼影像,指每一英寸長度中,取樣、可顯示或輸出點的數目。

一般用於打印機、鼠標等設備分辨率的度量單位。

比如:打印機輸出可達 600 DPI 的分辨率,表示打印機可以在每一平方英寸的面積中可以輸出 600 x 600 = 360000 個輸出點。

鼠標的 DPI 參數,指的是鼠標在桌面上移動一英寸的距離的同時,鼠標光標能夠在屏幕上移動多少「點」。

每英寸像素(PPI)

每英寸像素(英語:Pixels Per Inch,縮寫:PPI),又被稱爲像素密度。

一般用來計量計算機顯示器,電視機和手持電子設備屏幕的精細程度。通常情況下,每英寸像素值越高,屏幕能顯示的圖像也越精細。

分辨率、DPI、PPI 之間的關係

當我們把相同分辨率的圖片,放在具有相同像素顯示的屏幕上顯示時。每一個像素,屏幕上對應一個點顯示,此時 DPI = PPI。
比如:我們把分辨率爲 m x n 的圖片,放在最大支持 m x n 像素的屏幕上時,DPI = PPI。

但是,實際上,我們所需要顯示圖片的分辨率,跟屏幕參數匹配的概率還是很小的。我們來分析下,不匹配時的情況:

當我們把 1280 x 720 的圖片,放在 800 x 480 的 4 英寸的屏幕與 1920 x 1080 的 5.5 英寸的屏幕上顯示時的結果爲:

分辨率、DPI 與 PPI

PPI 是屏幕的顯示性能,所以與顯示的圖片沒有關係,是固定的值,但是 DPI 與顯示的圖片是有關係的。

  • 分辨率爲 1280 x 720 的圖片放在 800 x 480 的 4 英寸的屏幕上顯示

雖然圖片一行有 720 個像素,但是屏幕一行最多隻能顯示 480 個點,所以 DPI = PPI = 233,已經達到屏幕的最大顯示能力。

  • 分辨率爲 1280 x 720 的圖片放在 1920 x 1080 的 5.5 英寸的屏幕上顯示

雖然屏幕一行有 1080 個點,但是圖片一行最多隻能顯示 720 個像素,所以 DPI = 267 < PPI,並未達到屏幕的最大顯示能力,未達到屏幕的最佳顯示效果。

通過上面分析可以得到:

  • 分辨率:只能用來描述圖片的像素信息,不能描述圖片清晰度。
  • PPI:只能用來描述屏幕的顯示密度,也不能描述圖片的清晰度
  • DPI:才能用來描述圖片顯示的清晰度,表示圖片在屏幕上的顯示效果。

一句話總結下就是 DPI 表示印刷品點的密度,PPI 表示顯示設備點的密度。

PPI(左)和 DPI(右)的比較

PPI 計算公式

由於顯示器的 DPI 是固定的,不像打印機那樣可以調整,所以針對顯示器的設計時 DPI = PPI。

計算顯示器的每英寸像素值,需要確定屏幕的尺寸和分辨率。

PPI 計算公式

其中:

  • W(Width):爲屏幕橫向分辨率。
  • H(Height):爲屏幕縱向分辨率。
  • inch:爲屏幕對角線的長度(單位爲英寸)。

基於 PPI 屏幕分級

根據屏幕每英寸像素值的不同,Android 中將平板電腦和手機的屏幕分爲下面幾類:

密度名稱 每英寸像素值 圖標尺寸
低密度(LDPI) ~120dpi 36 x 36 px
中密度(MDPI) 120dpi ~ 160dpi 48 x 48 px
高密度(HDPI) 160dpi ~ 240dpi 72 x 72 px
超高密度(XHDPI) 240dpi ~ 320dpi 96 x 96 px
超超高密度(XXHDPI) 320dpi ~ 480dpi 144 x 144 px
超超超高密度(XXXHDPI) 480dpi ~ 640dpi 192 x 192 px

密度無關像素(DP)

DP 或者 DIP,是 Android 開發用的單位。1dp 表示在屏幕點密度爲 160ppi 時 1px 長度。

由於 Android 設備屏幕衆多,不可能爲每個屏幕單獨開發,所以用下面公式計算在不同屏幕上的像素數。

PX 計算公式

同一圖標在分辨率不同的設備上顯示時,會出現如下效果:

同一圖標不同設備顯示效果

可以看到上圖第 1 和第 2 個設備兩個屏幕尺寸相同,由於它們分辨率的不同,同一個圖標在兩個設備上顯示的尺寸相差很大。

那麼,圖片顯示大小是由什麼決定的呢,屏幕尺寸嗎?上圖第 1 和第 2 個設備屏幕都是 4.3 英寸。

還是因爲分辨率呢?上圖第 2 和第 3 個設備屏幕都是 720 x 1280 的分辨率。

最後我們找到了像素密度(density),也就是像素數和屏幕尺寸的比值。density 是每單位長度容納的像素數量,一般用像素/英寸,也就是 Pixel per inch(PPI)。

對比上圖可以知道,PPI 越低圖片顯示的越大,PPI 越高圖片顯示的越小。

要讓不同屏幕顯示圖片的大小相同,就需要對圖片進行縮放,給高 PPI 屏提供更大的圖片。

兼容高 PPI 屏幕

高 PPI 屏幕需要更大的圖片才能得到同樣的顯示效果,反之亦然。

PPI 和圖片 px 的關係,如下:

PPI 與 PX 計算公式

選定一個 PPI 值作爲基礎繪製圖片,用 PPI 的比值計算出圖片縮放比例,就可以適配各種屏幕。

公式換算 1

Android 選定的這個基礎值就是 160ppi

公式換算 2

我們已經解決了圖片放大縮小的問題,還需要一個單位用來描述長度。

因爲 px 不固定,inch 不方便,所以 Android 創造了一個新的單位 dp,中文名密度無關像素。並且規定在 160ppi 的屏幕上 1dp = 1px。

設計師只需要針對 160ppi 的顯示屏設計並製圖,安卓會根據當前手機屏幕的 PPI 值來放大縮小圖片,在不同的屏幕上得到相近的顯示效果。

公式換算 3

獨立比例像素(SP)

Android 設備的文字單位是 SP,簡單理解和 DP 是相同的。另外 SP 會隨着系統的字體大小改變,而 DP 則不會。

Google 推薦我們使用 12sp 以上的大小,通常可以使用 12sp、14sp、18sp、22sp。最好不要使用「奇數」和「小數」。

適配不同屏幕尺寸

佈局適配

wrap_content

設置視圖的寬高爲 wrap_content 時,視圖大小會根據內容自動增加。

match_parent

設置視圖寬高位 match_parent 時,視圖大小始終與父級一樣大。

LinerLayout

LinerLayout 是一個視圖組,用於使所有子視圖在單個方向(垂直或水平)保持對齊。 可以使用 android:orientation 屬性指定佈局方向。

  • weight

使用線性佈局時,將某一個或者多個子視圖的寬或者高設置爲 0dp,設置 weight 值爲 1

weight 值爲 1 的子視圖將會充滿父視圖剩餘的空間,如果設置多個子視圖的 weight 都爲 1,那麼這些子視圖將平分並充滿父視圖剩餘的空間。

RelativeLayout

RelativeLayout 是一個視圖組,顯示相對位置的子視圖。使用 RelativeLayout 可以將子視圖定位在任意位置。

限定符適配

尺寸限定符

  • small:提供給小屏幕設備的資源
  • normal:提供給中等屏幕設備的資源
  • large:提供給大屏幕設備的資源
  • xlarge:提供給超大屏幕的資源

使用方式如下:

|- layout // 無限定符,默認佈局
	|- main.xml
|- layout-small // 小屏幕設備
	|- main.xml
|- layout-normal // 中等屏幕設備
	|- main.xml
|- layout-large // 大屏幕設備
	|- main.xml
|- layout-xlarge // 超大屏幕設備
	|- main.xml

最小寬度限定符

最小寬度限定符就是設置設備屏幕大於或等於最小寬度時加載的視圖。

例如,7 英寸平板電腦最小寬度爲 600dp,如果希望的 UI 在這些屏幕上顯示兩列,但在較小屏幕上顯示單列,就可以使用最小寬度限定符。

|- layout // 無限定符,默認佈局顯示單列
	|- main.xml
|- layout-sw600dp // 設備寬度爲 600dp 以上時顯示兩列
	|- main.xml

佈局別名

如果適配的屏幕設備比較多,爲了方便視圖文件的管理,我們可以使用佈局別名。

|- layout
	|- main.xml // 單列布局
	|- main_twopanes.xml // 雙列布局
|- values-large
	|- layout.xml
|- values-sw600dp
	|- layout.xml
  • res/values-large/layout.xml
<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>
  • res/values-sw600p/layout.xml
<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

後兩個文件內容完全相同,但它們實際上並未定義佈局, 而只是將 main 設置爲 main_twopanes 的別名。由於這些文件具有 largesw600dp 選擇器,因此它們適用於任何 Android 版本的平板電腦和電視(低於 3.2 版本的平板電腦和電視匹配 large,高於 3.2 版本者將匹配 sw600dp)。

屏幕方向限定符

  • land:提供給橫屏設備的資源
  • port:提供給豎屏設備的資源
|- layout
	|- main.xml // 單列布局
	|- main_twopanes.xml // 雙列布局
|- values-large-land // 大屏幕橫向
	|- layout.xml
|- values-sw600dp-port // 最小寬度 600dp 橫向
	|- layout.xml

圖片適配

位圖

  • Logo 圖標

官方建議的圖標尺寸:

密度名稱 每英寸像素值 圖標尺寸 圖片資源目錄
LDPI ~120dpi 36 x 36 px mipmap-ldpi
MDPI(基準) 120dpi ~ 160dpi 48 x 48 px mipmap 或 mipmap-mdpi
HDPI(1.5倍) 160dpi ~ 240dpi 72 x 72 px mipmap-hdpi
XHDPI(2倍) 240dpi ~ 320dpi 96 x 96 px mipmap-xhdpi
XXHDPI(3倍) 320dpi ~ 480dpi 144 x 144 px mipmap-xxhdpi
XXXHDPI(4倍) 480dpi ~ 640dpi 192 x 192 px mipmap-xxxhdpi
  • 普通位圖和圖標

按照官方密度類型進行切圖即可,一般情況下只需要根據主流設備選擇所需要的資源文件即可。

目前已知主流設備屏幕:

屏幕分辨率 屏幕尺寸 PPI 對應密度 圖片資源文件夾
240 x 320 2.5 160 MDPI(基準) drawable-mdpi
400 x 800 4.0 224 HDPI(1.5倍) drawable-hdpi
720 x 1280 4.7 313 XHDPI(2倍) drawable-xhdpi
1080 x 1920 5.5 401 XXHDPI(3倍) drawable-xxhdpi
1440 x 2560 6.0 490 XXXHDPI(4倍) drawable-xxxhdpi

九宮格位圖

  • 拉伸區域

拉伸區域

紅色框區域:表示縱向拉伸的區域,當圖片需要縱向拉伸的時候它會只指定拉伸紅色區域,其他區域在縱向是不會拉伸。

綠色框區域:表示橫向拉伸的區域,當圖片需要橫向拉伸的時候它會只指定拉伸綠色區域,其他區域在橫向是不會拉伸的。

顯然紅色和綠色相交的部分是既會進行橫向拉伸也會進行縱向拉伸。

  • 顯示區域

顯示區域

黃色區域:表示前景能顯示的橫向範圍。即前景的最左邊可以顯示到什麼地方,最右邊可以顯示的什麼地方。

藍色區域:表示前景能顯示的縱向範圍。即前景的最上面可以顯示到什麼地方,最下面可以顯示的什麼地方。

藍色和黃色相交部分:表示整個前景能顯示的區域。一個區域是矩形的,藍色規定了上下邊界,黃色規定了左右邊界,兩者共同當然也就規定了一個矩形區域。

矢量圖

矢量圖是根據幾何特性來繪製圖形,矢量可以是一個點或一條線,矢量圖只能靠軟件生成,文件佔用內在空間較小,因爲這種類型的圖像文件包含獨立的分離圖像,可以自由無限制的重新組合。

它的特點是放大後圖像不會失真,和分辨率無關,適用於圖形設計、文字設計和一些標誌設計、版式設計等。

  • SVG

SVG 指可伸縮矢量圖形 (Scalable Vector Graphics)。用來定義用於網絡的基於矢量的圖形,SVG 使用 XML 格式定義圖形,圖像在放大或改變尺寸的情況下其圖形質量不會有所損失。

Android 中對矢量圖的支持就是對 SVG 的支持。使用方式比較簡單,這裏不再贅述。

ScaleType

ImageView 是 Android 中最常用的控件之一,而在使用 ImageView 時,必不可少的會使用到它的 scaleType 屬性。該屬性指定了你想讓 ImageView 如何顯示圖片,包括是否進行縮放、等比縮放、縮放後展示位置等。

Android 提供了八種 scaleType的 屬性值,每種都對應了一種展示方式。

  • center:保持原圖的大小,顯示在 ImageView 的中心。當原圖的尺寸大於 ImageView 的尺寸時,多出來的部分被裁切掉。
  • center_inside:以原圖正常顯示爲目的,如果原圖大小大於 ImageView 的尺寸,就按照比例縮小原圖的寬高,居中顯示在 ImageView 中。如果原圖尺寸小於 ImageView 的 size,則不做處理居中顯示圖片。
  • center_crop:以原圖填滿 ImageView 爲目的,如果原圖尺寸大於 ImageView 的尺寸,則與 center_inside 一樣,按比例縮小,居中顯示在 ImageView 上。如果原圖尺寸小於 ImageView 的尺寸,則按比例拉伸原圖的寬和高,填充 ImageView 居中顯示。
  • matrix:不改變原圖的大小,從 ImageView 的左上角開始繪製,超出部分做剪切處理。
  • fit_xy:把圖片按照指定的大小在 ImageView 中顯示,拉伸顯示圖片,不保持原比例,填滿ImageView。
  • fit_start:把原圖按照比例放大縮小到 ImageView的 高度,顯示在 ImageView 的 center(前部/上部)。
  • fit_center:把原圖按照比例放大縮小到 ImageView 的高度,顯示在 ImageView 的 center(中部/居中顯示)。
  • fit_end: 把原圖按照比例放大縮小到 ImageView 的高度,顯示在 ImageVIew 的 end(後部/尾部/底部)

scaleType

接口適配

本地加載圖片時,根據設備屏幕分辨率或像素密度,向服務器請求對應級別的圖片資源。

支持劉海屏幕

Android P 提供提供的劉海屏適配方案,詳見:Android 開發文檔 - 支持顯示切口

  • 對於有狀態欄的頁面,不會受到劉海屏特性的影響,因爲劉海屏包含在狀態欄中了;
  • 全屏顯示的頁面,系統劉海屏方案會對應用界面做下移處理,避開劉海區顯示,這時會看到劉海區域變成一條黑邊,完全看不到劉海了;
  • 已經適配 Android P 應用的全屏頁面可以通過谷歌提供的適配方案使用劉海區,真正做到全屏顯示。

劉海屏適配思路:

  • Android P 以後的設備:如果有狀態欄不需要適配,因爲劉海區域會包含在狀態欄中了(設置 LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 可將劉海區域變成一條黑色邊)。如果全屏顯示,獲取到危險區域(劉海區域),讓操作區域避開危險區域即可。
  • Android P 之前的設備:根據主流廠商 華爲、vivo、OPPO、小米等所提供的方案進行適配。

增加不支持的屏幕

Google 建議我們支持更多的設備,但是根據需求可能需要不支持某些設備。比如,僅支持手機不支持平板電腦。我們就可以增加不支持的屏幕設備。

如果應用僅支持小屏幕尺寸和標準屏幕尺寸,可以這麼做:

<manifest ... >
  <compatible-screens>
    <!-- 所有的小尺寸屏幕 -->
    <screen android:screenSize="small" android:screenDensity="ldpi" />
    <screen android:screenSize="small" android:screenDensity="mdpi" />
    <screen android:screenSize="small" android:screenDensity="hdpi" />
    <screen android:screenSize="small" android:screenDensity="xhdpi" />
    <!-- 所有的標準尺寸屏幕 -->
    <screen android:screenSize="normal" android:screenDensity="ldpi" />
    <screen android:screenSize="normal" android:screenDensity="mdpi" />
    <screen android:screenSize="normal" android:screenDensity="hdpi" />
    <screen android:screenSize="normal" android:screenDensity="xhdpi" />
  </compatible-screens>
  ...
  <application ... >
    ...
  <application>
</manifest>

如果應用僅支持平板電腦或電視,可以這麼做:

<manifest ... >
  <supports-screens android:smallScreens="false"
                    android:normalScreens="false"
                    android:largeScreens="true"
                    android:xlargeScreens="true"/>
  ...
</manifest>

今日頭條適配方案

2018 年 5 月,字節跳動技術團隊提出了一種低成本的屏幕適配方式,強烈推薦大家看一下:一種極低成本的 Android 屏幕適配方式

參考資料

我的 GitHub

github.com/jeanboydev

我的公衆號

歡迎關注我的公衆號,分享各種技術乾貨,各種學習資料,職業發展和行業動態。

Android 波斯灣

技術交流羣

歡迎加入技術交流羣,來一起交流學習。

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