屏幕适配
问题
Android 如何处理不同屏幕尺寸和密度的适配问题?常见的适配方案有哪些?
答案
1. 核心概念
| 概念 | 说明 |
|---|---|
| px | 物理像素点 |
| dp (dip) | 密度无关像素,1dp = 1px(160dpi 屏幕上) |
| sp | 缩放无关像素,用于字体大小(跟随系统字体设置缩放) |
| dpi | 每英寸像素数(dots per inch) |
| density | 密度系数,density = dpi / 160 |
px = dp × density
| 密度级别 | dpi | density | 比例 |
|---|---|---|---|
| ldpi | ~120 | 0.75 | 3 |
| mdpi | ~160 | 1.0 | 4 |
| hdpi | ~240 | 1.5 | 6 |
| xhdpi | ~320 | 2.0 | 8 |
| xxhdpi | ~480 | 3.0 | 12 |
| xxxhdpi | ~640 | 4.0 | 16 |
2. 常见适配方案
方案一:dp + 限定符(官方推荐)
res/
├── values/ # 默认
├── values-sw320dp/ # 最小宽度 ≥ 320dp
├── values-sw360dp/ # 最小宽度 ≥ 360dp
├── values-sw480dp/ # 最小宽度 ≥ 480dp(平板)
├── layout/ # 默认布局
├── layout-sw600dp/ # 平板布局
└── layout-land/ # 横屏布局
<!-- values-sw360dp/dimens.xml -->
<dimen name="title_text_size">18sp</dimen>
<dimen name="margin_horizontal">16dp</dimen>
方案二:ConstraintLayout 百分比布局
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
方案三:修改 density 适配(今日头条方案)
核心原理:px = dp × density,如果以设计稿宽度(如 360dp)为基准,动态修改 density = 屏幕实际宽度 px / 360,则 360dp 在任何设备上都等于屏幕宽度。
object DensityHelper {
private var originalDensity = 0f
private var originalScaledDensity = 0f
fun setCustomDensity(activity: Activity, designWidthDp: Float = 360f) {
val appMetrics = activity.application.resources.displayMetrics
if (originalDensity == 0f) {
originalDensity = appMetrics.density
originalScaledDensity = appMetrics.scaledDensity
}
val targetDensity = appMetrics.widthPixels / designWidthDp
val targetScaledDensity = targetDensity * (originalScaledDensity / originalDensity)
val targetDensityDpi = (targetDensity * 160).toInt()
// 修改 Activity 的 DisplayMetrics
activity.resources.displayMetrics.apply {
density = targetDensity
scaledDensity = targetScaledDensity
densityDpi = targetDensityDpi
}
}
}
注意
修改 density 方案虽然简单有效,但会影响所有 dp 计算,包括第三方库中的 View。需要在每个 Activity 的 onCreate 中调用。
3. 图片适配
不同密度提供对应分辨率的图片:
res/
├── drawable-mdpi/ # 1x
├── drawable-hdpi/ # 1.5x
├── drawable-xhdpi/ # 2x
├── drawable-xxhdpi/ # 3x (推荐只提供这个)
└── drawable-xxxhdpi/ # 4x
实际开发建议
- 通常只需提供 xxhdpi(3x) 一套切图,系统会自动缩放
- 使用 VectorDrawable(SVG) 替代位图,天然适配所有密度
- 纯色/简单形状用 Shape Drawable(XML) 而非图片
常见面试问题
Q1: dp 和 px 的关系?为什么要用 dp?
答案:
px = dp × density。不同设备的物理像素密度不同,如果直接用 px,同样 100px 的按钮在高密度屏幕上会显得很小。使用 dp 后,系统会根据设备密度自动将 dp 转换为合适的 px,使 UI 在不同设备上看起来物理尺寸一致。
Q2: smallestWidth 限定符和普通的 width 限定符有什么区别?
答案:
sw<N>dp(smallestWidth):设备最小宽度(不随旋转变化),即min(宽, 高)。如 sw360dp 表示设备最小宽度 ≥ 360dpw<N>dp(width):设备当前宽度(随旋转变化)。横屏和竖屏下值不同
推荐使用 sw 限定符,因为它不受屏幕旋转影响,行为更可预测。
Q3: Jetpack WindowManager 在多窗口/折叠屏适配中有什么作用?
答案:
Jetpack WindowManager 提供了窗口信息 API,支持:
- 检测折叠屏的折叠状态(
FoldingFeature) - 获取窗口实际布局区域
- 适配分屏/画中画模式
通过监听 WindowLayoutInfo 可以动态调整布局以适应不同窗口形态。
Q4: sp 和 dp 的区别?
答案:
- dp:与系统字体大小设置无关,用于 View 尺寸、边距等
- sp:跟随系统字体大小设置缩放,用于文字大小
如果用户在系统设置中放大了字体,sp 的值会同比增大(sp × density × fontScale),而 dp 不变。所以字体用 sp、尺寸用 dp。