Android全面的屏幕適配方案解析(四)__今日頭條適配方案

之前三篇把屏幕適配概念梳理了還講解了列舉的其中四種適配方案,還沒有看過的童鞋可以先參考這三篇:
Android全面的屏幕適配方案解析(一)__屏幕適配概念梳理
Android全面的屏幕適配方案解析(二)__寬高限定符屏幕適配
Android全面的屏幕適配方案解析(三)__sw限定符適配方案
Android全面的屏幕適配方案解析(四)__今日頭條適配方案

下面列舉常用的適配方案:

  • dp適配方案
  • 寬高限定符適配方案
  • AndroidAutoLayout適配方案
  • sw限定符適配方案
  • 今日頭條適配方案
  • AndroidAutoSize適配方案

這里還是有必要重申一下,有些過時的適配方案這里還講解啊,只能說每種適配方案都會有各自的優(yōu)缺點,從最原始的適配方案講起,才能更好的理解為啥會衍生出各種適配方案,話不多說,下面繼續(xù)講解。

今日頭條適配方案

今日頭條適配方案原理解析

今日頭條適配方案原理在于通過公式density = 設(shè)備真實寬度(單位px)/設(shè)計圖總寬度(單位dp),在確保設(shè)計圖總寬度(單位dp)一定時,通過修改density值,確保所有不同尺寸分辨率設(shè)備計算出的真實寬度值正好是屏幕寬度,這樣就能達(dá)到適配所有設(shè)備的目的啦。

舉個例子:比如UI設(shè)計稿總寬度為360dp,這里有兩臺不同尺寸分辨率的設(shè)備:

設(shè)備1:分辨率1080x1920,dpi為480,正常情況下計算density=dpi/160=480/160=3,此時屏幕總寬度dp=px/density=1080/3=360;

設(shè)備2:分辨率1440x2560,dpi為560,正常情況下計算desity=dpi/160=560/160=3.5,此時屏幕總寬度dp=px/density=1440/3.5=411;

正常情況下density 在每個設(shè)備上都是固定的,那要是我們想確保設(shè)計稿總寬度360不變,再來看看density值:

設(shè)備1:分辨率1080x1920,dpi為480,計算density = 設(shè)備真實寬度(單位px)/設(shè)計圖總寬度(單位dp) = 1080/360 = 3,此時屏幕總寬度dp=px/density=1080/3=360;

設(shè)備2:分辨率1440x2560,dpi為560,計算density = 設(shè)備真實寬度(單位px)/設(shè)計圖總寬度(單位dp) = 1440/360 = 4,此時屏幕總寬度dp=px/density=1440/4=360;

通過對比可以看出,修改density值,確實是能確保不同分辨率設(shè)備總寬度值始終是360dp,這樣就能保證UI在不同的設(shè)備上顯示效果是一致的。

今日頭條適配方案推導(dǎo)過程

通過上面例子我們知道需要修改density值,那下面我們就來看看怎么修改系統(tǒng)的density值:
首先我們得知道無論在布局文件中填寫的是什么樣單位的值,比如10dp、10sp、10px等等,最后都會被系統(tǒng)轉(zhuǎn)換成px,這個時候有童鞋就問了,你怎么知道???當(dāng)然是通過布局文件中dp轉(zhuǎn)換源碼知道的啦。

通過源碼可以知道,dp轉(zhuǎn)換最終都是調(diào)用系統(tǒng)工具類Typedvalue類中的applyDimension方法進(jìn)行轉(zhuǎn)換的:

      /**
     * @param unit 要轉(zhuǎn)換的單位
     * @param value 單位對應(yīng)的值
     * @param metrics 顯示指標(biāo)
     */
    public static float applyDimension(int unit, float value,DisplayMetrics metrics){
        switch (unit) {
        case COMPLEX_UNIT_PX://單位為px
            return value;
        case COMPLEX_UNIT_DIP://單位為dp
            return value * metrics.density;
        case COMPLEX_UNIT_SP://單位為sp
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT://單位為pt
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN://單位為in
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM://單位為mm
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

系統(tǒng)就是通過上面的方法,將你在項目中任何地方填寫的單位都轉(zhuǎn)換為px,在applyDimension方法中有個參數(shù)DisplayMetrics,我們來看看這個參數(shù)的含義:

 @param metrics Current display metrics to use in the conversion -- supplies display density and scaling information.

這句話的意思是轉(zhuǎn)換中使用的當(dāng)前顯示指標(biāo),提供顯示密度和縮放信息。通過applyDimension方法中dp轉(zhuǎn)換:

public static float applyDimension(int unit, float value,DisplayMetrics metrics){
        switch (unit) {
        ......
        case COMPLEX_UNIT_DIP://單位為dp
            return value * metrics.density;
        ......
    }

可以發(fā)現(xiàn)有我們需要的density值,通過Ctrl鍵追蹤位置發(fā)現(xiàn)這個值是系統(tǒng)工具類DisplayMetrics類的成員變量,如圖所示:

而DisplayMetrics實例可以通過系統(tǒng)資源文件Resources類中的getDisplayMetrics方法獲得,系統(tǒng)資源文件Resouces也可以通過Activity或者Application的Context獲得:

DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();

然后就可以通過DisplayMetrics實例,修改density值啦,這里需要注意有個scaledDensity變量的影響,這個變量是字體的縮放因子,正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值,防止用戶調(diào)節(jié)了系統(tǒng)字體。

今日頭條適配的最終方案:

這里是以設(shè)計圖總寬度360dp來適配,接下來只需要把我們計算好的 density 在系統(tǒng)中修改下即可,代碼實現(xiàn)如下:

    /**
     * 今日頭條適配方案
     *
     * @param activity
     * @param application
     */
    public static void setCustomDensity(Activity activity, final Application application) {
        //通過資源文件getResources類獲取DisplayMetrics
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            //保存之前density值
            sNoncompatDensity = appDisplayMetrics.density;
            //保存之前scaledDensity值,scaledDensity為字體的縮放因子,正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值
            sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            //監(jiān)聽設(shè)備系統(tǒng)字體切換
            application.registerComponentCallbacks(new ComponentCallbacks() {

                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        //調(diào)節(jié)系統(tǒng)字體大小后改變的值
                        sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                public void onLowMemory() {

                }
            });
        }

        //獲取以設(shè)計圖總寬度360dp下的density值
        float targetDensity = appDisplayMetrics.widthPixels / 360;
        //通過計算之前scaledDensity和density的比獲得scaledDensity值
        float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
        //獲取以設(shè)計圖總寬度360dp下的dpi值
        int targetDensityDpi = (int) (160 * targetDensity);
        //設(shè)置系統(tǒng)density值
        appDisplayMetrics.density = targetDensity;
        //設(shè)置系統(tǒng)scaledDensity值
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        //設(shè)置系統(tǒng)densityDpi值
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //獲取當(dāng)前activity的DisplayMetrics
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        //設(shè)置當(dāng)前activity的density值
        activityDisplayMetrics.density = targetDensity;
        //設(shè)置當(dāng)前activity的scaledDensity值
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        //設(shè)置當(dāng)前activity的densityDpi值
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

上面代碼是今日頭條的最終方案,為了方便閱讀,我只是基于個人理解做了標(biāo)注解析,不對的地方請大佬們指正。

今日頭條適配步驟:

下面來看驗證一下這種適配方案的可行性。

1、布局文件,這里沿用之前的布局,只有一張寬高為150x150(dp)的圖片,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

2、這里將今日頭條適配方案封裝成了工具類,如下所示:

public class CustomDensityUtil {
    private static float sNoncompatDensity;
    private static float sNoncompatScaledDensity;

    /**
     * 今日頭條適配方案
     *
     * @param activity
     * @param application
     */
    public static void setCustomDensity(Activity activity, final Application application) {
        //通過資源文件getResources類獲取DisplayMetrics
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            //保存之前density值
            sNoncompatDensity = appDisplayMetrics.density;
            //保存之前scaledDensity值,scaledDensity為字體的縮放因子,正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值
            sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            //監(jiān)聽設(shè)備系統(tǒng)字體切換
            application.registerComponentCallbacks(new ComponentCallbacks() {

                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        //調(diào)節(jié)系統(tǒng)字體大小后改變的值
                        sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                public void onLowMemory() {

                }
            });
        }

        //獲取以設(shè)計圖總寬度360dp下的density值
        float targetDensity = appDisplayMetrics.widthPixels / 360;
        //通過計算之前scaledDensity和density的比獲得scaledDensity值
        float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
        //獲取以設(shè)計圖總寬度360dp下的dpi值
        int targetDensityDpi = (int) (160 * targetDensity);
        //設(shè)置系統(tǒng)density值
        appDisplayMetrics.density = targetDensity;
        //設(shè)置系統(tǒng)scaledDensity值
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        //設(shè)置系統(tǒng)densityDpi值
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //獲取當(dāng)前activity的DisplayMetrics
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        //設(shè)置當(dāng)前activity的density值
        activityDisplayMetrics.density = targetDensity;
        //設(shè)置當(dāng)前activity的scaledDensity值
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        //設(shè)置當(dāng)前activity的densityDpi值
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }
}

3、在Activity中的onCreate方法中調(diào)用,如下所示:

CustomDensityUtil.setCustomDensity(MainActivity.this, getApplication());

到這里就完成了,是不是挺簡單的呢,下面來看看在分辨率:480x800、720x1280、1080x1920的測試顯示效果圖:

再來看一下沒有使用今日頭條適配方案之前不同手機(jī)的測試對比效果:

根據(jù)適配前后的對比效果還是挺明顯的。

今日頭條適配方案優(yōu)點

1、侵入性很低,而且沒有涉及私有API,該方案與項目完全解耦,今日頭條大廠在使用,穩(wěn)定性有保證。

2、使用成本非常低,操作簡單方便。

3、接入沒有任何的性能損耗,使用的都是系統(tǒng)API。

今日頭條適配方案缺點

1、只需要修改一次 density,項目中的所有地方都會自動適配,這個看似解放了雙手,減少了很多操作,但是實際上反映了一個缺點,那就是只能一刀切的將整個項目進(jìn)行適配,但適配范圍是不可控的。比如項目中使用了第三方庫控件等不是我們項目自身設(shè)計的控件,這時就會出現(xiàn)和我們項目自身的設(shè)計圖尺寸差距非常大的問題。

2、使用過程中需要進(jìn)行registerComponentCallbacks監(jiān)聽內(nèi)容文字的大小改變情況,解決退出應(yīng)用修改文字大小后,會出現(xiàn)文字大小不改變的情況。

AndroidAutoSize適配方案

所謂的AndroidAutoSize適配方案其實就是今日頭條適配方案的升級版,是基于今日頭條適配方案進(jìn)行拓展的開源庫,該庫在很大程度上解決了今日頭條適配方案的缺點,使用方式也比較簡單,只需要填寫設(shè)計圖尺寸這一步即可接入項目,還支持對Activity、Fragment進(jìn)行取消適配,靈活性會更強(qiáng)。

下面還是根據(jù)實例來講解集成過程:

1、根目錄build.gradle添加:

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2、添加該適配框架依賴

 implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'

3、在項目中的AndroidManifest配置文件注明設(shè)計稿的尺寸,這里測試以360x640為例:

<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>

如果只是想使用AndroidAutoSize適配方案的基礎(chǔ)功能,AndroidAutoSize框架的使用方法在這里就結(jié)束了,只需要上面這一步,即可幫助你以最簡單的方式接入AndroidAutoSize適配框架,我這里只做適配方案有效性演示,需要拓展需求的請參考下面Github地址有詳細(xì)介紹。

4、測試布局文件非常的簡單,只設(shè)置了圖片,為了突出跟今日頭條適配方案測試結(jié)果不同,這里設(shè)置圖片寬高為120x120(dp),測試布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

我們來看看這個實例在不同手機(jī)的測試對比效果圖:

再來看一下沒有使用AndroidAutoSize適配方案之前不同手機(jī)的測試對比效果:

根據(jù)適配前后的對比效果還是挺明顯的,AndroidAutoSize適配方案是根據(jù)今日頭條屏幕適配方案官方公布的 30 行不到的代碼,經(jīng)過不斷的優(yōu)化和擴(kuò)展,發(fā)展成了現(xiàn)在擁有將近20個類文件,上千行代碼的全面性屏幕適配框架,在迭代的過程中完善和優(yōu)化了很多功能,相比今日頭條屏幕適配方案官方公布的原始代碼,AndroidAutoSize適配方案更加穩(wěn)定、更加易用、更加強(qiáng)大,感興趣的可以去閱讀源碼,注釋非常詳細(xì),這里就不做過多的介紹啦,點擊進(jìn)入源碼地址

以上六種適配方案就全部講解完了,適配框架因為原作者已經(jīng)講解的很清楚了,我這里就只講解了基本用法,其它幾種都盡可能講解的詳細(xì)一點,我這里講解并沒有說哪種方案更好或者更壞,每個項目的需求都不一樣,適合的才是最好的。歡迎關(guān)注公眾號【龍旋】能獲取最新更新內(nèi)容哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內(nèi)容