基本定義
屏幕尺寸L
例子:華為P10(VTR-AL00/全網(wǎng)通)這臺(tái)手機(jī)的尺寸為5.1英寸
含義:手機(jī)對(duì)角線的物理尺寸
單位:英寸(inch)日矫,1英寸=2.54cm
屏幕分辨率W*H
例子:華為P10(VTR-AL00/全網(wǎng)通)的屏幕分辨率為1920x1080像素,即寬度方向上均勻填充1080個(gè)像素點(diǎn)盈魁,在高度方向上均勻填充1920個(gè)像素
含義:手機(jī)在橫向、縱向上的像素點(diǎn)數(shù)總和
一般描述成屏幕的"寬x高”=AxB赤套,屏幕在橫向方向(寬度)上有A個(gè)像素點(diǎn)珊膜,在縱向方向(高)有B個(gè)像素點(diǎn)
單位:px(pixel),UI設(shè)計(jì)師的設(shè)計(jì)圖會(huì)以px作為統(tǒng)一的計(jì)量單位
屏幕像素密度 dpi
含義:每英寸的像素點(diǎn)數(shù)
單位:dpi(dots per ich)
假設(shè)設(shè)備內(nèi)每英寸有160個(gè)像素剔氏,那么該設(shè)備的屏幕像素密度=160dpi
密度無(wú)關(guān)像素dp
含義:density-independent pixel谈跛,叫dp或dip塑陵,與終端上的實(shí)際物理像素點(diǎn)無(wú)關(guān)。
單位:dp阻桅,可以保證在不同屏幕像素密度的設(shè)備上顯示相同的效果
Android開(kāi)發(fā)時(shí)用dp而不是px單位設(shè)置圖片大小,是Android特有的單位
場(chǎng)景:假如同樣都是畫一條長(zhǎng)度是屏幕一半的線稽寒,如果使用px作為計(jì)量單位输瓜,那么在480x800分辨率手機(jī)上設(shè)置應(yīng)為240px;在320x480的手機(jī)上 應(yīng)設(shè)置為160px搔啊,二者設(shè)置就不同了北戏;如果使用dp為單位,在這兩種分辨率下旧蛾,160dp都顯示為屏幕一半的長(zhǎng)度蠕嫁。
dp與px的轉(zhuǎn)換 px = density * dp ,density = dpi / 160
在Android中病袄,規(guī)定以160dpi(即屏幕分辨率為320x480)為基準(zhǔn):1dp=1px = 1px/density = 1px * 160 / dpi
屏幕尺寸益缠、分辨率基公、像素密度三者關(guān)系
dpi = sqrt(W的平方+H的平方) / L = 對(duì)角線像素個(gè)數(shù) / 對(duì)角線物理尺寸 = 一英寸的屏幕長(zhǎng)度里填充了多少個(gè)像素點(diǎn)
density = dpi / 160 = 一英寸的屏幕長(zhǎng)度里填充了多少個(gè)像素點(diǎn) 乘以 一個(gè)固定常數(shù)
所以對(duì)于每臺(tái)手機(jī)來(lái)說(shuō),density值是有固定的物理意義的
探索方案
理解了上述定義胰伍,我們?cè)偻驴矗?/p>
約定設(shè)計(jì)規(guī)則
在屏幕適配的問(wèn)題上喇辽,首先要確保設(shè)計(jì)稿是按照一個(gè)恒定標(biāo)準(zhǔn)輸出的
比如按照一臺(tái)屏幕寬高比為16:9雨席,屏幕分辨率為1920*1080,density = 3的手機(jī)去設(shè)計(jì)UI
px = density * dp 抽米,由于規(guī)則恒定,我們可以根據(jù)設(shè)計(jì)稿給定的尺寸(像素)轉(zhuǎn)化為(密度無(wú)關(guān)像素dp)
我們可得知設(shè)計(jì)稿期望的是一個(gè)360dp(DESIGN_WIDTH)*640dp(DESIGN_HEIGHT)的畫布(屏幕分辨率除以density即為像素與dp的換算結(jié)果)
具體適配工作
故適配工作就變成了我們要盡可能的將屏幕在寬度上的像素點(diǎn)分為360組是目,在高度上的像素點(diǎn)分為640組懊纳,每組計(jì)為一個(gè)dp,則適配者需要的做的就是改變系統(tǒng)的density值使px 與 dp 的換算比例發(fā)生變化嗤疯,最終實(shí)現(xiàn)按照期望分組的愿望,density對(duì)于寬度與高度的像素轉(zhuǎn)化關(guān)系是共用的茂缚,故對(duì)于16:9的機(jī)型下述兩種方式都能得到期望的density值
density = W/ DESIGN_WIDTH
density = H / DESIGN_HEIGHT
不同屏幕寬高比所面臨的問(wèn)題
但這個(gè)愿望能實(shí)現(xiàn)的基礎(chǔ)是屏幕的像素寬高比是16:9(640:360)屋谭,對(duì)于大于或小于16:9的機(jī)型會(huì)出現(xiàn)什么問(wèn)題呢?
當(dāng)我們處理一個(gè)屏幕分辨率比值大于16:9的機(jī)型時(shí)桐磁,
如18:9(2340 1080)的機(jī)型時(shí)我擂,
若按照寬度的分辨率改變density的值,則
density = W / DESIGN_WIDTH = 3
此時(shí)density的改變會(huì)影響H(屏幕寬度)的像素分組
分組結(jié)果為 H / density = 2340 / 3 = 780dp
該結(jié)果大于我們期望的640dp,此時(shí)會(huì)造成設(shè)計(jì)稿只占用了屏幕寬度分組總數(shù)720組的640組像素郎任,使得80dp的空間為空白
若按照高度的分辨率改變density的值
density = 2340 / 6440 = 3.65625
此時(shí)density的改變會(huì)影響W(屏幕寬度)的像素分組
分組結(jié)果為 W / density = 1080 / 3.65625 = 295.38dp
則此時(shí)會(huì)造成設(shè)計(jì)稿占用了屏幕寬度分組總數(shù)295.38組的360組像素舶治,使得部分設(shè)計(jì)稿的視圖不足以展示在屏幕內(nèi)而被遮擋顯示不全
當(dāng)我們處理一個(gè)比值小于 16:9的機(jī)型時(shí)同理霉猛,若按照寬度的分辨率去改變density的值,則會(huì)使高度方向的設(shè)計(jì)內(nèi)容有一部分被遮擋惜浅,
若按照高度的分辨率去改變density的值,則會(huì)使寬度方向的設(shè)計(jì)內(nèi)容展示完全后有一部分空白
適應(yīng)場(chǎng)景
故針對(duì)此方案承绸,需要根據(jù)實(shí)際設(shè)計(jì)需求挣轨,區(qū)分對(duì)當(dāng)前activity來(lái)說(shuō),是寬度優(yōu)先還是高度優(yōu)先
代碼實(shí)現(xiàn)
kotlin實(shí)現(xiàn)的工具類如下
...
/**
* description :Android屏幕適配方式 按照設(shè)計(jì)稿寬度為360dp*640dp處理
* 解決問(wèn)題場(chǎng)景:假設(shè)UI設(shè)計(jì)圖按屏幕寬度360dp設(shè)計(jì)荡澎,在一個(gè)1920*1080摩幔、屏幕尺寸為5的手機(jī)上抖甘,
* dpi為440(sqrt(寬的平方+高的平方)/尺寸)衔彻,屏幕寬度其實(shí)為1080/(440/160)=392.7dp
* 此時(shí)屏幕是比設(shè)計(jì)圖要寬的,故此時(shí)在布局文件中使用dp也無(wú)法在不同設(shè)備上顯示為同樣效果澄港,同時(shí)還存在部分設(shè)備屏幕寬度不足360dp導(dǎo)致實(shí)際顯示不全的情況
* Created by Wangpeng on 2019-11-27
*/
private var sSysDensity: Float =0f
private var sSysScaledDensity: Float =0f
private val DESIGN_WIDTH =360
private val DESIGN_HEIGHT =640
// 在Android中回梧,規(guī)定以160dpi(即屏幕分辨率為320x480)為基準(zhǔn):1dp=1px
private val ANDROID_DESIGN_STANTARD =160
/**
* 只關(guān)注寬度的精確適配
*/
fun setCustomDensitySuitWidth(activity: Activity, application: Application) {
setCustomDensity(activity, application, true)
}
/**
* 只關(guān)注高度的精確適配
*/
fun setCustomDensitySuitHeight(activity: Activity, application: Application) {
setCustomDensity(activity, application, false)
}
private fun setCustomDensity(activity: Activity, application: Application, isSuitWidth: Boolean) {
val appDisplayMetrics = application.resources.displayMetrics
if (sSysDensity ==0f) {
sSysDensity = appDisplayMetrics.density
sSysScaledDensity = appDisplayMetrics.scaledDensity
// 監(jiān)聽(tīng)系統(tǒng)字體變化
application.registerComponentCallbacks(object : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration?) {
if (newConfig !=null &&newConfig.fontScale >0) {
sSysScaledDensity = application.resources.displayMetrics.scaledDensity
}
}
override fun onLowMemory() {}
})
}
// 設(shè)計(jì)稿寬度為360dp,高度為640dp狱意,屏幕寬度(像素為單位)除以設(shè)計(jì)稿寬度(dp為單位) 可以得到實(shí)際需要的density
var targetDensity =if (isSuitWidth) {
(appDisplayMetrics.widthPixels /DESIGN_WIDTH).toFloat()
}else {
(appDisplayMetrics.heightPixels /DESIGN_HEIGHT).toFloat()
}
val targetScaleDensity = targetDensity * (sSysScaledDensity /sSysDensity)
val targetDensityDpi = (ANDROID_DESIGN_STANTARD * targetDensity).toInt()
appDisplayMetrics.density = targetDensity
appDisplayMetrics.scaledDensity = targetScaleDensity
appDisplayMetrics.densityDpi = targetDensityDpi
val atyDisplayMetrics = activity.resources.displayMetrics
atyDisplayMetrics.density = targetDensity
atyDisplayMetrics.scaledDensity = targetScaleDensity
atyDisplayMetrics.densityDpi = targetDensityDpi
}