在 Android 开发中,如果你还在用 px 来布局,那真得是“像素级冒险”!
记住三句话就能避坑:
- ✅ 布局用 dp —— 保证在不同分辨率下视觉一致;
- ✅ 字体用 sp —— 自动跟随系统字体大小调整,提升可访问性;
- ✅ 像素密度 dpi —— 决定 dp 与 px 的换算关系,是屏幕适配的核心指标。
一、关键名词解析
px(Pixels)
屏幕的最小显示单位。
同样是 100px,在小屏高密度手机上看起来会比大屏低密度手机小很多。
👉 不建议直接在布局中使用。
分辨率(Resolution)
屏幕在水平和垂直方向上的总像素数,例如 2720x1260。
它仅描述像素数量,不代表物理显示大小。
dpi(dots per inch, 像素密度)
每英寸长度上排列的像素点数量,决定显示细腻度。
Android 将设备按 dpi 划分为密度桶(Density Bucket),常见有:
| 等级 | 标准 dpi | dp 转 px 换算比例 |
|---|---|---|
| ldpi | ~120 | 0.75 |
| mdpi | ~160 | 1.0 |
| hdpi | ~240 | 1.5 |
| xhdpi | ~320 | 2.0 |
| xxhdpi | ~480 | 3.0 |
| xxxhdpi | ~640 | 4.0 |
dp(device-independent pixel)
开发中最常用的长度单位,旨在让同一数值在不同 dpi 的设备上呈现接近相同的物理尺寸。
公式:
px = dp * (dpi / 160)
例如:在 320dpi 的屏幕上,1dp = 2px。
sp(scale-independent pixel)
专用于字体大小,类似 dp,但会额外受到用户系统字体设置的影响。
所有文字大小都应使用 sp。
二、计算规则与实例(小米 / 华为机型)
我们选取两款主流机型作为实例:小米 14(小屏旗舰)和华为 Mate 60 Pro(大屏旗舰),通过对比计算,更直观地理解单位换算逻辑。
实例 1:小米 14(小屏旗舰)
已知参数(来自小米官方数据):
- 屏幕尺寸:6.36 英寸
- 分辨率:2670 × 1200 像素
- 官方标称 PPI(像素密度) :460
验证对角线像素首先通过分辨率计算屏幕对角线的像素总数,公式为勾股定理:
对角线像素 = √(横向像素² + 纵向像素²)
计算过程:√(2670² + 1200²) = √(7,128,900 + 1,440,000) = √8,568,900 ≈ 2927 px
计算 dpi (像素密度) 用对角线像素除以屏幕实际物理尺寸(英寸):
dpi = 对角线像素 / 屏幕尺寸 = 2927 px / 6.36 in ≈ 460 dpi
这与官方标称的 460 PPI 完全一致。根据 Android 密度桶划分标准,460dpi 属于 xxhdpi 密度桶(范围 320-479dpi)。
dp 与 px 的换算公式:
px = dp * (dpi / 160)
假设设置一个 120dp 的按钮宽度:
在小米 14 上:120 * (460 / 160) ≈ 345 px
这意味着在小米 14 的屏幕上,120dp 会被渲染为约 345 个物理像素点。
实例 2:华为 Mate 60 Pro(大屏旗舰)
已知参数(来自华为官方数据):
- 屏幕尺寸:6.82 英寸
- 分辨率:2720 × 1260 像素
计算对角线像素:
对角线像素 = √(2720² + 1260²) = √(7,398,400 + 1,587,600) = √8,986,000 ≈ 2998 px
计算 dpi (像素密度):
dpi = 2998 px / 6.82 in ≈ 439 dpi
439dpi 同样属于 xxhdpi 密度桶,与小米 14 处于同一区间,但数值略低。
dp 与 px 的换算同样以 120dp 的按钮宽度为例:
在华为 Mate 60 Pro 上:120 * (439 / 160) ≈ 329 px
虽然换算出的 px 数值比小米 14 小,但由于华为 Mate 60 Pro 屏幕尺寸更大,120dp 对应的物理宽度与小米 14 上的 120dp 几乎一致(约 7.5 毫米),这正是 dp 作为 “设备独立像素” 的核心价值。
对比总结:
- 小米 14(460dpi) → 345px
- 华为 Mate 60 Pro(439dpi) → 329px
尽管像素数不同,但物理尺寸几乎相同,确保了跨设备一致性。
三、开发最佳实践
XML 布局
<Button
android:layout_width="120dp"
android:layout_height="48dp"
android:layout_marginTop="20dp"
android:text="点击按钮"
android:textSize="16sp" />
Kotlin 工具类
object DisplayUtils {
fun dpToPx(context: Context, dp: Float): Int {
val metrics = context.resources.displayMetrics
return (dp * metrics.density + 0.5f).toInt()
}
fun spToPx(context: Context, sp: Float): Int {
val metrics = context.resources.displayMetrics
return (sp * metrics.scaledDensity + 0.5f).toInt()
}
fun getDeviceDpi(context: Context): Int {
return context.resources.displayMetrics.densityDpi
}
}
四、屏幕适配方案
方案 1:官方推荐 — smallestWidth 限定符
自动生成不同屏幕下的 dp 文件,适合新项目或使用 smallestWidth 插件。
values-sw360dp/
values-sw480dp/
values-sw600dp/
方案 2:手动适配(老项目)
通过比例计算控件实际 px 值,保留老布局结构,常用于渐进式重构。
val epicDensity = designPpi / 160
val epicBaseWidth = designWidthPx / epicDensity
val epic = controlWidthPx / epicDensity
val actualPx = (epic * context.resources.displayMetrics.widthPixels / epicBaseWidth)
五、实用建议
- 布局尺寸 一律用 dp
- 文字大小 一律用 sp
- 图片资源 提供多分辨率版本
- 避免直接使用 px,除非特殊绘制或 Canvas 逻辑
总结
dp 让布局更通用,sp 让文字更友好,dpi 决定显示更精准。
掌握 dp、sp 和 dpi 的关系,你的 UI 在任何 Android 设备上都能保持“颜值稳定”。