讀書筆記,如需轉載,請註明作者:Yuloran (t.cn/EGU6c76)
吐槽
Android開發者真的挺苦逼的,不僅要適配常規解析度,如 480×854、720×1280、1080×1920、1440×2560,還要適配各種奇葩解析度,如 720×1440、1080×2160…
適配原因
不同解析度和螢幕畫素密度的手機,其寬高換算成dp後,有可能是不同的。如下表(Google規定:在160dpi的手機上,1dp = 1px):
注:dpi 跟 ppi 一個意思,都表示每英寸多少個畫素點
解析度(畫素) | 畫素密度(dpi) | 寬(dp) | 高(dp) |
---|---|---|---|
480×854 | 240 | 320 | 569.3 |
1080×1920 | 480 | 360 | 640 |
所以在1080×1920、480ppi的手機上,可以完整顯示330dp寬的按鈕,而在480×854、240ppi的手機上,卻顯示不下。如何解決呢?Google機智地設計了一套資源限定符,只需新建一個values-hdpi資料夾,再建一個dimens.xml(名字隨意),新增一句:
<dimen name="your_widget_name">300dp</dimen>
複製程式碼
按鈕寬度便會自動調整為300dp。
限定符優先順序
限定符 | 示例 | 含義 | 優先順序 | since version |
---|---|---|---|---|
MCC 和 MNC | 示例: mcc310 mcc310-mnc004 mcc208-mnc00 … |
移動國家程式碼 (MCC),(可選)後跟裝置 SIM 卡中的行動網路程式碼 (MNC)。
例如,mcc310 是指美國的任一運營商,mcc310-mnc004 是指美國的 Verizon 公司,mcc208-mnc00 是指法國的 Orange 公司。 |
高 | API 1 |
語言和區域 | 示例: en fr en-rUS … |
語言通過由兩個字母組成的 ISO 639-1 語言程式碼定義,可以選擇後跟兩個字母組成的 ISO 3166-1-alpha-2 區域碼(前帶小寫字母“r”)。
這些程式碼不區分大小寫;r字首用於區分割槽域碼。不能單獨指定區域。 |
↓ | API 1 |
佈局方向 | ldrtl ldltr |
應用的佈局方向。
ldrtl 是指“佈局方向從右到左”; |
↓ | API17 |
最小寬度 | swdp
示例: |
限定資源僅適用於最小螢幕寬度為Ndp的手機(不考慮螢幕方向)。
比如1080×1920 480ppi的手機,最小寬度限定符就是sw360dp; |
↓ | API 13 |
可用寬度 | wdp 示例: w320dp … |
限定資源僅適用於最小可用螢幕寬度為Ndp的手機(考慮螢幕方向)。
一般情況下,該限定符的值與swdp相同。 |
↓ | API 13 |
可用高度 | hdp
示例: |
限定資源僅適用於最小可用螢幕高度為Ndp的手機(考慮螢幕方向)。
計算N的時候必須減去StatusBar和NavigationBar的高度。 比如1080×2160 480ppi的手機,若StatusBar高度為25dp,NavigationBar高度為40dp,則N = 720 – 25 – 40 = 655,即可用高度限定符為h655dp。 |
↓ | API 13 |
螢幕尺寸 | small normal large xlarge |
small:QVGA,320×426(dp) normal:HVGA,320×470(dp) large:VGA,480×640(dp) xlarge:720×960(dp)【API 9】 |
↓ | API 4 |
螢幕縱橫比 | nong notlong |
long:寬屏,如 WQVGA、WVGA、FWVGA notlong:非寬屏,如 QVGA、HVGA 和 VGA 完全基於螢幕的縱橫比(寬屏較寬),與螢幕方向無關。 |
↓ | API 4 |
圓形螢幕 | round notround |
round:圓形螢幕,例如圓形可穿戴式裝置 notround:方形螢幕,例如手機或平板電腦 |
↓ | API 23 |
螢幕方向 | port land |
port:豎屏 land:橫屏 |
↓ | API 1 |
UI 模式 | car desk television appliance watch |
car:裝置正在車載手機座上顯示 desk:裝置正在桌面手機座上顯示 television:裝置正在電視上顯示,為使用者提供“十英尺”體驗,其 UI 位於遠離使用者的大螢幕上,主要面向方向鍵或其他非指標式互動【API 13】 appliance:裝置用作不帶螢幕的裝置 watch:裝置配有螢幕,戴在手腕上【API 20】 |
↓ | API 8 |
夜間模式 | night notnight |
night:晚上 notnight:白天 |
↓ | API 8 |
螢幕畫素密度 (dpi) | ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi nodpi tvdpi anydpi |
ldpi:120dpi mdpi: 160dpi hdpi:240dpi xhdpi:320dpi【API 8】 xxhdpi:480dpi【API 16】 xxxhdpi:640dpi【API 18】 nodpi:放不想縮放的點陣圖 tvdpi:213dpi【API 13】 anydpi:此限定符適合所有螢幕密度,其優先順序高於其他限定符。 這對於向量可繪製物件很有用。【API 21】 六個主要密度之間的縮放比為 3:4:6:8:12:16(忽略 tvdpi 密度)。因此,9×9 (ldpi) 點陣圖相當於 12×12 (mdpi)、18×18 (hdpi)、24×24 (xhdpi) 點陣圖,依此類推。 如果您認為影像資源在電視或其他某些裝置上呈現的效果不夠好,而想嘗試使用 tvdpi 資源,則縮放比例為 1.33*mdpi。例如,mdpi 螢幕的 100px x 100px 影像應該相當於 tvdpi 的133px x 133px。 |
↓ | API 1 |
觸控式螢幕型別 | notouch finger |
notouch:有觸控式螢幕 finger:裝置有一個專供使用者通過手指直接與其互動的觸控式螢幕 |
↓ | API 1 |
鍵盤可用性 | keysexposed keyshidden keyssoft |
keysexposed:裝置具有可用的鍵盤。如果裝置啟用了軟鍵盤(不無可能),那麼即使硬鍵盤沒有展示給使用者,哪怕裝置沒有硬鍵盤,也可以使用此限定符。 如果沒有提供或已經禁用軟鍵盤,則只有在顯示硬鍵盤時才會使用此限定符。 keyshidden:裝置具有可用的硬鍵盤,但它處於隱藏狀態,且裝置沒有啟用軟鍵盤。 keyssoft:裝置已經啟用軟鍵盤(無論是否可見)。 如果提供了 keysexposed 資源,但未提供 keyssoft 資源,那麼只要系統已經啟用軟鍵盤,就會使用 keysexposed 資源,而不考慮鍵盤是否可見。 |
↓ | API 1 |
主要文字輸入法 | nokeys qwerty 12keys |
nokeys:裝置沒有用於文字輸入的硬按鍵。 qwerty:裝置具有標準硬鍵盤(無論是否對使用者可見)。 12key:裝置具有 12 鍵硬鍵盤(無論是否對使用者可見)。 |
↓ | API 1 |
導航鍵可用性 | navexposed navhidden |
navexposed:導航鍵可供使用者使用。 navhidden:導航鍵不可用(例如,位於密封蓋子後面)。 不知道指的啥,反正不是底部的虛擬按鍵。那玩意兒需要根據各個廠商的定製方案去適配(監聽廣播或者讀資料庫)。 |
↓ | API 1 |
主要非觸控導航方法 | nonav dpad trackball wheel |
nonav:除了使用觸控式螢幕以外,裝置沒有其他導航設施。 dpad:裝置具有用於導航的方向鍵。 trackball:裝置具有用於導航的軌跡球,如HTC G2。 wheel:裝置具有用於導航的方向盤(不常見)。 |
↓ | API 1 |
平臺版本(API 級別) | 示例: v3 v4 v7 … |
從哪個版本開始支援此限定符 | 低 | API 1 |
- 目前共有19種限定符,當然常用的不多。適配時,根據業務需要,查表適配即可。
- 上表限定符適用於所有資源,而不僅僅是values。drawable、layout也可以使用。
限定符命名規則
-
可以同時使用多個限定符,用”-“連線。如values-sw360dp-h655dp-port;
-
不區分大小寫;
-
同一型別的限定符只能出現一次;
-
限定符的使用順序必須嚴格按照上表規定的優先順序,否則會直接編譯出錯。如:
D:HardwareDetectionappsrcmain
esvalues-port-sw360dp: Error: Invalid resource directory name
原因:sw360dp優先順序大於port
複製程式碼
注意點
-
不帶任何限定符的資原始檔夾,如values,是預設資料夾。該資料夾對應160ppi,即mdpi。當系統在其他資原始檔夾下找不到對應資源時,便會使用預設資料夾中的資源。若預設資料夾也找不到,則丟擲異常。
-
無需特殊適配的資源應當置於預設資料夾中。
-
螢幕畫素密度限定符(如values-hdpi、values-xxhdpi),是唯一一個不會因衝突而被淘汰的資料夾。什麼意思呢?就是說只要對應的資源存在,那麼不論該資源放在哪個dpi資料夾,系統都不會丟擲異常(系統會優先選擇高dpi資料夾下的資源)。而其他限定符則沒有這個特權,若系統淘汰了與當前裝置不匹配的限定符後找不到對應的資源,則會直接丟擲異常:
Caused by: java.lang.RuntimeException: Binary XML file line #10: You must supply a layout_width attribute.
at android.content.res.TypedArray.getLayoutDimension(TypedArray.java:606)
複製程式碼
限定符淘汰規則
-
假設res下存在以下目錄:
drawable/
drawable-en/
drawable-fr-rCA/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/ -
使用者手機的配置是:
語言區域 = en-GB
螢幕方向 = port
螢幕畫素密度 = hdpi
觸控式螢幕型別 = notouch
主要文字輸入法 = 12key
那麼該手機最終會採用哪個資料夾下的資源呢?
-
淘汰與裝置配置衝突的資原始檔:
drawable/
drawable-en/
drawable-fr-rCA/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/ -
查表,從最高優先順序(MCC)開始,若該限定符存在,則淘汰其它所有不含此限定符的資料夾,否則順位取下一個優先順序的限定符,本例先淘汰非en資料夾(en-GB > port > hdpi > notouch > 12key):
drawable/
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/ -
繼續淘汰非port資料夾:
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
僅剩一個資料夾了!所以該手機採用的就是 drawable-en-port 下的資源!
流程圖:
注:限定符的優先順序比與裝置完全匹配的限定符數量更加重要。本例中,drawable-port-notouch-12key 包含更多相匹配的限定符仍然被淘汰了,就是因為它使用的限定符優先順序比較低。
適配套路
-
挑出需要特殊適配的資源。這裡的資源主要指drawable、layout 和dimens;
-
設計師根據所需適配的解析度分別切圖和標註(畫素為單位);
-
開發人員將設計師提供的切圖放至對應的drawable資料夾下;
-
開發人員根據標註計算出對應的dp值,編寫dimens.xml,並放至對應的values資料夾中。
小技巧
有人問,這麼多解析度,我怎麼知道是xxdpi還是xxxdpi的資料夾?
So easy!
public class DisplayMetrics {
public static final int DENSITY_260 = 260;
public static final int DENSITY_280 = 280;
public static final int DENSITY_300 = 300;
public static final int DENSITY_340 = 340;
public static final int DENSITY_360 = 360;
public static final int DENSITY_400 = 400;
public static final int DENSITY_420 = 420;
public static final int DENSITY_560 = 560;
public static final int DENSITY_DEFAULT = 160;
public static final int DENSITY_DEVICE_STABLE = 0;
public static final int DENSITY_HIGH = 240;
public static final int DENSITY_LOW = 120;
public static final int DENSITY_MEDIUM = 160;
public static final int DENSITY_TV = 213;
public static final int DENSITY_XHIGH = 320;
public static final int DENSITY_XXHIGH = 480;
public static final int DENSITY_XXXHIGH = 640;
...省略若干行
複製程式碼
-
Android Studio中,雙擊shift,輸入DisplayMetrics,檢視class檔案(不要檢視java原始碼,註釋太多了!),看看是不是應有盡有?
-
什麼?你不知道手機的ppi是多少?easy!還是這個類,用下面這個操作即可:
int densityDpi = Resources.getSystem().getDisplayMetrics().densityDpi; // 畫素密度,值為320,480...
float density = Resources.getSystem().getDisplayMetrics().density; // dp 跟 px 的換算關係,值為1.5,2,3...
複製程式碼