Android Compose屏幕适配实战:区分手机/平板
前言
在Android Compose开发中,屏幕适配是绕不开的核心问题 : 尤其是当项目需要同时兼容手机(如6.3寸 2484*1116)和平板(如8.8寸 2560*1600)时,如何精准区分设备类型、适配不同布局,成为很多开发者的痛点。
1. 核心问题:为什么直接用分辨率/英寸不靠谱?
很多开发者初期会尝试用「原始分辨率」或「屏幕英寸」区分设备,比如判断"分辨率≥2560*1600就是平板",但实际会踩两个大坑:
- 分辨率依赖屏幕密度:相同像素宽度的设备,密度不同则实际可用空间(DP)天差地别(比如2484px宽度的设备,密度3时DP=828,密度2.75时DP=872.7);
- 屏幕英寸不可靠:部分设备会虚报物理尺寸,且代码中获取物理英寸需要权限,稳定性差;
- 旋转影响:分辨率的宽/高会随屏幕旋转互换,无法稳定判断设备类型。
Android官方早已给出解决方案:优先使用DP维度(而非像素/英寸),核心依赖「最小宽度DP(swDp)」和「WindowSizeClass断点」。
2. 基础认知:屏幕密度(density)到底是什么?
在讲适配前,必须先理清「屏幕密度」的核心概念------这是所有适配的底层逻辑。
2.1 两个易混淆的概念
2.1.1 物理 PPI(硬件客观值,厂商改不了)
是屏幕物理像素密度 ,由分辨率+屏幕尺寸决定:

这是硬件属性,屏幕造出来就固定,厂商不能改。
2.1.2 系统密度 density / densityDpi(软件逻辑值,厂商定、写入系统)
这才是 Compose / Android 系统用来做适配的:
densityDpi:整数档位(160、240、320、480、640...)density:缩放倍数 = densityDpi / 160f
这个值 ≠ 物理 PPI ,是厂商按规则选档后写入系统的。
2.2 厂商到底是怎么「决定密度」的?(标准流程)
谷歌有严格规范,厂商不能乱填,流程固定 3 步:
Step 1:算出这块屏幕的 物理 PPI
客观值,不可改。
Step 2:对照 Android 官方密度档位表「就近归档」
安卓不使用连续密度,只认固定档位(为了全生态适配统一):
| 档位名称 | densityDpi | density(倍数) | 适用设备大致区间 |
|---|---|---|---|
| mdpi | 160 | 1.0× | 老设备、低PPI |
| hdpi | 240 | 1.5× | 旧手机 |
| xhdpi | 320 | 2.0× | 720p 手机 |
| xxhdpi | 480 | 3.0× | 1080p 主流手机 |
| xxxhdpi | 640 | 4.0× | 2K 高PPI 小屏手机 |
规则:物理 PPI 靠近哪个档位,就定哪个。
现实中厂商会微调(比如 420dpi、450dpi),但依然是围绕官方档位、保证 UI 视觉正常,不会乱跳。
Step 3:把最终值写入系统出厂配置
厂商会把定好的 densityDpi 写进系统属性:
ro.sf.lcd_density=420
这是开机就加载、永久生效的出厂默认值。
2.3 在Compose中获取屏幕密度
kotlin
@Composable
fun getDensityCompose() {
val density = LocalDensity.current
val scaleFactor = density.density // 缩放因子(如3.0)
val densityDpi = density.densityDpi // 物理密度(如480)
val fontScale = density.fontScale // 字体缩放因子
}
3. 关键指标:SmallestWidthDp(swDp)
smallestWidthDp(简称swDp)是屏幕最短边的DP值(最短边像素 ÷ 密度),是Android官方推荐的"设备类型判断黄金标准",核心特性:
- 出厂固定:不受屏幕旋转、显示设置调整影响;
- 阈值统一:官方以600dp为界,区分手机(<600dp)和平板(≥600dp)。
3.1 主流设备swDp参考
| 设备类型 | 尺寸 | 分辨率 | 密度 | swDp(最短边DP) | 判定结果 |
|---|---|---|---|---|---|
| 6.3寸手机 | 6.3寸 | 2484*1116 | 3 | 1116÷3=372dp | 手机 |
| 8.8寸小平板 | 8.8寸 | 2560*1600 | 3 | 1600÷3≈533dp | 手机(可微调阈值) |
| 10寸标准平板 | 10寸 | 2560*1600 | 2 | 1600÷2=800dp | 平板 |
3.2 实战:用swDp精准区分手机/平板
封装工具类(全局复用):
kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
// 设备类型判断工具类
object DeviceTypeUtil {
// 官方手机/平板分界阈值(可根据小平板微调为500dp)
private const val TABLET_SW_THRESHOLD = 600
/**
* 获取设备最小宽度DP(swDp)
*/
@Composable
fun getSmallestWidthDp(): Int {
val config = LocalConfiguration.current
return config.smallestScreenWidthDp
}
/**
* 判断是否为平板
*/
@Composable
fun isTablet(): Boolean {
return getSmallestWidthDp() >= TABLET_SW_THRESHOLD
}
/**
* 判断是否为手机
*/
@Composable
fun isPhone(): Boolean {
return !isTablet()
}
/**
* 进阶:区分平板尺寸
*/
@Composable
fun getTabletSizeType(): TabletSizeType {
val swDp = getSmallestWidthDp()
return when {
swDp >= 800 -> TabletSizeType.LARGE // 10寸+大平板
swDp >= 600 -> TabletSizeType.MEDIUM // 7-10寸中小平板
else -> TabletSizeType.NONE // 非平板
}
}
enum class TabletSizeType {
NONE, MEDIUM, LARGE
}
}
布局中使用:
kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
@Composable
fun AdaptiveRootLayout() {
val isTablet = DeviceTypeUtil.isTablet()
val swDp = DeviceTypeUtil.getSmallestWidthDp()
Column(modifier = Modifier.fillMaxSize()) {
Text(text = "当前设备swDp:$swDp", fontSize = 16.sp)
if (isTablet) {
Text(text = "平板布局(swDp≥600)", fontSize = 24.sp)
TabletContent()
} else {
Text(text = "手机布局(swDp<600)", fontSize = 18.sp)
PhoneContent()
}
}
}
// 手机端专属布局
@Composable
fun PhoneContent() {
Text(text = "手机端:单列展示,紧凑间距")
// 你的手机布局逻辑(如单列列表、小间距)
}
// 平板端专属布局
@Composable
fun TabletContent() {
Text(text = "平板端:双列展示,宽松间距")
// 你的平板布局逻辑(如双列网格、大间距)
}
4. 多想一层 : 为什么要基于「宽度 DP」设计?
这个设计是Android适配的底层逻辑,目的是让UI适配"用户实际感知的屏幕大小",而非冰冷的像素数,核心原因有3个:
4.1. 核心:DP是"物理尺寸单位",像素不是
- 像素是屏幕的"点数量",密度是"每英寸的点数",DP = 像素 ÷ 密度 → 最终DP代表"物理英寸"的近似值(1dp≈0.01英寸);
- 举个极端例子:
- 手机C:6寸,1080×1920,密度3 → 宽度DP=1080÷3=360dp;
- 手表:1.5寸,1080×1920,密度10 → 宽度DP=1080÷10=108dp;
→ 像素数相同,但DP值反映了物理尺寸,手表的DP远小于手机,符合用户感知;
- 如果按像素判断,手表和手机的像素数一样,会被误判为同尺寸,而DP能精准区分。
4.2. 避免"像素碎片化",统一适配标准
Android设备的像素分辨率千差万别(比如手机有1080p、1440p、2K,平板有2K、4K),但相同物理尺寸的屏幕,DP值接近:
- 6.3寸手机:宽度DP≈390-420dp(竖屏);
- 8.8寸平板:宽度DP≈530-850dp(竖/横屏);
→ 用DP判断,能稳定区分"手机级可用空间"和"平板级可用空间",而像素无法做到(比如平板2560px宽度,密度4是640dp,密度3是853dp,但物理尺寸都是8.8寸,DP值都远大于手机竖屏的390dp)。
4.3. 聚焦"UI可用空间",而非"屏幕物理尺寸"
如果需要更精细的控制,在Compose中可结合WindowSizeClass,windowSizeClass的设计目标是适配"UI能用到的宽度",而非单纯的设备类型:
- 比如折叠屏手机展开后,宽度DP达到700dp,虽然是手机,但UI需要按平板的多列布局展示;
- 比如小平板(7寸)宽度DP只有600dp,虽然是平板,但UI可以按手机的紧凑布局展示;
→ 这种设计让适配从"判断设备类型"转向"判断可用空间",更灵活(这是Material Design 3的核心理念)。