關於Android全面屏虛擬導航欄的適配總結
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開發技術交流qq群;701740775