一種極低成本的Android屏幕適配方式

原始鏈接

在Android開發(fā)中,由于Android碎片化嚴(yán)重,屏幕分辨率千奇百怪,而想要在各種分辨率的設(shè)備上顯示基本一致的效果,適配成本越來(lái)越高。雖然Android官方提供了dp單位來(lái)適配,但其在各種奇怪分辨率下表現(xiàn)卻不盡如人意,因此下面探索一種簡(jiǎn)單且低侵入的適配方式。

image

傳統(tǒng)dp適配方式的缺點(diǎn)

android中的dp在渲染前會(huì)將dp轉(zhuǎn)為px,計(jì)算公式:

  • px = density * dp;

  • density = dpi / 160;

  • px = dp * (dpi / 160);

而dpi是根據(jù)屏幕真實(shí)的分辨率和尺寸來(lái)計(jì)算的,每個(gè)設(shè)備都可能不一樣的。

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

通常情況下,一部手機(jī)的分辨率是寬x高,屏幕大小是以寸為單位,那么三者的關(guān)系是:

image

舉個(gè)例子:屏幕分辨率為:1920*1080,屏幕尺寸為5吋的話,那么dpi為440。

image

這樣會(huì)存在什么問(wèn)題呢?

假設(shè)我們UI設(shè)計(jì)圖是按屏幕寬度為360dp來(lái)設(shè)計(jì)的,那么在上述設(shè)備上,屏幕寬度其實(shí)為1080/(440/160)=392.7dp,也就是屏幕是比設(shè)計(jì)圖要寬的。這種情況下, 即使使用dp也是無(wú)法在不同設(shè)備上顯示為同樣效果的。 同時(shí)還存在部分設(shè)備屏幕寬度不足360dp,這時(shí)就會(huì)導(dǎo)致按360dp寬度來(lái)開發(fā)實(shí)際顯示不全的情況。

而且上述屏幕尺寸、分辨率和像素密度的關(guān)系,很多設(shè)備并沒(méi)有按此規(guī)則來(lái)實(shí)現(xiàn), 因此dpi的值非常亂,沒(méi)有規(guī)律可循,從而導(dǎo)致使用dp適配效果差強(qiáng)人意。

image

探索新的適配方式

梳理需求

首先來(lái)梳理下我們的需求,一般我們?cè)O(shè)計(jì)圖都是以固定的尺寸來(lái)設(shè)計(jì)的。比如以分辨率1920px * 1080px來(lái)設(shè)計(jì),以density為3來(lái)標(biāo)注,也就是屏幕其實(shí)是640dp * 360dp。如果我們想在所有設(shè)備上顯示完全一致,其實(shí)是不現(xiàn)實(shí)的,因?yàn)槠聊桓邔挶炔皇枪潭ǖ模?6:9、4:3甚至其他寬高比層出不窮,寬高比不同,顯示完全一致就不可能了。但是通常下,我們只需要以寬或高一個(gè)維度去適配,比如我們Feed是上下滑動(dòng)的,只需要保證在所有設(shè)備中寬的維度上顯示一致即可,再比如一個(gè)不支持上下滑動(dòng)的頁(yè)面,那么需要保證在高這個(gè)維度上都顯示一致,尤其不能存在某些設(shè)備上顯示不全的情況。同時(shí)考慮到現(xiàn)在基本都是以dp為單位去做的適配,如果新的方案不支持dp,那么遷移成本也非常高。

因此,總結(jié)下大致需求如下:

  1. 支持以寬或者高一個(gè)維度去適配,保持該維度上和設(shè)計(jì)圖一致;

  2. 支持dp和sp單位,控制遷移成本到最小。

找兼容突破口

從dp和px的轉(zhuǎn)換公式 :px = dp * density

可以看出,如果設(shè)計(jì)圖寬為360dp,想要保證在所有設(shè)備計(jì)算得出的px值都正好是屏幕寬度的話,我們只能修改 density 的值。

通過(guò)閱讀源碼,我們可以得知,density 是 DisplayMetrics 中的成員變量,而 DisplayMetrics 實(shí)例通過(guò) Resources#getDisplayMetrics 可以獲得,而Resouces通過(guò)Activity或者Application的Context獲得。

先來(lái)熟悉下 DisplayMetrics 中和適配相關(guān)的幾個(gè)變量:

  • DisplayMetrics#density 就是上述的density

  • DisplayMetrics#densityDpi 就是上述的dpi

  • DisplayMetrics#scaledDensity 字體的縮放因子,正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會(huì)改變這個(gè)值

那么是不是所有的dp和px的轉(zhuǎn)換都是通過(guò) DisplayMetrics 中相關(guān)的值來(lái)計(jì)算的呢?

首先來(lái)看看布局文件中dp的轉(zhuǎn)換,最終都是調(diào)用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來(lái)進(jìn)行轉(zhuǎn)換:

image

這里用到的DisplayMetrics正是從Resources中獲得的。

再看看圖片的decode,BitmapFactory#decodeResourceStream方法:

image

可見也是通過(guò) DisplayMetrics 中的值來(lái)計(jì)算的。

當(dāng)然還有些其他dp轉(zhuǎn)換的場(chǎng)景,基本都是通過(guò) DisplayMetrics 來(lái)計(jì)算的,這里不再詳述。因此,想要滿足上述需求,我們只需要修改 DisplayMetrics 中和 dp 轉(zhuǎn)換相關(guān)的變量即可。

最終方案

下面假設(shè)設(shè)計(jì)圖寬度是360dp,以寬維度來(lái)適配。

那么適配后的 density = 設(shè)備真實(shí)寬(單位px) / 360,接下來(lái)只需要把我們計(jì)算好的 density 在系統(tǒng)中修改下即可,代碼實(shí)現(xiàn)如下:

image

同時(shí)在 Activity#onCreate 方法中調(diào)用下。代碼比較簡(jiǎn)單,也沒(méi)有涉及到系統(tǒng)非公開api的調(diào)用,因此理論上不會(huì)影響app穩(wěn)定性。

于是修改后上線灰度測(cè)試了一版,穩(wěn)定性符合預(yù)期,沒(méi)有收到由此帶來(lái)的crash,但是收到了很多字體過(guò)小的反饋:

image

原因是在上面的適配中,我們忽略了DisplayMetrics#scaledDensity的特殊性,將DisplayMetrics#scaledDensity和DisplayMetrics#density設(shè)置為同樣的值,從而某些用戶在系統(tǒng)中修改了字體大小失效了,但是我們還不能直接用原始的scaledDensity,直接用的話可能導(dǎo)致某些文字超過(guò)顯示區(qū)域,因此我們可以通過(guò)計(jì)算之前scaledDensity和density的比獲得現(xiàn)在的scaledDensity,方式如下:

image

但是測(cè)試后發(fā)現(xiàn)另外一個(gè)問(wèn)題,就是如果在系統(tǒng)設(shè)置中切換字體,再返回應(yīng)用,字體并沒(méi)有變化。于是還得監(jiān)聽下字體切換,調(diào)用 Application#registerComponentCallbacks 注冊(cè)下 onConfigurationChanged 監(jiān)聽即可。

因此最終方案如下:

image

當(dāng)然以上代碼只是以設(shè)計(jì)圖寬360dp去適配的,如果要以高維度適配,可以再擴(kuò)展下代碼即可。

image

Showcase

適配前后和設(shè)計(jì)圖對(duì)比:

image

適配后各機(jī)型的顯示效果:

image

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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