1. 程式人生 > >! Android最強螢幕適配方案對比解析

! Android最強螢幕適配方案對比解析

注: 本文已整理成部落格,見: https://blog.csdn.net/u011200604/article/details/84990040

注: 本文最終方案推薦源於JessYanCoding/AndroidAutoSize 的開源庫(詳見GitHub)

在Android開發中,由於Android碎片化嚴重,螢幕解析度千奇百怪,而想要在各種解析度的裝置上顯示基本一致的效果,適配成本越來越高。雖然Android官方提供了dp單位來適配,但其在各種奇怪解析度下表現卻不盡如人意.

參見Google官方的螢幕適配文件:Google-  screen  compatibility overview

https://developer.android.com/guide/practices/screens_support

主流適配方案

目前有如下幾種:拉丁吳老師的 : Android 目前最穩定和高效的UI適配方案 中,有如下5種

dp直接適配

寬高限定符適配(在資原始檔下生成不同解析度的資原始檔,然後在佈局檔案中引用對應的 dimens)

UI適配框架: 見 鴻洋大神的適配方案:  AndroidAutoLayout  (已停止維護,作者推薦使用JessYanCoding的AndroidAutoSize )

smallestWidth適配 

今日頭條適配方案

 

下面著重介紹後兩種方案


首先看今日頭條適配方案

一種極低成本的Android螢幕適配方式 -- -今日頭條適配方案

(見今日頭條技術團隊 5月25日 - 一種極低成本的Android螢幕適配方式: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA)

首先看螢幕尺寸、解析度、畫素密度三者關係,關於dip(即dp),dpi與 ppi,px,sp

螢幕解析度:在橫縱向上的畫素點數。單位:px即1px=1個畫素點  一般以縱向畫素*橫向畫素表示,如1920*1080

Dpi:

螢幕畫素密度,指每英寸上的畫素點數,dot per inch的縮寫,與螢幕尺寸和螢幕解析度有關

px:畫素,構成影象的最小單位,這個比較簡單,無需介紹。

dp/dip: dp和dip是一樣的,密度無關畫素,Density Independent Pixels的縮寫,以160dpi為基準在160dpi裝置  上1dp=1px,在240dpi裝置上1dp=1.5px,以此類推

sp:Scale-Independent Pixels,可以根據文字大小首選項進行放縮,常用於設定字型大小,儘量避免使用奇數或小數,因為容易造成精度的丟失

density : 裝置的邏輯密度,是dip的縮放因子。以160dpi的螢幕為基線,density=dpi/160。

 

通常情況下,一部手機的解析度是寬x高,螢幕大小是以寸為單位,那麼三者的關係是:

 

dpi是畫素密度,指的是在系統軟體上指定的單位尺寸的畫素數量,它往往是寫在系統出廠配置檔案的一個固定值。

另一個叫ppi的引數,這個在手機螢幕中指的也是畫素密度,但是這個是物理上的概念,它是客觀存在的不會改變。dpi是軟體參考了物理畫素密度後,人為指定的一個值,這樣保證了某一個區間內的物理畫素密度在軟體上都使用同一個值。這樣會有利於我們的UI適配

幾部相同解析度不同尺寸的手機的ppi可能分別是是430,440,450,那麼在Android系統中,可能dpi會全部指定為480

drawable

適用

常規: 360dp * 640dp

DP

比例值

比例係數

dp與px計算圖

ldpi:低密度螢幕;約為 120dpi。

mdpi:中等密度(傳統 HVGA)螢幕;約為 160dpi。

hdpi:高密度螢幕;約為 240dpi。

xhdpi:超高密度螢幕;約為 320dpi。API 級別 8 中新增配置

xxhdpi:超超高密度螢幕;約為 480dpi。API 級別 16 中新增配置

xxxhdpi:超超超高密度螢幕使用(僅限啟動器圖示,請參閱“支援多個螢幕”中的註釋);約為 640dpi。 API 級別 18 中新增配置

QVGA (240×320)

HVGA (320×480) 

WVGA (480×800),(480×854)

720P(1280*720) 

1080p(1920*1080 )

4K(3840×2160)

320dp

320dp

320dp

360dp

360dp

540dp

3

4

6

8

12

16

.075

1.0

1.5

2.0

3.0

4.0

ldpi:1dp=0.75px

mdpi:1dp=1px

hdpi:1dp=1.5px

xhdpi:1dp=2px

xxhdpi:1dp=3px

xxxhdpi:1dp=4px

六個主要密度之間的縮放比為 3:4:6:8:12:16(忽略 tvdpi 密度)。因此,9x9 (ldpi) 點陣圖相當於 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 點陣圖,依此類推。

 

 

傳統dp適配方式的缺點

android中的dp在渲染前會將dp轉為px,計算公式:

  • px = density * dp;

  • density = dpi / 160;

  • px = dp * (dpi / 160);

 

這樣會存在什麼問題呢

假設我們UI設計圖是按螢幕寬度為360dp來設計的,那麼在上述裝置上,螢幕寬度其實為1080/(440/160)=392.7dp,也就是螢幕是比設計圖要寬的。這種情況下, 即使使用dp也是無法在不同裝置上顯示為同樣效果的。 同時還存在部分裝置螢幕寬度不足360dp,這時就會導致按360dp寬度來開發實際顯示不全的情況。

 

而且上述螢幕尺寸、解析度和畫素密度的關係,很多裝置並沒有按此規則來實現, 因此dpi的值非常亂,沒有規律可循,從而導致使用dp適配效果差強人意。

 

梳理需求

首先來梳理下我們的需求,一般我們設計圖都是以固定的尺寸來設計的。比如以解析度1920px * 1080px來設計,以density為3來標註,也就是螢幕其實是640dp * 360dp。

如果我們想在所有裝置上顯示完全一致,其實是不現實的,因為螢幕高寬比不是固定的,16:9、4:3甚至其他寬高比層出不窮,寬高比不同,顯示完全一致就不可能了。

但是通常下,我們只需要以寬或高一個維度去適配,比如我們Feed是上下滑動的,只需要保證在所有裝置中寬的維度上顯示一致即可,再比如一個不支援上下滑動的頁面,那麼需要保證在高這個維度上都顯示一致,尤其不能存在某些裝置上顯示不全的情況。同時考慮到現在基本都是以dp為單位去做的適配,如果新的方案不支援dp,那麼遷移成本也非常高。

 

因此,總結下大致需求如下:

  1. 支援以寬或者高一個維度去適配,保持該維度上和設計圖一致

  2. 支援dp和sp單位,控制遷移成本到最小。

 

找相容突破口

從dp和px的轉換公式 :px = dp * density 

可以看出,如果設計圖寬為360dp,想要保證在所有裝置計算得出的px值都正好是螢幕寬度的話,我們只能修改 density 的值。

 

閱讀原始碼,我們可以得知,density 是 DisplayMetrics 中的成員變數,而 DisplayMetrics 例項通過 Resources#getDisplayMetrics 可以獲得,而Resouces通過Activity或者Application的Context獲得。

先來熟悉下 DisplayMetrics 中和適配相關的幾個變數:

  • DisplayMetrics#density 就是上述的density

  • DisplayMetrics#densityDpi 就是上述的dpi

  • DisplayMetrics#scaledDensity 字型的縮放因子,正常情況下和density相等,但是調節系統字型大小後會改變這個值

那麼是不是所有的dp和px的轉換都是通過 DisplayMetrics 中相關的值來計算的呢?

首先來看看佈局檔案中dp的轉換,最終都是呼叫 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來進行轉換:

不管你在佈局檔案中填寫的是什麼單位,最後都會被轉化為 px,系統就是通過下面的方法,將你在專案中任何地方填寫的單位都轉換為 px 
   

 public static float applyDimension(int unit, float value,DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);

        }
        return 0;
    }

再看看圖片的decode,BitmapFactory#decodeResourceStream方法:(此處省略,可檢視原始碼)

也是通過 DisplayMetrics 中的值來計算的。

當然還有些其他dp轉換的場景,基本都是通過 DisplayMetrics 來計算的,這裡不再詳述。因此,想要滿足上述需求,我們只需要修改 DisplayMetrics 中和 dp 轉換相關的變數即可。

 

最終方案

原理: 

螢幕的總 px 寬度 / density = 螢幕的總 dp 寬度

在這個公式中我們要保證 螢幕的總 dp 寬度 和 設計圖總寬度 一致,並且在所有解析度的螢幕上都保持不變,我們需要怎麼做呢?螢幕的總 px 寬度 每個裝置都不一致,這個值是肯定會變化的,這時今日頭條的公式就派上用場了

當前裝置螢幕總寬度(單位為畫素)/ 設計圖總寬度(單位為 dp) = density

這個公式就是把上面公式中的 螢幕的總 dp 寬度 換成 設計圖總寬度,原理都是一樣的,只要 density 根據不同的裝置進行實時計算並作出改變,就能保證 設計圖總寬度 不變,也就完成了適配

 

下面假設設計圖寬度是360dp,以寬維度來適配。

那麼適配後的 density = 裝置真實寬(單位px) / 360,接下來只需要把我們計算好的 density 在系統中修改下即可,程式碼實現如下:

同時在 Activity#onCreate 方法中呼叫下。程式碼比較簡單,也沒有涉及到系統非公開api的呼叫,因此理論上不會影響app穩定性。

出現的問題:

修改後上線灰度測試了一版,穩定性符合預期,沒有收到由此帶來的crash,但是收到了很多字型過小的反饋

原因是在上面的適配中,我們忽略了DisplayMetrics#scaledDensity的特殊性,將DisplayMetrics#scaledDensityDisplayMetrics#density設定為同樣的值,從而某些使用者在系統中修改了字型大小失效了,但是我們還不能直接用原始的scaledDensity,直接用的話可能導致某些文字超過顯示區域,因此我們可以通過計算之前scaledDensity和density的比獲得現在的scaledDensity;

但是測試後發現另外一個問題,就是如果在系統設定中切換字型,再返回應用,字型並沒有變化。

於是還得監聽下字型切換,呼叫 Application#registerComponentCallbacks 註冊下 onConfigurationChanged 監聽即可。

(當然以上程式碼只是以設計圖寬360dp去適配的,如果要以高維度適配,可以再擴充套件下程式碼即可。)

假設我們設計稿是寬度是 1080px,資源放在 xxhdpi,那麼我們寬度轉換為 dp 就是 1080 / 3 = 360dp,要在不同裝置上寬度都表現為 360dp,那麼就需要修改其 density = screenWidthPx / 360,這樣就滿足了上述條件,而和 density 相關的還有 densityDpi、scaledDensity,我們根據 density 等比修改 densityDpi、scaledDensity 即可。

 

暴露的問題:

這樣可能會導致獲取狀態列和導航欄高度有問題,其獲取狀態列高度程式碼為如下所示, 導航欄高度也可以這樣

public static int getStatusBarHeight() {

    Resources resources = Utils.getApp().getResources();

    int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");

    return resources.getDimensionPixelSize(resourceId);

}

由於使用的是 Application#getResources,這會導致最後計算狀態列高度使用的是修改過後的 density,

解決: Resources.getSystem() 來獲取系統的 Resources,果不其然可以獲取到正確高度的狀態列高度,程式碼如下所示:

public static int getStatusBarHeight() {

    Resources resources = Resources.getSystem();

    int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");

    return resources.getDimensionPixelSize(resourceId);

}

 

 

優點

  1. 使用成本非常低,操作非常簡單,使用該方案後在頁面佈局時不需要額外的程式碼和操作,這點可以說完虐其他螢幕適配方案

  2. 侵入性非常低,該方案和專案完全解耦,在專案佈局時不會依賴哪怕一行該方案的程式碼,而且使用的還是 Android 官方的 API,意味著當你遇到什麼問題無法解決,想切換為其他螢幕適配方案時,基本不需要更改之前的程式碼,整個切換過程幾乎在瞬間完成,會少很多麻煩,節約很多時間,試錯成本接近於 0

  3. 可適配三方庫的控制元件和系統的控制元件(不止是 ActivityFragmentDialogToast 等所有系統控制元件都可以適配),由於修改的 density 在整個專案中是全域性的,所以只要一次修改,專案中的所有地方都會受益

  4. 不會有任何效能的損耗

缺點

暫時沒發現其他什麼很明顯的缺點,已知的缺點有一個,那就是第三個優點,它既是這個方案的優點也同樣是缺點,但是就這一個缺點也是非常致命的

只需要修改一次 density,專案中的所有地方都會自動適配,這個看似解放了雙手,減少了很多操作,但是實際上反應了一個缺點,那就是隻能一刀切的將整個專案進行適配,但適配範圍是不可控的

這樣不是很好嗎?這樣本來是很好的,但是應用到這個方案是就不好了,因為我上面的原理也分析了,這個方案依賴於設計圖尺寸,但是專案中的系統控制元件、三方庫控制元件、等非我們專案自身設計的控制元件,它們的設計圖尺寸並不會和我們專案自身的設計圖尺寸一樣

當這個適配方案不分型別,將所有控制元件都強行使用我們專案自身的設計圖尺寸進行適配時,這時就會出現問題,當某個系統控制元件或三方庫控制元件的設計圖尺寸和和我們專案自身的設計圖尺寸差距非常大時,這個問題就越嚴重

總結: 

介紹這個今日頭條的螢幕適配方案並不是說他有多麼完美,只是他確實有效而且能幫我們減少很多開發成本

對於很多人說的 DPI 的存在,不就是為了讓大屏能顯示更多的內容,如果一個大屏手機和小屏手機,顯示的內容都相同,那使用者買大屏手機又有什麼意義呢,我覺得大家對 DPI 的理解是對的,這個觀點我也是認同的,Google 設計 DPI 時可能也是這麼想的,但是有一點大家沒考慮到,Android 的碎片化太嚴重了

為什麼 Android 誕生這麼多年,Android 的百分比庫層出不窮,按理說他們都違背了上面說的這個理念,但為什麼還有這麼多人去研究百分比庫通過各種方式去實現百分比佈局 (谷歌官方也曾出過百分比庫)?為什麼?很簡單,因為需求啊!為什麼需求,因為開發成本低啊!為什麼今日頭條的這個螢幕適配方案現在能這麼火,因為他的開發成本是目前所有螢幕適配方案中最低的啊!

DPI 的意義誰又不懂呢?今日頭條這種大公司的程式設計師不懂這個道理嗎?今日頭條這麼大的公司難道不想把每個機型每個版本的裝置都適配完美,讓自己的 App 體驗更好,哪怕付出更大的成本,有些時候想象是美好的,但是這個投入的成本,誰又能承擔呢,連今日頭條這麼大的公司,這麼雄厚的資本都沒選擇投入更大的成本,對每個機型進行更精細化的適配,難道市面上的中小型公司又有這個能力投入這麼大的成本嗎?

魚和熊掌不可兼得,DPI 的意義在 Google 的設計理念中是完全正確的,但不是所有公司都能承受這個成本,想必今日頭條的程式設計師,也是因為今日頭條 App 的使用者量足夠多,機型分佈足夠廣,也是被螢幕適配這個問題折磨的不要不要的,才想出這麼個不這麼完美但是卻很有效的方案

附錄: 下圖為Android手機碎片化,每個矩形代表一種螢幕尺寸


 

簡介 smallestWidth 限定符適配方案

下面要介紹的 smallestWidth 限定符螢幕適配方案

原理也同樣是按照百分比縮放佈局,理論上也會存在上面所說的 大屏手機和小屏手機顯示的內容相同 的問題,選擇與否請仔細斟酌

這個方案的的使用方式和我們平時在佈局中引用 dimens 無異,核心點在於生成 dimens.xml 檔案,但是已經有大神幫我們做了這 一步(一種非常好用的Android螢幕適配)

如果有人還記得上面這種 寬高限定符螢幕適配方案 的話,就可以把 smallestWidth 限定符螢幕適配方案 當成這種方案的升級版,smallestWidth 限定符螢幕適配方案 只是把 dimens.xml 檔案中的值從 px 換成了 dp,原理和使用方式都是沒變的,這些在上面的文章中都有介紹,下面就直接開始剖析原理,smallestWidth 限定符螢幕適配方案 長這樣

 

原理

其實 smallestWidth 限定符螢幕適配方案 的原理也很簡單,開發者先在專案中根據主流螢幕的 最小寬度 (smallestWidth) 生成一系列 values-sw<N>dp 資料夾 (含有 dimens.xml 檔案),當把專案執行到裝置上時,系統會根據當前裝置螢幕的 最小寬度 (smallestWidth) 去匹配對應的 values-sw<N>dp 資料夾,而對應的 values-sw<N>dp 資料夾中的 dimens.xml 文字中的值,又是根據當前裝置螢幕的 最小寬度 (smallestWidth) 而定製的,所以一定能適配當前裝置

如果系統根據當前裝置螢幕的 最小寬度 (smallestWidth) 沒找到對應的 values-sw<N>dp 資料夾,則會去尋找與之 最小寬度 (smallestWidth) 相近的 values-sw<N>dp 資料夾,系統只會尋找小於或等於當前裝置 最小寬度 (smallestWidth)values-sw<N>dp,這就是優於 寬高限定符螢幕適配方案 的容錯率,並且也可以少生成很多 values-sw<N>dp 資料夾,減輕 App 的體積

 

smallestWidth 翻譯為中文的意思就是 最小寬度,那這個 最小寬度 是什麼意思呢?

移動裝置都是允許螢幕可以旋轉的,當螢幕旋轉時,螢幕的高寬就會互換,加上 最小 這兩個字,是因為這個方案是不區分螢幕方向的,它只會把螢幕的高度和寬度中值最小的一方認為是 最小寬度,這個 最小寬度 是根據螢幕來定的,是固定不變的

 

如果想讓螢幕寬度隨著螢幕的旋轉而做出改變該怎麼辦呢?可以再根據 values-w<N>dp (去掉 sw 中的 s) 生成一套資原始檔

如果想區分螢幕的方向來做適配該怎麼辦呢?那就只有再根據 螢幕方向限定符 生成一套資原始檔咯,字尾加上 -land -port 即可,像這樣,values-sw400dp-land (最小寬度 400 dp 橫向)values-sw400dp-port (最小寬度 400 dp 縱向)

 

我們假設裝置的螢幕資訊是 1920 * 1080、480 dpi

根據上面的規則我們要在螢幕的高度和寬度中選擇值最小的一方作為最小寬度,1080 < 1920,明顯 1080 px 就是我們要找的 最小寬度 的值,但 最小寬度 的單位是 dp,所以我們要把 px 轉換為 dp

px / density = dpDPI / 160 = density,所以最終的公式是 px / (DPI / 160) = dp

所以我們得到的 最小寬度 的值是 360 dp (1080 / (480 / 160) = 360),系統會根據這個 最小寬度 幫助我們匹配到 values-sw360dp 資料夾下的 dimens.xml 檔案,如果專案中沒有 values-sw360dp 這個資料夾,系統才會去匹配相近的 values-sw<N>dp 資料夾

核心dimens.xml 生成原理

dimens.xml 的生成,就要涉及到兩個因數,第一個因素是 最小寬度基準值,第二個因素就是您的專案需要適配哪些 最小寬度,通俗理解就是需要生成多少個 values-sw<N>dp 資料夾

小寬度基準值 是什麼意思呢?簡單理解就是您需要把裝置的螢幕寬度分為多少份,假設我們現在把專案的 最小寬度基準值 定為 360,那這個方案就會理解為您想把所有裝置的螢幕寬度都分為 360 

values-sw400dp 指的是當前裝置螢幕的 最小寬度 400dp (該裝置高度大於寬度,則最小寬度就是寬度,所以該裝置寬度為 400dp),把螢幕寬度同樣分為 360份,這時每份就等於 1.1111dp 了,每個引用都遞增 1.1111dp,值最大的 dimens 引用 dp_360 同樣剛好覆蓋螢幕寬度,為 400dp

 

這樣就能保證不管將專案執行到哪個裝置上,只要當前裝置能匹配到對應的 values-sw<N>dp 資料夾,那佈局中的 dimens 引用就能根據當前螢幕的情況進行縮放,保證能完美適配,如果沒有匹配到對應的 values-sw<N>dp 資料夾,也沒關係,它會去尋找與之相近的 values-sw<N>dp 資料夾,雖然在這種情況下,佈局中的 dimens 引用的值可能有些許誤差,但是也能保證最大程度的完成適配

 

每個 values-sw<N>dp 資料夾其實都會佔用一定的 App 體積,values-sw<N>dp 資料夾越多,App 的體積也就會越大要合理分配 values-sw<N>dp,以越少的 values-sw<N>dp 資料夾,覆蓋越多的機型 ; 

 

 

優點

1. 非常穩定,極低概率出現意外

2. 不會有任何效能的損耗

3. 適配範圍可自由控制,不會影響其他三方庫

4. 在外掛的配合下,學習成本低

缺點

1. 在佈局中引用 dimens 的方式,雖然學習成本低,但是在日常維護修改時較麻煩

2. 侵入性高,如果專案想切換為其他螢幕適配方案,因為每個 Layout 檔案中都存在有大量 dimens 的引用,這時修改起來工作量非常巨大,切換成本非常高昂

3. 無法覆蓋全部機型,想覆蓋更多機型的做法就是生成更多的資原始檔,但這樣會增加 App 體積,在沒有覆蓋的機型上還會出現一定的誤差,所以有時需要在適配效果和佔用空間上做一些抉擇

4. 如果想使用 sp,也需要生成一系列的 dimens,導致再次增加 App 的體積

5. 不能自動支援橫豎屏切換時的適配,如上文所說,如果想自動支援橫豎屏切換時的適配,需要使用 values-w<N>dp 或 螢幕方向限定符 再生成一套資原始檔,這樣又會再次增加 App 的體積

6. 不能以高度為基準進行適配,考慮到這個方案的名字本身就叫 最小寬度限定符適配方案,所以在使用這個方案之前就應該要知道這個方案只能以寬度為基準進行適配,為什麼現在的螢幕適配方案只能以高度或寬度其中的一個作為基準進行適配,請看 這裡

smallestWidth 限定符螢幕適配方案 的原理和 今日頭條螢幕適配方案 挺像的,今日頭條螢幕適配方案 是根據螢幕的寬度或高度動態調整每個裝置的 density (每 dp 佔當前裝置螢幕多少畫素),而 smallestWidth 限定符螢幕適配方案 同樣是根據螢幕的寬度動態調整每個裝置 每份佔的 dp 值


 

上述兩個方案對比及新的方案

 

這兩個方案中,並不能以單個標準就能評判出誰一定比誰好,因為它們都有各自的優缺點,都不是完美的,從更客觀的角度來看,它們誰都不能成為最好的那個,只有可能明確了它們各自的優缺點,知道在它們的優缺點裡什麼是我能接受的,什麼是我不能接受的,是否能為了某些優點做出某些妥協,從而選擇出一個最適合自己專案的螢幕適配方案

 

對比專案

對比物件 A

對比結果

對比物件 B

適配效果(越高越好)

今日頭條適配方案

SW 限定符適配方案(在未覆蓋的機型上會存在一定的誤差)

穩定性(越高越好)

今日頭條適配方案

<

SW 限定符適配方案

靈活性(越高越好)

今日頭條適配方案

>

SW 限定符適配方案

擴充套件性(越高越好)

今日頭條適配方案

>

SW 限定符適配方案

侵入性(越低越好)

今日頭條適配方案

<

SW 限定符適配方案

使用成本(越低越好)

今日頭條適配方案

<

SW 限定符適配方案

維護成本(越低越好)

今日頭條適配方案

<

SW 限定符適配方案

效能損耗

今日頭條適配方案沒有效能損耗

=

SW 限定符適配方案沒有效能損耗

副作用

今日頭條適配方案會影響一些三方庫和系統控制元件

SW 限定符適配方案會影響 App 的體積

SmallestWidth 限定符適配方案今日頭條適配方案 的適配效果其實都是差不多的,我在前面的文章中也通過公式計算過它們的精確度,SmallestWidth 限定符適配方案 執行在未覆蓋的機型上雖然也可以適配,但是卻會出現一定的誤差,所以 今日頭條適配方案 的適配精確度確實要比 SmallestWidth 限定符適配方案 略高的,不過只要 SmallestWidth 限定符適配方案 合理的分配資原始檔,適配效果的差距應該也不大

SmallestWidth 限定符適配方案 主打的是穩定性,在執行過程中極少會出現安全隱患,適配範圍也可控,不會產生其他未知的影響,而 今日頭條適配方案 主打的是降低開發成本、提高開發效率,使用上更靈活,也能滿足更多的擴充套件需求,簡單一句話概括就是,這兩兄弟,一個求穩,一個求快!

 

 

AndroidAutoSize

關於今日螢幕適配的方案,參考了今日頭條於2018.5月公佈的適配方案,以及之前被廣泛接受的限定符適配(如sw-600dp,layout-land等)進行優劣對比,探索自身適合的適配方式

JessYanCoding/AndroidAutoSize 於2018.10月底公佈其基於今日頭條方案優化的適配庫 ((原始碼29k大小,一週2k star,目前5k+))

目前統計 :   bintray 每個月的下載數有 15k 以上,已經有幾千個專案引入了 AndroidAutoSize

 

建議是,可以在專案中先使用 今日頭條螢幕適配方案,感受下它的使用方式以及適配效果,今日頭條螢幕適配方案 的侵入性非常低,如果在使用過程中遇到什麼不能解決的問題,馬上可以切換為其他的螢幕適配方案,在切換的過程中也花費不了多少工作量,試錯成本非常低


但如果你在專案中先使用 SmallestWidth 限定符適配方案,之後在使用的過程中再遇到什麼不能解決的問題,這時想切換為其他的螢幕適配方案,這工作量可就大了,每個 Layout 檔案都含有大量的 dimens 引用,改起來這工作量得有多大,想想都覺得後怕,這就是侵入性太高導致的最致命的問題

 

功能介紹

AndroidAutoSize 在使用上非常簡單,只需要填寫設計圖尺寸這一步即可接入專案,但需要注意的是,AndroidAutoSize 有兩種型別的佈局單位可以選擇,一個是 主單位 (dp、sp),一個是 副單位 (pt、in、mm),兩種單位面向的應用場景都有不同,也都有各自的優缺點

  • 主單位: 使用 dp、sp 為單位進行佈局,侵入性最低,會影響其他三方庫頁面、三方庫控制元件以及系統控制元件的佈局效果,但 AndroidAutoSize 也通過這個特性,使用 ExternalAdaptManager 實現了在不修改三方庫原始碼的情況下適配三方庫的功能

  • 副單位: 使用 pt、in、mm 為單位進行佈局侵入性高對老專案的支援比較好,不會影響其他三方庫頁面、三方庫控制元件以及系統控制元件的佈局效果,可以徹底的遮蔽修改 density 所造成的所有未知和已知問題,但這樣 AndroidAutoSize 也就無法對三方庫進行適配

大家可以根據自己的應用場景在 主單位副單位 中選擇一個作為佈局單位,建議想引入老專案並且注重穩定性的人群使用 副單位,只是想試試本框架,隨時可能切換為其他螢幕適配方案的人群使用 主單位

其實 AndroidAutoSize 可以同時支援 主單位副單位,但 AndroidAutoSize 可以同時支援 主單位副單位 的目的,只是為了讓使用者可以在 主單位副單位 之間靈活切換

基本使用

引入庫(29k大小,manifest加入此配置即完成適配)

 

進階用法:(activity/fragment都適用)

  • 當某個 Activity 的設計圖尺寸與在 AndroidManifest 中填寫的全域性設計圖尺寸不同時,可以實現 CustomAdapt 介面擴充套件適配引數

  • 當某個 Activity 想放棄適配,請實現 CancelAdapt 介面

 

自動執行是如何做到的?

為什麼使用者只需要在 AndroidManifest.xml 中填寫一下 meta-data 標籤,其他什麼都不做,AndroidAutoSize 就能自動執行,並在 App 啟動時自動解析 AndroidManifest.xml 中填寫的設計圖尺寸,難道使用了什麼 黑科技?

其實這裡並沒有用到什麼 黑科技,原理反而非常簡單,只需要宣告一個 ContentProvider,在它的 onCreate 方法中啟動框架即可,在 App 啟動時,系統會在 App 的主程序中自動例項化你宣告的這個 ContentProvider,並呼叫它的 onCreate 方法,執行時機比 Application#onCreate 還靠前,可以做一些初始化的工作

這裡需要注意的是,如果你的專案擁有多程序,系統只會在主程序中例項化一個你宣告的 ContentProvider,並不會在其他非主程序中例項化 ContentProvider,如果在當前程序中 ContentProvider 沒有被例項化,那 ContentProvider#onCreate 就不會被呼叫,你的初始化程式碼在當前程序中也就不會執行,這時就需要在 Application#onCreate 中呼叫下 ContentProvider#query 執行一下查詢操作,這時 ContentProvider 就會在當前程序中例項化 (每個程序中只會保證有一個例項),所以應用到框架中就是,如果你需要在多個程序中都進行螢幕適配,那就需要在 Application#onCreate 中呼叫 AutoSize#initCompatMultiProcess 方法

進階用法:(activity/fragment都適用)

 

適配方案的問題彙總: 

https://github.com/JessYanCoding/AndroidAutoSize/issues/13

優缺點上述已經描述,總結下目前接入後遇到的問題:

  • 1.以設計圖尺寸接入,在過小屏手機上可能會使字型佈局等顯示較小. 這是此方案的優點也是此方案的缺點,單靠此方案無解,可以配合其他方案,比如像之前專為過小/過大屏專門寫一套尺寸
  • 2.在download專案,主fragment切換為搜尋fragment時,第二次切換後搜尋框不變

   關於此問題: 見issue解答: 單Activity多fragment適配問題   ,  主頁面有很多個fragment  (核心程式碼: AutoSize--autoConvertDensity(...)) ; 對於橫屏、平板電腦、TV 的適配

   fragment生命週期較為複雜,某些未知情況下CancelAdapt會失效,在Google規定下只有 Activity 能修改 DisplayMetrics,

    Fragment 裡面想修改 density,也必須從它依賴的 Activity 上拿到     DisplayMetrics,

    Activity 也是這樣做的,看上去 Activity 可以單獨設定 density,只不過在這個 Activity setContentView 之前,按照它的需求切換了 density,這個 density 是全域性的,不存在什麼單獨設定這一說

  • 3.此適配方案僅針對原生頁面,對webview無效, webview有自己單獨設定整體size大小的方法

4.DisplayMetrics#density 是公有的,誰都有許可權修改,AndroidAutoSize 可以把 DisplayMetrics#density 修改成一個可以完成螢幕適配的值,其他三方庫、Android 系統、以及專案成員就可以把 DisplayMetrics#density 修改或恢復成另一個值,這都將導致螢幕適配的失效,特別是在某些定製系統上

* 萬能解決方案 

在任何情況下本來適配正常的佈局突然出現適配失效,適配異常等問題,只要在合適的時機 (在佈局繪製到螢幕上之前) 呼叫 AutoSize#autoConvertDensity()AutoSize#autoConvertDensityOfGlobal 即可解決大部分問題,比如如果從桌面或後臺返回 APP 後當前佈局出現混亂的問題,在 Activity#onResume() 中呼叫 AutoSize#autoConvertDensity() 或 AutoSize#autoConvertDensityOfGlobal 即可解決大部分問題,如果是 Dialog、PopupWindow 等控制元件出現適配失效或適配異常,同樣在每次 show() 之前呼叫 AutoSize#autoConvertDensity() 即可


注: 框架的原理並沒有涉及到與 View 相關的操作,只是單純的修改 density,修改 density 只會影響 dp 轉 px 的過程

建議是:

可以在專案中先使用 今日頭條螢幕適配方案,感受下它的使用方式以及適配效果,今日頭條螢幕適配方案 的侵入性非常低,如果在使用過程中遇到什麼不能解決的問題,馬上可以切換為其他的螢幕適配方案,在切換的過程中也花費不了多少工作量,試錯成本非常低


但如果你在專案中先使用 SmallestWidth 限定符適配方案,之後在使用的過程中再遇到什麼不能解決的問題,這時想切換為其他的螢幕適配方案,這工作量可就大了,每個 Layout 檔案都含有大量的 dimens 引用,改起來這工作量得有多大,這就是侵入性太高導致的最致命的問題

 

另: 關於Blankj/AndroidUtilCode  也基於今日頭條方案出了一套適配util方案 (僅是util類中一個,非專門專案; 專案目前2萬+ star ,Android工具類最受歡迎專案,關於工具類的統一建議參考此專案)   

    見部落格:Android 螢幕適配從未如斯簡單 (2018.8.10)


參考:

騷年你的螢幕適配方式該升級了!(一)-今日頭條適配方案

騷年你的螢幕適配方式該升級了!(二)-smallestWidth 限定符適配方案

今日頭條螢幕適配方案終極版正式釋出!

Android 螢幕適配從未如斯簡單 (BlankJ): https://juejin.im/post/5b6250bee51d451918537021

一種極低成本的Android螢幕適配方式 -- 今日頭條技術團隊 : https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA

Android 目前穩定高效的UI適配方案  -- 拉丁吳 :  https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q

Google-  screen  compatibility overviewhttps://developer.android.com/guide/practices/screens_support

一種非常好用的Android螢幕適配 : (最小寬限定符) https://www.jianshu.com/p/1302ad5a4b04