sp — Scale-independent Pixels,Android 中文本的度量单位,考虑了设备上的字体大小设置。与 dp 不同,sp 会随系统字体大小一起缩放,确保界面的可访问性。文章解释了 sp 和 dp 之间的区别,展示了 XML 和 Kotlin 中的 textSize 配置,并揭示了 Android 中排版的最佳实践。
要点
sp (scale-independent pixel) — Android 中字体大小的度量单位,根据用户的系统设置自动缩放。当设备所有者在设置中增大字体大小时,所有具有 sp 值的元素会按比例增大。这确保了视力较弱人群的界面可访问性。
基本比例:在标准字体缩放 1.0(normal)下,1 sp = 1 dp。如果用户设置 Large (1.15×) 字体,16 sp 将变为 18.4 物理像素。在 Small (0.85×) 字体大小下,同样的 16 sp 将变为 13.6 px。缩放机制通过 Settings.System.FONT_SCALE 在 Android Framework 级别管理。
根据 Google Material Design Guidelines,文本应完全以 sp 设置。例外情况——在尺寸对布局至关重要的组件中的固定标签(例如,固定高度按钮内的文本)。在这种情况下,建议使用 dp 并手动控制可访问性。
sp 和 dp 之间的区别——Android 开发者面试和实际 code review 中最常见的问题之一。两个单位都绑定到 density-independent pixel(160 dpi = 1 dp = 1 px 在 mdpi 屏幕上),但在系统字体大小变化时表现不同。
| 特性 | sp | dp |
|---|---|---|
| 用途 | 文本大小 | 元素尺寸、间距、宽度、高度 |
| 随字体缩放 | 是(scale factor) | 否 |
| 随密度缩放 | 是 | 是 |
| 设置 API | android:textSize | android:layout_width, layout_height, padding, margin |
| Material Design Token | typescale | spacing, sizing |
在实践中,错误——对文本使用 dp。如果文本以 dp 设置,字体增大时不会缩放,使界面对视障用户不可访问。相反的错误——对按钮高度或间距使用 sp:字体增大时,按钮可能会"膨胀",破坏布局。
sp 缩放是在 Android Framework 级别的 TypedValue 类中实现的。换算公式:sp = dp × scaledDensity,其中 scaledDensity 是同时考虑屏幕密度和用户字体设置的缩放系数。
// 在 Kotlin 中以编程方式获取 scaledDensity
val scaledDensity = with(context.resources) {
displayMetrics.scaledDensity
}
// 将 sp 转换为 px
fun spToPx(sp: Float, context: Context): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
sp,
context.resources.displayMetrics
)
}
// 将 px 转换为 sp
fun pxToSp(px: Float, context: Context): Float {
return px / context.resources.displayMetrics.scaledDensity
}TypedValue.applyDimension 方法——在运行时将 sp 转换为像素的唯一正确方法。直接乘以 scaledDensity 不太可靠,因为它不处理边缘情况(零值、溢出)。scaledDensity 在通过 Settings > Display > Font size 更改字体设置时发生变化。
Android 提供五个预设的字体缩放级别:Small (0.85×)、Default (1.0×)、Large (1.15×)、Largest (1.3×) 以及 Android 14+ 中的额外级别 Very Large (>1.3×)。设备制造商(Samsung、Xiaomi)会添加自己的级别——最高可达 2.0×。sp 中的最小文本大小永远不保证固定的像素数。
在 XML 中,sp 仅用于 android:textSize 属性。所有其他尺寸——width、height、padding、margin、elevation——都以 dp 设置。规则很简单:如果元素显示文本——其大小用 sp,如果元素定义几何形状——大小用 dp。
<!-- 带有 textSize sp 的 TextView 布局 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textSize="16sp"
android:padding="12dp"
android:lineSpacingExtra="4dp" />
<!-- 使用 dimens 资源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/text_body_large" />建议将文本尺寸提取到 dimens.xml 文件中。这简化了排版的维护,允许为不同的屏幕配置覆盖尺寸,并确保整个应用程序的一致性。
<!-- res/values/dimens.xml -->
<resources>
<!-- Material Design Type Scale -->
<dimen name="text_display_large">34sp</dimen>
<dimen name="text_display_medium">28sp</dimen>
<dimen name="text_headline_large">24sp</dimen>
<dimen name="text_headline_medium">20sp</dimen>
<dimen name="text_title_large">18sp</dimen>
<dimen name="text_body_large">16sp</dimen>
<dimen name="text_body_medium">14sp</dimen>
<dimen name="text_label_large">14sp</dimen>
<dimen name="text_label_small">11sp</dimen>
</resources>Material Design Type Scale 包含 13 个尺寸级别——从用于小标签的 11sp 到用于大显示标题的 57sp。Android 项目通常使用 8–10 个级别。所有值应为 1sp 的倍数——小数值 (15.5sp) 会因亚像素定位而降低渲染效果。
在现代 Android 开发中,很少直接通过 sp 中的 textSize 设置文本。取而代之的是使用 MaterialTheme.typography 系统,其中每个样式都是预定义的,包括大小、粗细、行间距和字间距。
// 通过 Typography API 实现自定义排版
val AppTypography = Typography(
displayLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 34.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp
),
headlineLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 24.sp,
lineHeight = 32.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 18.sp,
lineHeight = 24.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)Jetpack Compose 中的 .sp 扩展函数会自动转换为 scale-independent pixels。当系统字体大小变化时,Compose 会根据新的 scaledDensity 重新计算 fontSize、lineHeight 和 spacing。TextUnit(表示 sp 的类)确保文本缩放,而间距则不缩放。
在 Jetpack Compose 中,sp 由 TextUnit 类型表示,不能意外用于元素尺寸。Kotlin 编译器会在 TextUnit 被传递到预期为 Dp 的位置时报告错误。这消除了与错误缩放相关的一整类错误。
// Compose 中的 TextUnit — 安全使用 sp
@Composable
fun ArticleCard(title: String, description: String) {
Card(modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium
)
}
}
// 在 sp 中显式设置 fontSize
Text(
text = "自定义大小",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)Compose 中没有 XML 属性 android:textSize 的直接等效项——文本大小通过 TextStyle.fontSize 设置。MaterialTheme.typography 系统提供与 Material Design Type Scale 对应的预定义样式。如果需要自定义大小,则使用字面量 20.sp——语言保证这是 TextUnit,而不是 Dp。
正确使用 sp 是可访问 Android 应用程序的基础。以下是根据 Google Material Design 和 Android Developers Guide 的建议收集的规则。
可访问性的主要规则:用户应该能够在不损失功能的情况下增大字体。如果应用程序在 1.3× 字体缩放下正常工作,它将通过基本的可访问性测试。如果 textSize 以 dp 设置且界面不缩放,Google Play 可能会拒绝该应用程序。
常见问题
sp (scale-independent pixel) 在系统字体大小变化时缩放,而 dp 则不会。sp 仅用于文本,dp 用于所有其他元素尺寸。换算系数:sp = dp × FONT_SCALE。
不可以。间距、按钮高度、卡片宽度应以 dp 设置。对间距使用 sp 会导致字体增大时元素不自然地增长,并可能超出屏幕边界。
打开 Settings > Display > Font size 并设置最大值 (Largest)。启动应用程序并检查所有文本是否可读、元素是否重叠、按钮是否超出屏幕边界。对于自动化,请使用 Espresso 和 UiAutomator。
Material Design 建议 labelSmall 的最小尺寸为 11sp。对于正文——从 14sp (bodyMedium) 到 16sp (bodyLarge)。不建议使用小于 11sp 的尺寸,因为在像素密度高的设备上会变得不可读。
文本将以固定大小显示,不受系统字体设置影响。字体放大的用户将无法舒适地阅读此类文本。这违反了 WCAG 可访问性要求,并可能导致应用程序在 Google Play 中被拒绝。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。