天天看點

Android 中基于寬度的螢幕适配方案

目錄

  • 1 一些基本概念
  • 2 傳統标準适配難題
  • 3 基于寬度的螢幕适配原則

1 一些基本概念

  • ppi:pixels per inch,即實體裝置每英寸的像素數。計算公式為:
ppi = sqrt(width² + height²) / 螢幕尺寸(inch)
           

為什麼一定要計算出螢幕斜線的像素數呢?因為手機的螢幕尺寸指的就是手機螢幕斜線的長度(機關:英寸)。是以,理論上來說,如果知道螢幕寬度的尺寸,也可以直接通過

ppi = width / 寬度尺寸(inch)

計算出來。

  • dpi:dot per inch,即每英寸的點數。這個機關原本用于印刷行業,描述的是列印機的列印精度,即列印機在印刷紙上每英寸範圍内可以均勻印射出多少個顔色點,dpi 越高說明列印機的列印精度越高,效果越細膩。安卓借用了 dpi 的概念來表示螢幕的像素密度,但根據 ppi 的概念我們知道 ppi 可以更直覺地表征螢幕像素密度,那為什麼安卓要借用印刷行業的 dpi 概念來重新表示螢幕密度呢?找遍了谷歌文檔,并沒有一個官方正式的解釋。一方面,在實踐中我們發現,dpi 是約等于實際的 ppi 的;另一方面,對于不同的螢幕密度需要提供不同分辨率的圖檔資源,而為了讓裝置能夠加載正确的圖檔資源,理想狀态下就需要對每一種具體的螢幕密度都提供一張不同分辨率的圖檔,這樣做顯然不現實,是以安卓對螢幕密度做了一個标準分類,如:ldpi(低密度≈120dpi)、mdpi(正常密度≈160dpi)、hdpi(高密度≈240dpi)、xhdpi(超高密度≈320dpi)、xxhdpi(超超高密度≈80dpi)、xxxhdpi(超超超高密度≈640dpi)。每一個裝置都需要劃分到這些标準分類中的一類中,一種比較簡單的做法就是,直接将其 dpi 設定成谷歌推薦的幾個值(120/160/240/320/480/640)之一,規則就是選擇與其實際 ppi 最接近的那一個,之是以不直接設定成 ppi,我覺得很可能是根據這幾個标準值最終計算出來的 density(後面會介紹)值更加易于計算。總之,實際運用中我們可以粗略地将 dpi 表征為實體螢幕的像素密度。
  • dip/dp:density-independent pixels,即密度無關像素。如前所述,dpi 表征了螢幕的像素密度。如果在一個 160dpi 的螢幕上和一個 320dpi 的螢幕上展現一個使用者看起來大小一樣的矩形,那麼實際上這個一樣的大小指的并不是其像素點的多少一樣,而是長寬的實體尺寸(機關為英寸或者厘米)相同。是以我們在進行開發的時候,需要一種機關來衡量在不同像素密度的裝置上相同大小的界面元素。理論上可以直接用英寸或者厘米或者毫米,但是這些機關因為與像素之間沒有直接關聯,是以在渲染的時候無法轉換為最終的像素點數。為此,安卓發明了 dp 這樣一個機關。其原理是以 160dpi 的裝置作為所有其它裝置的參考系,規定 160dpi 的裝置中,一個 px 等于一個 dp,并将其它所有裝置的 每一個英寸 都等分為 160 份,每一份即為該裝置的一個 dp。比如,320dpi 的裝置,其一個英寸等分為 160 份後,每一份由 2 個像素點組成,是以其一個 dp 表示 2 個 px。其它裝置以此類推。于是我們找到了一種既可以表征螢幕實體尺寸(英寸/厘米/毫米),又可以轉換為像素值(px)的機關——dp。
  • density:了解了 dpi 和 dp 的概念後,density 的概念也就很容易了解了。density 我将其了解為 pixels per dp,即機關 dp 裡的像素數。其計算公式為:
density = dpi / 160 (一個英寸等分為 160 份後,每一份的像素數即為 density)
           

這樣,dp,px,density 之間的關系為:

dp = px / density = px / (dpi / 160)
           

各标準螢幕密度下的 density 值如下:

密度 ldpi(120dpi) mdpi(160dpi) hdpi(240dpi) xhdpi(320dpi) xxhdpi(480dpi) xxxhdpi(640dpi)
density 0.75 1 1.5 2 3 4

2 傳統标準适配難題

如果直接按照官方的 density 标準,所有裝置的寬高尺寸就都可以從 px 機關轉換為 dp 機關。比如分辨率為 1080×1920px,480dpi 的裝置一(density=3),其 dp 尺寸變為 360×640dp;而分辨率為 1440×2880px,560dpi 的裝置二(density=3.5),其 dp 尺寸變為 411×823dp。如果我們在兩個裝置中放一個 60×60dp 的圖檔,那麼此時該圖像占裝置一寬度的 16.66%,而隻占裝置二寬度的 14.59%。

顯然,裝置二是一種相對寬屏的裝置,從設計初衷上來講是為了在一行容納更多的界面元素。如果裝置的尺寸差異并不太大,那麼适配難度是可以接受的,但是随着安卓碎片化的加劇,其适配的複雜性也越來越大。這是因為,當按照一種裝置尺寸給出設計圖後,用官方的 density 标準設定界面元素的 dp 尺寸後,該元素在不同裝置上占據的比例都是不一樣的,是以其剩餘可容納的空間也不一。比如我們以中等寬度的螢幕作為設計圖把一行填滿了,那麼在布局到更寬屏的裝置上時可能沒有問題,但是布局到更窄屏的裝置上時,就會發生行溢出,這時就得考慮溢出和不溢出的時候如何布局,或者手動調整為比例尺寸。

為了适應安卓碎片化的事實,現在業界推崇一種基于寬度的螢幕适配方案,以犧牲理想适配原則為代價,換取開發成本的大大降低。

3 基于寬度的螢幕适配原則

基于寬度的螢幕适配原則如下:

将所有的手機裝置的螢幕寬度都按照相同的份數進行等分。

沒看錯,原則就是這麼簡單!具體來說,假設等分的份數為 N(即設計稿給出的螢幕寬度),任意裝置的寬度為 W(px),于是重新定義該裝置的 density 為:

density = W / N
           

這實際上也重新定義了 dp,即所有裝置的寬度都為 Ndp。于是我們給定一張正方形的圖檔,我們要以 60×60dp 來顯示,那麼在任何裝置上,該圖檔占據裝置寬度的的比例都為 60/N。

預設情況下,所有裝置的機關尺寸都等分為 160 份,這樣每種裝置的寬度因為英寸數不一樣,最終等分的份數也不一樣。在該種方案中,我們将裝置的寬度都等分為一樣的份數,這樣每個界面元素的尺寸都按照這個 份(W/N) 為機關來進行衡量,那麼其在不同裝置中所占的屏寬比例都是一樣的。

比如,假定 N=375,

裝置一為 1080×1920px,其 density=1080/375=2.88,利用公式

px = dp × density

,60×60dp 的圖檔實際占據的像素尺寸為 172.8×172.8px。

裝置二為 1440×2880px,其 density=1440/375=3.84,60×60dp 的圖檔實際占據的像素尺寸為 230.4×230.4px。

但是圖檔在兩個裝置中占據的螢幕寬度比例都為 172.8/1080=230.4/1440=60/375=16%。

目前比較流行的方案有 今日頭條方案 和 SmallestWidth 限定符方案。它們都是遵循的以上原則,隻不過在具體實作上有差異。

今日頭條方案的實作原理是直接修改系統的 density 和 dpi 來達到全局适配的目的,簡單而高效。

而 SmallestWidth 限定符方案是通過生成大量的 values-sw(XX)dp 限定檔案夾來盡可能覆寫大部分機型尺寸,然後在各個限定檔案夾的 dimens.xml 檔案中批量寫入對應的虛拟 dp 值來實作适配。比如設計稿以 375 寬度為基準(即 N=375),在 values-sw360dp/dimens.xml 中,其一個虛拟 dp 值為:

vdp_1 = 360/375 = 0.96dp

,這樣我們在布局檔案中原本應該設定 60dp 的地方,改為設定 @dimen/vdp_60(即57.6dp) 即可,于是其實際像素值為 57.6×3=172.8px(vdp_60×系統預設density)。即,虛拟 dp 的計算公式為:

vdp = dp × W / N = dp × density
           

實際上在今日頭條方案中,修正後的 density(vdensity)即為:

vdensity = W / N
           

目前來看,基于寬度的适配方案在前端開發中也成為業界主流方案,因為前端開發也面臨和用戶端開發一樣的螢幕适配難題。這種方案可以極大的減輕程式員業務開發的負擔,減少開發成本,易于維護,而且理想型的适配相比這種折中方案來講在使用者體驗上并不具有明顯的市場優勢,是以也可以說這是市場選擇的結果。

關于前端開發中的寬度适配方案,可以參考 用rem實作移動裝置頁面元素适配

加入技術交流群

準備建立一個技術交流群,大家可以讨論技術、内推工作、互相幫助。因為二維碼容易失效,個人微信号加太多容易被封,是以請大家先關注公衆号——小舍,然後在公衆号給我發送消息,我拉大家入群。

Android 中基于寬度的螢幕适配方案

繼續閱讀