1. 程式人生 > >Android 目前最穩定和高效的UI適配方案

Android 目前最穩定和高效的UI適配方案

設計師 data ID 設置 評論 系統軟件 放縮 可用 github

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將會不同,比如:

...
1080720
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(19201080)的dpi是420,也就是說,在Pixel2中,1dp=2.625px,這樣會導致相同分辨率的手機中,這樣,一個100dp100dp的控件,在一般的1080P手機上,可能都是300px,而Pixel 2 中 ,就只有262.5px,這樣控件的實際大小會有所不同。
為了更形象的展示,假設我們在布局文件中把一個ImageView的寬度設置為360dp,那麽在下面兩張圖中表現是不一樣的:
圖一是1080P,480dpi的手機,圖二是1080P,420dpi的手機

從上面的布局中可以看到,同樣是1080P的手機,差異是比較明顯的。在這種情況下,我們的UI可能需要做一些微調甚至單獨適配。
第二個問題,這種方式無法快速高效的把設計師的設計稿實現到布局代碼中,通過dp直接適配,我們只能讓UI基本適配不同的手機,但是在設計圖和UI代碼之間的鴻溝,dp是無法解決的,因為dp不是真實像素。而且,設計稿的寬高往往和Android的手機真實寬高差別極大,以我們的設計稿為例,設計稿的寬高是375px750px,而真實手機可能普遍是10801920,
那麽在日常開發中我們是怎麽跨過這個鴻溝的呢?基本都是通過百分比啊,或者通過估算,或者設定一個規範值等等。總之,當我們拿到設計稿的時候,設計稿的ImageView是128px128px,當我們在編寫layout文件的時候,卻不能直接寫成128dp128dp。在把設計稿向UI代碼轉換的過程中,我們需要耗費相當的精力去轉換尺寸,這會極大的降低我們的生產力,拉低開發效率。
寬高限定符適配
為了高效的實現UI開發,出現了新的適配方案,我把它稱作寬高限定符適配。簡單說,就是窮舉市面上所有的Android手機的寬高像素值:

設定一個基準的分辨率,其他分辨率都根據這個基準分辨率來計算,在不同的尺寸文件夾內部,根據該尺寸編寫對應的dimens文件。
比如以480x320為基準分辨率

寬度為320,將任何分辨率的寬度整分為320份,取值為x1-x320
高度為480,將任何分辨率的高度整分為480份,取值為y1-y480

那麽對於800480的分辨率的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值之後,整個布局的實際尺寸都會發生改變,如果想要在老項目文件中使用,恐怕整個布局文件中的尺寸都可能要重新按照設計稿修改一遍才行。因此,如果你是在維護或者改造老項目,使用這套方案就要三思了。
福利贈送
生成diemns文件的過程以及數據計算方法上面已經講清楚了,大家完全可以自己去生成這些文件,我在這裏附贈生成values-sw的項目代碼,大家直接拿去用,是Java工程。點擊這裏獲取項目地址
關於一些問題
Q: 該適配方案怎麽用?
A:點擊進入上文的github項目,下載到本地,然後運行該Java工程,會在本地根目錄下生成相應的文件,如果需要生成更多尺寸,在DimenTypes 文件中填寫你需要的尺寸即可。
Q: 是否有推薦的尺寸?
A 300,320,360,411,450,這幾個尺寸是比較必要的,然後在其中插入一些其他的尺寸即可,如果不放心,可以在300-450之間,以10為步長生成十幾個文件。
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 目前最穩定和高效的UI適配方案