Android 最全面的屏幕適配方案

大家在Android開發(fā)時(shí)翔怎,肯定會(huì)覺得屏幕適配是個(gè)尤其痛苦的事配名,各種屏幕尺寸適配起來巨煩無比鹉梨。如果我們換個(gè)角度我們看下這個(gè)問題蠢终,不知道大家有沒有了解過web前端開發(fā)序攘,或者說大家對于網(wǎng)頁都不陌生吧茴她,其實(shí)適配的問題在web頁面的設(shè)計(jì)中理論上也存在,為什么這么說呢程奠?電腦的顯示器的分辨率丈牢、包括手機(jī)分辨率,我敢說分辨率的種類遠(yuǎn)超過Android設(shè)備的分辨率瞄沙,那么有一個(gè)很奇怪的現(xiàn)象:

為什么Web頁面設(shè)計(jì)人員從來沒有說過己沛,屏幕適配好麻煩?

那么距境,到底是什么原因申尼,讓網(wǎng)頁的設(shè)計(jì)可以在千差萬別的分辨率的分辨率中依舊能給用戶一個(gè)優(yōu)質(zhì)的體驗(yàn)?zāi)兀繋е@個(gè)疑惑垫桂,我問了下一個(gè)前端朋友师幕,朋友睜大眼睛問我:適配是什么?诬滩? 前端似乎看來的確沒有這類問題们衙,后來在我仔細(xì)的追問后,她告訴我碱呼,噢 這個(gè)尺寸呀蒙挑,我們一般都加個(gè)viewport,我都是設(shè)置為20%縮放的~~ 追根到底愚臀,其實(shí)就是一個(gè)原因忆蚀,網(wǎng)頁提供了縮放比計(jì)算大小。

同樣的姑裂,大家拿到UI給的設(shè)計(jì)圖以后馋袜,是不是抱怨過UI妹妹標(biāo)識的都是px,而我項(xiàng)目里面用dp舶斧,這都什么玩意??欣鳖,和UI解釋她也不理解,開發(fā)同樣也是一臉無奈茴厉。所以能不能有一套完美的解決方案來解決Android工程師和UI妹妹間的矛盾泽台,實(shí)現(xiàn)UI給出一個(gè)固定尺寸的設(shè)計(jì)稿,然后你在編寫布局的時(shí)候不用思考矾缓,無腦照抄上面標(biāo)識的像素值怀酷,就能達(dá)到完美適配。理想夠豐滿嗜闻,但現(xiàn)實(shí)夠殘酷:

由于Android系統(tǒng)的開放性蜕依,任何用戶、開發(fā)者、OEM廠商样眠、運(yùn)營商都可以對Android進(jìn)行定制友瘤,于是導(dǎo)致:

  • Android系統(tǒng)碎片化:小米定制的MIUI、魅族定制的flyme檐束、華為定制的EMUI等等,當(dāng)然其都是基于Google原生系統(tǒng)定制的

  • Android機(jī)型屏幕尺寸碎片化:5寸辫秧、5.5寸、6寸等等

  • Android屏幕分辨率碎片化:320x480厢塘、480x800、720x1280肌幽、1080x1920

據(jù)友盟指數(shù)顯示晚碾,統(tǒng)計(jì)至2015年12月,支持Android的設(shè)備共有27796種

當(dāng)Android系統(tǒng)喂急、屏幕尺寸格嘁、屏幕密度出現(xiàn)碎片化的時(shí)候,就很容易出現(xiàn)同一元素在不同手機(jī)上顯示不同的問題廊移。

試想一下這么一個(gè)場景:
為4.3寸屏幕準(zhǔn)備的UI設(shè)計(jì)圖糕簿,運(yùn)行在5.0寸的屏幕上,很可能在右側(cè)和下側(cè)存在大量的空白狡孔;而5.0寸的UI設(shè)計(jì)圖運(yùn)行到4.3寸的設(shè)備上懂诗,很可能顯示不下。

首先介紹一下Android屏幕中用到的一些相關(guān)重要概念:

屏幕尺寸

· 含義:手機(jī)對角線的物理尺寸

· 單位:英寸(inch)苗膝,1英寸=2.54cm

Android手機(jī)常見的尺寸有5寸殃恒、5.5寸、6寸等等

屏幕分辨率

· 含義:手機(jī)在橫向辱揭、縱向上的像素點(diǎn)數(shù)總和

  1. 一般描述成屏幕的"寬x高”=AxB

  2. 含義:屏幕在橫向方向(寬度)上有A個(gè)像素點(diǎn)离唐,在縱向方向(高)有B個(gè)像素點(diǎn)

例子:1080x1920,即寬度方向上有1080個(gè)像素點(diǎn)问窃,在高度方向上有1920個(gè)像素點(diǎn)

  • 單位:px(pixel)亥鬓,1px=1個(gè)像素點(diǎn)

UI設(shè)計(jì)師的設(shè)計(jì)圖會(huì)以px作為統(tǒng)一的計(jì)量單位

  • Android手機(jī)常見的分辨率:320x480、480x800域庇、720x1280嵌戈、1080x1920、 1080x2340

屏幕像素密度

  • 含義:每英寸的像素點(diǎn)數(shù)

  • 單位:dpi(dots per ich)

假設(shè)設(shè)備內(nèi)每英寸有160個(gè)像素听皿,那么該設(shè)備的屏幕像素密度=160dpi

  • 安卓手機(jī)對于每類手機(jī)屏幕大小都有一個(gè)相應(yīng)的屏幕像素密度:
密度類型 代表的分辨率(px) 屏幕密度(dpi)
低密度(ldpi) 240x320 120
中密度(mdpi) 320x480 160
高密度(hdpi) 480x800 240
超高密度(xhdpi) 720x1280 320
超超高密度(xxhdpi) 1080x1920 480

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

一部手機(jī)的分辨率是寬*高写穴,屏幕大小是以寸為單位惰拱,那么三者的關(guān)系是:

image.png

不懂沒關(guān)系,在這里舉個(gè)例子
假設(shè)一部手機(jī)的分辨率是1080x1920(px),屏幕大小是5寸偿短,問密度是多少欣孤?
:請直接套公式
image.png

密度無關(guān)像素

  • 含義:density-independent pixel,叫dp或dip昔逗,與終端上的實(shí)際物理像素點(diǎn)無關(guān)降传。
  • 單位:dp,可以保證在不同屏幕像素密度的設(shè)備上顯示相同的效果
  1. Android開發(fā)時(shí)用dp而不是px單位設(shè)置圖片大小勾怒,是Android特有的單位
  2. 場景:假如同樣都是畫一條長度是屏幕一半的線婆排,如果使用px作為計(jì)量單位,那么在480x800分辨率手機(jī)上設(shè)置應(yīng)為240px笔链;在320x480的手機(jī)上應(yīng)設(shè)置為160px段只,二者設(shè)置就不同了;如果使用dp為單位鉴扫,在這兩種分辨率下赞枕,160dp都顯示為屏幕一半的長度。
  • dp與px的轉(zhuǎn)換
    因?yàn)閡i設(shè)計(jì)師給你的設(shè)計(jì)圖是以px為單位的坪创,Android開發(fā)則是使用dp作為單位的炕婶,那么我們需要進(jìn)行轉(zhuǎn)換:
密度類型 代表的分辨率(px) 屏幕密度(dpi) 換算(px/dp)
低密度(ldpi) 240x320 120 1dp=0.75px
中密度(mdpi) 320x480 160 1dp=1px
高密度(hdpi) 480x800 240 1dp=1.5px
超高密度(xhdpi) 720x1280 320 1dp=2px
超超高密度(xxhdpi) 1080x1920 480 1dp=3px

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

獨(dú)立比例像素

  • 含義:scale-independent pixel莱预,叫sp或sip
  • 單位:sp

Android開發(fā)時(shí)用此單位設(shè)置文字大小柠掂,可根據(jù)字體大小首選項(xiàng)進(jìn)行縮放
推薦使用12sp、14sp依沮、18sp陪踩、22sp作為字體設(shè)置的大小,不推薦使用奇數(shù)和小數(shù)悉抵,容易造成精度的丟失問題肩狂;小于12sp的字體會(huì)太小導(dǎo)致用戶看不清

請把上面的概念記住,因?yàn)橄旅嬷v解都會(huì)用到姥饰!

適配方案比較

1. dp原生方案
2. dimen基于px和dp的適配(寬高限定符和smallestWidth適配)
3. 頭條屏幕適配方案
4. 頭條適配方案改進(jìn)版本

dp原生方案

前言:統(tǒng)一以px為單位有什么問題傻谁?

Android屏幕適配由來已久,關(guān)鍵在于屏幕尺寸與屏幕分辨率的變化巨大列粪,而很多UI工程師為了兼容iOS和Android的適配审磁,這樣導(dǎo)致設(shè)計(jì)出來的UI稿是以px單位標(biāo)注的。在成千上百種機(jī)型面前岂座,px單位已難以適應(yīng)态蒂。

1.同樣尺寸,不同分辨率:
1080px的寬度上顯示100px 比例是100/1080
720px的寬度上顯示100px 比例是100/720
2.同分辨率费什,不同尺寸:
1080px在4.7寸上顯示100px
1080px在6.1寸上顯示100px
如果使用多套px文件方案來適配钾恢,市面上少說上百種寸,需要的文件太多了

不同分辨率的屏幕該如何適配

這時(shí)候就需要用到dp方案來解決了,所以dp究竟解決了什么問題?

以下公式表示了瘩蚪,同樣尺寸上不同分辨率(不同density)的設(shè)備泉懦,每1dp所代表的像素?cái)?shù)量是不一樣的。

480 dpi上 1dp = 1 * 3 = 3px
320 dpi上 1dp = 1 * 2 = 2px
240 dpi上 1dp = 1 * 1.5 = 1.5px
160 dpi上 1dp = 1 * 1 = 1px
120 dpi上 1dp = 1 * 0.75 = 0.75px

但是所表示的物理長度(160dp=1in)是一樣的疹瘦。

160 dp在density=3上表示480px崩哩,物理長度為1 in
160 dp在density=2上表示320px,物理長度為1 in
160 dp在density=1.5上表示240px言沐,物理長度為1 in
160 dp在density=1上表示160px邓嘹,物理長度為1 in
160 dp在density=0.75上表示120px,物理長度為1 in

由上可知险胰,dp單位的使用就意味著你在這些同樣尺寸但是不同分辨率的設(shè)備上看到的大小一樣汹押,此時(shí)各設(shè)備上顯示的比例也就一致了。

dp方案沒有解決什么問題

舉個(gè)例子:

屏幕分辨率為:1920*1080鸯乃,屏幕尺寸為5吋的話鲸阻,那么dpi為440跋涣。假設(shè)我們UI設(shè)計(jì)圖是按屏幕寬度為360dp來設(shè)計(jì)的缨睡,那這樣會(huì)存在什么問題呢?

在上述設(shè)備上陈辱,屏幕寬度其實(shí)為1080/(440/160)=392.7dp奖年,也就是屏幕是比設(shè)計(jì)圖要寬的。這種情況下沛贪, 即使使用dp也是無法在不同設(shè)備上顯示為同樣效果的陋守。 同時(shí)還存在部分設(shè)備屏幕寬度不足360dp,這時(shí)就會(huì)導(dǎo)致按360dp寬度來開發(fā)實(shí)際顯示不全的情況利赋。

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

dimen基于px和dp的適配(寬高限定符和smallestWidth適配)

dimen基于dp適配 SmallestWidth限定符

原理:

這種適配依據(jù)的是最小寬度限定符疗涉。指的是Android會(huì)識別屏幕可用高度和寬度的最小尺寸的dp值(其實(shí)就是手機(jī)的寬度值),然后根據(jù)識別到的結(jié)果去資源文件中尋找對應(yīng)限定符的文件夾下的資源文件吟秩。這種機(jī)制和上文提到的寬高限定符適配原理上是一樣的咱扣,都是系統(tǒng)通過特定的規(guī)則來選擇對應(yīng)的文件。

舉個(gè)例子涵防,小米5的dpi是480,橫向像素是1080px闹伪,根據(jù)px=dp(dpi/160),橫向的dp值是1080/(480/160),也就是360dp,系統(tǒng)就會(huì)去尋找是否存在value-sw360dp的文件夾以及對應(yīng)的資源文件。

image
image

smallestWidth限定符適配和寬高限定符適配最大的區(qū)別在于祭往,有很好的容錯(cuò)機(jī)制伦意,如果沒有value-sw360dp文件夾,系統(tǒng)會(huì)向下尋找硼补,比如離360dp最近的只有value-sw350dp驮肉,那么Android就會(huì)選擇value-sw350dp文件夾下面的資源文件。這個(gè)特性就完美的解決了上文提到的寬高限定符的容錯(cuò)問題已骇。

缺點(diǎn):

  • 侵入性強(qiáng)
  • Android 私人訂制的原因离钝,寬度方面參差不齊,不可能適配所有的手機(jī)褪储。
  • 項(xiàng)目中增加了N個(gè)文件夾卵渴,上拉下拉查看文件非常不方便:想看string或者color資源文件需要拉很多再能到達(dá)。
  • 通過寬度限定符就近查找的原理鲤竹,可以看出來匹配出來的大小不夠準(zhǔn)確浪读。
  • 是在Android 3.2 以后引入的,Google的本意是用它來適配平板的布局文件(但是實(shí)際上顯然用于diemns適配的效果更好)辛藻,不過目前SPX所有的項(xiàng)目應(yīng)該最低支持版本應(yīng)該都是5.1了碘橘,所以這問題其實(shí)也不重要了。

dimens基于px的適配 寬高限定符適配

原理:

根據(jù)市面上手機(jī)分辨率的占比分析吱肌,我們選定一個(gè)占比例值大的(比如1280720)設(shè)定為一個(gè)基準(zhǔn)痘拆,然后其他分辨率根據(jù)這個(gè)基準(zhǔn)做適配。
基準(zhǔn)的意思(比如320
480的分辨率為基準(zhǔn))是:
寬為320氮墨,將任何分辨率的寬度分為320份纺蛆,取值為x1到x320
長為480,將任何分辨率的高度分為480份规揪,取值為y1到y(tǒng)480

例如對于800 * 480的分辨率設(shè)備來講桥氏,需要在項(xiàng)目中values-800x480目錄下的dimens.xml文件中的如下設(shè)置(當(dāng)然了,可以通過工具自動(dòng)生成):

<resources>
<dimen name="x1">1.5px</dimen>
<dimen name="x2">3.0px</dimen>
<dimen name="x3">4.5px</dimen>
<dimen name="x4">6.0px</dimen>
<dimen name="x5">7.5px</dimen></pre>

可以看到x1 = 480 / 基準(zhǔn) = 480 / 320 = 1.5 ;它的意思就是同樣的1px猛铅,在320/480分辨率的手機(jī)上是1px字支,在480/800的分辨率的手機(jī)上就是1*1.5px,px會(huì)根據(jù)我們指定的不同values文件夾自動(dòng)適配為合適的大小奕坟。

image
image

驗(yàn)證方案:
簡單通過計(jì)算驗(yàn)證下這種方案是否能達(dá)到適配的效果祥款,例如設(shè)計(jì)圖上有一個(gè)寬187dp的View。

分辨率為480 * 800

  • 設(shè)計(jì)圖占寬比: 187dp / 375dp = 0.498
  • 實(shí)際在480 800占寬比 = 187 1.28px / 480 = 0.498

分辨率為1080 * 1920

  • 設(shè)計(jì)圖占寬比: 187dp / 375dp = 0.498
  • 實(shí)際在1080 1920占寬比 = 187 2.88px / 1080 = 0.498
  • 計(jì)算高同理

缺點(diǎn):

  • 侵入性強(qiáng)
  • 需要精準(zhǔn)命中資源文件才能適配月杉,比如1920x1080的手機(jī)就一定要找到1920x1080的限定符刃跛,否則就只能用統(tǒng)一的默認(rèn)的dimens文件了。而使用默認(rèn)的尺寸的話苛萎,UI就很可能變形桨昙,簡單說检号,就是容錯(cuò)機(jī)制很差。
  • Android不同分辨率的手機(jī)實(shí)在太多了蛙酪,可能你說主流就可以齐苛,的確小公司主流就可以,淘寶這種App肯定不能只適配主流手機(jī)桂塞“挤洌控件在設(shè)計(jì)圖上顯示的大小以及控件之間的間隙在小分辨率和大分辨率手機(jī)上天壤之別,你會(huì)發(fā)現(xiàn)大屏幕手機(jī)上控件超級大阁危÷耆可能你會(huì)覺得正常,畢竟分辨率不同狂打。但實(shí)際效果大的有些夸張擂煞。
  • 占據(jù)資源大:好幾百KB,甚至多達(dá)1M或跟多趴乡。

頭條屏幕適配方案

梳理需求:

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

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

  • 支持以寬或者高一個(gè)維度去適配溺职,保持該維度上和設(shè)計(jì)圖一致岔擂;
  • 支持dp和sp單位位喂,控制遷移成本到最小。

找方案兼容突破口

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

\color{red}{px = dp * density}

可以看出乱灵,如果設(shè)計(jì)圖寬為360dp塑崖,想要保證在所有設(shè)備計(jì)算得出的px值都正好是屏幕寬度的話,我們只能修改 density 的值痛倚。通過閱讀源碼规婆,我們可以得知,density 是 DisplayMetrics 中的成員變量蝉稳,而 DisplayMetrics 實(shí)例通過 Resources#getDisplayMetrics 可以獲得聋呢,而Resouces通過Activity或者Application的Context獲得。

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

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

是不是Android中所有的dp和px的換算都是通過 DisplayMetrics 中相關(guān)的值來計(jì)算的呢?

  • 首先來看看布局文件中的dp轉(zhuǎn)化毕莱,最終都是調(diào)用TypedValue#applyDimension來進(jìn)行住轉(zhuǎn)化
    image.png
  • 圖片的decode器贩,BitmapFactory#decodeResourceStream方法:
    image.png

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

最終方案:

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

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


image.png

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

缺點(diǎn):

  • 只能支持以高或?qū)捴械囊粋€(gè)作為基準(zhǔn)進(jìn)行適配先巴。
  • 只需要修改一次 density,項(xiàng)目中的所有地方都會(huì)自動(dòng)適配冒冬,這個(gè)看似解放了雙手伸蚯,減少了很多操作,但是實(shí)際上反應(yīng)了一個(gè)缺點(diǎn)简烤,那就是只能一刀切的將整個(gè)項(xiàng)目進(jìn)行適配剂邮,但適配范圍是不可控的。項(xiàng)目中如果采用了系統(tǒng)控件乐埠、三方庫控件抗斤、等不是我們項(xiàng)目自身設(shè)計(jì)的控件囚企,這時(shí)就會(huì)出現(xiàn)和我們項(xiàng)目自身的設(shè)計(jì)圖尺寸差距非常大的問題。

頭條適配方案改進(jìn)版本

大致思路:在頭條適配方案的基礎(chǔ)上瑞眼,通過重寫Activity的getResources(),重寫冷門單位pt作為基準(zhǔn)單位龙宏,它是Android 中的一個(gè)長度單位:表示一個(gè)點(diǎn),是屏幕的物理尺寸伤疙,其大小為 1 英寸的 1 / 72银酗,也就是 72pt 等于 1 英寸

  • AdaptScreenUtils
public final class AdaptScreenUtils {
private static List<Field> sMetricsFields;

private AdaptScreenUtils() {
    throw new UnsupportedOperationException("u can't instantiate me...");
}

/**
 * Adapt for the horizontal screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptWidth(final Resources resources, final int designWidth) {
    float newXdpi = (resources.getDisplayMetrics().widthPixels * 72f) / designWidth;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

/**
 * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptHeight(final Resources resources, final int designHeight) {
    return adaptHeight(resources, designHeight, false);
}

/**
 * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptHeight(final Resources resources, final int designHeight, final boolean includeNavBar) {
    float screenHeight = (resources.getDisplayMetrics().heightPixels
            + (includeNavBar ? getNavBarHeight(resources) : 0)) * 72f;
    float newXdpi = screenHeight / designHeight;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

private static int getNavBarHeight(final Resources resources) {
    int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId != 0) {
        return resources.getDimensionPixelSize(resourceId);
    } else {
        return 0;
    }
}

/**
 * @param resources The resources.
 * @return the resource
 */
public static Resources closeAdapt(final Resources resources) {
    float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

/**
 * Value of pt to value of px.
 *
 * @param ptValue The value of pt.
 * @return value of px
 */
public static int pt2Px(final float ptValue) {
    DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();
    return (int) (ptValue * metrics.xdpi / 72f + 0.5);
}

/**
 * Value of px to value of pt.
 *
 * @param pxValue The value of px.
 * @return value of pt
 */
public static int px2Pt(final float pxValue) {
    DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();
    return (int) (pxValue * 72 / metrics.xdpi + 0.5);
}

private static void applyDisplayMetrics(final Resources resources, final float newXdpi) {
    resources.getDisplayMetrics().xdpi = newXdpi;
    FWAdSDK.sContext.getResources().getDisplayMetrics().xdpi = newXdpi;
    applyOtherDisplayMetrics(resources, newXdpi);
}

static void preLoad() {
    applyDisplayMetrics(Resources.getSystem(), Resources.getSystem().getDisplayMetrics().xdpi);
}

private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {
    if (sMetricsFields == null) {
        sMetricsFields = new ArrayList<>();
        Class resCls = resources.getClass();
        Field[] declaredFields = resCls.getDeclaredFields();
        while (declaredFields != null && declaredFields.length > 0) {
            for (Field field : declaredFields) {
                if (field.getType().isAssignableFrom(DisplayMetrics.class)) {
                    field.setAccessible(true);
                    DisplayMetrics tmpDm = getMetricsFromField(resources, field);
                    if (tmpDm != null) {
                        sMetricsFields.add(field);
                        tmpDm.xdpi = newXdpi;
                    }
                }
            }
            resCls = resCls.getSuperclass();
            if (resCls != null) {
                declaredFields = resCls.getDeclaredFields();
            } else {
                break;
            }
        }
    } else {
        applyMetricsFields(resources, newXdpi);
    }
}

private static void applyMetricsFields(final Resources resources, final float newXdpi) {
    for (Field metricsField : sMetricsFields) {
        try {
            DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);
            if (dm != null) dm.xdpi = newXdpi;
        } catch (Exception e) {
            Log.e("AdaptScreenUtils", "applyMetricsFields: " + e);
        }
    }
}

private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {
    try {
        return (DisplayMetrics) field.get(resources);
    } catch (Exception e) {
        Log.e("AdaptScreenUtils", "getMetricsFromField: " + e);
        return null;
    }
}
}
  • 使用方法
    以寬度320為基準(zhǔn)進(jìn)行適配
@Override
public Resources getResources() {
    return AdaptScreenUtils.adaptWidth(super.getResources(),320);
}

假設(shè)我現(xiàn)在需要在屏幕中心有個(gè)按鈕,寬度和高度為我們屏幕寬度的1/2徒像,我可以怎么編寫布局文件呢黍特?

<FrameLayout>
<Button
    android:layout_gravity="center"
    android:gravity="center"
    android:text="@string/hello_world"
    android:layout_width="160pt"
    android:layout_height="160pt"/>
</FrameLayout>

效果

480 x 800 - mdpi(160dpi)

image.png

720 x 1280 - xhdpi(320dpi)
image.png

1080 x 1920 - xxhdpi(480dpi)
image.png

可以看到效果圖中 WebView 對之后的 View 并沒有產(chǎn)生適配失效的問題,這是之前適配所不能解決的問題锯蛀。

優(yōu)點(diǎn)

1. 無侵入性
用了這個(gè)之后依然可以使用dp包括其他任何單位灭衷,對從前使用的布局不會(huì)造成任何影響,在老項(xiàng)目中開發(fā)新功能你可以膽大地加入該適配方案旁涤,新項(xiàng)目的話更可以毫不猶豫地采用該適配翔曲,并且在關(guān)閉該關(guān)閉后,pt 效果等同于 dp 哦劈愚。

2. 靈活性高
如果你想要對某個(gè) View 做到不同分辨率的設(shè)備下瞳遍,使其尺寸在適配維度上所占比例一致的話,那么對它使用 pt 單位即可菌羽,如果你不想要這樣的效果掠械,而是想要更大尺寸的設(shè)備顯示更多的內(nèi)容,那么可以像從前那樣寫 dp注祖、sp 什么的即可猾蒂,結(jié)合這兩點(diǎn),在界面布局上你就可以游刃有余地做到你想要的效果氓轰。

3. 不會(huì)影響系統(tǒng) View 和三方 View 的大小
這點(diǎn)其實(shí)在無侵入性中已經(jīng)表現(xiàn)出來了婚夫,由于頭條的方案是直接修改 DisplayMetrics#densitydp 適配浸卦,這樣會(huì)導(dǎo)致系統(tǒng) View 尺寸和原先不一致署鸡,比如 DialogToast限嫌、 尺寸靴庆,同樣,三方 View 的大小也會(huì)和原先效果不一致怒医,這也就是選擇 pt 適配的原因之一炉抒。

4. 不會(huì)失效
因?yàn)椴徽擃^條的適配還是其他三方庫適配,都會(huì)存在 DisplayMetrics#density 被還原的情況稚叹,需要自己重新設(shè)置回去焰薄,最顯著的就是界面中存在 WebView 的話拿诸,由于其初始化的時(shí)候會(huì)還原 DisplayMetrics#density 的值導(dǎo)致適配失效,當(dāng)然這點(diǎn)已經(jīng)有解決方案了塞茅,但還會(huì)有很多其他情況會(huì)還原 DisplayMetrics#density 的值導(dǎo)致適配失效亩码。而我這方案就是為了解決這個(gè)痛點(diǎn),不讓 DisplayMetrics 中的值被還原導(dǎo)致適配失效野瘦。

缺點(diǎn):

只能適配寬或者高其中一邊描沟,但這也是絕大部分適配方案的痛點(diǎn)所在,長和寬只能適配其一鞭光,好在大部分公司在采用這些方案去適配是都采用優(yōu)先適配寬吏廉,然后在長上面以滑動(dòng)形式去進(jìn)行解決;

小結(jié):

雖然 dimen基于px和dp的適配這種方案能涵蓋市面上所有機(jī)型屏幕的適配惰许,但是冗余的dimen文件會(huì)讓工程師們生不如死席覆,而且這種方案侵入性非常強(qiáng),一旦使用將使得回退變得非常的困難汹买;頭條適配方案和頭條適配優(yōu)化方案作為一種侵入性不是很強(qiáng)的方式進(jìn)行接入娜睛,能完美解決代碼冗余問題,而且總體方案靈活性很高卦睹,但只能選擇寬或者高作為唯一維度去進(jìn)行適配畦戒;
上述方案都能用來解決屏幕適配問題,每種方案都有其獨(dú)特的優(yōu)缺點(diǎn)结序,因此最終選取哪種方案因人而異

參考文章:

Android屏幕適配和方案【整理】
Android 屏幕適配:最全面的解決方案
Android 屏幕適配方案
一種極低成本的Android屏幕適配方式
Android聽說你還在用dp單位做屏幕適配障斋?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市徐鹤,隨后出現(xiàn)的幾起案子垃环,更是在濱河造成了極大的恐慌,老刑警劉巖返敬,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂庄,死亡現(xiàn)場離奇詭異,居然都是意外死亡劲赠,警方通過查閱死者的電腦和手機(jī)涛目,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凛澎,“玉大人霹肝,你說我怎么就攤上這事∷芗澹” “怎么了沫换?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長最铁。 經(jīng)常有香客問我讯赏,道長垮兑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任漱挎,我火速辦了婚禮甥角,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘识樱。我一直安慰自己嗤无,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布怜庸。 她就那樣靜靜地躺著当犯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪割疾。 梳的紋絲不亂的頭發(fā)上嚎卫,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音宏榕,去河邊找鬼拓诸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛麻昼,可吹牛的內(nèi)容都是我干的奠支。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼抚芦,長吁一口氣:“原來是場噩夢啊……” “哼舅巷!你這毒婦竟也來了下硕?” 一聲冷哼從身側(cè)響起海渊,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤角撞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后褥民,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體季春,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年消返,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了载弄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侦副,死狀恐怖侦锯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秦驯,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布挣棕,位于F島的核電站译隘,受9級特大地震影響亲桥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜固耘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一题篷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厅目,春花似錦番枚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拗馒,卻和暖如春路星,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诱桂。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工洋丐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挥等。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓友绝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肝劲。 傳聞我的和親對象是個(gè)殘疾皇子九榔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容