Appearance
多屏幕适配
字数统计:约 9000 字
难度等级:⭐⭐⭐⭐
面试重要度:⭐⭐⭐⭐
目录
1. 屏幕适配基础
1.1 屏幕参数
关键概念:
- 屏幕尺寸 (Screen Size) - 对角线长度(英寸)
- 屏幕密度 (Screen Density) - 每英寸像素数 (DPI/PPI)
- 分辨率 (Resolution) - 像素数量(宽 x 高)
- 密度无关像素 (dp/dip) - 与密度无关的单位1.2 密度分类
密度分类:
- ldpi: 120 dpi (0.75x)
- mdpi: 160 dpi (1.0x) - 基准
- hdpi: 240 dpi (1.5x)
- xhdpi: 320 dpi (2.0x)
- xxhdpi: 480 dpi (3.0x)
- xxxhdpi: 640 dpi (4.0x)
换算公式:
px = dp × (dpi / 160)
dp = px / (dpi / 160)1.3 尺寸分类
尺寸分类:
- small: 小于 3 英寸
- normal: 3-5 英寸(手机)
- large: 5-7 英寸(平板)
- xlarge: 大于 7 英寸(平板)1.4 单位使用
kotlin
// ✅ 推荐单位
// dp - 布局尺寸(与密度无关)
// sp - 字体大小(与用户设置相关)
// px - 仅在必要时使用(如 1 像素边框)
// ❌ 避免
// 硬编码 px 值
val width = 100 // 错误
// ✅ 正确
val width = 100.dp // 使用 dp2. 布局适配
2.1 备用布局
目录结构:
res/
├── layout/ # 默认布局
│ └── activity_main.xml
├── layout-sw600dp/ # 最小宽度 600dp(7 寸平板)
│ └── activity_main.xml
├── layout-land/ # 横屏布局
│ └── activity_main.xml
├── layout-port/ # 竖屏布局
│ └── activity_main.xml
├── layout-large/ # 大屏幕
│ └── activity_main.xml
└── layout-xlarge/ # 超大屏幕
└── activity_main.xml2.2 响应式布局
xml
<!-- 使用 ConstraintLayout 实现响应式 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 在小屏幕上占满宽度 -->
<!-- 在大屏幕上居中显示 -->
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxWidth="600dp"
app:layout_constraintWidth_percent="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>2.3 尺寸资源
xml
<!-- res/values/dimens.xml (默认) -->
<resources>
<dimen name="padding_small">8dp</dimen>
<dimen name="padding_normal">16dp</dimen>
<dimen name="padding_large">24dp</dimen>
<dimen name="text_size_small">12sp</dimen>
<dimen name="text_size_normal">16sp</dimen>
<dimen name="text_size_large">20sp</dimen>
</resources>
<!-- res/values-sw600dp/dimens.xml (平板) -->
<resources>
<dimen name="padding_small">16dp</dimen>
<dimen name="padding_normal">24dp</dimen>
<dimen name="padding_large">32dp</dimen>
<dimen name="text_size_small">14sp</dimen>
<dimen name="text_size_normal">18sp</dimen>
<dimen name="text_size_large">24sp</dimen>
</resources>2.4 代码适配
kotlin
class ScreenAdapter {
// 获取屏幕宽度
fun getScreenWidth(context: Context): Int {
return context.resources.displayMetrics.widthPixels
}
// 获取屏幕高度
fun getScreenHeight(context: Context): Int {
return context.resources.displayMetrics.heightPixels
}
// 获取屏幕密度
fun getDensity(context: Context): Float {
return context.resources.displayMetrics.density
}
// dp 转 px
fun dpToPx(context: Context, dp: Float): Int {
return (dp * context.resources.displayMetrics.density + 0.5f).toInt()
}
// px 转 dp
fun pxToDp(context: Context, px: Int): Float {
return px / context.resources.displayMetrics.density
}
// sp 转 px
fun spToPx(context: Context, sp: Float): Int {
return (sp * context.resources.displayMetrics.scaledDensity + 0.5f).toInt()
}
// 判断是否为平板
fun isTablet(context: Context): Boolean {
return context.resources.configuration.smallestScreenWidthDp >= 600
}
// 判断是否为横屏
fun isLandscape(context: Context): Boolean {
return context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
}
// 使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val adapter = ScreenAdapter()
if (adapter.isTablet(this)) {
// 平板布局
setContentView(R.layout.activity_main_tablet)
} else {
// 手机布局
setContentView(R.layout.activity_main_phone)
}
}
}3. 图片资源适配
3.1 多密度资源
目录结构:
res/
├── drawable-mdpi/ # 160 dpi (1x)
│ └── icon.png (48x48)
├── drawable-hdpi/ # 240 dpi (1.5x)
│ └── icon.png (72x72)
├── drawable-xhdpi/ # 320 dpi (2x)
│ └── icon.png (96x96)
├── drawable-xxhdpi/ # 480 dpi (3x)
│ └── icon.png (144x144)
└── drawable-xxxhdpi/ # 640 dpi (4x)
└── icon.png (192x192)3.2 Vector Drawable
xml
<!-- 使用矢量图(推荐) -->
<!-- res/drawable/ic_star.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>
// 使用
imageView.setImageResource(R.drawable.ic_star)3.3 Adaptive Icon
xml
<!-- res/mipmap-anydpi-v26/ic_launcher.xml -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>4. 折叠屏适配
4.1 检测折叠状态
kotlin
class FoldableAdapter {
fun isFoldable(context: Context): Boolean {
return context.packageManager.hasSystemFeature(
PackageManager.FEATURE_HARWARE_TYPE_FOLDABLE
)
}
fun getFoldState(context: Context): Int {
val display = context.display ?: return FOLD_STATE_UNKNOWN
return when (display.foldState) {
Display.FOLD_STATE_CLOSED -> FOLD_STATE_CLOSED
Display.FOLD_STATE_HALF_OPENED -> FOLD_STATE_HALF_OPENED
Display.FOLD_STATE_OPENED -> FOLD_STATE_OPENED
else -> FOLD_STATE_UNKNOWN
}
}
companion object {
const val FOLD_STATE_UNKNOWN = 0
const val FOLD_STATE_CLOSED = 1
const val FOLD_STATE_HALF_OPENED = 2
const val FOLD_STATE_OPENED = 3
}
}4.2 响应折叠变化
kotlin
class FoldableActivity : AppCompatActivity() {
private val foldChangeListener = Consumer<Display> { display ->
when (display.foldState) {
Display.FOLD_STATE_OPENED -> {
// 展开状态 - 使用双栏布局
updateLayoutForExpanded()
}
Display.FOLD_STATE_CLOSED -> {
// 折叠状态 - 使用单栏布局
updateLayoutForCollapsed()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 注册监听器
windowManager.currentWindowMetrics.display
.addOnFoldChangeListener(foldChangeListener)
}
override fun onDestroy() {
super.onDestroy()
windowManager.currentWindowMetrics.display
.removeOnFoldChangeListener(foldChangeListener)
}
}4.3 跨屏连续性
kotlin
class ContinuityManager {
fun saveState(state: Bundle) {
// 保存当前状态
state.putInt("scroll_position", recyclerView.scrollPosition)
state.putString("current_item", currentItem)
}
fun restoreState(state: Bundle) {
// 恢复状态
val position = state.getInt("scroll_position")
recyclerView.scrollToPosition(position)
}
}5. 最佳实践
5.1 使用 ConstraintLayout
xml
<!-- 响应式布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 自适应宽度 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxWidth="600dp"
app:layout_constraintWidth_percent="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 链式布局 -->
<androidx.constraintlayout.widget.Chain
app:layout_constraintHorizontal_chainStyle="spread" />
</androidx.constraintlayout.widget.ConstraintLayout>5.2 使用 Fragment
kotlin
// 单栏布局(手机)
class MainActivity : AppCompatActivity() {
// 只显示一个 Fragment
}
// 双栏布局(平板)
class TabletMainActivity : AppCompatActivity() {
// 同时显示两个 Fragment
// Master-Detail 模式
}5.3 测试多屏幕
kotlin
// Android Studio 模拟器
// 创建不同尺寸的设备:
// - Phone: Pixel 6 (1080x2400, 411x891dp)
// - Tablet: Pixel Tablet (1600x2560, 820x1344dp)
// - Foldable: Pixel Fold (1840x2208, 760x932dp)
// 测试不同方向
// 竖屏 / 横屏
// 测试不同密度
// mdpi / hdpi / xhdpi / xxhdpi6. 面试考点
6.1 基础概念
Q1: dp 和 px 的区别?
答案要点:
- dp: 密度无关像素,与屏幕密度无关
- px: 物理像素,与屏幕密度相关
- 换算:px = dp × (dpi / 160)
- 建议使用 dp 进行布局Q2: 如何实现多屏幕适配?
答案要点:
1. 使用 dp/sp 单位
2. 提供备用布局(layout-sw600dp 等)
3. 使用 ConstraintLayout
4. 提供多密度图片资源
5. 使用 Vector Drawable6.2 实战问题
Q3: 如何判断设备是手机还是平板?
kotlin
fun isTablet(context: Context): Boolean {
return context.resources.configuration.smallestScreenWidthDp >= 600
}
// 或使用屏幕尺寸
fun isTablet(context: Context): Boolean {
val config = context.resources.configuration
return config.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >=
Configuration.SCREENLAYOUT_SIZE_LARGE
}Q4: 如何适配折叠屏?
kotlin
// 1. 检测折叠状态
val foldState = display.foldState
// 2. 监听折叠变化
display.addOnFoldChangeListener { display ->
when (display.foldState) {
FOLD_STATE_OPENED -> updateLayoutForExpanded()
FOLD_STATE_CLOSED -> updateLayoutForCollapsed()
}
}
// 3. 使用响应式布局
// ConstraintLayout + Fragment6.3 高级问题
Q5: 什么是 smallestWidth?
答案要点:
- smallestScreenWidthDp
- 屏幕最短边的 dp 值
- 用于判断设备类型
- < 600dp: 手机
- >= 600dp: 7 寸平板
- >= 720dp: 10 寸平板参考资料
本文完,感谢阅读!