Android设备碎片化严重,从千元机到折叠屏,分辨率、像素密度千差万别。如果布局写死了px数值,应用在高清屏上会显得特别小,在低分辨率设备上又会大得离谱。要做好屏幕适配,第一步就是弄清楚dp与px的关系,然后结合合适的适配方案,让界面在各种设备上呈现一致的效果。

一、先搞懂几个基础概念:px、dpi、dp、sp
px就是物理像素,是屏幕上最小的显示单元。一块1080x1920的屏幕,横向就有1080个物理像素点。dpi(Dots Per Inch)表示每英寸的像素数,用来衡量屏幕密度,Android中常用的是DPI,比如常见的160dpi、320dpi、480dpi等。屏幕密度越高,同样的物理尺寸内塞进的像素点越多,单个像素点就越小。
dp也叫dip,即密度无关像素,是Android定义的抽象单位。它以160dpi的屏幕为基准:在160dpi的屏幕上,1dp等于1px;在320dpi的屏幕上,1dp等于2px。这样一来,1dp在不同密度的屏幕上对应的物理尺寸大致相同,这就是它能够解决适配问题的核心原理。sp则用于文字大小,它除了随屏幕密度缩放,还会跟随用户在系统里设置的字体大小偏好,所以在设置文字字号时应使用sp,而不要用dp。
Android系统对屏幕密度做了归一化处理,常见的档位有:ldpi(120dpi)、mdpi(160dpi)、hdpi(240dpi)、xhdpi(320dpi)、xxhdpi(480dpi)、xxxhdpi(640dpi)。开发时如果将target densityDpi设置不同,同样的dp值换算出的px数量也不同,这点在做高清图或自定义View时尤其要注意。
二、dp与px的互相转换及代码实现
虽然布局文件中写dp就能自动适配,但在自定义View的onDraw方法、Canvas绘图、通过代码动态设置宽高等场景下,API往往要求传入px值,这时候就需要手动做dp转px的换算。换算公式很简单:px = dp * density,其中density就是当前设备的屏幕密度与160的比值,比如xxhdpi屏幕的density是3.0。反过来,dp = px / density。
下面是常用的转换工具类代码:
public class DensityUtils {
// dp转px
public static int dp2px(Context context, float dpValue) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * density + 0.5f); // 加0.5是为了四舍五入
}
// px转dp
public static int px2dp(Context context, float pxValue) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / density + 0.5f);
}
// sp转px
public static int sp2px(Context context, float spValue) {
float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * scaledDensity + 0.5f);
}
}在Kotlin中可以写成扩展属性,用起来更简洁。也可以直接使用Android自带的TypedValue.applyDimension方法,它是系统内部使用的换算逻辑,兼容性最好:
// Kotlin扩展函数写法
val Float.dp: Int
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
).toInt()
// 使用方式
val width = 16.dp // 直接得到对应的px值有一个容易踩的坑要提醒:如果使用系统默认的 Resources.getSystem(),它拿到的是系统全局的DisplayMetrics,不会跟随应用主题或某些适配方案动态修改的density。如果项目中使用了今日头条的动态适配方案,转换时一定要用Activity的Resources,否则换算结果会和实际显示不一致。
三、主流多分辨率适配方案对比与选择
dp本身能解决大部分适配问题,但遇到屏幕宽高比差异大的设备(比如平板、折叠屏、全面屏手机),单纯靠dp会导致布局留白或被拉伸。业界目前主要有两种主流方案,各有优劣。
第一种是smallest width限定符方案,也就是俗称的values-sw360dp这类多 dimens 文件方案。原理是生成一系列以最小宽度限定符命名的资源目录,比如values-sw320dp、values-sw360dp、values-sw411dp,每个目录里放置同名但数值不同的dimen值,布局文件统一引用dimen,系统根据设备的最小宽度自动匹配。它的优点是稳定性高,不侵入系统API,所有版本都兼容;缺点是会增加APK体积,需要脚本生成大量文件,而且只能按照宽度等比缩放,无法处理特殊比例的屏幕。
第二种是今日头条的动态修改density方案。核心思路是统一以设计稿宽度为基准,在Application或Activity中重写getResources之前,主动修改DisplayMetrics中的density、densityDpi等字段,使得1dp对应的px值随屏幕宽度变化,从而保证任何屏幕上布局占屏比例一致。核心代码如下:
public class DensityAdapt {
// 设计稿宽度,以375dp为基准
private static final float DESIGN_WIDTH = 375f;
public static void adapt(Activity activity) {
DisplayMetrics appDm = activity.getApplication()
.getResources().getDisplayMetrics();
DisplayMetrics activityDm = activity.getResources()
.getDisplayMetrics();
// 计算目标density:屏幕真实px宽度除以设计稿dp宽度
float targetDensity = appDm.widthPixels / DESIGN_WIDTH;
activityDm.density = targetDensity;
activityDm.densityDpi = (int) 160 * targetDensity;
}
}这个方案的优势是零侵入、不用生成额外文件、切换适配基准非常灵活,适合按宽度等比缩放的需求。但它也有明显缺点:文字大小会跟着变化,如果用户调大了系统字体,可能出现文字过大的问题;另外系统某些控件内部对density有依赖,极端情况下会有显示异常。使用时建议只修改density,同时处理scaledDensity跟随系统字体设置的逻辑,并且在多Fragment切换时注意各自的要求。
除了这两种,还有限定符布局(layout-sw600dp)、使用百分比布局、ConstraintLayout的百分比约束等辅助手段。实际项目中的常见组合是:常规布局用dp加ConstraintLayout权重约束,文字用sp,特殊页面按设计稿精确还原时叠加今日头条方案,再配合values-sw限定符处理平板横竖屏的差异。对于折叠屏这种宽高比剧烈变化的设备,则需要用响应式布局思路,根据宽度断点切换不同的布局文件,而不是简单等比缩放。
总结一下,屏幕适配没有银弹,关键在于理解px与dp的换算关系,明白每种方案背后的原理和适用边界。把基础概念吃透,再根据业务场景选择合适的组合方案,才能在碎片化的Android生态中做到一份代码、处处美观。
dp与px转换Android屏幕适配多分辨率适配修改时间:2026-09-03 08:08:37