上一篇写 Text、Button、Toggle 时,代码里已经出现了很多数字:
Text('消息通知')
.fontSize(18)
.margin({ top: 6 })
Button('保存设置')
.width('100%')
.height(44)
这些数字不是随手写的。它们背后有单位,只是 ArkUI 在很多属性上允许省略单位。刚开始学 HarmonyOS 时,如果你带着 Android 的习惯直接套 dp、sp、px,很容易把三件事混在一起:
- 元素尺寸:卡片多宽、按钮多高、内边距多少。
- 文字尺寸:标题多大、说明文字多大。
- 真实像素:截图、图片、Canvas、设备屏幕上的物理像素。
这篇先从屏幕和像素讲起,再回到代码里:写页面尺寸时用什么单位,写文字时用什么单位,什么时候才需要关心 px。
先把屏幕上的 px 讲清楚
先看几个买手机、买显示器时经常出现的词:1080p、2K、4K、8K、16:9、20:9。
它们说的不是同一件事:
16:9、20:9说的是屏幕宽高比例,也就是横向和纵向的比例关系。1080p、2K、4K、8K通常说的是分辨率级别,也就是屏幕横向和纵向大约有多少个像素点。px是物理像素,屏幕最终发光显示出来的一个个点。
例如一块 1920 × 1080 的屏幕,一共有 1920 列、1080 行物理像素;一块 3840 × 2160 的 4K 屏幕,横纵像素数量都更多。问题在于:像素数量变多,不等于屏幕一定变大。手机屏幕可以很小但像素很密,电视屏幕可以很大但观看距离也更远。
这里就引出了 dpi。dpi 是 dots per inch,意思是“每英寸有多少个点”。严格说,屏幕领域更常见的物理指标是 ppi(pixels per inch,每英寸像素数),但 Android 文档和开发语境里经常用 dpi 来表示屏幕密度。写 UI 时可以先把它理解成:同样一英寸长度里塞进多少个像素点。塞得越多,密度越高,画面越细腻,同样数量的 px 看起来也越小。
举个直观的例子:
同样是 1080 × 1920 像素
5 英寸手机屏:像素挤得更密,dpi 更高
10 英寸平板屏:像素分布得更开,dpi 更低
所以判断界面元素大小时,不能只看分辨率,还要看屏幕物理尺寸和像素密度。px 只告诉你用了多少个像素点,dpi 才说明这些像素点在真实屏幕上挤得有多密。
如果按对角线粗略估算,上面两个屏幕的像素密度差异会非常明显:
1080 × 1920,5 英寸:大约 441 ppi/dpi
1080 × 1920,10 英寸:大约 220 ppi/dpi
同样是 44px,放在 5 英寸手机上只占很短一段物理长度;放在 10 英寸平板上会变长很多。dpi 这个概念要解决的就是:把“像素个数”换算到“真实屏幕上的视觉大小”时,必须知道像素密度。
如果应用直接用 px 写界面,就会碰到一个很尴尬的问题:
同样写 44px
低密度屏幕:看起来还行
高密度屏幕:因为像素太密,看起来会变得很小
UI 布局真正关心的不是“占多少个物理像素”,而是“在人眼看来占多大一块视觉空间”。这就是 Android 演进出 dp / sp,HarmonyOS ArkUI 使用 vp / fp 的原因:开发者写一个相对稳定的视觉单位,系统再根据设备密度、字体设置换算成最终的 px。
可以先用一张表把关系放住:
| 层次 | Android | HarmonyOS ArkUI | 解决的问题 |
|---|---|---|---|
| 物理像素 | px |
px |
屏幕最终显示多少个像素点 |
| 布局视觉尺寸 | dp |
vp |
不同屏幕密度下,按钮、间距、圆角看起来大小接近 |
| 文字视觉尺寸 | sp |
fp |
文字既适配屏幕密度,也照顾用户字体大小设置 |
底层换算可以粗略理解成两步:
布局/文字单位 --乘以设备密度和字体缩放--> 物理像素 px
Android 官方常用的密度换算公式是:
px = dp * (dpi / 160)
其中 160dpi 是 Android 早期定义的基准密度,dpi / 160 可以理解为密度倍率。160dpi 附近的设备上,1dp 大约对应 1px;320dpi 设备上,密度倍率约为 2,1dp 大约对应 2px;到了更高密度的屏幕,1dp 会换算成更多 px。这样同样写 16dp,系统会在高密度屏幕上使用更多物理像素,换来接近的视觉大小。
把数字代进去会更直观:
160dpi:44dp * (160 / 160) = 44px
320dpi:44dp * (320 / 160) = 88px
480dpi:44dp * (480 / 160) = 132px
按钮在高密度屏幕上用了更多物理像素,但开发者写的仍然是 44dp。这就是 dp / vp 这类单位存在的价值:开发者表达视觉尺寸,平台负责换算成最终像素。
sp 在 dp 的密度换算基础上,还会叠加用户字体缩放。HarmonyOS 侧的 vp 和 fp 也承担类似职责:vp 负责布局视觉尺寸,fp 负责字体尺寸。日常写页面时,先不要从 px 出发,而是先判断这个数字属于布局、文字,还是渲染结果。
先看一张设置卡片里的单位
还是用上一节的通知设置卡片。把代码里的数字按用途标一下:
@Component
struct NotifySettingCard {
@State enableNotify: boolean = true
build() {
Column() {
Row() {
Column() {
Text('消息通知')
.fontSize(18) // 文字大小:18fp
.fontWeight(FontWeight.Medium)
Text(this.enableNotify ? '已开启,重要消息会及时提醒' : '已关闭,你可能错过重要消息')
.fontSize(13) // 文字大小:13fp
.fontColor('#6B7280')
.margin({ top: 6 }) // 间距:6vp
}
.layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: this.enableNotify })
.onChange((isOn: boolean) => {
this.enableNotify = isOn
})
}
.width('100%')
.alignItems(VerticalAlign.Center)
Button('保存设置')
.width('100%')
.height(44) // 高度:44vp
.margin({ top: 16 }) // 间距:16vp
.onClick(() => {
console.info(`save notify setting: ${this.enableNotify}`)
})
}
.width('100%')
.padding(16) // 内边距:16vp
.backgroundColor(Color.White)
.borderRadius(12) // 圆角:12vp
}
}
在 ArkUI 里,可以先按这个规则记:
- 宽度、高度、padding、margin、圆角这类布局尺寸,优先理解为
vp。 fontSize这类文字尺寸,优先理解为fp。- 只有在处理图片原始尺寸、Canvas 绘制、屏幕截图、设备像素密度时,再把
px拿出来。
这个规则不是为了背概念,而是为了避免最常见的错误:把“元素尺寸”和“文字尺寸”都当成同一种数字。
Android 侧怎么写:dp 管尺寸,sp 管文字
Android 里对应的写法更常见:
@Composable
fun NotifySettingCard() {
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(12.dp))
.padding(16.dp)
) {
Text(
text = "消息通知",
fontSize = 18.sp,
fontWeight = FontWeight.Medium
)
Text(
text = "已开启,重要消息会及时提醒",
fontSize = 13.sp,
color = Color(0xFF6B7280),
modifier = Modifier.padding(top = 6.dp)
)
Button(
onClick = { },
modifier = Modifier
.fillMaxWidth()
.height(44.dp)
.padding(top = 16.dp)
) {
Text("保存设置")
}
}
}
Android 的日常规则可以写得很直白:
dp用于布局尺寸:宽高、间距、圆角、阴影偏移、点击区域。sp用于文字尺寸:标题、正文、说明文字。px是屏幕上的真实像素,不建议直接拿来写普通布局。
dp 的核心是屏幕密度无关。回到前面的换算逻辑,同样写 16.dp,在低密度屏幕上会占用较少物理像素,在高密度屏幕上会占用更多物理像素。开发者写的是“视觉上的 16 个密度无关单位”,系统再换算成真实像素。
sp 可以理解为“用于字体的 dp”。它不只考虑屏幕密度,还要考虑用户的字体大小设置。用户把系统字体调大时,sp 文字会跟着变大;普通 dp 间距不会因为字体设置直接变大。
HarmonyOS 侧怎么写:vp 管尺寸,fp 管文字
HarmonyOS ArkUI 里,常见的是 vp 和 fp:
Column() {
Text('消息通知')
.fontSize(18) // 18fp
Text('已开启,重要消息会及时提醒')
.fontSize(13) // 13fp
.margin({ top: 6 }) // 6vp
Button('保存设置')
.height(44) // 44vp
.margin({ top: 16 }) // 16vp
}
.padding(16) // 16vp
.borderRadius(12) // 12vp
如果你从 Android 过来,可以先建立这组对应关系:
Android dp ≈ HarmonyOS vp 用于布局尺寸
Android sp ≈ HarmonyOS fp 用于文字尺寸
Android px ≈ HarmonyOS px 真实物理像素
这里的“≈”很重要。它不是说两边内部实现完全一样,而是说它们承担的开发职责相近:
- Android 用
dp避免布局在不同屏幕密度下忽大忽小。 - HarmonyOS 用
vp表达屏幕密度无关的视觉尺寸。 - Android 用
sp让文字跟随用户字体设置。 - HarmonyOS 用
fp表达字体像素,适合文字大小。
如果只问“布局尺寸”这件事,dp 和 vp 基本可以当成同类概念:它们都不是物理像素,都要按设备密度换算成最终 px;都用来避免 44px 这种写法在高密度屏幕上变得太小。
所以这里的“≈”不要理解成“差异很大,只能勉强类比”。更准确的说法是:对业务 UI 来说,dp 和 vp 的主要差异不是设计目标,而是平台命名与框架默认单位规则不同。
Android 的 dp 明确挂在 Android 的 density 体系上,基准是 160dpi,换算时看的是 Android 运行环境给出的密度倍率。Compose 里通常把这个单位写得很显性:16.dp、44.dp。
HarmonyOS ArkUI 的 vp 是 ArkUI 的虚拟像素单位,框架以 vp 作为很多尺寸属性的默认单位,再按当前设备/窗口的显示密度换算成物理像素。你写 .padding(16) 时,看起来只是一个数字,但它不是裸 px,而是这个属性默认按 vp 解释。
这组“≈”可以直接读成:
dp 和 vp:都是布局视觉尺寸单位
日常写页面时可以按同类概念理解
区别主要在平台命名、代码写法和默认单位规则
放到代码里,差异主要落在三层:
| 差异点 | Android | HarmonyOS ArkUI |
|---|---|---|
| 代码写法 | Compose 里通常显式写 16.dp、18.sp |
ArkUI 很多属性可直接写数字,布局属性默认按 vp,字体属性默认按 fp |
| 类型体系 | Dp、TextUnit 这类类型在代码里更显性 |
更依赖组件属性语义:.padding(16) 和 .fontSize(16) 看起来都是数字,但默认单位不同 |
| 额外单位 | 日常主要是 dp、sp、px |
除了 vp、fp、px,还常见 lpx,用于按设计稿宽度做比例缩放 |
因此,dp ≈ vp 的重点不是强调差异,而是建立对应关系:Android 写布局尺寸时优先用 dp,ArkUI 写布局尺寸时优先用 vp。 如果你把一个 Android 页面按视觉稿迁到 ArkUI,16dp -> 16vp 通常就是合理的起点;后面再根据目标设备和设计验收微调即可。
所以把 Android UI 搬到 ArkUI 时,不要机械地把 16dp 改成 16px。更接近的写法通常是:
16dp -> 16vp
18sp -> 18fp
在 ArkUI 代码里,很多 API 可以直接写数字,所以你看到的是 .padding(16)、.fontSize(18)。这不代表“没有单位”,而是对应属性有默认单位:布局尺寸按 vp 理解,字体大小按 fp 理解。
px:它不是不能用,而是不该管普通布局
px 是物理像素。手机屏幕最终当然都是由像素显示出来的,但应用代码不应该把普通布局直接绑死在 px 上。
假设你写一个按钮高度:
Button('保存设置')
.height('44px')
这个按钮在不同屏幕密度上会出现明显差异:同样 44 个物理像素,在高密度屏幕上会显得更小。页面不是按“物理像素个数”设计的,而是按“人眼看到的大小”设计的。
px 更适合这些场景:
- 处理图片原始宽高,例如一张图片实际是
1080px * 1920px。 - 做 Canvas 或自定义绘制时,需要和像素缓冲区、位图、截图结果对齐。
- 做性能或渲染诊断时,需要看设备真实像素密度、截图尺寸、GPU 渲染输出。
普通页面里的宽度、高度、边距、圆角,不要优先使用 px。
lpx:HarmonyOS 里的设计稿比例单位
ArkUI 里还会看到 lpx。它和 vp、fp 的用途不一样。
lpx 更偏向“按设计稿宽度做比例缩放”。例如设计稿以某个宽度作为基准,组件在不同屏幕宽度下按比例换算。这类单位适合某些需要严格按设计稿比例铺开的场景,但不适合所有页面都默认使用。
日常写业务页面时,可以先按这个优先级:
- 布局尺寸:
vp - 文字尺寸:
fp - 特殊设计稿比例:
lpx - 真实像素/绘制/图片:
px
不要把 lpx 当成 vp 的替代品。它解决的是“按设计稿比例缩放”的问题,不是普通布局的默认单位。
一个常见误区:文字也用 vp/dp
有些代码会把所有数字都统一成一个单位:布局用 16,文字也用 16,看起来很整齐,但会带来一个问题:文字没有真正跟随用户字体设置。
Android 里,如果你把文字写成 16.dp,用户调整系统字体大小时,这段文字不会按预期缩放。正确写法是 16.sp。
HarmonyOS 里也类似。文字大小应该落在 fp 这条线上,而不是把所有尺寸都当成 vp。
可以这样记:
布局是空间问题:用 dp / vp
文字是阅读问题:用 sp / fp
像素是渲染结果:用 px
布局关心的是元素之间的相对空间;文字关心的是用户能不能读清楚;像素关心的是屏幕最终怎么画出来。
什么时候需要做单位换算
大多数 UI 代码不需要手动换算。你只需要写 16.dp、18.sp、.padding(16)、.fontSize(18),让系统按设备密度和字体设置去换算。
需要手动换算的情况通常是这些:
- 你拿到的是一张图片的
px尺寸,要把它放进布局里。 - 你在自定义绘制里拿到 Canvas 的物理像素坐标。
- 你要处理手势坐标、截图坐标、缩放比例。
- 你在写跨端对照表,需要说明
dp/vp与px的关系。
如果只是写一张设置卡片,不要先写换算工具。先把属性单位用对。
小结表:这些单位该怎么理解
| 单位 | 平台 | 主要用途 | 可以怎么理解 | 常见错误 |
|---|---|---|---|---|
dp |
Android | 布局尺寸 | density-independent pixel | 用 px 写普通布局 |
sp |
Android | 文字尺寸 | 会受用户字体大小影响的字体单位 | 用 dp 写文字 |
vp |
HarmonyOS / ArkUI | 布局尺寸 | virtual pixel,类似 Android 的 dp 职责 |
把 Android 的 dp 改成 px |
fp |
HarmonyOS / ArkUI | 文字尺寸 | font pixel,类似 Android 的 sp 职责 |
用 vp 统一所有文字 |
px |
两端都有 | 真实物理像素 | 屏幕最终显示的像素点 | 直接拿来写按钮高度和间距 |
lpx |
HarmonyOS / ArkUI | 按设计稿比例缩放 | 适合特定设计稿比例场景 | 当成默认布局单位 |
写 UI 时先分清三类数字:
- 控件占多少空间:
dp/vp - 文字多大:
sp/fp - 屏幕最终多少像素:
px
只要这个边界清楚,后面讲基础属性、列表项、输入框、响应式布局时,数字就不会变成一堆“看起来差不多”的魔法值。
参考素材
- Android:Support different pixel densities:https://developer.android.com/training/multiscreen/screendensities
- Android:Style text in Compose:https://developer.android.com/develop/ui/compose/text/style-text
- Android:Material Design scale in Compose:https://developer.android.com/develop/ui/compose/designsystems/material#scale
- HarmonyOS:像素单位(ArkUI):https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-pixel-units
- HarmonyOS:像素单位(ArkUI V5):https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-pixel-units-V5
- HarmonyOS:单位使用说明:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-common-components-units-V5
// Kai@CodeHubble // 观测坐标:Android-HarmonyOS/2026-05-17