Android系統釋出十多年以來,關于Android的UI的适配一直是開發環節中最重要的問題,但是我看到還是有很多小夥伴對Android适配方案不了解。剛好,近期準備對糗事百科Android用戶端設計一套UI尺寸适配方案,可以和小夥伴們詳細的聊一聊這個問題。
Android适配最核心的問題有兩個,其一,就是适配的效率,即把設計圖轉化為App界面的過程是否高效,其二如何保證實作UI界面在不同尺寸和分辨率的手機中UI的一緻性。這兩個問題都很重要,一個是保證我們開發的高效,一個是保證我們适配的成效;今天我們就這兩個核心的問題來聊一聊Android的适配方案。
首先,大家都知道,在辨別尺寸的時候,Android并不推薦我們使用px這個真實像素機關,因為不同的手機之間,分辨率是不同的,比如一個96*96像素的控件在分辨率越來越高的手機上會在整體UI中看起來越來越小。
出現類似于上圖這樣這樣,整體的布局效果可能會變形,是以px這個機關在布局檔案中是不推薦的。
dp直接适配
針對這種情況,Android推薦使用dp作為尺寸機關來适配UI.
那麼什麼是dp?dp指的是裝置獨立像素,以dp為尺寸機關的控件,在不同分辨率和尺寸的手機上代表了不同的真實像素,比如在分辨率較低的手機中,可能1dp=1px,而在分辨率較高的手機中,可能1dp=2px,這樣的話,一個96*96dp的控件,在不同的手機中就能表現出差不多的大小了。那麼這個dp是如何計算的呢? 我們都知道一個公式: px = dp(dpi/160) 系統都是通過這個來判斷px和dp的數學關系,
那麼這裡又出現了一個問題,dpi是什麼呢?
dpi是像素密度,指的是在系統軟體上指定的機關尺寸的像素數量,它往往是寫在系統出廠配置檔案的一個固定值。
我為什麼要強調它是軟體系統上的概念?因為大家買手機的時候,往往會聽到另一個叫ppi的參數,這個在手機螢幕中指的也是像素密度,但是這個是實體上的概念,它是客觀存在的不會改變。dpi是軟體參考了實體像素密度後,人為指定的一個值,這樣保證了某一個區間内的實體像素密度在軟體上都使用同一個值。這樣會有利于我們的UI适配。
比如,幾部相同分辨率不同尺寸的手機的ppi可能分别是是430,440,450,那麼在Android系統中,可能dpi會全部指定為480.這樣的話,dpi/160就會是一個相對固定的數值,這樣就能保證相同分辨率下不同尺寸的手機表現一緻。
而在不同分辨率下,dpi将會不同,比如:
... | 1080*720 | 1920*1080 |
---|---|---|
dpi | 320 | 480 |
dpi/160 | 2 | 3 |
根據上面的表格,我們可以發現,720P,和1080P的手機,dpi是不同的,這也就意味着,不同的分辨率中,1dp對應不同數量的px(720P中,1dp=2px,1080P中1dp=3px),這就實作了,當我們使用dp來定義一個控件大小的時候,他在不同的手機裡表現出相應大小的像素值。
我們可以說,通過dp加上自适應布局和weight比例布局可以基本解決不同手機上适配的問題,這基本是最原始的Android适配方案。
這種方式存在兩個小問題,第一,這隻能保證我們寫出來的界面适配絕大部分手機,部分手機仍然需要單獨适配,為什麼dp隻解決了90%的适配問題,因為并不是所有的1080P的手機dpi都是480,比如Google 的Pixel2(1920*1080)的dpi是420,也就是說,在Pixel2中,1dp=2.625px,這樣會導緻相同分辨率的手機中,這樣,一個100dp*100dp的控件,在一般的1080P手機上,可能都是300px,而Pixel 2 中 ,就隻有262.5px,這樣控件的實際大小會有所不同。
為了更形象的展示,假設我們在布局檔案中把一個ImageView的寬度設定為360dp,那麼在下面兩張圖中表現是不一樣的:
圖一是1080P,480dpi的手機,圖二是1080P,420dpi的手機
從上面的布局中可以看到,同樣是1080P的手機,差異是比較明顯的。在這種情況下,我們的UI可能需要做一些微調甚至單獨适配。
第二個問題,這種方式無法快速高效的把設計師的設計稿實作到布局代碼中,通過dp直接适配,我們隻能讓UI基本适配不同的手機,但是在設計圖和UI代碼之間的鴻溝,dp是無法解決的,因為dp不是真實像素。而且,設計稿的寬高往往和Android的手機真實寬高差别極大,以我們的設計稿為例,設計稿的寬高是375px*750px,而真實手機可能普遍是1080*1920,
那麼在日常開發中我們是怎麼跨過這個鴻溝的呢?基本都是通過百分比啊,或者通過估算,或者設定一個規範值等等。總之,當我們拿到設計稿的時候,設計稿的ImageView是128px*128px,當我們在編寫layout檔案的時候,卻不能直接寫成128dp*128dp。在把設計稿向UI代碼轉換的過程中,我們需要耗費相當的精力去轉換尺寸,這會極大的降低我們的生産力,拉低開發效率。
寬高限定符适配
為了高效的實作UI開發,出現了新的适配方案,我把它稱作寬高限定符适配。簡單說,就是窮舉市面上所有的Android手機的寬高像素值:
設定一個基準的分辨率,其他分辨率都根據這個基準分辨率來計算,在不同的尺寸檔案夾内部,根據該尺寸編寫對應的dimens檔案。
比如以480x320為基準分辨率
- 寬度為320,将任何分辨率的寬度整分為320份,取值為x1-x320
- 高度為480,将任何分辨率的高度整分為480份,取值為y1-y480
那麼對于800*480的分辨率的dimens檔案來說,
x1=(480/320)*1=1.5px
x2=(480/320)*2=3px
這個時候,如果我們的UI設計界面使用的就是基準分辨率,那麼我們就可以按照設計稿上的尺寸填寫相對應的dimens引用了,而當APP運作在不同分辨率的手機中時,這些系統會根據這些dimens引用去該分辨率的檔案夾下面尋找對應的值。這樣基本解決了我們的适配問題,而且極大的提升了我們UI開發的效率,
但是這個方案有一個緻命的缺陷,那就是需要精準命中才能适配,比如1920x1080的手機就一定要找到1920x1080的限定符,否則就隻能用統一的預設的dimens檔案了。而使用預設的尺寸的話,UI就很可能變形,簡單說,就是容錯機制很差。
不過這個方案有一些團隊用過,我們可以認為它是一個比較成熟有效的方案了。
UI适配架構(已經停止維護)
鴻洋大佬的适配方案的項目也來自于寬高限定符方案的啟發。
使用方法也很簡單:
第一步: 在你的項目的AndroidManifest中注明你的設計稿的尺寸。
<meta-data android:name="design_width" android:value="768">
</meta-data>
<meta-data android:name="design_height" android:value="1280">
</meta-data>
複制代碼
第二步: 讓你的Activity繼承自AutoLayoutActivity.
然後我們就可以直接在布局檔案裡面使用具體的像素值了,比如,設計稿上是96*96,那麼我們可以直接寫96px,APP運作時,架構會幫助我們根據不同手機的具體尺寸按比例伸縮。
這可以說是一個極好的方案,因為它在寬高限定符适配的基礎上更進一步,并且解決了容錯機制的問題,可以說完美的達成了開發高效和适配精準的兩個要求。
但是我們能夠想到,因為架構要在運作時會在onMeasure裡面做變換,我們自定義的控件可能會被影響或限制,可能有些特定的控件,需要單獨适配,這裡面可能存在的暗坑是不可預見的,還有一個比較重要的問題,那就是整個适配工作是有架構完成的,而不是系統完成的,一旦使用這個架構,未來一旦遇到很難解決的問題,替換起來是非常麻煩的,而且項目一旦停止維護,後續的更新就隻能靠你自己了,這種代價團隊能否承受?當然,它已經停止維護了。
不過僅僅就技術方案而言,不可否認,這是一個很好的開源項目。
小結
讨論的上述幾種适配方案都是可以實際用于開發中的比較成熟的方案,而且确實有很多開發者正在使用。不過由于他們各自都存在一些缺陷,是以我們使用了上述方案後還需要花費額外的精力着手解決這些可能存在的缺陷。
那麼,是否存在一種相對比較完美,沒有明顯的缺陷的方案呢?
smallestWidth适配
smallestWidth适配,或者叫sw限定符适配。指的是Android會識别螢幕可用高度和寬度的最小尺寸的dp值(其實就是手機的寬度值),然後根據識别到的結果去資源檔案中尋找對應限定符的檔案夾下的資源檔案。
這種機制和上文提到的寬高限定符适配原理上是一樣的,都是系統通過特定的規則來選擇對應的檔案。
舉個例子,小米5的dpi是480,橫向像素是1080px,根據px=dp(dpi/160),橫向的dp值是1080/(480/160),也就是360dp,系統就會去尋找是否存在value-sw360dp的檔案夾以及對應的資源檔案。
smallestWidth限定符适配和寬高限定符适配最大的差別在于,前者有很好的容錯機制,如果沒有value-sw360dp檔案夾,系統會向下尋找,比如離360dp最近的隻有value-sw350dp,那麼Android就會選擇value-sw350dp檔案夾下面的資源檔案。這個特性就完美的解決了上文提到的寬高限定符的容錯問題。
這套方案是上述幾種方案中最接近完美的方案。 首先,從開發效率上,它不遜色于上述任意一種方案。根據固定的放縮比例,我們基本可以按照UI設計的尺寸不假思索的填寫對應的dimens引用。 我們還有以375個像素寬度的設計稿為例,在values-sw360dp檔案夾下的diemns檔案應該怎麼編寫呢?這個檔案夾下,意味着手機的最小寬度的dp值是360,我們把360dp等分成375等份,每一個設計稿中的像素,大概代表smallestWidth值為360dp的手機中的0.96dp,那麼接下來的事情就很簡單了,假如設計稿上出現了一個10px*10px的ImageView,那麼,我們就可以不假思索的在layout檔案中寫下對應的尺寸。
而這種diemns引用,在不同的values-sw<N>dp檔案夾下的數值是不同的,比如values-sw360dp和values-sw400dp,
當系統識别到手機的smallestWidth值時,就會自動去尋找和目标資料最近的資源檔案的尺寸。
其次,從穩定性上,它也優于上述方案。原生的dp适配可能會碰到Pixel 2這種有些特别的手機需要單獨适配,但是在smallestWidth适配中,通過計算Pixel 2手機的的smallestWidth的值是411,我們隻需要生成一個values-sw411dp(或者取整生成values-sw410dp也沒問題)就能解決問題。
smallestWidth的适配機制由系統保證,我們隻需要針對這套規則生成對應的資源檔案即可,不會出現什麼難以解決的問題,也根本不會影響我們的業務邏輯代碼,而且隻要我們生成的資源檔案分布合理,,即使對應的smallestWidth值沒有找到完全對應的資源檔案,它也能向下相容,尋找最接近的資源檔案。
當然,smallestWidth适配方案有一個小問題,那就是它是在Android 3.2 以後引入的,Google的本意是用它來适配平闆的布局檔案(但是實際上顯然用于diemns适配的效果更好),不過目前所有的項目應該最低支援版本應該都是4.0了(糗事百科這麼老的項目最低都是4.0哦),是以,這問題其實也不重要了。
評論中還說到了一個缺陷我忘了提,那就是多個dimens檔案可能導緻apk變大,這是事實,根據生成的dimens檔案的覆寫範圍和尺寸範圍,apk可能會增大300kb-800kb左右,目前糗百的dimens檔案大小是406kb,我認為這是可以接受的。
今日頭條适配方案(更新)
文章連結,之前确實沒有接觸過,我簡單看了一遍,可以說,這也是相對比較完美的方案,我先簡單說一下這個方案的思路,它是通過修改density值,強行把所有不同尺寸分辨率的手機的寬度dp值改成一個統一的值,這樣就解決了所有的适配問題。
比如,設計稿寬度是360px,那麼開發這邊就會把目标dp值設為360dp,在不同的裝置中,動态修改density值,進而保證(手機像素寬度)px/density這個值始終是360dp,這樣的話,就能保證UI在不同的裝置上表現一緻了。
這個方案侵入性很低,而且也沒有涉及私有API,應該也是極不錯的方案,我暫時也想不到強行修改density是否會有其他影響,既然有今日頭條的大廠在用,穩定性應當是有保證的。
但是根據我的觀察,這套方案對老項目是不太友好的,因為修改了系統的density值之後,整個布局的實際尺寸都會發生改變,如果想要在老項目檔案中使用,恐怕整個布局檔案中的尺寸都可能要重新按照設計稿修改一遍才行。是以,如果你是在維護或者改造老項目,使用這套方案就要三思了。
關于一些問題
Q: 該适配方案怎麼用?
A:點選進入上文的github項目,下載下傳到本地,然後運作該Java工程,會在本地根目錄下生成相應的檔案,如果需要生成更多尺寸,在DimenTypes 檔案中填寫你需要的尺寸即可。
Q: 是否有推薦的尺寸?
A 300,320,360,390,411,450,這幾個尺寸是比較必要的,然後在其中插入一些其他的尺寸即可,如果不放心,可以在300-450之間,以10為步長生成十幾個檔案就OK了。
Q:平闆适配的問題?
A: 這個可以分成兩個問題,第一,團隊有沒有專門針對平闆設計UI?第二,才是如何對平闆适配。如果團隊内部沒有針對平闆設計UI,那麼大家對于App在平闆上運作的要求大抵也就是不要太難看即可。針對這種情況的适配方法是被動适配,即不要生成480以上的适配檔案,這樣在平闆上,系統就會使用480這個尺寸的dimens檔案,這樣效果比主動适配更好;而如果團隊主動設計了平闆的UI,那麼我們就需要主動生成平闆的适配檔案,大概在600-800之間,關鍵尺寸是640,768。然後按照UI設計的圖來寫即可。
Q:用了這套方案是否就不需要使用wrap_content等來布局了?
A:這是絕對錯誤的做法!如果UI設計上明顯更适合使用wrap_content,match_parent,layout_weight等,我們就要毫不猶豫的使用,而且在高這個次元上,我們要依照情況設計為可滑動的方式,或者match_parent,盡量不要寫死。總之,所有的适配方案都不是用來取代match_parent,wrap_content的,而是用來完善他們的。
更多Android進階技術,面試資料系統整理分享,職業生涯規劃,産品,思維,行業觀察,談天說地。可以加Android架構師群;701740775。