原文地址:https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
在Android開發(fā)中,由于Android碎片化嚴重技潘,屏幕分辨率千奇百怪舟扎,而想要在各種分辨率的設備上顯示基本一致的效果分飞,適配成本越來越高。雖然Android官方提供了dp單位來適配睹限,但其在各種奇怪分辨率下表現(xiàn)卻不盡如人意譬猫,因此下面探索一種簡單且低侵入的適配方式。
傳統(tǒng)dp適配方式的缺點
android中的dp在渲染前會將dp轉為px羡疗,計算公式:
px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);
而dpi是根據(jù)屏幕真實的分辨率和尺寸來計算的染服,每個設備都可能不一樣的。
屏幕尺寸叨恨、分辨率柳刮、像素密度三者關系
通常情況下嫩挤,一部手機的分辨率是寬x高爽蝴,屏幕大小是以寸為單位,那么三者的關系是:
舉個例子:屏幕分辨率為:1920*1080澜建,屏幕尺寸為5吋的話送矩,那么dpi為440蚕甥。
這樣會存在什么問題呢?
假設我們UI設計圖是按屏幕寬度為360dp來設計的益愈,那么在上述設備上梢灭,屏幕寬度其實為1080/(440/160)=392.7dp夷家,也就是屏幕是比設計圖要寬的蒸其。這種情況下, 即使使用dp也是無法在不同設備上顯示為同樣效果的库快。 同時還存在部分設備屏幕寬度不足360dp摸袁,這時就會導致按360dp寬度來開發(fā)實際顯示不全的情況。
而且上述屏幕尺寸义屏、分辨率和像素密度的關系靠汁,很多設備并沒有按此規(guī)則來實現(xiàn), 因此dpi的值非常亂闽铐,沒有規(guī)律可循蝶怔,從而導致使用dp適配效果差強人意。
探索新的適配方式
梳理需求
首先來梳理下我們的需求兄墅,一般我們設計圖都是以固定的尺寸來設計的踢星。比如以分辨率1920px * 1080px來設計,以density為3來標注隙咸,也就是屏幕其實是640dp * 360dp沐悦。如果我們想在所有設備上顯示完全一致成洗,其實是不現(xiàn)實的,因為屏幕高寬比不是固定的藏否,16:9瓶殃、4:3甚至其他寬高比層出不窮,寬高比不同副签,顯示完全一致就不可能了遥椿。但是通常下,我們只需要以寬或高一個維度去適配淆储,比如我們Feed是上下滑動的修壕,只需要保證在所有設備中寬的維度上顯示一致即可,再比如一個不支持上下滑動的頁面遏考,那么需要保證在高這個維度上都顯示一致慈鸠,尤其不能存在某些設備上顯示不全的情況。同時考慮到現(xiàn)在基本都是以dp為單位去做的適配灌具,如果新的方案不支持dp青团,那么遷移成本也非常高。
因此咖楣,總結下大致需求如下:
支持以寬或者高一個維度去適配督笆,保持該維度上和設計圖一致;
支持dp和sp單位诱贿,控制遷移成本到最小娃肿。
找兼容突破口
從dp和px的轉換公式 :px = dp * density
可以看出,如果設計圖寬為360dp珠十,想要保證在所有設備計算得出的px值都正好是屏幕寬度的話料扰,我們只能修改 density 的值。
通過閱讀源碼焙蹭,我們可以得知晒杈,density 是 DisplayMetrics 中的成員變量,而 DisplayMetrics 實例通過 Resources#getDisplayMetrics 可以獲得孔厉,而Resouces通過Activity或者Application的Context獲得拯钻。
先來熟悉下 DisplayMetrics 中和適配相關的幾個變量:
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi
DisplayMetrics#scaledDensity 字體的縮放因子,正常情況下和density相等撰豺,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值
那么是不是所有的dp和px的轉換都是通過 DisplayMetrics 中相關的值來計算的呢粪般?
首先來看看布局文件中dp的轉換,最終都是調(diào)用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來進行轉換:
這里用到的DisplayMetrics正是從Resources中獲得的污桦。
再看看圖片的decode亩歹,BitmapFactory#decodeResourceStream方法:
可見也是通過 DisplayMetrics 中的值來計算的。
當然還有些其他dp轉換的場景,基本都是通過 DisplayMetrics 來計算的捆憎,這里不再詳述舅柜。因此,想要滿足上述需求躲惰,我們只需要修改 DisplayMetrics 中和 dp 轉換相關的變量即可致份。
最終方案
下面假設設計圖寬度是360dp,以寬維度來適配础拨。
那么適配后的 density = 設備真實寬(單位px) / 360氮块,接下來只需要把我們計算好的 density 在系統(tǒng)中修改下即可,代碼實現(xiàn)如下:
同時在 Activity#onCreate 方法中調(diào)用下诡宗。代碼比較簡單滔蝉,也沒有涉及到系統(tǒng)非公開api的調(diào)用,因此理論上不會影響app穩(wěn)定性塔沃。
于是修改后上線灰度測試了一版蝠引,穩(wěn)定性符合預期,沒有收到由此帶來的crash蛀柴,但是收到了很多字體過小的反饋:
原因是在上面的適配中螃概,我們忽略了DisplayMetrics#scaledDensity的特殊性,將DisplayMetrics#scaledDensity和DisplayMetrics#density設置為同樣的值鸽疾,從而某些用戶在系統(tǒng)中修改了字體大小失效了吊洼,但是我們還不能直接用原始的scaledDensity,直接用的話可能導致某些文字超過顯示區(qū)域制肮,因此我們可以通過計算之前scaledDensity和density的比獲得現(xiàn)在的scaledDensity冒窍,方式如下:
但是測試后發(fā)現(xiàn)另外一個問題,就是如果在系統(tǒng)設置中切換字體豺鼻,再返回應用综液,字體并沒有變化。于是還得監(jiān)聽下字體切換拘领,調(diào)用 Application#registerComponentCallbacks 注冊下 onConfigurationChanged 監(jiān)聽即可意乓。
因此最終方案如下:
當然以上代碼只是以設計圖寬360dp去適配的樱调,如果要以高維度適配约素,可以再擴展下代碼即可。
Showcase
適配前后和設計圖對比:
適配后各機型的顯示效果:
參考
https://developer.android.com/guide/practices/screens_support.html