4. 分辨率适配核心

在 Android 屏幕适配体系中,分辨率适配并不是简单地“把图片放大缩小”,而是围绕密度无关像素(dp/sp)、资源限定符和资源目录结构三件事协同工作。理解这三者的关系,才能让同一套代码在不同尺寸、不同像素密度的设备上呈现一致的用户体验。

一、核心原理:dp、sp 与 px 的换算关系

Android 用 dp(density-independent pixel,密度无关像素)作为布局尺寸单位,用 sp(scale-independent pixel,缩放无关像素)作为字体单位。它们最终都会被系统换算成物理像素 px 再绘制到屏幕上。换算公式如下:

px = dp × (dpi / 160)
px = sp × (dpi / 160) × fontScale

其中:
dpi        = 屏幕像素密度(每英寸像素数)
density    = dpi / 160(密度倍数)
fontScale  = 用户系统字体缩放比例,默认 1.0

例如在 320dpi 的设备上,density = 2.0,那么 1dp = 2px,100dp 的按钮实际占 200px。若直接写 100px,在 480dpi 设备上就会显得过小。因此布局尺寸一律用 dp,文字一律用 sp,这是适配的第一原则。

关键结论:dp 解决的是“不同密度下物理尺寸一致”,sp 解决的是“尊重用户字体偏好”。二者不能混用,也不要用 px 写死布局。

二、资源限定符:让系统自动挑选最合适的资源

Android 的资源系统允许在 res/ 下用限定符命名目录,系统会根据当前设备配置自动匹配最合适的资源。常见限定符包括:

  • 密度限定符:drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxhdpi,用于按密度提供不同倍率的位图。
  • 尺寸限定符:layout-sw600dp、layout-w600dp,用于平板或大屏设备。
  • 方向限定符:layout-land、layout-port,用于横竖屏切换。
  • 语言限定符:values-zh、values-en,用于多语言。

匹配规则遵循“优先最具体、就近匹配”的原则:系统先找同时满足密度、尺寸、方向等条件的目录,找不到再逐级回退。因此目录命名越精确,适配越可控。

三、drawable 与 mipmap 目录结构

很多初学者会疑惑:为什么应用图标放在 mipmap 而不是 drawable?原因是启动图标可能被启动器以不同密度缓存和缩放,mipmap 目录会保留所有密度的资源副本,不随 APK 优化被裁剪,从而保证图标清晰。普通图片、背景、形状资源则放在 drawable 系列目录中。

典型目录结构如下:

res/
├── drawable/                 # 默认密度资源(无密度限定)
├── drawable-mdpi/            # 1x   基准
├── drawable-hdpi/            # 1.5x
├── drawable-xhdpi/           # 2x
├── drawable-xxhdpi/          # 3x
├── drawable-xxxhdpi/         # 4x
├── mipmap-mdpi/              # 启动图标 1x
├── mipmap-hdpi/              # 启动图标 1.5x
├── mipmap-xhdpi/             # 启动图标 2x
├── mipmap-xxhdpi/            # 启动图标 3x
├── mipmap-xxxhdpi/           # 启动图标 4x
├── layout/                   # 默认布局
├── layout-land/              # 横屏布局
├── layout-sw600dp/           # 最小宽度 600dp 布局
└── values/
    ├── dimens.xml            # 尺寸常量
    └── styles.xml            # 样式

下面这张图展示了从“设计稿尺寸”到“设备实际像素”的完整适配链路,以及 drawable 与 mipmap 在其中的位置:

分辨率适配链路:设计稿 → 资源目录 → 设备像素 设计稿 360dp × 640dp 标注单位:dp / sp 资源目录(限定符匹配) drawable-xhdpi / drawable-xxhdpi mipmap-xxxhdpi(启动图标) layout-sw600dp / layout-land values/dimens.xml 设备实际渲染 density = dpi / 160 px = dp × density 密度限定符与倍率对照 限定符 dpi density 1dp = ?px 典型设备 mdpi 160 1.0 1px 早期低端机 hdpi 240 1.5 1.5px 中端机型 xhdpi 320 2.0 2px 主流 1080P xxhdpi 480 3.0 3px 旗舰 2K xxxhdpi 640 4.0 4px 超高清屏

四、案例场景:一套布局适配手机与平板

假设设计稿基于 360dp 宽度,要求手机单列、平板双列。做法是:默认 layout/activity_main.xml 写单列,另建 layout-sw600dp/activity_main.xml 写双列,两个文件同名。系统在最小宽度 ≥600dp 的设备上自动加载双列布局,无需在 Java/Kotlin 中判断屏幕尺寸。

res/layout/activity_main.xml          → 手机单列
res/layout-sw600dp/activity_main.xml  → 平板双列

<!-- dimens.xml 中统一管理间距 -->
<dimen name="page_margin">16dp</dimen>
<dimen name="card_gap">12dp</dimen>

图片资源则按密度放置:同一张 48dp 的图标,在 drawable-mdpi 放 48px,drawable-xhdpi 放 96px,drawable-xxhdpi 放 144px。系统按设备密度选择最接近的版本,避免拉伸模糊。

五、常见误区与复盘

误区一:只放一套 drawable 图片。系统会强制缩放,导致高密度设备图片模糊或低密度设备内存浪费。
误区二:用 px 写布局。不同密度下物理尺寸不一致,是适配事故的高发点。
误区三:把启动图标放 drawable。启动器缓存图标时可能被裁剪,应使用 mipmap。
误区四:滥用 layout-sw600dp。若业务只需手机适配,过度增加限定符目录会提高维护成本。
复盘建议:新建项目时先建立 drawable-mdpi ~ xxxhdpi、mipmap-mdpi ~ xxxhdpi 的完整目录骨架,再在 dimens.xml 中集中定义间距与字号。这样后续新增页面时只需引用常量,适配成本最低。

总结来看,分辨率适配的核心是“用 dp/sp 描述逻辑尺寸,用限定符目录描述资源差异,用 drawable/mipmap 区分普通资源与图标资源”。三者配合,才能让应用在从 320dpi 到 640dpi、从手机到平板的广泛设备上保持一致的视觉与交互体验。