天天看點

今日頭條螢幕适配方案

原文連結:https://mp.weixin.qq.com/s__biz=MzAxMTI4MTkwNQ==&mid=2650826103&idx=1&sn=a11a8cb56988df2a0f0978a90e358dc2&chksm=80b7b1e9b7c038ff9988c15cccd5656ab3aab8fa2ee5d527298f85693217078aa7d0744df5b2&scene=21#wechat_redirect

參考連結:

https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247484502&idx=2&sn=a60ea223de4171dd2022bc2c71e09351&scene=21#wechat_redirect

原理

今日頭條螢幕适配方案的核心原理在于,根據以下公式算出 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 的。

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

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

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

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

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

  • 裝置 1,螢幕寬度為 1080px,480DPI,螢幕總 dp 寬度為 1080 / (480 / 160) = 360dp
  • 裝置 2,螢幕寬度為 1440,560DPI,螢幕總 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 在任何分辨率的螢幕上,與螢幕的比例都是相同的。

這時我們該怎麼做呢?改變每個 View 的 dp 值?不現實,在每個裝置上都要通過代碼動态計算 View 的 dp 值,工作量太大。

如果每個 View 的 dp 值是固定不變的,那我們隻要保證每個裝置的螢幕總 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,根據今日頭條的的公式求出 density,1080 / 375 = 2.88 (density)。

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

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

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

上面隻能确定在所有螢幕總寬度為 1080 px 的裝置上能完成等比例适配,那我們再來試試其他分辨率的裝置。

驗證裝置 2

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

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

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

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

優點

  1. 使用成本非常低,操作非常簡單,使用該方案後在頁面布局時不需要額外的代碼和操作,這點可以說完虐其他螢幕适配方案
  2. 侵入性非常低,該方案和項目完全解耦,在項目布局時不會依賴哪怕一行該方案的代碼,而且使用的還是 Android 官方的 API,意味着當你遇到什麼問題無法解決,想切換為其他螢幕适配方案時,基本不需要更改之前的代碼,整個切換過程幾乎在瞬間完成,會少很多麻煩,節約很多時間,試錯成本接近于 0
  3. 可适配三方庫的控件和系統的控件(不止是是 Activity 和 Fragment,Dialog、Toast 等所有系統控件都可以适配),由于修改的 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 的适配效果,改用其他的适配方案。

4

使用中的問題

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

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

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

第一個坑

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

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

一個在設計圖上 300px * 300px 的 View,你可以直接在 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, 在這個螢幕寬度為 1080px,480dpi 的裝置上,density 就固定為 3 (480 / 160 = 3)。

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

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

第二個坑

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

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

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