Android 資源限定符命名規則

Youlou發表於2019-02-26

讀書筆記,如需轉載,請註明作者:Yuloran (t.cn/EGU6c76)

吐槽

Android開發者真的挺苦逼的,不僅要適配常規解析度,如 480x854、720x1280、1080x1920、1440x2560,還要適配各種奇葩解析度,如 720x1440、1080x2160...

適配原因

不同解析度和螢幕畫素密度的手機,其寬高換算成dp後,有可能是不同的。如下表(Google規定:在160dpi的手機上,1dp = 1px):

注:dpi 跟 ppi 一個意思,都表示每英寸多少個畫素點

解析度(畫素) 畫素密度(dpi) 寬(dp) 高(dp)
480x854 240 320 569.3
1080x1920 480 360 640

所以在1080x1920、480ppi的手機上,可以完整顯示330dp寬的按鈕,而在480x854、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 是指“佈局方向從右到左”;
ldltr 是指“佈局方向從左到右”,這是預設的隱式值。
API17
最小寬度 swdp

示例:
sw320dp
sw360dp
...
限定資源僅適用於最小螢幕寬度為Ndp的手機(不考慮螢幕方向)。

比如1080x1920 480ppi的手機,最小寬度限定符就是sw360dp;
720x1280 320ppi的手機,最小寬度限定符也是sw360dp。
API 13
可用寬度 wdp
示例:
w320dp
...
限定資源僅適用於最小可用螢幕寬度為Ndp的手機(考慮螢幕方向)。

一般情況下,該限定符的值與swdp相同。
API 13
可用高度 hdp

示例:
h655dp
...
限定資源僅適用於最小可用螢幕高度為Ndp的手機(考慮螢幕方向)。

計算N的時候必須減去StatusBar和NavigationBar的高度。

比如1080x2160 480ppi的手機,若StatusBar高度為25dp,NavigationBar高度為40dp,則N = 720 - 25 - 40 = 655,即可用高度限定符為h655dp。
API 13
螢幕尺寸 small
normal
large
xlarge
small:QVGA,320x426(dp)
normal:HVGA,320x470(dp)
large:VGA,480x640(dp)
xlarge:720x960(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 密度)。因此,9x9 (ldpi) 點陣圖相當於 12x12 (mdpi)、18x18 (hdpi)、24x24 (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也可以使用。

限定符命名規則

  1. 可以同時使用多個限定符,用"-"連線。如values-sw360dp-h655dp-port;

  2. 不區分大小寫;

  3. 同一型別的限定符只能出現一次;

  4. 限定符的使用順序必須嚴格按照上表規定的優先順序,否則會直接編譯出錯。如:

D:\HardwareDetection\app\src\main\res\values-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

那麼該手機最終會採用哪個資料夾下的資源呢?

  1. 淘汰與裝置配置衝突的資原始檔: drawable/ drawable-en/ drawable-fr-rCA/ drawable-en-port/ drawable-en-notouch-12key/ drawable-port-ldpi/ drawable-port-notouch-12key/

  2. 查表,從最高優先順序(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/

  3. 繼續淘汰非port資料夾: drawable-en/ drawable-en-port/ drawable-en-notouch-12key/

僅剩一個資料夾了!所以該手機採用的就是 drawable-en-port 下的資源!

流程圖:

限定符淘汰規則.png

:限定符的優先順序比與裝置完全匹配的限定符數量更加重要。本例中,drawable-port-notouch-12key 包含更多相匹配的限定符仍然被淘汰了,就是因為它使用的限定符優先順序比較低。

適配套路

  1. 挑出需要特殊適配的資源。這裡的資源主要指drawable、layout 和dimens;

  2. 設計師根據所需適配的解析度分別切圖和標註(畫素為單位);

  3. 開發人員將設計師提供的切圖放至對應的drawable資料夾下;

  4. 開發人員根據標註計算出對應的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...
複製程式碼

Android 資源限定符命名規則

Google官方適配教程

相關文章