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

JessYan發表於2018-08-20

原文地址: www.jianshu.com/p/55e0fca23…

以下是 騷年你的螢幕適配方式該升級了! 系列文章,歡迎轉發以及分享:

加入技術交流 QQ 群 455850365

前言

這個月在 Android 技術圈中 螢幕適配 這個詞曝光率挺高的,為什麼這麼說呢?因為這個月陸續有多個大佬釋出了螢幕適配相關的文章,公佈了自己認可的螢幕適配方案

上上個星期 Blankj 老師發表了一篇力挺今日頭條螢幕適配方案的 文章,提出了很多優化的方案,並開源了相關原始碼

上個星期 拉丁吳 老師在 鴻神 的公眾號上釋出了一篇 文章,詳細描述了市面上主流的幾種螢幕適配方案,併發布了他的 smallestWidth 限定符適配方案和相關原始碼 (其實早就釋出了),文章寫的很好,建議大家去看看

其實大家最關注的不是市面上有多少種螢幕適配方案,而是自己的專案該選擇哪種螢幕適配方案,可以看出兩位老師最終選擇的螢幕適配方案都是不同的

我下面就來分析分析,我作為一個才接觸這兩個螢幕適配方案的吃瓜群眾,我是怎麼來驗證這兩種螢幕適配方案是否可行,以及怎樣根據它們的優缺點來選擇一個最適合自己專案的螢幕適配方案

這是我推薦給大家的螢幕適配框架,本來想放到最後作為福利的,害怕大家看不到,所以就將連結放到這裡,提前送給大家

Github : 您的 Star 是我堅持的動力 ✊

淺談適配方案

拉丁吳 老師的文章中談到了兩個比較經典的螢幕適配方案,在我印象中十分深刻,我想大多數兄弟都用過,在我的開發生涯裡也是有很長一段時間都在用這兩種螢幕適配方案

第一種就是寬高限定符適配,什麼是寬高限定符適配呢

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440
複製程式碼

就是這種,在資原始檔下生成不同解析度的資原始檔,然後在佈局檔案中引用對應的 dimens,大家一定還有印象

第二種就是 鴻神AndroidAutoLayout

這兩種方案都已經逐漸退出了歷史的舞臺,為什麼想必大家都知道,不知道的建議看看 拉丁吳 老師的文章,所以這兩種方案我在文章中就不在闡述了,主要講講現在最主流的兩種螢幕適配方案,今日頭條適配方案smallestWidth 限定符適配方案

建議大家不清楚這兩個方案的先看看這兩篇文章,才清楚我在講什麼,後面我要講解它們的原理,以及驗證這兩種方案是否真的可行,最後對他們進行深入對比,對於他們的一些缺點給予對應的解決方案,絕對乾貨

今日頭條螢幕適配方案

原理

上面已經告知,不瞭解這兩個方案的先看看上面的兩篇文章,所以這裡我就假設大家已經看了上面的文章或者之前就瞭解過這兩個方案,所以在本文中我就不再闡述 DPIDensity 以及一些比較基礎的知識點,上面的文章已經闡述的夠清楚了

今日頭條螢幕適配方案的核心原理在於,根據以下公式算出 density

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

density 的意思就是 1 dp 佔當前裝置多少畫素

為什麼要算出 density,這和螢幕適配有什麼關係呢?

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;
    }
複製程式碼

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

所以我們常用的 pxdp 的公式 dp = px / density,就是根據上面的方法得來的,density 在公式的運算中扮演著至關重要的一步

要看懂下面的內容,還得明白,今日頭條的適配方式,今日頭條適配方案預設專案中只能以高或寬中的一個作為基準,進行適配,為什麼不像 AndroidAutoLayout 一樣,高以高為基準,寬以寬為基準,同時進行適配呢

這就引出了一個現在比較棘手的問題,大部分市面上的 Android 裝置的螢幕高寬比都不一致,特別是現在大量全面屏的問世,這個問題更加嚴重,不同廠商推出的全面屏手機的螢幕高寬比都可能不一致

這時我們只以高或寬其中的一個作為基準進行適配,就會有效的避免佈局在高寬比不一致的螢幕上出現變形的問題

明白這個後,我再來說說 densitydensity 在每個裝置上都是固定的,DPI / 160 = density螢幕的總 px 寬度 / density = 螢幕的總 dp 寬度

  • 裝置 1,螢幕寬度為 1080px480DPI,螢幕總 dp 寬度為 1080 / (480 / 160) = 360dp

  • 裝置 2,螢幕寬度為 1440560DPI,螢幕總 dp 寬度為 1440 / (560 / 160) = 411dp

可以看到螢幕的總 dp 寬度在不同的裝置上是會變化的,但是我們在佈局中填寫的 dp 值卻是固定不變的

這會導致什麼呢?假設我們佈局中有一個 View 的寬度為 100dp,在裝置 1 中 該 View 的寬度佔整個螢幕寬度的 27.8% (100 / 360 = 0.278)

但在裝置 2 中該 View 的寬度就只能佔整個螢幕寬度的 24.3% (100 / 411 = 0.243),可以看到這個 View 在畫素越高的螢幕上,dp 值雖然沒變,但是與螢幕的實際比例卻發生了較大的變化,所以肉眼的觀看效果,會越來越小,這就導致了傳統的填寫 dp 的螢幕適配方式產生了較大的誤差

這時我們要想完美適配,那就必須保證這個 View 在任何解析度的螢幕上,與螢幕的比例都是相同的

這時我們該怎麼做呢?改變每個 Viewdp 值?不現實,在每個裝置上都要通過程式碼動態計算 Viewdp 值,工作量太大

如果每個 Viewdp 值是固定不變的,那我們只要保證每個裝置的螢幕總 dp 寬度不變,就能保證每個 View 在所有解析度的螢幕上與螢幕的比例都保持不變,從而完成等比例適配,並且這個螢幕總 dp 寬度如果還能保證和設計圖的寬度一致的話,那我們在佈局時就可以直接按照設計圖上的尺寸填寫 dp

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

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

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

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

驗證方案可行性

上面已經把原理分析的很清楚了,很多文章只是一筆帶過這個公式,公式雖然很簡單但我們還是想曉得這是怎麼來的,所以我就反向推理了一遍,如果還是看不懂,那我只能說我盡力了,原理講完了,那我們再來現場驗證一下這個方案是否可行?

假設設計圖總寬度為 375 dp,一個 View 在這個設計圖上的尺寸是 50dp * 50dp,這個 View 的寬度佔整個設計圖寬度的 13.3% (50 / 375 = 0.133),那我們就來驗證下在使用今日頭條螢幕適配方案的情況下,這個 View 與螢幕寬度的比例在解析度不同的裝置上是否還能保持和設計圖中的比例一致

驗證裝置 1

螢幕總寬度為 1080 px,根據今日頭條的的公式求出 density1080 / 375 = 2.88 (density)

這個 50dp * 50dpView,系統最後會將高寬都換算成 px50dp * 2.88 = 144 px (根據公式 dp * density = px)

144 / 1080 = 0.133View 實際寬度與 螢幕總寬度 的比例和 View 在設計圖中的比例一致 (50 / 375 = 0.133),所以完成了等比例縮放

某些裝置總寬度為 1080 px,但是 DPI 可能不同,是否會對今日頭條適配方案產生影響?其實這個方案根本沒有根據 DPI 求出 density,是根據自己的公式求出的 density,所以這對今日頭條的方案沒有影響

上面只能確定在所有螢幕總寬度為 1080 px 的裝置上能完成等比例適配,那我們再來試試其他解析度的裝置

驗證裝置 2

螢幕總寬度為 1440 px,根據今日頭條的的公式求出 density1440 / 375 = 3.84 (density)

這個 50dp * 50dpView,系統最後會將高寬都換算成 px50dp * 3.84 = 192 px (根據公式 dp * density = px)

192 / 1440 = 0.133View 實際寬度與 螢幕總寬度 的比例和 View 在設計圖中的比例一致 (50 / 375 = 0.133),所以也完成了等比例縮放

兩個不同解析度的裝置都完成了等比例縮放,證明今日頭條螢幕適配方案在不同解析度的裝置上都是有效的,如果大家還心存疑慮,可以再試試其他解析度的裝置,其實到最後得出的比例不會有任何偏差, 都是 0.133

優點

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

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

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

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

缺點

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

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

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

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

舉個例子

假設一個三方庫的 View,作者在設計時,把它設計為 100dp * 100dp,設計圖的最大寬度為 1000dp,這個 View 在設計圖中的比例是 100 / 1000 = 0.1,意思是這個 View 的寬度在設計圖中佔整個寬度的 10%,如果我們要完成等比例適配,那這個三方庫 View 在所有的裝置上與螢幕的總寬度的比例,都必須保持在 10%

這時在一個使用今日頭條螢幕適配方案的專案上,設定的設計圖最大寬度如果是 1000dp,那這個三方庫 View,與專案自身都可以完美的適配,但當我們專案自身的設計圖最大寬度不是 1000dp,是 500dp 時,100 / 500 = 0.2,可以看到,比例發生了較大的變化,從 10% 上升為 20%,明顯這個三方庫 View 高於作者的預期,比之前更大了

這就是兩個設計圖尺寸不一致導致的非常嚴重的問題,當兩個設計圖尺寸差距越大,那適配的效果也就天差萬別了

解決方案

方案 1

調整設計圖尺寸,因為三方庫可能是遠端依賴的,無法修改原始碼,也就無法讓三方庫來適應我們專案的設計圖尺寸,所以只有我們自身作出修改,去適應三方庫的設計圖尺寸,我們將專案自身的設計圖尺寸修改為這個三方庫的設計圖尺寸,就能完成專案自身和三方庫的適配

這時專案的設計圖尺寸修改了,所以專案佈局檔案中的 dp 值,也應該按照修改的設計圖尺寸,按比例增減,保持與之前設計圖中的比例不變

但是如果為了適配一個三方庫修改整個專案的設計圖尺寸,是非常不值得的,所以這個方案支援以 Activity 為單位修改設計圖尺寸,相當於每個 Activity 都可以自定義設計圖尺寸,因為有些 Activity 不會使用三方庫 View,也就不需要自定義尺寸,所以每個 Activity 都有控制權的話,這也是最靈活的

但這也有個問題,當一個 Activity 使用了多個設計圖尺寸不一樣的三方庫 View,就會同樣出現上面的問題,這也就只有把設計圖改為與幾個三方庫比較折中的尺寸,才能勉強緩解這個問題

方案 2

第二個方案是最簡單的,也是按 Activity 為單位,取消當前 Activity 的適配效果,改用其他的適配方案

使用中的問題

有些文章中提到了今日頭條螢幕適配方案可以將設計圖尺寸填寫成以 px 為單位的寬度和高度,這樣我們在佈局檔案中,也就能直接填寫設計圖上標註的 px 值,省掉了將 px 換算為 dp 的時間 (大部分公司的設計圖都只標註 px 值),而且照樣能完美適配

但是我建議大家千萬不要這樣做,還是老老實實的以 dp 為單位填寫 dp 值,為什麼呢?

直接填寫 px 雖然剛開始佈局的時候很爽,但是這個坑就已經埋上了,會讓你後面很爽,有哪些坑?

第一個坑

這樣無疑於使專案強耦合於這個方案,當你遇到無法解決的問題想切換為其他螢幕適配方案的時候,layout 檔案裡曾經填寫的 px 值都會作為 dp

比如你的設計圖實際寬度為 1080px,你不換算為 360dp (1080 / 3 = 360),卻直接將 1080px 作為這個方案的設計圖尺寸,那你在 layout 檔案中,填寫的也都是設計圖上標註的 px 值,但是單位卻是 dp

一個在設計圖上 300px * 300pxView,你可以直接在 layout 檔案中填寫為 300dp,而由於這個方案可以動態改變 density 的原因還是可以做到等比例適配,非常爽!

但你不要忘了,這樣你就強耦合於這個方案了,因為當你不使用這個方案時,density 是不可變的!

舉個例子

使用這個方案時,在螢幕寬度為 1080px 的裝置上,將設計圖寬度直接填寫為 1080,根據今日頭條公式

當前裝置螢幕總寬度 / 設計圖總寬度 = density

這時得出 density 為 1 (1080 / 1080 = 1),所以你在 layout 檔案中你填寫的 300dp 最後轉換為 px 也是 300px (300dp * 1 = 300px 根據公式 dp * density = px)

在這個方案的幫助下非常完美,和設計圖一模一樣完成了適配

但當你不使用這個方案時,density 的換算公式就變為官方的 DPI / 160 = density, 在這個螢幕寬度為 1080px480dpi 的裝置上,density 就固定為 3 (480 / 160 = 3)

這時再來看看你之前在 layout 檔案中填寫的 dp,換算成 px900 px (300dp * 3 = 900px 根據公式 dp * density = px)

原本在在設計圖上為 300pxView,這時卻達到了驚人的 900px,3倍的差距,恭喜你,你已經強耦合於這個方案了,你要不所有 layout 檔案都改一遍,要不繼續使用這個方案

第二個坑

第二個坑其實就是剛剛在上面說的今日頭條適配方案的缺點,當某個系統控制元件或三方庫控制元件的設計圖尺寸和和我們專案自身的設計圖尺寸差距非常大時,這個問題就越嚴重

你如果直接填寫以 px 為設計圖的尺寸,這不用想,肯定和所有的三方庫以及系統控制元件的設計圖尺寸都不一樣,而且差距都非常之大,至少兩三倍的差距,這時你在當前頁面彈個 Toast 就可以明顯看到,比之前小很多,可以說是天差萬別,用其他三方庫 View,也是一樣的,會小很多

因為你以 px 為單位填寫設計圖尺寸,人家卻用的 dp,差距能不大嗎,你如果老老實實用 dp,哪怕三方庫的設計圖尺寸和你專案自身的設計圖尺寸不一樣,那也差距不大,小到一定程度,基本都不用調整,可以忽略不計,而且很多三方庫的設計圖尺寸其實也都是那幾個大眾尺寸,很大可能和你專案自身的設計圖尺寸一樣

總結

可以看到我講的非常詳細,可以說比今日頭條官方以及任何部落格寫的都清楚,從原理到優缺點再到解決方案一應俱全,因為篇幅有限,如果我還想把 smallestWidth 限定符適配方案寫的這麼詳細,那估計這篇文章得有一萬字了

所以我把這次的螢幕適配文章歸位一個系列,一共分為三篇,第一篇詳細的講 今日頭條螢幕適配方案,第二篇詳細的講 smallestWidth 限定符適配方案,第三篇詳細講兩個方案的深入對比以及如何選擇,併發布我根據 今日頭條螢幕適配方案 優化的螢幕適配框架 AndroidAutoSize

今日頭條螢幕適配方案 官方公佈的核心原始碼只有 30 行不到,但我這個框架的原始碼有 1500 行以上,在保留原有特性的情況下增加了不少功能和特性,功能增加了不少,但是使用上卻變簡單了

<manifest>
    <application>            
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="640"/>           
     </application>           
</manifest>
複製程式碼

只要這一步填寫了設計圖的高寬以 dp 為單位,你什麼都不做,框架就開始適配了

大家可以提前看看我是怎麼封裝和優化的,我後面的第三篇文章會給出這個框架的原理分析,敬請期待


關於大家的評論以及關注的問題,我在這裡統一回復一下:

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

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

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

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

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

公眾號

搜尋關注我的公眾號 JessYan,一起學習進步,如果框架有更新,我也會在公眾號上第一時間通知大家


以下是 騷年你的螢幕適配方式該升級了! 系列文章,歡迎轉發以及分享:


Hello 我叫 JessYan,如果您喜歡我的文章,可以在以下平臺關注我

-- The end

相關文章