說來慚愧,這個方案在微信公眾號推出來的時候,我才去了解我司自己的適配方案。字節跳動屏幕適配方案
重點
- 為什么要做屏幕適配
- 從數據上告訴你安卓手機屏幕的碎片化
- 我司的適配方案
- 適配中出現的問題
- 實際演練
為什么要做屏幕適配
在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
什么?還不會,那我只能把所有的圖都貼出來了。
現在準備完成了,可以開始開發了
比對效果
未使用前,為了適配小屏手機,在大屏手機上看就會很丑
使用后,兩個頁面基本相同了