阅读 452

Android 资源限定符命名规则

读书笔记,如需转载,请注明作者: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...
复制代码

Google官方适配教程

关注下面的标签,发现更多相似文章
评论