更新:由于該適配方案越來越多人使用,也有很多人遇到不太理解的問題。所以為了大家更好的使用,我將文章很多內容更新了,老用戶可以重新看下整篇文章。主要更新的內容是使用方法跟以前不同了,以前是不改變最小寬度基準值,根據 UI 圖來計算布局中設置的值?,F在改成最小寬度基準值與設計圖一致,然后設計圖標注多少 dp 就寫多少 dp,非常方便。還整理了評論區(qū)比較多人問到的問題作統(tǒng)一回答。
前言
網上關于屏幕適配的文章已經鋪天蓋地了,為什么我還要講?因為網上現在基本都是使用 屏幕分辨率限定符 進行適配,即每種屏幕分辨率的設備需要定義一套 dimens.xml 文件。由于不同分辨率的設備太多了,而且有些設備還有虛擬按鍵(例如華為手機),這樣就還需要每個有虛擬按鍵的設備加多一套 dimens.xml 文件,再加上平板那些你會發(fā)現 dimens.xml 文件所占的體積已經超過 2M 了!這絕對不是我們想要的。
我這里要講的是使用 sw<N>dp 限定符,即 smallestWidth(最小寬度) 限定符 來進行適配,使用這種方式只需要少量 dimens.xml 文件即可達到適配,而且根本不用考慮虛擬按鍵的問題。如果只適配手機,dimens.xml 文件所占的體積只有 100 多 KB,即使加上平板和 TV,也就 500 多 KB,完全可以接收。這種方案已經在自己多個項目中應用過了,經過幾十臺手機測試過,基本不會出現適配有問題的情況。制作生成對應 dimens.xml 文件插件(后面會講)的作者也說過他在待過的兩家大公司實踐過,所以請放心使用。
一、為什么要進行屏幕適配?
關于為什么要進行屏幕適配,什么是 dp、dpi 這些概念我就不去一一講解了,網上很多文章。這里我推薦幾篇講的比較好的:
二、屏幕分辨率限定符與 smallestWidth 限定符適配原理
2.1 屏幕分辨率限定符適配原理
屏幕分辨率限定符適配需要在 res 文件夾下創(chuàng)建各種屏幕分辨率對應的 values-xxx 文件夾,如下圖:

然后根據一個基準分辨率,例如基準分辨率為 1280x720,將寬度分成 720 份,取值為 1px~720px,將高度分成 1280 份,取值為 1px~1280px,生成各種分辨率對應的 dimens.xml 文件。如下分別為分辨率 1280x720 與 1920x1080 所對應的橫向 dimens.xml 文件:

假設設計圖上的一個控件的寬度為 720px,那么布局中就寫 android:layout_width="@dimen/x720" ,當運行程序的時候,系統(tǒng)會根據設備的分辨率去尋找對應的 dimens.xml 文件。例如運行在分辨率為 1280x720 的設備上,系統(tǒng)會自動找到對應的 values-1280x720 文件夾下的 lay_x.xml 文件,由上圖可知 x720 對應的值為
720.px,可鋪滿該屏幕寬度。運行在分辨率為 1920x1080 的設備上,系統(tǒng)會自動找到對應的 values-1920x1080 文件夾下的 lay_x.xml 文件,由上圖可知 x720 對應的值為 1080.0px,可鋪滿該屏幕寬度。這樣就達到了屏幕適配的要求!
2.2 smallestWidth 限定符適配原理
smallestWidth 限定符適配原理與屏幕分辨率限定符適配原理一樣,系統(tǒng)都是根據限定符去尋找對應的 dimens.xml 文件。例如程序運行在最小寬度為 360dp 的設備上,系統(tǒng)會自動找到對應的 values-sw360dp 文件夾下的 dimens.xml 文件。區(qū)別就在于屏幕分辨率限定符適配是拿 px 值等比例縮放,而 smallestWidth 限定符適配是拿 dp 值來等比縮放而已。需要注意的是“最小寬度”是不區(qū)分方向的,即無論是寬度還是高度,哪一邊小就認為哪一邊是“最小寬度”。如下分別為最小寬度為 360dp 與最小寬度為 640dp 所對應的 dimens.xml 文件:

- 獲取設備最小寬度代碼為:
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int heightPixels = ScreenUtils.getScreenHeight(this);
int widthPixels = ScreenUtils.getScreenWidth(this);
float density = dm.density;
float heightDP = heightPixels / density;
float widthDP = widthPixels / density;
float smallestWidthDP;
if(widthDP < heightDP) {
smallestWidthDP = widthDP;
}else {
smallestWidthDP = heightDP;
}
ScreenUtils——>ScreenUtils
三、為什么選擇 smallestWidth 限定符適配?
既然原理都一樣,都需要多套 dimens.xml 文件,那為什么要選擇 smallestWidth 限定符適配呢?
- 屏幕分辨率限定符適配是根據屏幕分辨率的,Android 設備分辨率一大堆,而且還要考慮虛擬鍵盤,這樣就需要大量的 dimens.xml 文件。因為無論手機屏幕的像素多少,密度多少,90% 的手機的最小寬度都為 360dp,所以采用 smallestWidth 限定符適配只需要少量 dimens.xml 文件即可。
- 屏幕分辨率限定符適配采用的是 px 單位,而 smallestWidth 限定符適配采用的單位是 dp 和 sp,dp 和 sp 是 google 推薦使用的計量單位。又由于很多應用要求字體大小隨系統(tǒng)改變,所以字體單位使用 sp 也更靈活。
- 屏幕分辨率限定符適配需要設備分辨率與 values-xx 文件夾完全匹配才能達到適配,而 smallestWidth 限定符適配尋找 dimens.xml 文件的原理是從大往小找,例如設備的最小寬度為 360dp,就會先去找 values-360dp,發(fā)現沒有則會向下找 values-320dp,如果還是沒有才找默認的 values 下的 demens.xml 文件,所以即使沒有完全匹配也能達到不錯的適配效果。
四、使用步驟
4.1 獲取設計圖最小寬度(單位為 dp)
大多數 UI 設計師提供的設計圖無非就幾種,它們對應的獲取方式如下:
- 藍湖:開發(fā)平臺切換到 Android,設計圖寬度即為最小寬度。
- psd 源文件:用像素大廚查看,設計圖寬度即為最小寬度。(注意像素大廚需要選擇與設計圖對應的 dpi 進行顯示)
- dp 單位的設計圖:設計圖寬度即為最小寬度。
- px 單位的設計圖:問 UI 設計師是幾倍圖,然后最小寬度 = 設計圖寬度/倍數。
4.2 以設計圖最小寬度作為基準值,生成所有設備對應的 dimens.xml 文件
這些文件當然不會手動去寫,網上已經有大神提供了自動生成這些文件的插件 ScreenMatch。但是這個插件還是有點問題的:
- 默認沒有適配最小寬度為 320dp 的設備。其實自己測試還是有很多設備最小寬度是 320dp 的,所以需要加上。
- 最小寬度為 392.7272 與 411.4285 的手機不能達到完全適配。原因是該插件的默認值都是取整的,即 392.7272 與 411.4285 在插件中寫的是 392 與 411。
基于以上問題,我在該插件的源碼上優(yōu)化生成了新的插件 ScreenMatch,由于插件庫已經有原作者的插件了,所以我就不重復造輪子上傳到插件庫了,你直接用本地安裝的方式安裝即可。
工具使用步驟:
- 在 Android Studio 中安裝 ScreenMatch 插件
下載插件 ScreenMatch 到本地,點擊菜單欄上的 File -> Settings -> Plugins -> Install plugin from disk,然后選擇我們剛剛下載的插件,最后點擊 “OK”,重啟 Andorid Studio 即可。如下圖所示:

- 在項目的默認 values 文件夾中需要一份 dimens.xml 文件
我在 github 源碼已經提供了一份,直接復制過來即可。

- 執(zhí)行生成
插件安裝好后,在項目的任意目錄或文件上右鍵,選擇 ScreenMatch 選項。如下圖:

然后選擇在哪個 module 下執(zhí)行適配。即基于哪個 module 下的 res/values/dimens.xml 文件作為基準 dimens.xml 文件,生成的其他尺寸 dimens.xml 文件放在哪個 module 下。例如選擇 app,然后點擊 OK ,出現如下界面表示生成文件成功。如下圖:

然后再看看 res 目錄下會自動生成一堆 dimens.xml 文件,如下圖:

通過上面的步驟就已經生成了所有設備對應的 dimens.xml 文件。
- 根據設計圖填寫最小寬度基準值,并填寫需要適配的設備最小寬度 dp 值
步驟 3 是以插件默認的最小寬度基準值為 360dp,適配的設備最小寬度為
320,360,384,392.7272,400,410,411.4285,432,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365(包含了平板和 TV )生成的文件,但實際情況要根據設計圖和需求設置。
例如設計圖的最小寬度為 375dp,則需要更改最小寬度基準值為 375dp。如果項目只需要適配手機的話,適配的設備最小寬度保留 320,360,384,392.7272,400,410,411.4285,432,480 即可,若發(fā)現手機還有其他最小寬度自行加上即可,也麻煩把該最小寬度提供給我,我們一起來完善該份適配。
以上修改需要在配置文件里修改,即 screenMatch.properties 文件,該配置文件是執(zhí)行完上面第 3 步后自動生成在項目的跟目錄下的。如下圖:

打開配置文件,修改下圖中 1、3、4 的值即可。(圖中單位均為 dp)
1:最小寬度基準值,填寫設計圖的最小寬度值即可。
2:插件默認適配的最小寬度值,即默認情況下會生成如下值的 dimens.xml 文件。
3:需要適配的最小寬度值(如果是小數,則保留 4 位小數。例如 392.727272...,則取 392.7272),即你想生成哪些 dimens.xml 文件。
4:忽略不需要適配的最小寬度值,即忽略掉插件默認生成的 dimens.xml 文件。

配置文件修改完成后,重新執(zhí)行第 3 步,生成新的 dimens.xml 文件。
當然!如果你的設計圖也是標準的 360dp,那么上面的步驟你可以忽略。直接復制我 github 上你需要的 dimens.xml 文件到你的項目即可,默認的 values 文件夾下也需要一份。
4.3 根據設計圖標注,在布局寫上對應的值。
設計圖標注多少 dp,布局中就寫多少 dp ,非常方便!
大多數 UI 設計師提供的設計圖無非就幾種,它們對應的使用方式如下:
- 上傳到藍湖:顯示多少 dp 就寫多少 dp。
- psd 源文件:用像素大廚查看,顯示多少 dp 就寫多少 dp(注意像素大廚需要選擇與設計圖對應的 dpi 進行顯示)
- dp 單位的設計圖:標注多少 dp 就寫多少 dp。
- px 單位的設計圖:問 UI 設計師是幾倍圖,然后控件的寬度 = 設計圖中控件標注的寬度/倍數(高度同理)。
- 舉例:例如設計圖上一個Button 的寬為 360dp,高為 50dp,字體大小為 15 sp,在布局中則這樣使用:
<Button
android:layout_width="@dimen/dp_360"
android:layout_height="@dimen/dp_50"
android:textSize="@dimen/sp_15"/>
- 代碼中動態(tài)設置 dp 或 sp:
如果需要在代碼中動態(tài)設置 dp 或 sp,則需要通過 getDimension()方法獲取對應資源文件下的 dp 或 sp 值再設置(具體參考 github 上的 demo)。如下:
/*獲取sp值*/
float pxValue = getResources().getDimension(R.dimen.sp_15);//獲取對應資源文件下的sp值
int spValue = ConvertUtils.px2sp(this, pxValue);//將px值轉換成sp值
mTvShowParams.setTextSize(spValue);//設置文字大小
/*獲取dp值*/
float pxValue2 = getResources().getDimension(R.dimen.dp_360);//獲取對應資源文件下的dp值
int dpValue = ConvertUtils.px2dp(this, pxValue2);//將px值轉換成dp值
4.4 使用步驟總結
說了這么多,其實只需要簡單的 2 步:
- 以設計圖最小寬度(單位為 dp)作為基準值,利用插件生成所有設備對應的 dimens.xml 文件
- 根據設計圖標注,標注多少 dp,布局中就寫多少dp,格式為@dimen/dp_XX。
五、怎么適配其他 module?
- 問題:在項目的其他 module 中怎么實現適配?難道也要多套 dimens 文件?
- 解決:并不需要多套 dimens 文件,只需要在 values 文件夾下有一套與 app module 一樣的 dimens 文件即可達到適配。因為經過編譯,所有 module 中的 dimen 數據都會統(tǒng)一歸類到主 module(即 app module)中的 values/dimens.xml 文件中了,然后系統(tǒng)又會根據你設置的值去找對應 values-swxxxdp 文件夾下的dimens.xml 文件中的值。
- 驗證:將我 github 上的 demo 分別運行在不同 widthDP 的設備上(用模擬器即可),然后觀察顯示的效果會發(fā)現確實是這樣的。
六、其他適配技巧
很多人肯定會有疑問,難道我用了這套適配方案就可以全部直接寫死寬高了?那肯定不是的,如果一些好用的適配技巧能實現的,那就不要用直接寫死寬高的方式。這套適配方案搭配下面這些適配技巧可以讓你的屏幕適配更完美。
6.1 善于使用 wrap_content、match_parent、weight
例如寫一個 TextView 控件,一般都不會直接寫死寬高,而是用 wrap_content 代替寬高。例如應用需要增加類似微信可以改變字體大小的功能,如果控件寬高固定的話,調大字體會導致控件顯示不下,所以一般采用 wrap_content 達到自適應的效果。
關于字體大小適配可以看我另一篇文章:聊聊 Android 中的字體大小適配例如有個按鈕的寬度等于設計圖的寬度,這時候一般不會直接寫死寬度,而是使用 match_parent 自動鋪滿寬度。
例如 UI 設計圖的寬度為 360dp,設計了橫向兩個按鈕,寬度分別是 120dp,240dp,這時候一般不會直接寫 @dimen/dp_120、@dimen/dp_240,而是使用 weight,然后分別設置為 1、2,即按比例適配。如下:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent">
<Button
android:layout_width="0"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:layout_width="0"
android:layout_height="wrap_content"
android:layout_weight="2"/>
</LinearLayout>
6.2 使用相對布局 / 約束布局,禁用絕對布局
絕對布局(AbsoluteLayout)直接使用 X、Y 坐標來控制控件的位置,對于屏幕碎片化這么嚴重的今天,使用絕對布局對于屏幕適配來說就是災難性的,所以 Google 已經廢棄了該控件。
相對布局(RelativeLayout)或者約束布局(ConstraintLayout)就不一樣了,相對布局的子控件之間使用 相對位置 的方式排列,即使屏幕的大小改變,控件的相對位置也不會變化,與屏幕大小無關,靈活性很強。約束布局也是類似的,通過對某些控件進行約束來確定它們之間的位置。
6.3 善于使用 Nine-Patch 圖片
Nine-Patch 圖片是一種被特殊處理過的 PNG 圖片,你可以指定哪些區(qū)域可以拉伸而哪些區(qū)域不可以。例如聊天界面中的聊天氣泡背景圖就需要做成 Nine-Patch 圖片,因為每條消息的字數不是固定的,如果背景圖片不能隨著字數的長短進行縮放,那么就會導致背景圖片變形。
七、常見問題匯總
7.1 為什么寬度適配了,高度有時候沒有完全適配?
因為各種屏幕高寬比并不是固定的,有 16:9、4:3,還有全面屏的 19.5:9 等等,如果強行將寬高都適配那只會導致布局變形。
例如一個控件的寬高為 360dp 和 640dp,如果將它顯示在寬高為 360dp 和 640dp 的設備上是正常鋪滿整個屏幕的,但是顯示在寬高為 360dp 和 780dp 的設備上高度則不能鋪滿,如果你讓高度鋪滿,而寬度又保持不變,那就會出現變形的情況。所以這也就是為什么目前市面上的屏幕適配方案只能以寬或高一個維度去適配,另一個方向用滑動或權重的方式去適配的原因。
那你為什么說高度也能適配呢?
這里說的高度也能適配指的是在不同分辨率和密度的手機上能達到等比縮放的適配,其他屏幕適配方案也是一樣的。
7.2 如何同時適配橫豎屏?
方案一:(不推薦)
計算出設備寬度和高度的 dp 值,然后生成對應的寬高 dimens.xml 文件。然后去掉所有 values-swXXXdp 目錄上的 s,即改為 values-wXXXdp。這樣設備不管橫豎屏都能找到對應的 values-wXXXdp 目錄下的 dimens.xml 文件了。 雖然也能達到一定程度的適配,但是這樣會增加很多 dimens.xml 文件,而且使用豎屏的設計圖顯示出來的效果也不夠好。方案二:(推薦)
因為橫屏時寬高變化太大,想要橫屏時也能完全適配,那就只能讓設計師出一套橫屏的設計圖,然后單獨寫一套橫屏的布局文件。這時候還需要在 res 文件夾下創(chuàng)建 layout-land 文件夾用來放橫屏的布局文件,默認的 layout 文件夾放豎屏的布局文件。
注意:smallestWidth 限定符適配的效果是讓不同分辨率和密度的設備上能達到以設計圖等比縮放的適配,如果設備與設計圖相差太大時并不能達到很好的適配效果,需要單獨出圖,其他屏幕適配方案也是一樣的。
7.3 如何適配平板、TV?
同橫屏道理一樣,平板、TV 與手機的寬高差距太大,想要平板、TV 也能完全適配,那就只能讓設計師出一套平板、TV 的設計圖,然后單獨寫一套平板、TV 的布局文件。
如果 同時適配手機與平板 或者 同時適配手機與 TV,還需要在 res 文件夾下創(chuàng)建 layout-sw533dp 文件夾用來放平板或 TV 的布局文件,默認的 layout 文件夾放手機的布局文件。
注意:這里用的是 533dp 的原因是目前手機最大的寬度為 480dp,所以大于 480dp 的就認為是平板或 TV。如果 同時適配手機、平板與 TV,這種情況平板與 TV 的設計圖可以只出一套,因為平板與 TV 的樣式比較類似,而且平板與 TV 的最小寬度值可能會出現一樣的情況,這時候適配看成上面那種方式即可。
注意:再說一遍,smallestWidth 限定符適配的效果是讓不同分辨率和密度的設備上能達到以設計圖等比縮放的適配,如果設備與設計圖相差太大時并不能達到很好的適配效果,需要單獨出圖,其他屏幕適配方案也是一樣的。
github 地址:ScreenAdaptation
參考資料: