Android系統(tǒng)發(fā)布十多年以來(lái)拇派,關(guān)于Android的UI的適配一直是開(kāi)發(fā)環(huán)節(jié)中最重要的問(wèn)題牺陶,但是我看到還是有很多小伙伴對(duì)Android適配方案不了解捡需。剛好凡蜻,近期準(zhǔn)備對(duì)糗事百科Android客戶端設(shè)計(jì)一套UI尺寸適配方案搭综,可以和小伙伴們?cè)敿?xì)的聊一聊這個(gè)問(wèn)題。
Android適配最核心的問(wèn)題有兩個(gè)划栓,其一兑巾,就是適配的效率,即把設(shè)計(jì)圖轉(zhuǎn)化為App界面的過(guò)程是否高效忠荞,其二如何保證實(shí)現(xiàn)UI界面在不同尺寸和分辨率的手機(jī)中UI的一致性蒋歌。這兩個(gè)問(wèn)題都很重要,一個(gè)是保證我們開(kāi)發(fā)的高效委煤,一個(gè)是保證我們適配的成效堂油;今天我們就這兩個(gè)核心的問(wèn)題來(lái)聊一聊Android的適配方案。
首先碧绞,大家都知道府框,在標(biāo)識(shí)尺寸的時(shí)候,Android并不推薦我們使用px這個(gè)真實(shí)像素單位头遭,因?yàn)椴煌氖謾C(jī)之間寓免,分辨率是不同的,比如一個(gè)96*96像素的控件在分辨率越來(lái)越高的手機(jī)上會(huì)在整體UI中看起來(lái)越來(lái)越小计维。
出現(xiàn)類(lèi)似于上圖這樣這樣袜香,整體的布局效果可能會(huì)變形,所以px這個(gè)單位在布局文件中是不推薦的鲫惶。
dp直接適配
針對(duì)這種情況蜈首,Android推薦使用dp作為尺寸單位來(lái)適配UI.
那么什么是dp?dp指的是設(shè)備獨(dú)立像素欠母,以dp為尺寸單位的控件欢策,在不同分辨率和尺寸的手機(jī)上代表了不同的真實(shí)像素,比如在分辨率較低的手機(jī)中赏淌,可能1dp=1px,而在分辨率較高的手機(jī)中踩寇,可能1dp=2px,這樣的話六水,一個(gè)96*96dp的控件俺孙,在不同的手機(jī)中就能表現(xiàn)出差不多的大小了辣卒。那么這個(gè)dp是如何計(jì)算的呢? 我們都知道一個(gè)公式: px = dp(dpi/160) 系統(tǒng)都是通過(guò)這個(gè)來(lái)判斷px和dp的數(shù)學(xué)關(guān)系睛榄,
那么這里又出現(xiàn)了一個(gè)問(wèn)題荣茫,dpi是什么呢?
dpi是像素密度场靴,指的是在系統(tǒng)軟件上指定的單位尺寸的像素?cái)?shù)量啡莉,它往往是寫(xiě)在系統(tǒng)出廠配置文件的一個(gè)固定值。
我為什么要強(qiáng)調(diào)它是軟件系統(tǒng)上的概念旨剥?因?yàn)榇蠹屹I(mǎi)手機(jī)的時(shí)候咧欣,往往會(huì)聽(tīng)到另一個(gè)叫ppi的參數(shù),這個(gè)在手機(jī)屏幕中指的也是像素密度轨帜,但是這個(gè)是物理上的概念该押,它是客觀存在的不會(huì)改變。dpi是軟件參考了物理像素密度后阵谚,人為指定的一個(gè)值蚕礼,這樣保證了某一個(gè)區(qū)間內(nèi)的物理像素密度在軟件上都使用同一個(gè)值。這樣會(huì)有利于我們的UI適配梢什。
比如奠蹬,幾部相同分辨率不同尺寸的手機(jī)的ppi可能分別是是430,440,450,那么在Android系統(tǒng)中,可能dpi會(huì)全部指定為480.這樣的話嗡午,dpi/160就會(huì)是一個(gè)相對(duì)固定的數(shù)值囤躁,這樣就能保證相同分辨率下不同尺寸的手機(jī)表現(xiàn)一致。
而在不同分辨率下荔睹,dpi將會(huì)不同狸演,比如:
... | 1080*720 | 1920*1080 |
---|---|---|
dpi | 320 | 480 |
dpi/160 | 2 | 3 |
根據(jù)上面的表格,我們可以發(fā)現(xiàn)僻他,720P,和1080P的手機(jī)宵距,dpi是不同的,這也就意味著吨拗,不同的分辨率中满哪,1dp對(duì)應(yīng)不同數(shù)量的px(720P中,1dp=2px劝篷,1080P中1dp=3px)哨鸭,這就實(shí)現(xiàn)了,當(dāng)我們使用dp來(lái)定義一個(gè)控件大小的時(shí)候娇妓,他在不同的手機(jī)里表現(xiàn)出相應(yīng)大小的像素值像鸡。
我們可以說(shuō),通過(guò)dp加上自適應(yīng)布局和weight比例布局可以基本解決不同手機(jī)上適配的問(wèn)題哈恰,這基本是最原始的Android適配方案只估。
這種方式存在兩個(gè)小問(wèn)題华望,第一,這只能保證我們寫(xiě)出來(lái)的界面適配絕大部分手機(jī)仅乓,部分手機(jī)仍然需要單獨(dú)適配,為什么dp只解決了90%的適配問(wèn)題蓬戚,因?yàn)椴⒉皇撬械?080P的手機(jī)dpi都是480夸楣,比如Google 的Pixel2(1920*1080)的dpi是420,也就是說(shuō)子漩,在Pixel2中豫喧,1dp=2.625px,這樣會(huì)導(dǎo)致相同分辨率的手機(jī)中,這樣幢泼,一個(gè)100dp*100dp的控件紧显,在一般的1080P手機(jī)上,可能都是300px,而Pixel 2 中 缕棵,就只有262.5px,這樣控件的實(shí)際大小會(huì)有所不同孵班。
為了更形象的展示,假設(shè)我們?cè)诓季治募邪岩粋€(gè)ImageView的寬度設(shè)置為360dp,那么在下面兩張圖中表現(xiàn)是不一樣的:
圖一是1080P,480dpi的手機(jī)招驴,圖二是1080P,420dpi的手機(jī)
從上面的布局中可以看到篙程,同樣是1080P的手機(jī),差異是比較明顯的别厘。在這種情況下虱饿,我們的UI可能需要做一些微調(diào)甚至單獨(dú)適配。
第二個(gè)問(wèn)題触趴,這種方式無(wú)法快速高效的把設(shè)計(jì)師的設(shè)計(jì)稿實(shí)現(xiàn)到布局代碼中氮发,通過(guò)dp直接適配,我們只能讓UI基本適配不同的手機(jī),但是在設(shè)計(jì)圖和UI代碼之間的鴻溝冗懦,dp是無(wú)法解決的爽冕,因?yàn)閐p不是真實(shí)像素。而且披蕉,設(shè)計(jì)稿的寬高往往和Android的手機(jī)真實(shí)寬高差別極大扇售,以我們的設(shè)計(jì)稿為例,設(shè)計(jì)稿的寬高是375px*750px嚣艇,而真實(shí)手機(jī)可能普遍是1080*1920,
那么在日常開(kāi)發(fā)中我們是怎么跨過(guò)這個(gè)鴻溝的呢承冰?基本都是通過(guò)百分比啊,或者通過(guò)估算食零,或者設(shè)定一個(gè)規(guī)范值等等困乒。總之贰谣,當(dāng)我們拿到設(shè)計(jì)稿的時(shí)候娜搂,設(shè)計(jì)稿的ImageView是128px*128px迁霎,當(dāng)我們?cè)诰帉?xiě)layout文件的時(shí)候,卻不能直接寫(xiě)成128dp*128dp百宇。在把設(shè)計(jì)稿向UI代碼轉(zhuǎn)換的過(guò)程中考廉,我們需要耗費(fèi)相當(dāng)?shù)木θマD(zhuǎn)換尺寸,這會(huì)極大的降低我們的生產(chǎn)力携御,拉低開(kāi)發(fā)效率昌粤。
寬高限定符適配
為了高效的實(shí)現(xiàn)UI開(kāi)發(fā),出現(xiàn)了新的適配方案啄刹,我把它稱作寬高限定符適配涮坐。簡(jiǎn)單說(shuō),就是窮舉市面上所有的Android手機(jī)的寬高像素值:
設(shè)定一個(gè)基準(zhǔn)的分辨率誓军,其他分辨率都根據(jù)這個(gè)基準(zhǔn)分辨率來(lái)計(jì)算袱讹,在不同的尺寸文件夾內(nèi)部,根據(jù)該尺寸編寫(xiě)對(duì)應(yīng)的dimens文件昵时。
比如以480x320為基準(zhǔn)分辨率
- 寬度為320捷雕,將任何分辨率的寬度整分為320份,取值為x1-x320
- 高度為480壹甥,將任何分辨率的高度整分為480份非区,取值為y1-y480
那么對(duì)于800*480的分辨率的dimens文件來(lái)說(shuō),
x1=(480/320)*1=1.5px
x2=(480/320)*2=3px
...
這個(gè)時(shí)候盹廷,如果我們的UI設(shè)計(jì)界面使用的就是基準(zhǔn)分辨率征绸,那么我們就可以按照設(shè)計(jì)稿上的尺寸填寫(xiě)相對(duì)應(yīng)的dimens引用了,而當(dāng)APP運(yùn)行在不同分辨率的手機(jī)中時(shí),這些系統(tǒng)會(huì)根據(jù)這些dimens引用去該分辨率的文件夾下面尋找對(duì)應(yīng)的值俄占。這樣基本解決了我們的適配問(wèn)題管怠,而且極大的提升了我們UI開(kāi)發(fā)的效率,
但是這個(gè)方案有一個(gè)致命的缺陷缸榄,那就是需要精準(zhǔn)命中才能適配渤弛,比如1920x1080的手機(jī)就一定要找到1920x1080的限定符,否則就只能用統(tǒng)一的默認(rèn)的dimens文件了甚带。而使用默認(rèn)的尺寸的話她肯,UI就很可能變形,簡(jiǎn)單說(shuō)鹰贵,就是容錯(cuò)機(jī)制很差晴氨。
不過(guò)這個(gè)方案有一些團(tuán)隊(duì)用過(guò),我們可以認(rèn)為它是一個(gè)比較成熟有效的方案了碉输。
UI適配框架(已經(jīng)停止維護(hù))
鴻洋大佬的適配方案的項(xiàng)目也來(lái)自于寬高限定符方案的啟發(fā)籽前。
使用方法也很簡(jiǎn)單:
第一步:
在你的項(xiàng)目的AndroidManifest中注明你的設(shè)計(jì)稿的尺寸。
<meta-data android:name="design_width" android:value="768">
</meta-data>
<meta-data android:name="design_height" android:value="1280">
</meta-data>
第二步:
讓你的Activity繼承自AutoLayoutActivity.
然后我們就可以直接在布局文件里面使用具體的像素值了,比如枝哄,設(shè)計(jì)稿上是96*96,那么我們可以直接寫(xiě)96px肄梨,APP運(yùn)行時(shí),框架會(huì)幫助我們根據(jù)不同手機(jī)的具體尺寸按比例伸縮挠锥。
這可以說(shuō)是一個(gè)極好的方案众羡,因?yàn)樗趯捀呦薅ǚm配的基礎(chǔ)上更進(jìn)一步,并且解決了容錯(cuò)機(jī)制的問(wèn)題蓖租,可以說(shuō)完美的達(dá)成了開(kāi)發(fā)高效和適配精準(zhǔn)的兩個(gè)要求粱侣。
但是我們能夠想到,因?yàn)榭蚣芤谶\(yùn)行時(shí)會(huì)在onMeasure里面做變換菜秦,我們自定義的控件可能會(huì)被影響或限制,可能有些特定的控件舶掖,需要單獨(dú)適配球昨,這里面可能存在的暗坑是不可預(yù)見(jiàn)的,還有一個(gè)比較重要的問(wèn)題眨攘,那就是整個(gè)適配工作是有框架完成的主慰,而不是系統(tǒng)完成的,一旦使用這個(gè)框架,未來(lái)一旦遇到很難解決的問(wèn)題,替換起來(lái)是非常麻煩的钥组,而且項(xiàng)目一旦停止維護(hù)掀淘,后續(xù)的升級(jí)就只能靠你自己了,這種代價(jià)團(tuán)隊(duì)能否承受抡砂?當(dāng)然,它已經(jīng)停止維護(hù)了。
不過(guò)僅僅就技術(shù)方案而言雏蛮,不可否認(rèn),這是一個(gè)很好的開(kāi)源項(xiàng)目阱州。
小結(jié)
討論的上述幾種適配方案都是可以實(shí)際用于開(kāi)發(fā)中的比較成熟的方案挑秉,而且確實(shí)有很多開(kāi)發(fā)者正在使用。不過(guò)由于他們各自都存在一些缺陷苔货,所以我們使用了上述方案后還需要花費(fèi)額外的精力著手解決這些可能存在的缺陷犀概。
那么,是否存在一種相對(duì)比較完美夜惭,沒(méi)有明顯的缺陷的方案呢姻灶?
smallestWidth適配
smallestWidth適配,或者叫sw限定符適配诈茧。指的是Android會(huì)識(shí)別屏幕可用高度和寬度的最小尺寸的dp值(其實(shí)就是手機(jī)的寬度值)木蹬,然后根據(jù)識(shí)別到的結(jié)果去資源文件中尋找對(duì)應(yīng)限定符的文件夾下的資源文件。
這種機(jī)制和上文提到的寬高限定符適配原理上是一樣的,都是系統(tǒng)通過(guò)特定的規(guī)則來(lái)選擇對(duì)應(yīng)的文件镊叁。
舉個(gè)例子尘颓,小米5的dpi是480,橫向像素是1080px,根據(jù)px=dp(dpi/160)晦譬,橫向的dp值是1080/(480/160),也就是360dp,系統(tǒng)就會(huì)去尋找是否存在value-sw360dp的文件夾以及對(duì)應(yīng)的資源文件疤苹。
smallestWidth限定符適配和寬高限定符適配最大的區(qū)別在于,前者有很好的容錯(cuò)機(jī)制敛腌,如果沒(méi)有value-sw360dp文件夾卧土,系統(tǒng)會(huì)向下尋找,比如離360dp最近的只有value-sw350dp像樊,那么Android就會(huì)選擇value-sw350dp文件夾下面的資源文件尤莺。這個(gè)特性就完美的解決了上文提到的寬高限定符的容錯(cuò)問(wèn)題。
這套方案是上述幾種方案中最接近完美的方案生棍。
首先颤霎,從開(kāi)發(fā)效率上,它不遜色于上述任意一種方案涂滴。根據(jù)固定的放縮比例友酱,我們基本可以按照UI設(shè)計(jì)的尺寸不假思索的填寫(xiě)對(duì)應(yīng)的dimens引用。
我們還有以375個(gè)像素寬度的設(shè)計(jì)稿為例柔纵,在values-sw360dp文件夾下的diemns文件應(yīng)該怎么編寫(xiě)呢缔杉?這個(gè)文件夾下,意味著手機(jī)的最小寬度的dp值是360搁料,我們把360dp等分成375等份或详,每一個(gè)設(shè)計(jì)稿中的像素,大概代表smallestWidth值為360dp的手機(jī)中的0.96dp郭计,那么接下來(lái)的事情就很簡(jiǎn)單了鸭叙,假如設(shè)計(jì)稿上出現(xiàn)了一個(gè)10px*10px的ImageView,那么,我們就可以不假思索的在layout文件中寫(xiě)下對(duì)應(yīng)的尺寸拣宏。
而這種diemns引用沈贝,在不同的values-sw<N>dp文件夾下的數(shù)值是不同的,比如values-sw360dp和values-sw400dp,
當(dāng)系統(tǒng)識(shí)別到手機(jī)的smallestWidth值時(shí)勋乾,就會(huì)自動(dòng)去尋找和目標(biāo)數(shù)據(jù)最近的資源文件的尺寸宋下。
其次,從穩(wěn)定性上辑莫,它也優(yōu)于上述方案学歧。原生的dp適配可能會(huì)碰到Pixel 2這種有些特別的手機(jī)需要單獨(dú)適配,但是在smallestWidth適配中各吨,通過(guò)計(jì)算Pixel 2手機(jī)的的smallestWidth的值是411枝笨,我們只需要生成一個(gè)values-sw411dp(或者取整生成values-sw410dp也沒(méi)問(wèn)題)就能解決問(wèn)題。
smallestWidth的適配機(jī)制由系統(tǒng)保證,我們只需要針對(duì)這套規(guī)則生成對(duì)應(yīng)的資源文件即可横浑,不會(huì)出現(xiàn)什么難以解決的問(wèn)題剔桨,也根本不會(huì)影響我們的業(yè)務(wù)邏輯代碼,而且只要我們生成的資源文件分布合理徙融,洒缀,即使對(duì)應(yīng)的smallestWidth值沒(méi)有找到完全對(duì)應(yīng)的資源文件,它也能向下兼容欺冀,尋找最接近的資源文件树绩。
當(dāng)然,smallestWidth適配方案有一個(gè)小問(wèn)題隐轩,那就是它是在Android 3.2 以后引入的饺饭,Google的本意是用它來(lái)適配平板的布局文件(但是實(shí)際上顯然用于diemns適配的效果更好),不過(guò)目前所有的項(xiàng)目應(yīng)該最低支持版本應(yīng)該都是4.0了(糗事百科這么老的項(xiàng)目最低都是4.0哦)职车,所以瘫俊,這問(wèn)題其實(shí)也不重要了。
還有一個(gè)缺陷我忘了提提鸟,那就是多個(gè)dimens文件可能導(dǎo)致apk變大军援,這是事實(shí)仅淑,根據(jù)生成的dimens文件的覆蓋范圍和尺寸范圍称勋,apk可能會(huì)增大300kb-800kb左右,目前糗百的dimens文件大小是406kb涯竟,我認(rèn)為這是可以接受的赡鲜。
今日頭條適配方案(更新)
文章鏈接,之前確實(shí)沒(méi)有接觸過(guò)庐船,我簡(jiǎn)單看了一遍银酬,可以說(shuō),這也是相對(duì)比較完美的方案筐钟,我先簡(jiǎn)單說(shuō)一下這個(gè)方案的思路揩瞪,它是通過(guò)修改density值,強(qiáng)行把所有不同尺寸分辨率的手機(jī)的寬度dp值改成一個(gè)統(tǒng)一的值篓冲,這樣就解決了所有的適配問(wèn)題李破。
比如,設(shè)計(jì)稿寬度是360px壹将,那么開(kāi)發(fā)這邊就會(huì)把目標(biāo)dp值設(shè)為360dp嗤攻,在不同的設(shè)備中,動(dòng)態(tài)修改density值诽俯,從而保證(手機(jī)像素寬度)px/density這個(gè)值始終是360dp,這樣的話妇菱,就能保證UI在不同的設(shè)備上表現(xiàn)一致了。
這個(gè)方案侵入性很低,而且也沒(méi)有涉及私有API闯团,應(yīng)該也是極不錯(cuò)的方案辛臊,我暫時(shí)也想不到強(qiáng)行修改density是否會(huì)有其他影響,既然有今日頭條的大廠在用偷俭,穩(wěn)定性應(yīng)當(dāng)是有保證的浪讳。
但是根據(jù)我的觀察,這套方案對(duì)老項(xiàng)目是不太友好的涌萤,因?yàn)樾薷牧讼到y(tǒng)的density值之后淹遵,整個(gè)布局的實(shí)際尺寸都會(huì)發(fā)生改變,如果想要在老項(xiàng)目文件中使用负溪,恐怕整個(gè)布局文件中的尺寸都可能要重新按照設(shè)計(jì)稿修改一遍才行透揣。因此,如果你是在維護(hù)或者改造老項(xiàng)目川抡,使用這套方案就要三思了辐真。
福利贈(zèng)送
生成diemns文件的過(guò)程以及數(shù)據(jù)計(jì)算方法上面已經(jīng)講清楚了,大家完全可以自己去生成這些文件崖堤,我在這里附贈(zèng)生成values-sw<N>的項(xiàng)目代碼侍咱,大家直接拿去用,是Java工程密幔。點(diǎn)擊這里獲取項(xiàng)目地址
關(guān)于一些問(wèn)題
Q: 該適配方案怎么用楔脯?
A:點(diǎn)擊進(jìn)入上文的github項(xiàng)目,下載到本地胯甩,然后運(yùn)行該Java工程昧廷,會(huì)在本地根目錄下生成相應(yīng)的文件,如果需要生成更多尺寸偎箫,在DimenTypes 文件中填寫(xiě)你需要的尺寸即可木柬。
Q: 是否有推薦的尺寸?
A 300,320,360,411淹办,450眉枕,這幾個(gè)尺寸是比較必要的,然后在其中插入一些其他的尺寸即可怜森,如果不放心速挑,可以在300-450之間,以10為步長(zhǎng)生成十幾個(gè)文件塔插。
Q:平板適配的問(wèn)題梗摇?
A: 這個(gè)可以分成兩個(gè)問(wèn)題,第一想许,團(tuán)隊(duì)有沒(méi)有專(zhuān)門(mén)針對(duì)平板設(shè)計(jì)UI?第二伶授,才是如何對(duì)平板適配断序。如果團(tuán)隊(duì)內(nèi)部沒(méi)有針對(duì)平板設(shè)計(jì)UI,那么大家對(duì)于App在平板上運(yùn)行的要求大抵也就是不要太難看即可。針對(duì)這種情況的適配方法是被動(dòng)適配糜烹,即不要生成480以上的適配文件违诗,這樣在平板上,系統(tǒng)就會(huì)使用480這個(gè)尺寸的dimens文件疮蹦,這樣效果比主動(dòng)適配更好诸迟;而如果團(tuán)隊(duì)主動(dòng)設(shè)計(jì)了平板的UI,那么我們就需要主動(dòng)生成平板的適配文件愕乎,大概在600-800之間阵苇,關(guān)鍵尺寸是640,768。然后按照UI設(shè)計(jì)的圖來(lái)寫(xiě)即可感论。
Q:用了這套方案是否就不需要使用wrap_content等來(lái)布局了绅项?
A:這是絕對(duì)錯(cuò)誤的做法!如果UI設(shè)計(jì)上明顯更適合使用wrap_content,match_parent,layout_weight等,我們就要毫不猶豫的使用比肄,而且在高這個(gè)維度上快耿,我們要依照情況設(shè)計(jì)為可滑動(dòng)的方式,或者match_parent,盡量不要寫(xiě)死芳绩∠坪ィ總之,所有的適配方案都不是用來(lái)取代match_parent,wrap_content的妥色,而是用來(lái)完善他們的搪花。
后記
糗百app幾天內(nèi)會(huì)發(fā)布11.2.0的UI全新改版的新版本,里面就大范圍用到了sw適配垛膝,到時(shí)候大家可以用各種Android手機(jī)試用鳍侣,看看適配效果丁稀,如果哪里適配有問(wèn)題可及時(shí)聯(lián)系我吼拥。