Android系統(tǒng)發(fā)布十多年以來抢埋,關(guān)于Android的UI的適配一直是開發(fā)環(huán)節(jié)中最重要的問題犁柜,但是我看到還是有很多小伙伴對Android適配方案不了解。剛好蝇更,近期準(zhǔn)備對糗事百科Android客戶端設(shè)計(jì)一套UI尺寸適配方案岭洲,可以和小伙伴們詳細(xì)的聊一聊這個(gè)問題宛逗。
Android適配最核心的問題有兩個(gè),其一盾剩,就是適配的效率雷激,即把設(shè)計(jì)圖轉(zhuǎn)化為App界面的過程是否高效替蔬,其二如何保證實(shí)現(xiàn)UI界面在不同尺寸和分辨率的手機(jī)中UI的一致性。這兩個(gè)問題都很重要屎暇,一個(gè)是保證我們開發(fā)的高效进栽,一個(gè)是保證我們適配的成效;今天我們就這兩個(gè)核心的問題來聊一聊Android的適配方案恭垦。
首先快毛,大家都知道,在標(biāo)識(shí)尺寸的時(shí)候番挺,Android并不推薦我們使用px這個(gè)真實(shí)像素單位唠帝,因?yàn)椴煌氖謾C(jī)之間,分辨率是不同的玄柏,比如一個(gè)96*96像素的控件在分辨率越來越高的手機(jī)上會(huì)在整體UI中看起來越來越小襟衰。
出現(xiàn)類似于上圖這樣這樣,整體的布局效果可能會(huì)變形粪摘,所以px這個(gè)單位在布局文件中是不推薦的瀑晒。
dp直接適配
針對這種情況,Android推薦使用dp作為尺寸單位來適配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)都是通過這個(gè)來判斷px和dp的數(shù)學(xué)關(guān)系,
那么這里又出現(xiàn)了一個(gè)問題诸狭,dpi是什么呢券膀?
dpi是像素密度,指的是在系統(tǒng)軟件上指定的單位尺寸的像素?cái)?shù)量作谚,它往往是寫在系統(tǒng)出廠配置文件的一個(gè)固定值三娩。
我為什么要強(qiáng)調(diào)它是軟件系統(tǒng)上的概念庵芭?因?yàn)榇蠹屹I手機(jī)的時(shí)候妹懒,往往會(huì)聽到另一個(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è)相對固定的數(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對應(yīng)不同數(shù)量的px(720P中讳癌,1dp=2px,1080P中1dp=3px)存皂,這就實(shí)現(xiàn)了析桥,當(dāng)我們使用dp來定義一個(gè)控件大小的時(shí)候,他在不同的手機(jī)里表現(xiàn)出相應(yīng)大小的像素值艰垂。
我們可以說泡仗,通過dp加上自適應(yīng)布局和weight比例布局可以基本解決不同手機(jī)上適配的問題,這基本是最原始的Android適配方案猜憎。
這種方式存在兩個(gè)小問題娩怎,第一,這只能保證我們寫出來的界面適配絕大部分手機(jī)胰柑,部分手機(jī)仍然需要單獨(dú)適配截亦,為什么dp只解決了90%的適配問題,因?yàn)椴⒉皇撬械?080P的手機(jī)dpi都是480柬讨,比如Google 的Pixel2(1920*1080)的dpi是420崩瓤,也就是說,在Pixel2中踩官,1dp=2.625px,這樣會(huì)導(dǎo)致相同分辨率的手機(jī)中却桶,這樣,一個(gè)100dp*100dp的控件,在一般的1080P手機(jī)上颖系,可能都是300px,而Pixel 2 中 嗅剖,就只有262.5px,這樣控件的實(shí)際大小會(huì)有所不同。
為了更形象的展示嘁扼,假設(shè)我們在布局文件中把一個(gè)ImageView的寬度設(shè)置為360dp,那么在下面兩張圖中表現(xiàn)是不一樣的:
圖一是1080P,480dpi的手機(jī)信粮,圖二是1080P,420dpi的手機(jī)
從上面的布局中可以看到,同樣是1080P的手機(jī)趁啸,差異是比較明顯的强缘。在這種情況下,我們的UI可能需要做一些微調(diào)甚至單獨(dú)適配不傅。
第二個(gè)問題欺旧,這種方式無法快速高效的把設(shè)計(jì)師的設(shè)計(jì)稿實(shí)現(xiàn)到布局代碼中,通過dp直接適配蛤签,我們只能讓UI基本適配不同的手機(jī),但是在設(shè)計(jì)圖和UI代碼之間的鴻溝辞友,dp是無法解決的,因?yàn)閐p不是真實(shí)像素震肮。而且称龙,設(shè)計(jì)稿的寬高往往和Android的手機(jī)真實(shí)寬高差別極大,以我們的設(shè)計(jì)稿為例戳晌,設(shè)計(jì)稿的寬高是375px*750px鲫尊,而真實(shí)手機(jī)可能普遍是1080*1920,
那么在日常開發(fā)中我們是怎么跨過這個(gè)鴻溝的呢?基本都是通過百分比啊沦偎,或者通過估算疫向,或者設(shè)定一個(gè)規(guī)范值等等『篮浚總之搔驼,當(dāng)我們拿到設(shè)計(jì)稿的時(shí)候,設(shè)計(jì)稿的ImageView是128px*128px侈询,當(dāng)我們在編寫layout文件的時(shí)候舌涨,卻不能直接寫成128dp*128dp。在把設(shè)計(jì)稿向UI代碼轉(zhuǎn)換的過程中扔字,我們需要耗費(fèi)相當(dāng)?shù)木θマD(zhuǎn)換尺寸囊嘉,這會(huì)極大的降低我們的生產(chǎn)力,拉低開發(fā)效率革为。
寬高限定符適配
為了高效的實(shí)現(xiàn)UI開發(fā)扭粱,出現(xiàn)了新的適配方案,我把它稱作寬高限定符適配震檩。簡單說琢蛤,就是窮舉市面上所有的Android手機(jī)的寬高像素值:
設(shè)定一個(gè)基準(zhǔn)的分辨率,其他分辨率都根據(jù)這個(gè)基準(zhǔn)分辨率來計(jì)算,在不同的尺寸文件夾內(nèi)部虐块,根據(jù)該尺寸編寫對應(yīng)的dimens文件俩滥。
比如以480x320為基準(zhǔn)分辨率
- 寬度為320嘉蕾,將任何分辨率的寬度整分為320份贺奠,取值為x1-x320
- 高度為480,將任何分辨率的高度整分為480份错忱,取值為y1-y480
那么對于800*480的分辨率的dimens文件來說儡率,
x1=(480/320)*1=1.5px
x2=(480/320)*2=3px
...
這個(gè)時(shí)候,如果我們的UI設(shè)計(jì)界面使用的就是基準(zhǔn)分辨率以清,那么我們就可以按照設(shè)計(jì)稿上的尺寸填寫相對應(yīng)的dimens引用了,而當(dāng)APP運(yùn)行在不同分辨率的手機(jī)中時(shí)儿普,這些系統(tǒng)會(huì)根據(jù)這些dimens引用去該分辨率的文件夾下面尋找對應(yīng)的值。這樣基本解決了我們的適配問題掷倔,而且極大的提升了我們UI開發(fā)的效率眉孩,
但是這個(gè)方案有一個(gè)致命的缺陷,那就是需要精準(zhǔn)命中才能適配勒葱,比如1920x1080的手機(jī)就一定要找到1920x1080的限定符浪汪,否則就只能用統(tǒng)一的默認(rèn)的dimens文件了。而使用默認(rèn)的尺寸的話凛虽,UI就很可能變形死遭,簡單說,就是容錯(cuò)機(jī)制很差凯旋。
不過這個(gè)方案有一些團(tuán)隊(duì)用過呀潭,我們可以認(rèn)為它是一個(gè)比較成熟有效的方案了。
UI適配框架(已經(jīng)停止維護(hù))
鴻洋大佬的適配方案的項(xiàng)目也來自于寬高限定符方案的啟發(fā)至非。
使用方法也很簡單:
第一步:
在你的項(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,那么我們可以直接寫96px踏幻,APP運(yùn)行時(shí),框架會(huì)幫助我們根據(jù)不同手機(jī)的具體尺寸按比例伸縮戳杀。
這可以說是一個(gè)極好的方案该面,因?yàn)樗趯捀呦薅ǚm配的基礎(chǔ)上更進(jìn)一步,并且解決了容錯(cuò)機(jī)制的問題信卡,可以說完美的達(dá)成了開發(fā)高效和適配精準(zhǔn)的兩個(gè)要求隔缀。
但是我們能夠想到,因?yàn)榭蚣芤谶\(yùn)行時(shí)會(huì)在onMeasure里面做變換傍菇,我們自定義的控件可能會(huì)被影響或限制猾瘸,可能有些特定的控件,需要單獨(dú)適配,這里面可能存在的暗坑是不可預(yù)見的牵触,還有一個(gè)比較重要的問題淮悼,那就是整個(gè)適配工作是有框架完成的,而不是系統(tǒng)完成的揽思,一旦使用這個(gè)框架袜腥,未來一旦遇到很難解決的問題,替換起來是非常麻煩的钉汗,而且項(xiàng)目一旦停止維護(hù)羹令,后續(xù)的升級就只能靠你自己了,這種代價(jià)團(tuán)隊(duì)能否承受损痰?當(dāng)然福侈,它已經(jīng)停止維護(hù)了。
不過僅僅就技術(shù)方案而言卢未,不可否認(rèn)肪凛,這是一個(gè)很好的開源項(xiàng)目。
小結(jié)
討論的上述幾種適配方案都是可以實(shí)際用于開發(fā)中的比較成熟的方案辽社,而且確實(shí)有很多開發(fā)者正在使用伟墙。不過由于他們各自都存在一些缺陷,所以我們使用了上述方案后還需要花費(fèi)額外的精力著手解決這些可能存在的缺陷爹袁。
那么远荠,是否存在一種相對比較完美,沒有明顯的缺陷的方案呢失息?
smallestWidth適配
smallestWidth適配譬淳,或者叫sw限定符適配。指的是Android會(huì)識(shí)別屏幕可用高度和寬度的最小尺寸的dp值(其實(shí)就是手機(jī)的寬度值)盹兢,然后根據(jù)識(shí)別到的結(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)的資源文件剂娄。
smallestWidth限定符適配和寬高限定符適配最大的區(qū)別在于,前者有很好的容錯(cuò)機(jī)制玄呛,如果沒有value-sw360dp文件夾阅懦,系統(tǒng)會(huì)向下尋找,比如離360dp最近的只有value-sw350dp徘铝,那么Android就會(huì)選擇value-sw350dp文件夾下面的資源文件耳胎。這個(gè)特性就完美的解決了上文提到的寬高限定符的容錯(cuò)問題惯吕。
這套方案是上述幾種方案中最接近完美的方案。
首先怕午,從開發(fā)效率上废登,它不遜色于上述任意一種方案。根據(jù)固定的放縮比例郁惜,我們基本可以按照UI設(shè)計(jì)的尺寸不假思索的填寫對應(yīng)的dimens引用堡距。
我們還有以375個(gè)像素寬度的設(shè)計(jì)稿為例,在values-sw360dp文件夾下的diemns文件應(yīng)該怎么編寫呢扳炬?這個(gè)文件夾下吏颖,意味著手機(jī)的最小寬度的dp值是360搔体,我們把360dp等分成375等份恨樟,每一個(gè)設(shè)計(jì)稿中的像素,大概代表smallestWidth值為360dp的手機(jī)中的0.96dp疚俱,那么接下來的事情就很簡單了劝术,假如設(shè)計(jì)稿上出現(xiàn)了一個(gè)10px*10px的ImageView,那么,我們就可以不假思索的在layout文件中寫下對應(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適配中拇勃,通過計(jì)算Pixel 2手機(jī)的的smallestWidth的值是411四苇,我們只需要生成一個(gè)values-sw411dp(或者取整生成values-sw410dp也沒問題)就能解決問題。
smallestWidth的適配機(jī)制由系統(tǒng)保證方咆,我們只需要針對這套規(guī)則生成對應(yīng)的資源文件即可月腋,不會(huì)出現(xiàn)什么難以解決的問題,也根本不會(huì)影響我們的業(yè)務(wù)邏輯代碼瓣赂,而且只要我們生成的資源文件分布合理榆骚,,即使對應(yīng)的smallestWidth值沒有找到完全對應(yīng)的資源文件煌集,它也能向下兼容妓肢,尋找最接近的資源文件。
當(dāng)然牙勘,smallestWidth適配方案有一個(gè)小問題职恳,那就是它是在Android 3.2 以后引入的所禀,Google的本意是用它來適配平板的布局文件(但是實(shí)際上顯然用于diemns適配的效果更好),不過目前所有的項(xiàng)目應(yīng)該最低支持版本應(yīng)該都是4.0了(糗事百科這么老的項(xiàng)目最低都是4.0哦)放钦,所以色徘,這問題其實(shí)也不重要了代赁。
還有一個(gè)缺陷我忘了提宪哩,那就是多個(gè)dimens文件可能導(dǎo)致apk變大,這是事實(shí)折联,根據(jù)生成的dimens文件的覆蓋范圍和尺寸范圍颓屑,apk可能會(huì)增大300kb-800kb左右斤寂,目前糗百的dimens文件大小是406kb,我認(rèn)為這是可以接受的揪惦。
今日頭條適配方案(更新)
文章鏈接遍搞,之前確實(shí)沒有接觸過,我簡單看了一遍器腋,可以說溪猿,這也是相對比較完美的方案,我先簡單說一下這個(gè)方案的思路纫塌,它是通過修改density值诊县,強(qiáng)行把所有不同尺寸分辨率的手機(jī)的寬度dp值改成一個(gè)統(tǒng)一的值,這樣就解決了所有的適配問題措左。
比如依痊,設(shè)計(jì)稿寬度是360px,那么開發(fā)這邊就會(huì)把目標(biāo)dp值設(shè)為360dp怎披,在不同的設(shè)備中胸嘁,動(dòng)態(tài)修改density值,從而保證(手機(jī)像素寬度)px/density這個(gè)值始終是360dp,這樣的話钳枕,就能保證UI在不同的設(shè)備上表現(xiàn)一致了缴渊。
這個(gè)方案侵入性很低,而且也沒有涉及私有API鱼炒,應(yīng)該也是極不錯(cuò)的方案衔沼,我暫時(shí)也想不到強(qiáng)行修改density是否會(huì)有其他影響,既然有今日頭條的大廠在用昔瞧,穩(wěn)定性應(yīng)當(dāng)是有保證的指蚁。
但是根據(jù)我的觀察,這套方案對老項(xiàng)目是不太友好的自晰,因?yàn)樾薷牧讼到y(tǒng)的density值之后凝化,整個(gè)布局的實(shí)際尺寸都會(huì)發(fā)生改變,如果想要在老項(xiàng)目文件中使用酬荞,恐怕整個(gè)布局文件中的尺寸都可能要重新按照設(shè)計(jì)稿修改一遍才行搓劫。因此瞧哟,如果你是在維護(hù)或者改造老項(xiàng)目,使用這套方案就要三思了枪向。
福利贈(zèng)送
生成diemns文件的過程以及數(shù)據(jù)計(jì)算方法上面已經(jīng)講清楚了勤揩,大家完全可以自己去生成這些文件,我在這里附贈(zèng)生成values-sw<N>的項(xiàng)目代碼秘蛔,大家直接拿去用陨亡,是Java工程。點(diǎn)擊這里獲取項(xiàng)目地址
關(guān)于一些問題
Q: 該適配方案怎么用深员?
A:點(diǎn)擊進(jìn)入上文的github項(xiàng)目负蠕,下載到本地,然后運(yùn)行該Java工程倦畅,會(huì)在本地根目錄下生成相應(yīng)的文件遮糖,如果需要生成更多尺寸,在DimenTypes 文件中填寫你需要的尺寸即可滔迈。
Q: 是否有推薦的尺寸止吁?
A 300,320,360,411被辑,450燎悍,這幾個(gè)尺寸是比較必要的,然后在其中插入一些其他的尺寸即可盼理,如果不放心谈山,可以在300-450之間,以10為步長生成十幾個(gè)文件宏怔。
Q:平板適配的問題奏路?
A: 這個(gè)可以分成兩個(gè)問題,第一臊诊,團(tuán)隊(duì)有沒有專門針對平板設(shè)計(jì)UI?第二鸽粉,才是如何對平板適配。如果團(tuán)隊(duì)內(nèi)部沒有針對平板設(shè)計(jì)UI,那么大家對于App在平板上運(yùn)行的要求大抵也就是不要太難看即可抓艳。針對這種情況的適配方法是被動(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ì)的圖來寫即可氛濒。
Q:用了這套方案是否就不需要使用wrap_content等來布局了产场?
A:這是絕對錯(cuò)誤的做法!如果UI設(shè)計(jì)上明顯更適合使用wrap_content,match_parent,layout_weight等,我們就要毫不猶豫的使用舞竿,而且在高這個(gè)維度上涝动,我們要依照情況設(shè)計(jì)為可滑動(dòng)的方式,或者match_parent,盡量不要寫死炬灭〈姿冢總之,所有的適配方案都不是用來取代match_parent,wrap_content的重归,而是用來完善他們的米愿。
后記
糗百app幾天內(nèi)會(huì)發(fā)布11.2.0的UI全新改版的新版本,里面就大范圍用到了sw適配鼻吮,到時(shí)候大家可以用各種Android手機(jī)試用育苟,看看適配效果,如果哪里適配有問題可及時(shí)聯(lián)系我椎木。