说来惭愧,这个方案在微信公众号推出来的时候,我才去了解我司自己的适配方案。字节跳动屏幕适配方案
重点
- 为什么要做屏幕适配
- 从数据上告诉你安卓手机屏幕的碎片化
- 我司的适配方案
- 适配中出现的问题
- 实际演练
为什么要做屏幕适配
在Android开发中,由于Android的碎片化严重,屏幕分辨率千奇百怪,想要在各种分辨率的设备上显示一致的效果,适配成本越来越高。虽然Android官方提供了dp单位来适配,但是在各种奇怪分辨率的手机下表现的不近人意。
Android手机屏幕的碎片化
在大部分场景下我们是不需要关心屏幕适配,因为很多ui不会有太多元素,要不然就是RecyclerView来展示数据,但是也有不少的场景UI会比较复杂,我们需要关心手机适配,接下来,从Google和友盟的数据上看下当前手机屏幕的大小和分辨率
Google数据
可以看到Normal占比是最多的,Normal就是宽度为480dp的手机。而在Normal手机中dpi为320的手机又居多。
small -> 320dp
Normal -> 480dp
Large -> 600dp
XLarge -> 720dp
友盟数据
名次 | 屏幕分辨率 | 活跃占比 | 趋势 |
---|---|---|---|
1 | 1920x1080 (16:9) | 28.9 | ↓ |
2 | 1280x720 (16:9) | 22.5 | ↓ |
3 | 1080x1821 (3:5) | 3.9 | ↓ |
4 | 960x540 (16:9) | 3.3 | ↓ |
5 | 854x480 (16:9) | 2.8 | ↓ |
6 | 720x1208 (9:16) | 2.6 | ↓ |
7 | 1184x720 (5:3) | 2.4 | ↓ |
8 | 1776x1080 (5:3) | 2.1 | ↑ |
9 | 2560x1440 (16:9) | 2 | ↑ |
10 | 2016x1080 (17:9) | 1.9 | ↑ |
11 | 1794x1080 (5:3) | 1.8 | ↑ |
12 | 2040x1080 (17:9) | 1.3 | ↑ |
13 | 2160x1080 (18:9) | 1.3 | ↑ |
从上面的数据可以看到安卓的手机市场,各种比例、分辨率、尺寸的手机层出不穷,如果我们按照Google官方的适配方案,那么我们基本上可以不用做其他需求了,ui改动一个像素点,你可能需要改动十几个layout的布局文件。
字节跳动屏幕适配方案
这节会从基础到新的适配方案,详细看下同事是怎样解决这个世纪难题。
传统dp适配方式
android中的dp在渲染前会将dp转换为px,计算公式为
px = density * dp
density = dpi/160
px = dp * (dpi/160)
dpi(pot per inch)就是每英寸的多少个点,dpi是根据屏幕真实的分辨率和尺寸计算的,每个设配可能都不一样。
[图片上传失败...(image-436913-1547873896006)]
拿MIX2做个例子,MIX2的尺寸是5.99英寸
,分辨率是2160*1080象素
。根据上面的公式可以算出MIX2的dpi为402。
这里有个小问题
我们通过代码
val mix2dpi= resources.displayMetrics.densityDpi
println("mix2 dpi is : $mix2dpi")
//打印出 440
为什么和我们计算出来的不一致,难道公式是假的?那肯定不是的,小米手机虽然在手机信息上写的5.99英寸
但是这是整个手机屏幕,而不是真正的显示区域,MIX2的真正显示区域是5.5英寸
,这样是不是就对应起来了。
这样问题就来了
假设我们的设计小哥哥给的UI设计图是按照屏幕375dp设计的,那么在上述的设备上,屏幕的宽度是1080/(440/160)=392.7dp
,也就是要比设计图要款。这种情况下,即时使用dp,也无法达到和设计图相同的效果。同事还存在部分设备宽度不足375dp
,那么就会出现超出屏幕的情况出现。
除此之外还有很多设备并没有按照上述的公式来实现,因此dpi的值是非常乱的。用dp进行适配也是差强人意。
字节跳动适配方案
先了解几个知识点
- dp和px的转换公式为:px = dp * density
- dp转换的场景都是通过DisplayMetrics来进行计算的
- DisplayMetrics#density 就是上述的density
- DisplayMetrics#densityDpi 就是上述的dpi
- DisPlayMetrics#scaledDensity 字体的缩放银子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
那么应用中所有dp的计算都是更DisplayMetrics这个类有关,所以只用针对这个类进行操作就好了。
我们可以将DisPlayMetrics理解为三个部分
- System,初始分配,包含第三方库。
- Application,整个应用的DisplayMetrics
- Activity,单个Activity页面的DisplayMetrics
//系统的屏幕尺寸
val systemDM = Resources.getSystem().displayMetrics
//app整体的屏幕尺寸
val appDM = activity.application.resources.displayMetrics
//activity的屏幕尺寸
val activityDM = activity.resources.displayMetrics
我们这里以宽度为375dp的宽度为基准去适配,这个值你可以改为任何值。
// 适配屏幕的宽度
activityDM.density = activityDM.widthPixels / tartgetDP.toFloat()
// 适配相应比例的字体大小
activityDM.scaledDensity = activityDM.density * (systemDM.scaledDensity / systemDM.density)
// 适配dpi
activityDM.densityDpi = (160 * activityDM.density).toInt()
看下整个适配的工具类,特别简单
object ScreenUtil {
fun adapterScreen(activity: Activity, targetDP: Int, isVertical: Boolean) {
//系统的屏幕尺寸
val systemDM = Resources.getSystem().displayMetrics
//app整体的屏幕尺寸
val appDM = activity.application.resources.displayMetrics
//activity的屏幕尺寸
val activityDM = activity.resources.displayMetrics
if (isVertical) {
// 适配屏幕的高度
activityDM.density = activityDM.heightPixels / targetDP.toFloat()
} else {
// 适配屏幕的宽度
activityDM.density = activityDM.widthPixels / targetDP.toFloat()
}
// 适配相应比例的字体大小
activityDM.scaledDensity = activityDM.density * (systemDM.scaledDensity / systemDM.density)
// 适配dpi
activityDM.densityDpi = (160 * activityDM.density).toInt()
}
fun resetScreen(activity: Activity) {
//系统的屏幕尺寸
val systemDM = Resources.getSystem().displayMetrics
//app整体的屏幕尺寸
val appDM = activity.application.resources.displayMetrics
//activity的屏幕尺寸
val activityDM = activity.resources.displayMetrics
activityDM.density = systemDM.density
activityDM.scaledDensity = systemDM.scaledDensity
activityDM.densityDpi = systemDM.densityDpi
appDM.density = systemDM.density
appDM.scaledDensity = systemDM.scaledDensity
appDM.densityDpi = systemDM.densityDpi
}
}
使用中出现的问题
使用时需要注意以下几点:
- 尽量只在当前页面生效,包括
activity,fragment,dialog,view
,需要在setCOntentView()
或者inflate
之前调用这个方法,在结束onDestroy,onDismiss,onDetachWindow()
的时候调用resetScreen()
方法。 - 在页面中需要弹出
toast
和dialog
的时候需要调用resetScreen
,不然toast
和dialog
的页面大小和字体大小会被影响。 - 记住一点使用前调用
adapterScreen
,时候后调用resetScreen
实际操作
工欲善其事必先利其器
我们要使用这种方式,首先要把Android Studio的xml预览界面调节成设计师给的设计图的屏幕尺寸。这里已经向抖音的设计师确认,给我们的所有设计图都是按照750*1334象素 -> 375*667dp
的规格设计。也就是说density=2
,对应dpi=160*2=320
,对应的手机屏幕大小为4.8英寸
在预览界面怎么创建相应的界面呢?
tools -> AVD Manager -> Create Virtual Device... -> New Hardware Profile -> screen sieze:
4.8
Resolution:750
x1334
什么?还不会,那我只能把所有的图都贴出来了。
现在准备完成了,可以开始开发了
比对效果
未使用前,为了适配小屏手机,在大屏手机上看就会很丑
使用后,两个页面基本相同了