大綱
- 使用dp而不是px
- 盡量使用自動(dòng)適配布局,而不要指定分辨率
- 使用寬高限定符
- values-1080x1920,以1080P為基準(zhǔn)計(jì)算每種常見分辨率對應(yīng)的尺寸。
- 需要盡可能全的添加各種設(shè)備的分辨率(有工具)
- 容錯(cuò)性不足,如果設(shè)備分辨率不能精確匹配對應(yīng)限定符,會(huì)默認(rèn)使用統(tǒng)一默認(rèn)的dimens
- 第三方自動(dòng)適配UI框架
- 原理:自定義RelativeLayout,在onMeasure中對控件分辨率做變換
- 第三方框架,維護(hù)性很成問題
- 一些自定義View,處理比較麻煩
- 最小寬度限定符,類似寬高限定符
- values-sw240dp,同樣以某一dp寬度為基準(zhǔn)計(jì)算其他寬度dp的值
- values-sw360dp、values-sw480dp
- 相比寬高限定符,最小寬度限定符不進(jìn)行精確匹配,會(huì)遵循就近原則,可以較好的解決容錯(cuò)問題。
- 如:設(shè)備寬364dp,系統(tǒng)會(huì)自動(dòng)就近配置values-sw360dp下的dimens,顯示效果相差不會(huì)很大
- 今日頭條——修改density值
- 原理:px = dp x (dpi/160) = dp x density
- 既然如此,將density
- 需要UI出設(shè)計(jì)圖時(shí)以統(tǒng)一的dp為基準(zhǔn)
- https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
基本概念
- 像素——px
- 密度獨(dú)立像素——dp或dip
- 像素密度——dpi,單位面積內(nèi)的像素?cái)?shù)。
- 軟件系統(tǒng)的概念。
- 在系統(tǒng)出廠時(shí),配置文件中的固定值。
- 通常的取值有:160、240、360、480等。
- 不同于物理概念上的屏幕密度ppi,如ppi為415、430和470時(shí),dpi可能會(huì)統(tǒng)一設(shè)置為480。
- density——當(dāng)dpi=160時(shí),1px = 1pd,此時(shí)denstiy的值為1,dpi=240時(shí),1.5px = 1dp,density的值為1.5。
- 上述值的關(guān)系:
- denstiy = dpi / 160;
- px = dp x density = dp x (dpi / 160)
Android設(shè)備的碎片化極為嚴(yán)重,各種尺寸和分辨率的設(shè)備無比繁多。使得在Android開發(fā)中,UI適配變成了開發(fā)過程中極為重要的一步。為此Google提出了密度獨(dú)立像素dip或dp的概率,旨在更友好的處理Android UI適配問題。
但是效果嘛,只能說差強(qiáng)人意,可以解決大部分的業(yè)務(wù)場景,但是剩下的個(gè)別情況就搞死人了,原因在于Android設(shè)備碎片化實(shí)在太嚴(yán)重了,存在各種分辨率和dpi的設(shè)備。
比如兩臺(tái)設(shè)備A和B,分辨率是1920x1080,dpi分別為420和480,在布局中編寫一個(gè)100dp寬的ImageView,按照上面的公式ImageView的顯示寬度分別為:100dp x 420 / 160 = 262.5和100dp x 480 / 160 = 300,ImageView在B設(shè)備上明顯顯示要大一些。差異可能還不明顯,我們把寬度改為360dp呢,A設(shè)備顯示寬度為:948px,B設(shè)備顯示寬度為:1080px。這就扯淡了,一個(gè)寬度填充滿屏幕,一個(gè)不滿。這種情況肯定是需要開發(fā)來背鍋解決的。
適配方案
雖然上面提到了使用dp無法解決全部業(yè)務(wù)場景,但是相對于直接使用px已經(jīng)可以解決大部分場景下的適配問題了。
所以UI適配的第一條就是:
1. 使用dp代替px來編寫布局。
又因?yàn)樯厦鏌o法適配的個(gè)別場景,所以UI適配的第二條是:
2.盡量使用自動(dòng)適配布局,而不要指定分辨率
這一條也很好理解,盡量使用ConstraintLayout約束布局和LinearLayout等父布局,不要寫死分辨率,比如上面的例子如果使用match_parent而不是360dp,也可以避免出現(xiàn)顯示不一致問題(但是僅限于上列)。
限定符
Google同樣意識(shí)到dp滿足所以業(yè)務(wù)場景的需要,所以提供了寬度限定符的概念。
雖然您的布局應(yīng)始終通過拉伸其視圖內(nèi)部和周圍的空間來應(yīng)對不同的屏幕尺寸,但這可能無法針對每種屏幕尺寸提供最佳用戶體驗(yàn)。例如,您為手機(jī)設(shè)計(jì)的界面或許無法在平板電腦上提供良好的體驗(yàn)。因此,您的應(yīng)用還應(yīng)提供備用布局資源,以針對特定屏幕尺寸優(yōu)化界面設(shè)計(jì)。
最小寬度限定符
使用“最小寬度”屏幕尺寸限定符,您可以為具有最小寬度(以密度無關(guān)像素 dp 或 dip 為度量單位)的屏幕提供備用布局。
通過將屏幕尺寸描述為密度無關(guān)像素的度量值,Android 允許您創(chuàng)建專為非常具體的屏幕尺寸而設(shè)計(jì)的布局,同時(shí)讓您不必對不同的像素密度有任何擔(dān)心。
通俗一點(diǎn)翻譯就是:可用通過xxxx-swXXXdp的方式定義一些最小限定符的資源文件,比如:values-sw400dp、values-sw600dp,系統(tǒng)會(huì)自動(dòng)匹配如屏幕寬度相近資源文件夾。
我們再來看上面的例子兩臺(tái)設(shè)備A和B,分辨率是1920x1080,dpi分別為360和400。我們簡化下問題比如設(shè)計(jì)圖給的是1920x1080 360dpi,包含一個(gè)22.5px * 22.5px = 10dp * 10dp的圖片。按經(jīng)驗(yàn)布局應(yīng)該如下編寫:
<ImageView
android:id="@+id/img_iv"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@mipmap/ic_launcher"/>
在不同設(shè)備上運(yùn)行的結(jié)果:
- 1280 x 720 240dpi的設(shè)備,圖片顯示為15px * 15px;
- 1920 x1080 360dpi的A設(shè)備,圖片顯示為22.5px * 22.5px;
- 1920 x1080 400dpi的B設(shè)備,圖片顯示為25px * 25px;
可以看到B設(shè)備圖片顯示是有問題的,為了解決這個(gè)問題,我們使用最小寬度限定符定義兩個(gè)資源文件夾:values-sw360dp和values-sw400dp。
在values-sw360dp中添加dimen.xml內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dp_1">1dp</dimen>
<dimen name="dp_2">2dp</dimen>
<dimen name="dp_3">3dp</dimen>
<dimen name="dp_4">4dp</dimen>
<dimen name="dp_5">5dp</dimen>
<dimen name="dp_6">6dp</dimen>
<dimen name="dp_7">7dp</dimen>
<!-- 省略其他值 -->
<dimen name="dp_360">360dp</dimen>
<!-- 因?yàn)樵O(shè)計(jì)圖是360dpi,所以控件尺寸通常不會(huì)超過360dp,定義最大360dp的值足夠使用 -->
</resources>
在values-sw420dp中添加dimen.xml,文件中的dimen值很容易換算出來:在360dpi中dp_1 = 1dp,那么在400dpi中dp_1 = 360 / 400 = 0.9dp,文件內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dp_1">0.9dp</dimen>
<dimen name="dp_2">1.8dp</dimen>
<dimen name="dp_3">2.7dp</dimen>
<dimen name="dp_4">3.6dp</dimen>
<dimen name="dp_5">4.5dp</dimen>
<dimen name="dp_6">5.4dp</dimen>
<dimen name="dp_7">6.3dp</dimen>
<!-- 省略其他值 -->
</resources>
注意要在values文件夾下添加默認(rèn)dimen.xml,文件內(nèi)容與values-sw360dp中添加dimen.xml一致(因?yàn)樵O(shè)計(jì)圖恰好是360dpi的)。
布局中的ImageView自然要改寫為:
<ImageView
android:id="@+id/img_iv"
android:layout_width="@dimen/dp_10"
android:layout_height="@dimen/dp_10"
android:background="@mipmap/ic_launcher"/>
我們再來看一下不同設(shè)備運(yùn)行結(jié)果:
- 1280 x 720 240dpi的設(shè)備,未匹配到限定符使用values中的dimen,
dp_10 = 10dp, px = 10 * 240 / 160 = 15px,圖片顯示尺寸為15px * 15px。 - 1920 x1080 360dpi的A設(shè)備,匹配到sw360dp限定符,
dp_10 = 10dp, px = 10 * 360 / 160 = 20px,圖片顯示尺寸為22.5px * 22.5px。 - 1920 x1080 400dpi的B設(shè)備,匹配到sw420dp限定符,
dp_10 = 9dp, px = 9 * 400 / 160 = 20px,圖片顯示尺寸為22.5px * 22.5px。
完美的解決了設(shè)備A和B的顯示問題,所以UI適配的第三條是:
3. 使用最?。捎茫挾认薅ǚ?,解決同樣分辨率不同dpi的設(shè)備適配問題。
這種方案看似完美,但是也有一些隱含的問題:此方案只能解決同樣分辨率不同dpi設(shè)備的適配問題:
- 一旦出現(xiàn)不同分辨率相同dpi的情況就無效了(當(dāng)然這種情況的可能性不高)。
- 以上舉例只是基于1920x1080這一種分辨率為例說明,試想一下如果1280x720的設(shè)備存在240dpi和280dpi的情況呢?我們只能針對特殊情況適配處理,無法解決全部場景適配問題。
寬高限定符
類似于上面說的最小寬度限定符,但是需要精確指定要匹配的設(shè)備寬高,values-1920x1080、values-1280x720等。配置與使用方式也與上面類似,如設(shè)計(jì)圖尺寸為1920x1080 360dpi,那么只需要以1920x1080為基準(zhǔn)計(jì)算所有分辨率對應(yīng)的尺寸就可以了,布局編寫時(shí)按照給的尺寸一一對應(yīng)就可以,比如:給出的ImageView是20px*20px的,那在布局中同樣指定width和height為@dimen/dp_20就可以了。
values-1920x1080中dimens.xml如下:
<resources>
<dimen name="dp_1">1px</dimen>
<dimen name="dp_2">2px</dimen>
<dimen name="dp_3">3px</dimen>
<dimen name="dp_4">4px</dimen>
<dimen name="dp_5">5px</dimen>
<dimen name="dp_6">6px</dimen>
<dimen name="dp_7">7px</dimen>
<dimen name="dp_8">8px</dimen>
<dimen name="dp_9">9px</dimen>
<!-- 省略其他 -->
<dimen name="dp_1920">1920px</dimen>
</resources>
values-1280x720中dimens.xml換算為:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dp_1">0.66px</dimen>
<dimen name="dp_2">1.33px</dimen>
<dimen name="dp_3">2.0px</dimen>
<dimen name="dp_4">2.66px</dimen>
<dimen name="dp_5">3.33px</dimen>
<dimen name="dp_6">4.0px</dimen>
<dimen name="dp_7">4.66px</dimen>
<!-- 省略其他 -->
</resources>
同樣需要在values添加默認(rèn)尺寸dimen.xml,內(nèi)容同基準(zhǔn)分辨率文件。
因?yàn)椴皇撬性O(shè)備屏幕都是16:9的,也可以按照寬高拆分成兩個(gè)dimens.xml文件dimen_x.xml和dimen_y.xml,按照寬高:1920x1080分別換算得到x和y的值,但是頁面設(shè)計(jì)通常是豎屏可滑動(dòng)的,所以對高度不敏感,只需要根據(jù)一個(gè)維度計(jì)算統(tǒng)一值就可以了。
以上計(jì)算方式比較簡單了,不需要自己編寫換算可以通過代碼工具或者自己寫個(gè)類實(shí)現(xiàn)。(網(wǎng)上有好多,找一下應(yīng)該可以找到)。
理論上只要盡可能多的枚舉所有設(shè)備分辨率,就可以完美的解決屏幕適配問題,所以UI適配的第四條是:
4.使用寬高限定符,精確匹配屏幕分辨率。
這種方案已經(jīng)近乎完美了,一度成為比較熱門的解決方案,也有很多團(tuán)隊(duì)使用過此方案。但是之前也說過Android設(shè)備的碎片化太嚴(yán)重了,綜合考慮基本不可能在項(xiàng)目中枚舉所有的屏幕尺寸進(jìn)行適配,如果設(shè)備沒有匹配到對應(yīng)尺寸會(huì)使用values下的默認(rèn)尺寸文件,可能會(huì)出現(xiàn)嚴(yán)重的UI適配問題。
但是不可否認(rèn)此種方案實(shí)現(xiàn)簡單,對于編寫布局也很友好(直接填入設(shè)計(jì)圖的尺寸值就行,不需要換算),可以解決絕大多數(shù)的設(shè)備適配問題,是一種很友好的解決方案。
第三方UI適配框架
有很多第三方庫的解決方案,是從ViewGroup入手的,要么重寫常用的如:RelativeLayout、LinearLayout和FrameLayout等在控件內(nèi)部做轉(zhuǎn)換來適配不同尺寸的設(shè)備,要么提供新的Layout如:Google的PercentLayout布局。但是這些方案基本都不在維護(hù)了,這里就不詳細(xì)展開了,感興趣的可以自行搜索了解。
UI適配的第五條是:
5. 使用第三方自適配框架,解決UI適配問題。
感興趣的可以參考以下文檔:
其他適配方案
參考字節(jié)的實(shí)現(xiàn)方案:
這篇文章著實(shí)屬于拾人牙慧了,起因是因?yàn)榭吹搅诉@篇博客Android 目前最穩(wěn)定和高效的UI適配方案。所以想著確實(shí)應(yīng)該把這部分知識(shí)梳理一下,所以寫了這篇文檔加了一些自己的里面,主要也是為了梳理知識(shí)點(diǎn)加深理解。
文中列舉的幾種UI適配方案,沒有嚴(yán)格的優(yōu)劣之分,可以根據(jù)自己的業(yè)務(wù)需求選擇,也可以選擇幾種搭配使用,比如筆者目前主要做智能電視(盒子)的應(yīng)用開發(fā),Android電視不同于手機(jī),碎片化沒有那么嚴(yán)重,電視分辨率種類屈指可數(shù),所以在日常項(xiàng)目中基本選擇使用寬高限定符的方案進(jìn)行適配,效果也是極好的。