Android屏幕適配方式探索

基本定義

屏幕尺寸L

例子:華為P10(VTR-AL00/全網(wǎng)通)這臺手機的尺寸為5.1英寸
含義:手機對角線的物理尺寸
單位:英寸(inch),1英寸=2.54cm

屏幕分辨率W*H

例子:華為P10(VTR-AL00/全網(wǎng)通)的屏幕分辨率為1920x1080像素,即寬度方向上均勻填充1080個像素點,在高度方向上均勻填充1920個像素
含義:手機在橫向、縱向上的像素點數(shù)總和
一般描述成屏幕的"寬x高”=AxB,屏幕在橫向方向(寬度)上有A個像素點,在縱向方向(高)有B個像素點
單位:px(pixel),UI設(shè)計師的設(shè)計圖會以px作為統(tǒng)一的計量單位

屏幕像素密度 dpi

含義:每英寸的像素點數(shù)
單位:dpi(dots per ich)
假設(shè)設(shè)備內(nèi)每英寸有160個像素,那么該設(shè)備的屏幕像素密度=160dpi

密度無關(guān)像素dp

含義:density-independent pixel,叫dp或dip,與終端上的實際物理像素點無關(guān)。
單位:dp,可以保證在不同屏幕像素密度的設(shè)備上顯示相同的效果
Android開發(fā)時用dp而不是px單位設(shè)置圖片大小,是Android特有的單位
場景:假如同樣都是畫一條長度是屏幕一半的線,如果使用px作為計量單位,那么在480x800分辨率手機上設(shè)置應(yīng)為240px;在320x480的手機上 應(yīng)設(shè)置為160px,二者設(shè)置就不同了;如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長度。

dp與px的轉(zhuǎn)換 px = density * dp ,density = dpi / 160

在Android中,規(guī)定以160dpi(即屏幕分辨率為320x480)為基準:1dp=1px = 1px/density = 1px * 160 / dpi

屏幕尺寸、分辨率、像素密度三者關(guān)系

dpi = sqrt(W的平方+H的平方) / L = 對角線像素個數(shù) / 對角線物理尺寸 = 一英寸的屏幕長度里填充了多少個像素點
density = dpi / 160 = 一英寸的屏幕長度里填充了多少個像素點 乘以 一個固定常數(shù)
所以對于每臺手機來說,density值是有固定的物理意義的

探索方案

理解了上述定義,我們再往下看:

約定設(shè)計規(guī)則

在屏幕適配的問題上,首先要確保設(shè)計稿是按照一個恒定標準輸出的

比如按照一臺屏幕寬高比為16:9,屏幕分辨率為1920*1080,density = 3的手機去設(shè)計UI
px = density * dp ,由于規(guī)則恒定,我們可以根據(jù)設(shè)計稿給定的尺寸(像素)轉(zhuǎn)化為(密度無關(guān)像素dp)

我們可得知設(shè)計稿期望的是一個360dp(DESIGN_WIDTH)*640dp(DESIGN_HEIGHT)的畫布(屏幕分辨率除以density即為像素與dp的換算結(jié)果)

具體適配工作

故適配工作就變成了我們要盡可能的將屏幕在寬度上的像素點分為360組,在高度上的像素點分為640組,每組計為一個dp,則適配者需要的做的就是改變系統(tǒng)的density值使px 與 dp 的換算比例發(fā)生變化,最終實現(xiàn)按照期望分組的愿望,density對于寬度與高度的像素轉(zhuǎn)化關(guān)系是共用的,故對于16:9的機型下述兩種方式都能得到期望的density值
density = W/ DESIGN_WIDTH
density = H / DESIGN_HEIGHT

不同屏幕寬高比所面臨的問題

但這個愿望能實現(xiàn)的基礎(chǔ)是屏幕的像素寬高比是16:9(640:360),對于大于或小于16:9的機型會出現(xiàn)什么問題呢?
當我們處理一個屏幕分辨率比值大于16:9的機型時,
如18:9(2340 1080)的機型時,
按照寬度的分辨率改變density的值,則
density = W / DESIGN_WIDTH = 3
此時density的改變會影響H(屏幕寬度)的像素分組
分組結(jié)果為 H / density = 2340 / 3 = 780dp
該結(jié)果大于我們期望的640dp,此時會造成設(shè)計稿只占用了屏幕寬度分組總數(shù)720組的640組像素,使得80dp的空間為空白
按照高度的分辨率改變density的值
density = 2340 / 6440 = 3.65625
此時density的改變會影響W(屏幕寬度)的像素分組
分組結(jié)果為 W / density = 1080 / 3.65625 = 295.38dp
則此時會造成設(shè)計稿占用了屏幕寬度分組總數(shù)295.38組的360組像素,使得部分設(shè)計稿的視圖不足以展示在屏幕內(nèi)而被遮擋顯示不全

當我們處理一個比值小于 16:9的機型時同理,若按照寬度的分辨率去改變density的值,則會使高度方向的設(shè)計內(nèi)容有一部分被遮擋,
若按照高度的分辨率去改變density的值,則會使寬度方向的設(shè)計內(nèi)容展示完全后有一部分空白

適應(yīng)場景

故針對此方案,需要根據(jù)實際設(shè)計需求,區(qū)分對當前activity來說,是寬度優(yōu)先還是高度優(yōu)先

代碼實現(xiàn)

kotlin實現(xiàn)的工具類如下

...
/**
* description :Android屏幕適配方式 按照設(shè)計稿寬度為360dp*640dp處理
* 解決問題場景:假設(shè)UI設(shè)計圖按屏幕寬度360dp設(shè)計,在一個1920*1080、屏幕尺寸為5的手機上,
* dpi為440(sqrt(寬的平方+高的平方)/尺寸),屏幕寬度其實為1080/(440/160)=392.7dp
* 此時屏幕是比設(shè)計圖要寬的,故此時在布局文件中使用dp也無法在不同設(shè)備上顯示為同樣效果,同時還存在部分設(shè)備屏幕寬度不足360dp導致實際顯示不全的情況
* 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)為基準: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)字體變化
  application.registerComponentCallbacks(object : ComponentCallbacks {
    override fun onConfigurationChanged(newConfig: Configuration?) {
      if (newConfig !=null &&newConfig.fontScale >0) {
        sSysScaledDensity = application.resources.displayMetrics.scaledDensity
      }
    }
    override fun onLowMemory() {}
    })
  }
  // 設(shè)計稿寬度為360dp,高度為640dp,屏幕寬度(像素為單位)除以設(shè)計稿寬度(dp為單位) 可以得到實際需要的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
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容