14、屏幕适配基础:dp、sp与px的换算、不同分辨率下的布局策略
做Android TV开发,屏幕适配是个绕不开的坎。
我记得刚接手第一个TV项目时,拿着设计师给的1920x1080的标注图,直接写死了像素值。结果换到3840x2160的4K电视上,界面缩成了邮票大小。嗯,那次教训挺深刻的。
说白了,屏幕适配就是要让你的UI在不同尺寸、不同分辨率的屏幕上,看起来都差不多。今天我们就来聊聊最基础的东西——dp、sp、px的换算,以及不同分辨率下的布局策略。
核心观点:TV端屏幕适配的核心,是理解密度无关像素(dp)和缩放无关像素(sp),并合理运用布局策略。不要直接使用px。
14.1 三个关键单位:dp、sp、px
先搞清楚这三个东西是什么。
- px(像素):屏幕上的物理点。一个1920x1080的屏幕,就是有1920列、1080行像素点。
- dp(密度无关像素):Android推荐的尺寸单位。1dp在不同密度的屏幕上,对应的物理尺寸大致相同。
- sp(缩放无关像素):专门用于字体大小。它和dp类似,但还会跟随用户的系统字体缩放设置。
为什么不用px?因为不同屏幕的像素密度不一样。同样是100px,在320dpi的屏幕上看起来比160dpi的屏幕上小一半。你想想看,这还怎么适配?
14.2 dp与px的换算公式
换算公式其实很简单:
px = dp × (dpi / 160)
其中dpi是屏幕的像素密度。Android把屏幕密度分成了几个档次:
| 密度等级 | dpi值 | 比例因子 |
|---|---|---|
| mdpi | 160 | 1x |
| hdpi | 240 | 1.5x |
| xhdpi | 320 | 2x |
| xxhdpi | 480 | 3x |
| xxxhdpi | 640 | 4x |
举个例子:如果你在布局里写了一个48dp的按钮,在mdpi屏幕上就是48px,在xhdpi屏幕上就是96px。这样按钮的物理尺寸就差不多。
我的习惯:设计稿如果是1920x1080,我一般按xhdpi(2x)来处理。也就是设计稿上的1px,对应0.5dp。这样换算起来比较方便。
14.3 sp与px的换算
sp和dp的换算方式一样,只是多了一个字体缩放因子:
px = sp × (dpi / 160) × fontScale
fontScale是用户设置的字体缩放比例,默认是1.0。在TV上,我建议不要依赖用户的字体设置,因为电视的字体缩放通常和手机不同。我个人习惯在TV应用里统一使用dp来设置字体大小,避免sp带来的不确定性。
注意:在TV开发中,如果用户调整了系统字体大小,使用sp可能会导致文字显示不全或布局错乱。建议在TV应用中固定使用dp作为字体单位。
14.4 不同分辨率下的布局策略
搞懂了单位换算,接下来就是布局策略了。TV端常见的分辨率有:
- 1920x1080(全高清)
- 3840x2160(4K超高清)
- 1280x720(高清)—— 较少见,但部分老电视还在用
针对不同分辨率,我总结了三种策略:
策略一:使用wrap_content和match_parent
这是最基础的做法。让视图根据内容自适应,或者填满父容器。比如:
<Button
android:layout_width="wrap_content"
android:layout_height="48dp"
android:text="确认" />
这样按钮的宽度由文字决定,高度固定为48dp。在不同分辨率下,按钮的物理高度基本一致。
策略二:使用权重(weight)分配空间
LinearLayout的权重属性,可以按比例分配剩余空间。这在TV上特别有用,因为不同屏幕的宽高比可能不同。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="取消" />
<Button
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="2"
android:text="确认" />
</LinearLayout>
这样两个按钮的宽度比例始终是1:2,不管屏幕多宽。
策略三:使用限定符(Qualifiers)
Android支持根据屏幕尺寸、密度等条件加载不同的资源。比如:
layout-sw600dp:屏幕宽度大于等于600dp时加载layout-sw720dp:屏幕宽度大于等于720dp时加载values-hdpi:hdpi密度下加载
我在项目中遇到过这样的情况:同一个界面在1080p和4K电视上,需要显示不同数量的卡片。用限定符就很好解决——在layout-sw600dp里放3列布局,在layout-sw720dp里放4列布局。
避坑指南:我曾经在4K电视上测试时,发现所有图片都模糊了。后来发现是图片资源只放在了drawable-xhdpi里,4K屏幕是xxhdpi甚至xxxhdpi,系统找不到对应资源就自动缩放了。解决办法是把图片放到drawable-nodpi目录下,或者提供多套资源。
14.5 知识体系结构图
下面这张图总结了本章的核心知识点:
14.6 实战建议
最后,分享几个我在TV项目中的实战经验:
- 统一使用dp:所有尺寸、间距、字体大小都用dp,不要混用px和sp。
- 设计稿按1920x1080出:这是TV端最通用的分辨率,按2x密度处理即可。
- 测试要覆盖4K:现在4K电视越来越普及,一定要在4K屏幕上跑一遍。
- 图片资源放nodpi目录:避免系统自动缩放导致模糊或内存浪费。
- 多用ConstraintLayout:它的相对定位和比例约束,在TV适配中非常好用。
一个小技巧:在开发阶段,可以在Activity的onCreate里打印出屏幕的density和widthPixels/heightPixels,方便调试时确认当前设备的参数。
屏幕适配说难不难,说简单也不简单。核心就是理解dp/sp的本质,然后选择合适的布局策略。多测试、多踩坑,慢慢就有感觉了。