Android 屏幕適配方案解析(二)

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

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

1咳燕、dp適配方案

眾所周知垦巴,在開發(fā)過程中標(biāo)識(shí)尺寸的時(shí)候彭则,Android并不推薦使用px這個(gè)真實(shí)像素單位,因?yàn)橄嗤叽缭诓煌直媛实氖謾C(jī)顯示效果各不相同仔夺,如下為圖片寬高為250x250(px),在分辨率:480x800、720x1280、1080x1920的顯示效果圖:
image.png

出現(xiàn)類似于上圖這樣的效果工猜,整體的布局效果可能會(huì)變形,所以px這個(gè)單位在布局文件中是不推薦的菱蔬。

相對(duì)應(yīng)的Android推薦使用dp作為尺寸單位來適配UI域慷,之前我們講過,dp為密度無關(guān)像素,與終端上的實(shí)際物理像素點(diǎn)無關(guān)犹褒,可以保證在不同屏幕像素密度的設(shè)備上顯示相同的效果。

下面還是根據(jù)實(shí)例來說明一下弛针,如下為圖片寬高為250x250(dp)叠骑,在分辨率:480x800、720x1280削茁、1080x1920的顯示效果圖:

image.png

從效果圖可以看出宙枷,寬高250dp在不同分辨率手機(jī)整體的布局效果差異并不大,為啥會(huì)這樣呢茧跋?

我們知道同樣尺寸在不同分辨率的設(shè)備慰丛,每1dp所代表的像素?cái)?shù)量是不一樣的,如表格所示:

image.png

由表格可以看出瘾杭,在480x800诅病、720x1280、1080x1920的手機(jī)粥烁,dpi是不同的贤笆,這也是說,1dp在這些不同分辨率的手機(jī)中讨阻,分別對(duì)應(yīng)的1.5px芥永、2px、3px钝吮,這樣當(dāng)我們用dp作為控件大小單位的時(shí)候埋涧,在不同分辨率的手機(jī)上看到的大小一樣,此時(shí)各手機(jī)上顯示的比例也就一致啦奇瘦。

以上的適配方式棘催,通過dp再加上自適應(yīng)布局和weight比例布局可以基本解決不同手機(jī)上適配的問題,這基本上就是最原始的Android適配方案链患。

那這種方案有沒缺點(diǎn)呢巧鸭?
自然是有的,不然也不會(huì)推出這么多的適配方案麻捻,那我們來看看有啥缺點(diǎn)纲仍。
我們知道Android之所以碎片化這么嚴(yán)重,跟Android系統(tǒng)贸毕、屏幕尺寸郑叠、屏幕密度的碎片化有很大的關(guān)系,而手機(jī)廠商生產(chǎn)的手機(jī)設(shè)備也并沒有按照屏幕尺寸明棍、分辨率和像素密度的關(guān)系規(guī)則來實(shí)現(xiàn)乡革,比如屏幕分辨率1080x1920,屏幕尺寸為5,那么dip為440沸版,假如UI設(shè)計(jì)圖按屏幕寬度為375dp設(shè)計(jì)嘁傀,那么這樣會(huì)存在啥問題呢?

根據(jù)上述描述视粮,我們可以通過計(jì)算出屏幕寬度為:1080/(440/160) = 393dp细办,也就是說實(shí)際的屏幕寬度是比設(shè)計(jì)圖的要寬的,那這樣即使用dp為單位也無法跟其它設(shè)備顯示同樣的效果蕾殴,這就需要通過估算或者設(shè)定規(guī)范值等等進(jìn)行換算設(shè)置笑撞,這也就需要我們耗費(fèi)精力去轉(zhuǎn)換尺寸,這會(huì)極大降低開發(fā)效率钓觉。

2茴肥、寬高限定符適配方案

所謂的寬高限定符適配就是窮舉市面上所有的Android手機(jī)的寬高像素值,不過需要先設(shè)定一個(gè)基準(zhǔn)荡灾,然后其它分辨率根據(jù)這個(gè)基準(zhǔn)做適配瓤狐,如圖所示:

image.png

那什么叫設(shè)定一個(gè)基準(zhǔn)呢?
比如設(shè)定320x480的分辨率為基準(zhǔn)卧晓,那么:

基準(zhǔn)寬度為320芬首,即將任何分辨率的寬度分為320份,取值為x1到x320逼裆。

基準(zhǔn)長(zhǎng)度為480郁稍,即將任何分辨率的長(zhǎng)度分為480份,取值為y1到y(tǒng)480胜宇。

那么該基準(zhǔn)尺寸編寫對(duì)應(yīng)的dimens文件為:


image.png

那什么又叫其它分辨率根據(jù)這個(gè)基準(zhǔn)做適配呢耀怜?
比如拿480x800分辨率的手機(jī)來說,需要在項(xiàng)目中values-800x480目錄下的dimens.xml文件中的如下設(shè)置桐愉,如圖所示:

image.png

那么這份數(shù)據(jù)是怎么計(jì)算得到的呢财破,那當(dāng)然是在基準(zhǔn)分辨率的基礎(chǔ)上計(jì)算得到的,以下為寬度x演示即:

x1=(480/基準(zhǔn))1=(480/320)1=1.5px

x2=(480/基準(zhǔn))2=(480/320)2=3px

x320=(480/基準(zhǔn))320=(480/320)320=480px

同理長(zhǎng)度y計(jì)算也是一樣的从诲。

這個(gè)時(shí)候有童鞋又說了左痢,市面上我怎么知道有多少分辨率的手機(jī)啊,就算知道所有分辨率系洛,每個(gè)這么計(jì)算不得寫廢了啊俊性,淡定哈,這些都是可以通過工具自動(dòng)生成描扯,這得感謝鴻洋大佬提供的生成工具定页。

1、分析需要的支持的分辨率

對(duì)于主流的分辨率已經(jīng)集成到程序里了绽诚,對(duì)于比較特殊的可以通過參數(shù)指定典徊,而關(guān)于屏幕分辨率信息杭煎,可以通過該網(wǎng)站查詢:點(diǎn)擊這里跳轉(zhuǎn)

2、自動(dòng)生成文件的程序地址

點(diǎn)擊這里跳轉(zhuǎn)獲取自動(dòng)生成程序

image.png

這里提供了個(gè)jar包卒落,默認(rèn)情況下羡铲,雙擊即可生成,如圖所示:
image.png

這里默認(rèn)內(nèi)置了常用的分辨率导绷,默認(rèn)基準(zhǔn)為480*320犀勒,對(duì)于特殊需求,可以通過命令行指定即可妥曲,比如我想以分辨率1280 * 800為基準(zhǔn) ,并且額外支持尺寸:1152 * 735钦购;4500 * 3200檐盟,如圖所示:


image.png

格式如下:

java -jar xx.jar width height width,height_width,height

最后自動(dòng)生成文件如圖所示:


image.png

這樣就更改了默認(rèn)的基準(zhǔn),還添加了額外支持的分辨率押桃。

使用這種適配方案葵萎,可以按照UI設(shè)計(jì)稿的尺寸為基準(zhǔn)分辨率,這時(shí)運(yùn)行在不同分辨率的手機(jī)中唱凯,這些系統(tǒng)會(huì)根據(jù)這些dimens引用去該分辨率的文件夾下面尋找對(duì)應(yīng)的值羡忘,這樣基本解決了我們的適配問題,而且極大的提升了我們UI開發(fā)的效率磕昼。

那這種方案有沒缺點(diǎn)呢卷雕?當(dāng)然也是有的

  • 最明顯的肯定就是占用資源大,會(huì)增加APK的體積
  • 容錯(cuò)機(jī)制大票从,需要精準(zhǔn)命中資源文件才能適配漫雕,比如1920x1080的手機(jī)就一定要找到1920x1080的限定符,否則就只能用統(tǒng)一的默認(rèn)的dimens文件了峰鄙。而使用默認(rèn)的尺寸的話浸间,UI就很可能變形。

3吟榴、AndroidAutoLayout適配方案

所謂的AndroidAutoLayout適配方案魁蒜,其實(shí)就是UI適配框架,這個(gè)是鴻洋大佬寫的適配方案吩翻,這也是受寬高限定符適配方案啟發(fā)寫的兜看,使用方式也比較簡(jiǎn)單,直接填寫設(shè)計(jì)圖上的像素尺寸即可完成適配仿野,不過目前這個(gè)框架已停止維護(hù)铣减。
下面還是根據(jù)實(shí)例來講解集成過程:

1、引入該適配框架
implementation 'com.zhy:autolayout:1.4.5'
2脚作、在項(xiàng)目中的AndroidManifest配置文件注明設(shè)計(jì)稿的尺寸葫哗,這里測(cè)試以720x1280為例:
       <!-- 測(cè)試手機(jī)的尺寸 -->
       <meta-data
           android:name="design_width"
           android:value="720" />

       <meta-data
           android:name="design_height"
           android:value="1280" />

3缔刹、將需要適配的Activity繼承自AutoLayoutActivity,如果你不希望繼承AutoLayoutActivity劣针,可以在編寫布局文件時(shí)校镐,將:
  • LinearLayout -> AutoLinearLayout
  • RelativeLayout -> AutoRelativeLayout
  • FrameLayout -> AutoFrameLayout

這樣也可以完成適配,我這里測(cè)試實(shí)例使用AutoRelativeLayout進(jìn)行測(cè)試捺典,測(cè)試布局如下:

<?xml version="1.0" encoding="utf-8"?>
<com.zhy.autolayout.AutoRelativeLayout 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="250px"
       android:layout_height="250px"
       android:layout_centerInParent="true"
       android:src="@mipmap/ic_launcher" />

</com.zhy.autolayout.AutoRelativeLayout>

測(cè)試布局文件非常的簡(jiǎn)單鸟廓,只設(shè)置了圖片,細(xì)心的童鞋也發(fā)現(xiàn)了襟己,這里圖片的寬高都是設(shè)置以px為單位引谜,而不是我們常用的dp為單位,如圖所示:
image.png

是的擎浴,你沒有看錯(cuò)员咽,這里可以直接在布局文件里面使用具體的像素值,例如設(shè)計(jì)稿上標(biāo)注的圖片是250*250贮预,那么我們可以直接寫250px贝室,運(yùn)行時(shí)框架會(huì)幫助我們根據(jù)不同手機(jī)的具體尺寸按比例伸縮,我們來看看這個(gè)實(shí)例在不同手機(jī)的測(cè)試對(duì)比效果圖:


image.png

我們?cè)賮砜匆幌聸]有使用AndroidAutoLayout適配方案之前不同手機(jī)的測(cè)試對(duì)比效果:


image.png

根據(jù)適配前后的對(duì)比效果還是挺明顯的仿吞,我這里主要介紹的是基本的適配方案滑频,如果你想了解更詳細(xì)的適配方式,可以到這里點(diǎn)擊這里

毫無疑問唤冈,這個(gè)UI框架是個(gè)極好的適配方案峡迷,使用簡(jiǎn)單,所要做的就是抄抄設(shè)計(jì)稿上面的px务傲,直接寫入布局文件即可完成適配凉当,而且在寬高限定符適配方案的基礎(chǔ)上更近一步,完美解決了容錯(cuò)機(jī)制的問題售葡,讓開發(fā)更加的高效看杭。

那這個(gè)方案有沒缺點(diǎn)呢?
當(dāng)然也是有的挟伙,首先是這個(gè)UI適配框架已經(jīng)停止適配楼雹,這也意味著一旦這個(gè)框架在使用過程中遇到難以解決的問題,替換起來會(huì)非常的麻煩且耗精力尖阔,當(dāng)然如果你已經(jīng)熟悉了這套源碼除外哈贮缅。

4、sw限定符適配方案

sw限定符適配即smallestWidth適配介却,也叫最小寬度限定符適配谴供,指的是Android會(huì)識(shí)別到屏幕可用高度或?qū)挾鹊淖钚〕叽绲膁p值,對(duì)于手機(jī)來說齿坷,這個(gè)值其實(shí)就是手機(jī)的寬度值桂肌,而對(duì)于平板來說数焊,這個(gè)值就是平板的高度值。然后根據(jù)識(shí)別到的結(jié)果去資源文件中尋找對(duì)應(yīng)限定符文件夾下的資源文件崎场。其實(shí)這種適配方案跟之前介紹的寬高限定符適配方案原理是一樣一樣的佩耳,都是系統(tǒng)通過特定的規(guī)則來找到對(duì)應(yīng)的資源文件。

我們先來看一下資源文件谭跨,這份是通過插件生成的以375dp為基準(zhǔn)的資源文件干厚,插件的使用后面會(huì)介紹,如圖所示:


image.png

舉例說明:比如我有臺(tái)測(cè)試機(jī)為小米螃宙,手機(jī)分辨率為1080x1920蛮瞄,屏幕像素密度為480,根據(jù)最小寬度限定符適配方案谆扎,該手機(jī)最小寬度為1080px裕坊,再根據(jù)px=dp(dpi/160),橫向的dp值是1080/(480/160)燕酷,也就是360dp,根據(jù)這個(gè)結(jié)果系統(tǒng)就會(huì)去資源文件中尋找values-sw360dp文件夾下的資源文件周瞎,如圖所示:


image.png

前面說了苗缩,該方案的原理跟寬高限定符適配方案是一樣的,所以也需要提前設(shè)置基準(zhǔn)尺寸声诸,比如我這里以375dp為基準(zhǔn)酱讶,即將任何分辨率的寬度分為375份,我們來看看基準(zhǔn)資源文件彼乌,如圖所示:
image.png

我們?cè)倌蒙厦嫘∶资謾C(jī)舉例說明泻肯,我們來看看values-sw360dp文件夾下的資源文件,如圖所示:


image.png

那么這份數(shù)據(jù)是怎么計(jì)算得到的呢慰照,當(dāng)然也是在基準(zhǔn)尺寸的基礎(chǔ)上計(jì)算得到的灶挟,即:
dp_1=(360/基準(zhǔn))1=(360/375)1=0.96dp

dp_2=(360/基準(zhǔn))2=(360/375)2=1.92dp

dp_375=(360/基準(zhǔn))375=(360/375)375=360dp

其它手機(jī)尺寸也是同理這樣計(jì)算得到的。

那下面來看看資源文件是怎么生成的毒租?

實(shí)現(xiàn)步驟:

1稚铣、在Android studio中安裝ScreenMatch插件

點(diǎn)擊菜單欄上的 File -> Settings -> Plugins ,搜索ScreenMatch關(guān)鍵字墅垮,安裝插件惕医,安裝成功后,點(diǎn)擊 “OK”算色,重啟 Andorid Studio 即可抬伺,如圖所示:


image.png
2、在項(xiàng)目values文件夾下需要一份dimens.xml文件灾梦,即前面說的默認(rèn)基準(zhǔn)尺寸文件峡钓,如圖所示:
image.png
3妓笙、插件安裝后,在項(xiàng)目的任意目錄或文件上右鍵椒楣,選擇 ScreenMatch 選項(xiàng)给郊,如圖所示:
image.png
4、然后選擇需要適配的module 捧灰,我這里測(cè)試只有一個(gè)淆九,如圖所示:
image.png
5、點(diǎn)擊 OK后 毛俏,出現(xiàn)如下彈窗即表示生成資源文件成功柿汛,如圖所示:
image.png

通過以上的步驟res 目錄下就生成了默認(rèn)尺寸的所有資源文件啦。

上面步驟是以最小寬度基準(zhǔn)值為375dp光督,適配的設(shè)備最小寬度為:240,320,384,392,400,410,411,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365生成的文件淑翼,當(dāng)然實(shí)際開發(fā)需要根據(jù)設(shè)計(jì)圖需求進(jìn)行設(shè)置,這個(gè)時(shí)候就需要更改基準(zhǔn)值及需要適配的適配啦阀溶,那下面看看怎么更改的腻脏。
例如現(xiàn)在UI的設(shè)計(jì)圖最小寬度為360dp,則需要更改最小寬度基準(zhǔn)值為 360dp银锻。假如我們需要適配的設(shè)備最小寬度保留 320,360,384,392,400,410,411,480即可永品,如果還需要添加其他最小寬度的設(shè)備自行加上即可,首先我們需要找到插件的screenMatch.properties配置文件并打開击纬,如圖所示:


image.png

然后我們需要在配置文件修改鼎姐,修改下圖中 1、3更振、4處的值即可炕桨,如圖所示:


image.png

值得注意的是上圖設(shè)置的值單位都為dp。

我們?cè)賮砜纯?肯腕、2献宫、3、4表示什么意思:
1乎芳、表示最小寬度基準(zhǔn)值遵蚜,填寫設(shè)計(jì)圖的最小寬度值即可。
2奈惑、表示插件默認(rèn)適配的最小寬度值吭净,即默認(rèn)情況下會(huì)生成如下值的 dimens.xml 文件。
3肴甸、表示需要適配的最小寬度值寂殉,即你想生成哪些 dimens.xml 文件。
4原在、忽略不需要適配的最小寬度值友扰,即忽略掉插件默認(rèn)生成的 dimens.xml 文件彤叉。

修改完配置文件后,重新執(zhí)行步驟3就會(huì)生成新的資源文件村怪。

我們?cè)賮砜匆幌聅w限定符適配方案的優(yōu)點(diǎn):
sw限定符適配方案和寬高限定符適配方案最大的區(qū)別在于:前者有很好的容錯(cuò)機(jī)制秽浇,如果沒有value-sw360dp文件夾,系統(tǒng)會(huì)向下尋找甚负,比如離360dp最近的只有value-sw350dp柬焕,那么Android就會(huì)選擇value-sw350dp文件夾下面的資源文件。這個(gè)特性就完美的解決了寬高限定符適配方案的容錯(cuò)問題梭域。而且sw限定符適配方案非常穩(wěn)定斑举,極低概率出現(xiàn)意外。在插件的配合下病涨,學(xué)習(xí)成本也比較低富玷。

那這種方案有沒缺點(diǎn):

  • 最明顯的肯定也是占用資源大,會(huì)增加APK的體積
  • 在布局中引用dimens的方式既穆,在維護(hù)過程中修改會(huì)比較麻煩赎懦。
  • 侵入性高,比如想切換其它適配方案幻工,由于每個(gè)layout文件中有大量的dimens引用铲敛,修改起來工作量會(huì)非常巨大,切換成本非常高会钝。

今日頭條適配方案

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

舉個(gè)例子:比如UI設(shè)計(jì)稿總寬度為360dp奸鬓,這里有兩臺(tái)不同尺寸分辨率的設(shè)備:
設(shè)備1:分辨率1080x1920,dpi為480掸读,正常情況下計(jì)算density=dpi/160=480/160=3串远,此時(shí)屏幕總寬度dp=px/density=1080/3=360;

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

正常情況下density 在每個(gè)設(shè)備上都是固定的留搔,那要是我們想確保設(shè)計(jì)稿總寬度360不變,再來看看density值:
設(shè)備1:分辨率1080x1920铛铁,dpi為480隔显,計(jì)算density = 設(shè)備真實(shí)寬度(單位px)/設(shè)計(jì)圖總寬度(單位dp) = 1080/360 = 3却妨,此時(shí)屏幕總寬度dp=px/density=1080/3=360;

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

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

今日頭條適配方案推導(dǎo)過程
通過上面例子我們知道需要修改density值,那下面我們就來看看怎么修改系統(tǒng)的density值:
首先我們得知道無論在布局文件中填寫的是什么樣單位的值生年,比如10dp婴程、10sp、10px等等抱婉,最后都會(huì)被系統(tǒng)轉(zhuǎn)換成px档叔,這個(gè)時(shí)候有童鞋就問了,你怎么知道罢艏ā衙四?當(dāng)然是通過布局文件中dp轉(zhuǎn)換源碼知道的啦。

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

     /**
    * @param unit 要轉(zhuǎn)換的單位
    * @param value 單位對(duì)應(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)就是通過上面的方法传蹈,將你在項(xiàng)目中任何地方填寫的單位都轉(zhuǎn)換為px,在applyDimension方法中有個(gè)參數(shù)DisplayMetrics步藕,我們來看看這個(gè)參數(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)這個(gè)值是系統(tǒng)工具類DisplayMetrics類的成員變量沾歪,如圖所示:


image.png

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

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

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

今日頭條適配的最終方案:
這里是以設(shè)計(jì)圖總寬度360dp來適配蕾域,接下來只需要把我們計(jì)算好的 density 在系統(tǒng)中修改下即可,代碼實(shí)現(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)字體大小后會(huì)改變這個(gè)值
           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è)計(jì)圖總寬度360dp下的density值
       float targetDensity = appDisplayMetrics.widthPixels / 360;
       //通過計(jì)算之前scaledDensity和density的比獲得scaledDensity值
       float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
       //獲取以設(shè)計(jì)圖總寬度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;
   }

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

今日頭條適配步驟:
下面來看驗(yàn)證一下這種適配方案的可行性斧吐。

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)字體大小后會(huì)改變這個(gè)值
           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è)計(jì)圖總寬度360dp下的density值
       float targetDensity = appDisplayMetrics.widthPixels / 360;
       //通過計(jì)算之前scaledDensity和density的比獲得scaledDensity值
       float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
       //獲取以設(shè)計(jì)圖總寬度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());

到這里就完成了妒茬,是不是挺簡(jiǎn)單的呢担锤,下面來看看在分辨率:480x800、720x1280乍钻、1080x1920的測(cè)試顯示效果圖:


image.png

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


image.png

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

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

  • 1、侵入性很低银择,而且沒有涉及私有API育拨,該方案與項(xiàng)目完全解耦,今日頭條大廠在使用欢摄,穩(wěn)定性有保證。
  • 2笋粟、使用成本非常低怀挠,操作簡(jiǎn)單方便。
  • 3害捕、接入沒有任何的性能損耗绿淋,使用的都是系統(tǒng)API。

今日頭條適配方案缺點(diǎn)

  • 1尝盼、只需要修改一次 density吞滞,項(xiàng)目中的所有地方都會(huì)自動(dòng)適配,這個(gè)看似解放了雙手,減少了很多操作裁赠,但是實(shí)際上反映了一個(gè)缺點(diǎn)殿漠,那就是只能一刀切的將整個(gè)項(xiàng)目進(jìn)行適配,但適配范圍是不可控的佩捞。比如項(xiàng)目中使用了第三方庫控件等不是我們項(xiàng)目自身設(shè)計(jì)的控件绞幌,這時(shí)就會(huì)出現(xiàn)和我們項(xiàng)目自身的設(shè)計(jì)圖尺寸差距非常大的問題。
  • 2一忱、使用過程中需要進(jìn)行registerComponentCallbacks監(jiān)聽內(nèi)容文字的大小改變情況莲蜘,解決退出應(yīng)用修改文字大小后,會(huì)出現(xiàn)文字大小不改變的情況帘营。

AndroidAutoSize適配方案

所謂的AndroidAutoSize適配方案其實(shí)就是今日頭條適配方案的升級(jí)版票渠,是基于今日頭條適配方案進(jìn)行拓展的開源庫,該庫在很大程度上解決了今日頭條適配方案的缺點(diǎn)芬迄,使用方式也比較簡(jiǎn)單问顷,只需要填寫設(shè)計(jì)圖尺寸這一步即可接入項(xiàng)目,還支持對(duì)Activity薯鼠、Fragment進(jìn)行取消適配择诈,靈活性會(huì)更強(qiáng)。
下面還是根據(jù)實(shí)例來講解集成過程:

1出皇、根目錄build.gradle添加:
allprojects {
   repositories {
       ...
       maven { url "https://jitpack.io" }
   }
}
2羞芍、添加該適配框架依賴
implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'
3、在項(xiàng)目中的AndroidManifest配置文件注明設(shè)計(jì)稿的尺寸郊艘,這里測(cè)試以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é)束了,只需要上面這一步纱注,即可幫助你以最簡(jiǎn)單的方式接入AndroidAutoSize適配框架畏浆,我這里只做適配方案有效性演示,需要拓展需求的請(qǐng)參考下面Github地址有詳細(xì)介紹狞贱。

4刻获、測(cè)試布局文件非常的簡(jiǎn)單,只設(shè)置了圖片瞎嬉,為了突出跟今日頭條適配方案測(cè)試結(jié)果不同蝎毡,這里設(shè)置圖片寬高為120x120(dp),測(cè)試布局如下:
<?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>

我們來看看這個(gè)實(shí)例在不同手機(jī)的測(cè)試對(duì)比效果圖:


image.png

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


image.png

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啡浊,一起剝皮案震驚了整個(gè)濱河市觅够,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巷嚣,老刑警劉巖喘先,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異廷粒,居然都是意外死亡窘拯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門坝茎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涤姊,“玉大人,你說我怎么就攤上這事嗤放∷己埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵次酌,是天一觀的道長(zhǎng)恨课。 經(jīng)常有香客問我,道長(zhǎng)岳服,這世上最難降的妖魔是什么剂公? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮吊宋,結(jié)果婚禮上纲辽,老公的妹妹穿的比我還像新娘。我一直安慰自己璃搜,他們只是感情好拖吼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腺劣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪因块。 梳的紋絲不亂的頭發(fā)上橘原,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼趾断。 笑死拒名,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芋酌。 我是一名探鬼主播增显,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脐帝!你這毒婦竟也來了同云?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤堵腹,失蹤者是張志新(化名)和其女友劉穎炸站,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疚顷,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旱易,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腿堤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阀坏。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖笆檀,靈堂內(nèi)的尸體忽然破棺而出忌堂,到底是詐尸還是另有隱情,我是刑警寧澤误债,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布浸船,位于F島的核電站,受9級(jí)特大地震影響寝蹈,放射性物質(zhì)發(fā)生泄漏李命。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一箫老、第九天 我趴在偏房一處隱蔽的房頂上張望封字。 院中可真熱鬧,春花似錦耍鬓、人聲如沸阔籽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笆制。三九已至,卻和暖如春涣达,著一層夾襖步出監(jiān)牢的瞬間在辆,已是汗流浹背证薇。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匆篓,地道東北人浑度。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸦概,于是被迫代替她去往敵國和親箩张。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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