Android UI適配方案

大綱

  1. 使用dp而不是px
  2. 盡量使用自動(dòng)適配布局,而不要指定分辨率
  3. 使用寬高限定符
    1. values-1080x1920,以1080P為基準(zhǔn)計(jì)算每種常見分辨率對應(yīng)的尺寸。
    2. 需要盡可能全的添加各種設(shè)備的分辨率(有工具)
    3. 容錯(cuò)性不足,如果設(shè)備分辨率不能精確匹配對應(yīng)限定符,會(huì)默認(rèn)使用統(tǒng)一默認(rèn)的dimens
  4. 第三方自動(dòng)適配UI框架
    1. 原理:自定義RelativeLayout,在onMeasure中對控件分辨率做變換
    2. 第三方框架,維護(hù)性很成問題
    3. 一些自定義View,處理比較麻煩
  5. 最小寬度限定符,類似寬高限定符
    1. values-sw240dp,同樣以某一dp寬度為基準(zhǔn)計(jì)算其他寬度dp的值
    2. values-sw360dp、values-sw480dp
    3. 相比寬高限定符,最小寬度限定符不進(jìn)行精確匹配,會(huì)遵循就近原則,可以較好的解決容錯(cuò)問題。
    4. 如:設(shè)備寬364dp,系統(tǒng)會(huì)自動(dòng)就近配置values-sw360dp下的dimens,顯示效果相差不會(huì)很大
  6. 今日頭條——修改density值
    1. 原理:px = dp x (dpi/160) = dp x density
    2. 既然如此,將density
    3. 需要UI出設(shè)計(jì)圖時(shí)以統(tǒng)一的dp為基準(zhǔn)
    4. 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.5100dp 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)方案:

一種極低成本的Android屏幕適配方式

這篇文章著實(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)行適配,效果也是極好的。

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

相關(guān)閱讀更多精彩內(nèi)容

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