跳到主要内容

屏幕适配

问题

Android 如何处理不同屏幕尺寸和密度的适配问题?常见的适配方案有哪些?

答案

1. 核心概念

概念说明
px物理像素点
dp (dip)密度无关像素,1dp = 1px(160dpi 屏幕上)
sp缩放无关像素,用于字体大小(跟随系统字体设置缩放)
dpi每英寸像素数(dots per inch)
density密度系数,density = dpi / 160
px = dp × density
密度级别dpidensity比例
ldpi~1200.753
mdpi~1601.04
hdpi~2401.56
xhdpi~3202.08
xxhdpi~4803.012
xxxhdpi~6404.016

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 表示设备最小宽度 ≥ 360dp
  • w<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。

相关链接