字节跳动屏幕适配方案解读

说来惭愧,这个方案在微信公众号推出来的时候,我才去了解我司自己的适配方案。字节跳动屏幕适配方案

重点

  1. 为什么要做屏幕适配
  2. 从数据上告诉你安卓手机屏幕的碎片化
  3. 我司的适配方案
  4. 适配中出现的问题
  5. 实际演练

为什么要做屏幕适配

在Android开发中,由于Android的碎片化严重,屏幕分辨率千奇百怪,想要在各种分辨率的设备上显示一致的效果,适配成本越来越高。虽然Android官方提供了dp单位来适配,但是在各种奇怪分辨率的手机下表现的不近人意。

Android手机屏幕的碎片化

在大部分场景下我们是不需要关心屏幕适配,因为很多ui不会有太多元素,要不然就是RecyclerView来展示数据,但是也有不少的场景UI会比较复杂,我们需要关心手机适配,接下来,从Google和友盟的数据上看下当前手机屏幕的大小和分辨率

Google数据

字节跳动屏幕适配方案解读_第1张图片
image

可以看到Normal占比是最多的,Normal就是宽度为480dp的手机。而在Normal手机中dpi为320的手机又居多。

small -> 320dp
Normal -> 480dp
Large -> 600dp
XLarge -> 720dp

友盟数据

字节跳动屏幕适配方案解读_第2张图片
image

字节跳动屏幕适配方案解读_第3张图片
image
名次 屏幕分辨率 活跃占比 趋势
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理解为三个部分

  1. System,初始分配,包含第三方库。
  2. Application,整个应用的DisplayMetrics
  3. 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
    }
}

使用中出现的问题

使用时需要注意以下几点:

  1. 尽量只在当前页面生效,包括activity,fragment,dialog,view,需要在setCOntentView()或者inflate之前调用这个方法,在结束onDestroy,onDismiss,onDetachWindow()的时候调用resetScreen()方法。
  2. 在页面中需要弹出toastdialog的时候需要调用resetScreen,不然toastdialog的页面大小和字体大小会被影响。
  3. 记住一点使用前调用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 x 1334

什么?还不会,那我只能把所有的图都贴出来了。


字节跳动屏幕适配方案解读_第4张图片
image

字节跳动屏幕适配方案解读_第5张图片
image

字节跳动屏幕适配方案解读_第6张图片
image

现在准备完成了,可以开始开发了

比对效果

未使用前,为了适配小屏手机,在大屏手机上看就会很丑

beforeAdapter.png

使用后,两个页面基本相同了

afterAdapter.png

你可能感兴趣的:(字节跳动屏幕适配方案解读)