一種非常好用的Android屏幕適配

更新:由于該適配方案越來越多人使用,也有很多人遇到不太理解的問題。所以為了大家更好的使用,我將文章很多內(nèi)容更新了,老用戶可以重新看下整篇文章。主要更新的內(nèi)容是使用方法跟以前不同了,以前是不改變最小寬度基準(zhǔn)值,根據(jù) UI 圖來計(jì)算布局中設(shè)置的值?,F(xiàn)在改成最小寬度基準(zhǔn)值與設(shè)計(jì)圖一致,然后設(shè)計(jì)圖標(biāo)注多少 dp 就寫多少 dp,非常方便。還整理了評(píng)論區(qū)比較多人問到的問題作統(tǒng)一回答。

前言

網(wǎng)上關(guān)于屏幕適配的文章已經(jīng)鋪天蓋地了,為什么我還要講?因?yàn)榫W(wǎng)上現(xiàn)在基本都是使用 屏幕分辨率限定符 進(jìn)行適配,即每種屏幕分辨率的設(shè)備需要定義一套 dimens.xml 文件。由于不同分辨率的設(shè)備太多了,而且有些設(shè)備還有虛擬按鍵(例如華為手機(jī)),這樣就還需要每個(gè)有虛擬按鍵的設(shè)備加多一套 dimens.xml 文件,再加上平板那些你會(huì)發(fā)現(xiàn) dimens.xml 文件所占的體積已經(jīng)超過 2M 了!這絕對(duì)不是我們想要的。

我這里要講的是使用 sw<N>dp 限定符,即 smallestWidth(最小寬度) 限定符 來進(jìn)行適配,使用這種方式只需要少量 dimens.xml 文件即可達(dá)到適配,而且根本不用考慮虛擬按鍵的問題。如果只適配手機(jī),dimens.xml 文件所占的體積只有 100 多 KB,即使加上平板和 TV,也就 500 多 KB,完全可以接收。這種方案已經(jīng)在自己多個(gè)項(xiàng)目中應(yīng)用過了,經(jīng)過幾十臺(tái)手機(jī)測試過,基本不會(huì)出現(xiàn)適配有問題的情況。制作生成對(duì)應(yīng) dimens.xml 文件插件(后面會(huì)講)的作者也說過他在待過的兩家大公司實(shí)踐過,所以請(qǐng)放心使用。

一、為什么要進(jìn)行屏幕適配?

關(guān)于為什么要進(jìn)行屏幕適配,什么是 dp、dpi 這些概念我就不去一一講解了,網(wǎng)上很多文章。這里我推薦幾篇講的比較好的:

二、屏幕分辨率限定符與 smallestWidth 限定符適配原理

2.1 屏幕分辨率限定符適配原理

屏幕分辨率限定符適配需要在 res 文件夾下創(chuàng)建各種屏幕分辨率對(duì)應(yīng)的 values-xxx 文件夾,如下圖:


然后根據(jù)一個(gè)基準(zhǔn)分辨率,例如基準(zhǔn)分辨率為 1280x720,將寬度分成 720 份,取值為 1px~720px,將高度分成 1280 份,取值為 1px~1280px,生成各種分辨率對(duì)應(yīng)的 dimens.xml 文件。如下分別為分辨率 1280x720 與 1920x1080 所對(duì)應(yīng)的橫向 dimens.xml 文件:


假設(shè)設(shè)計(jì)圖上的一個(gè)控件的寬度為 720px,那么布局中就寫 android:layout_width="@dimen/x720" ,當(dāng)運(yùn)行程序的時(shí)候,系統(tǒng)會(huì)根據(jù)設(shè)備的分辨率去尋找對(duì)應(yīng)的 dimens.xml 文件。例如運(yùn)行在分辨率為 1280x720 的設(shè)備上,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-1280x720 文件夾下的 lay_x.xml 文件,由上圖可知 x720 對(duì)應(yīng)的值為
720.px,可鋪滿該屏幕寬度。運(yùn)行在分辨率為 1920x1080 的設(shè)備上,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-1920x1080 文件夾下的 lay_x.xml 文件,由上圖可知 x720 對(duì)應(yīng)的值為 1080.0px,可鋪滿該屏幕寬度。這樣就達(dá)到了屏幕適配的要求!

2.2 smallestWidth 限定符適配原理

smallestWidth 限定符適配原理與屏幕分辨率限定符適配原理一樣,系統(tǒng)都是根據(jù)限定符去尋找對(duì)應(yīng)的 dimens.xml 文件。例如程序運(yùn)行在最小寬度為 360dp 的設(shè)備上,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-sw360dp 文件夾下的 dimens.xml 文件。區(qū)別就在于屏幕分辨率限定符適配是拿 px 值等比例縮放,而 smallestWidth 限定符適配是拿 dp 值來等比縮放而已。需要注意的是“最小寬度”是不區(qū)分方向的,即無論是寬度還是高度,哪一邊小就認(rèn)為哪一邊是“最小寬度”。如下分別為最小寬度為 360dp 與最小寬度為 640dp 所對(duì)應(yīng)的 dimens.xml 文件:


  • 獲取設(shè)備最小寬度代碼為:
        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 限定符適配呢?

  1. 屏幕分辨率限定符適配是根據(jù)屏幕分辨率的,Android 設(shè)備分辨率一大堆,而且還要考慮虛擬鍵盤,這樣就需要大量的 dimens.xml 文件。因?yàn)闊o論手機(jī)屏幕的像素多少,密度多少,90% 的手機(jī)的最小寬度都為 360dp,所以采用 smallestWidth 限定符適配只需要少量 dimens.xml 文件即可。
  2. 屏幕分辨率限定符適配采用的是 px 單位,而 smallestWidth 限定符適配采用的單位是 dp 和 sp,dp 和 sp 是 google 推薦使用的計(jì)量單位。又由于很多應(yīng)用要求字體大小隨系統(tǒng)改變,所以字體單位使用 sp 也更靈活。
  3. 屏幕分辨率限定符適配需要設(shè)備分辨率與 values-xx 文件夾完全匹配才能達(dá)到適配,而 smallestWidth 限定符適配尋找 dimens.xml 文件的原理是從大往小找,例如設(shè)備的最小寬度為 360dp,就會(huì)先去找 values-360dp,發(fā)現(xiàn)沒有則會(huì)向下找 values-320dp,如果還是沒有才找默認(rèn)的 values 下的 demens.xml 文件,所以即使沒有完全匹配也能達(dá)到不錯(cuò)的適配效果。

四、使用步驟

4.1 獲取設(shè)計(jì)圖最小寬度(單位為 dp)

大多數(shù) UI 設(shè)計(jì)師提供的設(shè)計(jì)圖無非就幾種,它們對(duì)應(yīng)的獲取方式如下:

  • 藍(lán)湖:開發(fā)平臺(tái)切換到 Android,設(shè)計(jì)圖寬度即為最小寬度。
  • psd 源文件:用像素大廚查看,設(shè)計(jì)圖寬度即為最小寬度。(注意像素大廚需要選擇與設(shè)計(jì)圖對(duì)應(yīng)的 dpi 進(jìn)行顯示)
  • dp 單位的設(shè)計(jì)圖:設(shè)計(jì)圖寬度即為最小寬度。
  • px 單位的設(shè)計(jì)圖:問 UI 設(shè)計(jì)師是幾倍圖,然后最小寬度 = 設(shè)計(jì)圖寬度/倍數(shù)。

4.2 以設(shè)計(jì)圖最小寬度作為基準(zhǔn)值,生成所有設(shè)備對(duì)應(yīng)的 dimens.xml 文件

這些文件當(dāng)然不會(huì)手動(dòng)去寫,網(wǎng)上已經(jīng)有大神提供了自動(dòng)生成這些文件的插件 ScreenMatch。但是這個(gè)插件還是有點(diǎn)問題的:

  • 默認(rèn)沒有適配最小寬度為 320dp 的設(shè)備。其實(shí)自己測試還是有很多設(shè)備最小寬度是 320dp 的,所以需要加上。
  • 最小寬度為 392.7272 與 411.4285 的手機(jī)不能達(dá)到完全適配。原因是該插件的默認(rèn)值都是取整的,即 392.7272 與 411.4285 在插件中寫的是 392 與 411。

基于以上問題,我在該插件的源碼上優(yōu)化生成了新的插件 ScreenMatch,由于插件庫已經(jīng)有原作者的插件了,所以我就不重復(fù)造輪子上傳到插件庫了,你直接用本地安裝的方式安裝即可。

工具使用步驟:

  1. 在 Android Studio 中安裝 ScreenMatch 插件
    下載插件 ScreenMatch 到本地,點(diǎn)擊菜單欄上的 File -> Settings -> Plugins -> Install plugin from disk,然后選擇我們剛剛下載的插件,最后點(diǎn)擊 “OK”,重啟 Andorid Studio 即可。如下圖所示:
  1. 在項(xiàng)目的默認(rèn) values 文件夾中需要一份 dimens.xml 文件
    我在 github 源碼已經(jīng)提供了一份,直接復(fù)制過來即可。
  1. 執(zhí)行生成
    插件安裝好后,在項(xiàng)目的任意目錄或文件上右鍵,選擇 ScreenMatch 選項(xiàng)。如下圖:

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

然后再看看 res 目錄下會(huì)自動(dòng)生成一堆 dimens.xml 文件,如下圖:

通過上面的步驟就已經(jīng)生成了所有設(shè)備對(duì)應(yīng)的 dimens.xml 文件。

  1. 根據(jù)設(shè)計(jì)圖填寫最小寬度基準(zhǔn)值,并填寫需要適配的設(shè)備最小寬度 dp 值

步驟 3 是以插件默認(rèn)的最小寬度基準(zhǔn)值為 360dp,適配的設(shè)備最小寬度為
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 )生成的文件,但實(shí)際情況要根據(jù)設(shè)計(jì)圖和需求設(shè)置。

例如設(shè)計(jì)圖的最小寬度為 375dp,則需要更改最小寬度基準(zhǔn)值為 375dp。如果項(xiàng)目只需要適配手機(jī)的話,適配的設(shè)備最小寬度保留 320,360,384,392.7272,400,410,411.4285,432,480 即可,若發(fā)現(xiàn)手機(jī)還有其他最小寬度自行加上即可,也麻煩把該最小寬度提供給我,我們一起來完善該份適配。

以上修改需要在配置文件里修改,即 screenMatch.properties 文件,該配置文件是執(zhí)行完上面第 3 步后自動(dòng)生成在項(xiàng)目的跟目錄下的。如下圖:


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

配置文件修改完成后,重新執(zhí)行第 3 步,生成新的 dimens.xml 文件。

當(dāng)然!如果你的設(shè)計(jì)圖也是標(biāo)準(zhǔn)的 360dp,那么上面的步驟你可以忽略。直接復(fù)制我 github 上你需要的 dimens.xml 文件到你的項(xiàng)目即可,默認(rèn)的 values 文件夾下也需要一份。

4.3 根據(jù)設(shè)計(jì)圖標(biāo)注,在布局寫上對(duì)應(yīng)的值。

設(shè)計(jì)圖標(biāo)注多少 dp,布局中就寫多少 dp ,非常方便!

大多數(shù) UI 設(shè)計(jì)師提供的設(shè)計(jì)圖無非就幾種,它們對(duì)應(yīng)的使用方式如下:

  • 上傳到藍(lán)湖:顯示多少 dp 就寫多少 dp。
  • psd 源文件:用像素大廚查看,顯示多少 dp 就寫多少 dp(注意像素大廚需要選擇與設(shè)計(jì)圖對(duì)應(yīng)的 dpi 進(jìn)行顯示)
  • dp 單位的設(shè)計(jì)圖:標(biāo)注多少 dp 就寫多少 dp。
  • px 單位的設(shè)計(jì)圖:問 UI 設(shè)計(jì)師是幾倍圖,然后控件的寬度 = 設(shè)計(jì)圖中控件標(biāo)注的寬度/倍數(shù)(高度同理)。
  • 舉例:例如設(shè)計(jì)圖上一個(gè)Button 的寬為 360dp,高為 50dp,字體大小為 15 sp,在布局中則這樣使用:
    <Button
        android:layout_width="@dimen/dp_360"
        android:layout_height="@dimen/dp_50"
        android:textSize="@dimen/sp_15"/>
  • 代碼中動(dòng)態(tài)設(shè)置 dp 或 sp:
    如果需要在代碼中動(dòng)態(tài)設(shè)置 dp 或 sp,則需要通過 getDimension()方法獲取對(duì)應(yīng)資源文件下的 dp 或 sp 值再設(shè)置(具體參考 github 上的 demo)。如下:
        /*獲取sp值*/
        float pxValue = getResources().getDimension(R.dimen.sp_15);//獲取對(duì)應(yīng)資源文件下的sp值
        int spValue = ConvertUtils.px2sp(this, pxValue);//將px值轉(zhuǎn)換成sp值
        mTvShowParams.setTextSize(spValue);//設(shè)置文字大小

        /*獲取dp值*/
        float pxValue2 = getResources().getDimension(R.dimen.dp_360);//獲取對(duì)應(yīng)資源文件下的dp值
        int dpValue = ConvertUtils.px2dp(this, pxValue2);//將px值轉(zhuǎn)換成dp值

4.4 使用步驟總結(jié)

說了這么多,其實(shí)只需要簡單的 2 步:

  1. 以設(shè)計(jì)圖最小寬度(單位為 dp)作為基準(zhǔn)值,利用插件生成所有設(shè)備對(duì)應(yīng)的 dimens.xml 文件
  2. 根據(jù)設(shè)計(jì)圖標(biāo)注,標(biāo)注多少 dp,布局中就寫多少dp,格式為@dimen/dp_XX。

五、怎么適配其他 module?

  • 問題:在項(xiàng)目的其他 module 中怎么實(shí)現(xiàn)適配?難道也要多套 dimens 文件?
  • 解決:并不需要多套 dimens 文件,只需要在 values 文件夾下有一套與 app module 一樣的 dimens 文件即可達(dá)到適配。因?yàn)榻?jīng)過編譯,所有 module 中的 dimen 數(shù)據(jù)都會(huì)統(tǒng)一歸類到主 module(即 app module)中的 values/dimens.xml 文件中了,然后系統(tǒng)又會(huì)根據(jù)你設(shè)置的值去找對(duì)應(yīng) values-swxxxdp 文件夾下的dimens.xml 文件中的值。
  • 驗(yàn)證:將我 github 上的 demo 分別運(yùn)行在不同 widthDP 的設(shè)備上(用模擬器即可),然后觀察顯示的效果會(huì)發(fā)現(xiàn)確實(shí)是這樣的。

六、其他適配技巧

很多人肯定會(huì)有疑問,難道我用了這套適配方案就可以全部直接寫死寬高了?那肯定不是的,如果一些好用的適配技巧能實(shí)現(xiàn)的,那就不要用直接寫死寬高的方式。這套適配方案搭配下面這些適配技巧可以讓你的屏幕適配更完美。

6.1 善于使用 wrap_content、match_parent、weight

  • 例如寫一個(gè) TextView 控件,一般都不會(huì)直接寫死寬高,而是用 wrap_content 代替寬高。例如應(yīng)用需要增加類似微信可以改變字體大小的功能,如果控件寬高固定的話,調(diào)大字體會(huì)導(dǎo)致控件顯示不下,所以一般采用 wrap_content 達(dá)到自適應(yīng)的效果。
    關(guān)于字體大小適配可以看我另一篇文章:聊聊 Android 中的字體大小適配

  • 例如有個(gè)按鈕的寬度等于設(shè)計(jì)圖的寬度,這時(shí)候一般不會(huì)直接寫死寬度,而是使用 match_parent 自動(dòng)鋪滿寬度。

  • 例如 UI 設(shè)計(jì)圖的寬度為 360dp,設(shè)計(jì)了橫向兩個(gè)按鈕,寬度分別是 120dp,240dp,這時(shí)候一般不會(huì)直接寫 @dimen/dp_120、@dimen/dp_240,而是使用 weight,然后分別設(shè)置為 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 使用相對(duì)布局 / 約束布局,禁用絕對(duì)布局

絕對(duì)布局(AbsoluteLayout)直接使用 X、Y 坐標(biāo)來控制控件的位置,對(duì)于屏幕碎片化這么嚴(yán)重的今天,使用絕對(duì)布局對(duì)于屏幕適配來說就是災(zāi)難性的,所以 Google 已經(jīng)廢棄了該控件。

相對(duì)布局(RelativeLayout)或者約束布局(ConstraintLayout)就不一樣了,相對(duì)布局的子控件之間使用 相對(duì)位置 的方式排列,即使屏幕的大小改變,控件的相對(duì)位置也不會(huì)變化,與屏幕大小無關(guān),靈活性很強(qiáng)。約束布局也是類似的,通過對(duì)某些控件進(jìn)行約束來確定它們之間的位置。

6.3 善于使用 Nine-Patch 圖片

Nine-Patch 圖片是一種被特殊處理過的 PNG 圖片,你可以指定哪些區(qū)域可以拉伸而哪些區(qū)域不可以。例如聊天界面中的聊天氣泡背景圖就需要做成 Nine-Patch 圖片,因?yàn)槊織l消息的字?jǐn)?shù)不是固定的,如果背景圖片不能隨著字?jǐn)?shù)的長短進(jìn)行縮放,那么就會(huì)導(dǎo)致背景圖片變形。

七、常見問題匯總

7.1 為什么寬度適配了,高度有時(shí)候沒有完全適配?

因?yàn)楦鞣N屏幕高寬比并不是固定的,有 16:9、4:3,還有全面屏的 19.5:9 等等,如果強(qiáng)行將寬高都適配那只會(huì)導(dǎo)致布局變形。

例如一個(gè)控件的寬高為 360dp 和 640dp,如果將它顯示在寬高為 360dp 和 640dp 的設(shè)備上是正常鋪滿整個(gè)屏幕的,但是顯示在寬高為 360dp 和 780dp 的設(shè)備上高度則不能鋪滿,如果你讓高度鋪滿,而寬度又保持不變,那就會(huì)出現(xiàn)變形的情況。所以這也就是為什么目前市面上的屏幕適配方案只能以寬或高一個(gè)維度去適配,另一個(gè)方向用滑動(dòng)或權(quán)重的方式去適配的原因。

那你為什么說高度也能適配呢?
這里說的高度也能適配指的是在不同分辨率和密度的手機(jī)上能達(dá)到等比縮放的適配,其他屏幕適配方案也是一樣的。

7.2 如何同時(shí)適配橫豎屏?

  • 方案一:(不推薦)
    計(jì)算出設(shè)備寬度和高度的 dp 值,然后生成對(duì)應(yīng)的寬高 dimens.xml 文件。然后去掉所有 values-swXXXdp 目錄上的 s,即改為 values-wXXXdp。這樣設(shè)備不管橫豎屏都能找到對(duì)應(yīng)的 values-wXXXdp 目錄下的 dimens.xml 文件了。 雖然也能達(dá)到一定程度的適配,但是這樣會(huì)增加很多 dimens.xml 文件,而且使用豎屏的設(shè)計(jì)圖顯示出來的效果也不夠好。

  • 方案二:(推薦)
    因?yàn)闄M屏?xí)r寬高變化太大,想要橫屏?xí)r也能完全適配,那就只能讓設(shè)計(jì)師出一套橫屏的設(shè)計(jì)圖,然后單獨(dú)寫一套橫屏的布局文件。這時(shí)候還需要在 res 文件夾下創(chuàng)建 layout-land 文件夾用來放橫屏的布局文件,默認(rèn)的 layout 文件夾放豎屏的布局文件。

注意:smallestWidth 限定符適配的效果是讓不同分辨率和密度的設(shè)備上能達(dá)到以設(shè)計(jì)圖等比縮放的適配,如果設(shè)備與設(shè)計(jì)圖相差太大時(shí)并不能達(dá)到很好的適配效果,需要單獨(dú)出圖,其他屏幕適配方案也是一樣的。

7.3 如何適配平板、TV?

同橫屏道理一樣,平板、TV 與手機(jī)的寬高差距太大,想要平板、TV 也能完全適配,那就只能讓設(shè)計(jì)師出一套平板、TV 的設(shè)計(jì)圖,然后單獨(dú)寫一套平板、TV 的布局文件。

  • 如果 同時(shí)適配手機(jī)與平板 或者 同時(shí)適配手機(jī)與 TV,還需要在 res 文件夾下創(chuàng)建 layout-sw533dp 文件夾用來放平板或 TV 的布局文件,默認(rèn)的 layout 文件夾放手機(jī)的布局文件。
    注意:這里用的是 533dp 的原因是目前手機(jī)最大的寬度為 480dp,所以大于 480dp 的就認(rèn)為是平板或 TV。

  • 如果 同時(shí)適配手機(jī)、平板與 TV,這種情況平板與 TV 的設(shè)計(jì)圖可以只出一套,因?yàn)槠桨迮c TV 的樣式比較類似,而且平板與 TV 的最小寬度值可能會(huì)出現(xiàn)一樣的情況,這時(shí)候適配看成上面那種方式即可。

注意:再說一遍,smallestWidth 限定符適配的效果是讓不同分辨率和密度的設(shè)備上能達(dá)到以設(shè)計(jì)圖等比縮放的適配,如果設(shè)備與設(shè)計(jì)圖相差太大時(shí)并不能達(dá)到很好的適配效果,需要單獨(dú)出圖,其他屏幕適配方案也是一樣的。

github 地址:ScreenAdaptation

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容