AndroidAutoSize開源庫螢幕適配分析
1、AndroidAutoSize實戰
1.1 AndroidAutoSize簡介
- 基於今日頭條螢幕適配方案的一個開源庫(Star: 4329);
- 通過修改Application/Activity等的DisplayMetrics中核心資料,使得在不同解析度手機上對應的dp相等而達到每個顯示的View佔用螢幕的比例相同。
1.2 程式碼實現
1.2.1 依賴
compile 'me.jessyan:autosize:1.0.1'
1.2.2 manifest配置
<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>
1.2.3 Activity支援
- 預設情況下已經支援適配(寬度)
- 取消適配
取消適配只需要我們的Activity實現CancelAdapt即可,具體如下:
public class TestActivity extends Activity implements CancelAdapt{
}
效果圖如下:
取消適配後可以看到寬度360dp 已經不能佔滿整個寬屏。
- 自定義寬/高(dp)
如果我們的Activity不能使用Manifest中配置的寬/高,需要個性化配置。Activity只需要實現CustomAdapt 介面並實現isBaseOnWidth和getSizeInDp兩個函式即可,具體程式碼如下:
public class TestActivity extends Activity implements CustomAdapt {
@Override
public boolean isBaseOnWidth() {
// true: 以寬度等比例縮放 false: 以高度等比例縮放
return true;
}
@Override
public float getSizeInDp() {
// 對應的寬/高
return 540;
}
}
效果圖如下:
通過自定義總寬度值,可以看到寬度180dp只佔有整個螢幕寬度的1/3。
1.2.4 Fragment支援
(略)
1.3 不同解析度螢幕效果對比
- 裝置介紹
螢幕解析度 | dpi | 螢幕寬度dp | |
---|---|---|---|
手機 A | 720x1280 | 160 | 720(px=dp*(dpi/160)) |
手機 B | 720x1280 | 320 | 360(px=dp*(dpi/160)) |
- 適配前效果對比(左:手機A, 右:手機B)
可以發現,僅僅通過dp來適配,不同的裝置顯示的差異非常大。
- 適配後效果對比(左:手機A, 右:手機B)
為了與上圖有一個比較,此處將Manifest中meta-data的design_width_in_dp設定為400。
適配過後,不同解析度的裝置顯示非常相似。
2、AndroidAutoSize原理分析
2.1 基本概念
2.1.1 一些重要的單位
名稱 | 簡介 |
---|---|
px | pixels(畫素),螢幕上的實際畫素點,無論控制元件或文字最終都會轉化為px單位來顯示其大小。 |
dp | 與dip雷同,指的是裝置獨立畫素,在不同解析度和尺寸的手機上代表了不同的真實畫素,計算公式:px = dp(dpi/160) |
dpi | 畫素密度,指的是在系統軟體上指定的單位尺寸的畫素數量,它往往是寫在系統出廠配置檔案的一個固定值。 |
sp | 全稱scaled pixels,放大畫素的縮寫,專門用於處理字型的大小。它不僅與螢幕dpi有關,還與系統的預設字型大小有關。 |
2.1.2 單位轉換中涉及到的兩個重要類
- DisplayMetrics.java
public class DisplayMetrics {
public static final int DENSITY_MEDIUM = 160;
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT;
public static int DENSITY_DEVICE = getDeviceDensity();
public int widthPixels; // 螢幕畫素(寬)
public int heightPixels; // 螢幕畫素(高)
public float density; // 螢幕密度, density = dpi/160, dp與px之間的轉化就是用此引數
public int densityDpi; // dpi
public float scaledDensity; // 字型大小轉換會用到此引數 px = sp*scaledDensity
public float xdpi;
public float ydpi;
......
public void setToDefaults() {
widthPixels = 0;
heightPixels = 0;
density = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
densityDpi = DENSITY_DEVICE;
scaledDensity = density;
xdpi = DENSITY_DEVICE;
ydpi = DENSITY_DEVICE;
......
}
}
這裡面有幾個重要的引數: density、densityDpi、scaledDensity,AndroidAutoSize的原理主要就是修改這幾個引數值來實現螢幕的適配。下面還有一個系統提供的單位轉化API,系統內部基本上都是呼叫此API來實現單位轉化。
- TypedValue.java
public static float applyDimension(int unit, float value,
DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
根據unit,將value轉化為px值。
2.2 實現原理
通過上面的單位介紹及之間的轉換,我們可以得到如下結論:
- p x= dp(dpi/160), density = dpi/160 => px = dp*density => dp = px/density;
明白上面這個結論,下面我們來討論為什麼我們日常對控制元件設定的寬/高為某一dp時,無法做到各個手機螢幕的適配。
- 裝置 A , 螢幕寬度為 720px, dpi為160,則螢幕總dp為 720/(160/160) = 720 dp
- 裝置 B , 螢幕寬度為 720px, dpi為320,則螢幕總dp為 720/(320/160) = 360 dp
可以看到螢幕的總 dp 寬度在不同的裝置上是會變化的,但是我們在佈局中填寫的 dp 值卻是固定不變的,這就導致我們設定的固定寬度在不同的裝置上顯示的比例不一樣。 例如我們佈局中有一個View設定固定寬度為180dp,在裝置A中會佔螢幕寬度的1/4,但是在裝置B中只會佔螢幕寬度的1/2,這種差別是十分巨大的。
這時我們要想完美適配,那就必須保證這個 View 在任何解析度的螢幕上,與螢幕的比例都是相同的。
要做到在任何解析度的螢幕上顯示比例相同,我們該怎麼做呢?
- 方案一: 動態改變每個View的dp值
由於每種裝置的寬度dp值是不同的,為使得View能夠在不同裝置上顯示的比例一致,可以通過程式碼計算動態的設定每個View的dp值,這種方式顯然是不合適的,工作量太大,太複雜。
- 方案二: 每個View設定固定的dp值,通過修改density 值而達到每種解析度手機的寬度dp值相同
由公式:dp = px/density 可知,由於px是螢幕解析度,這個值有硬體確定,我們是無法改變的,那麼我們可以通過修改density 的值使得不同解析度的手機寬度dp值是相同的,這樣當我們對View設定為某一特定的dp寬度時,佔總寬度的dp比例是相同的,這樣也就達到佔螢幕的比例相同。
那麼問題來了,我們如何確定density 的值呢?
由dp = px/density => 螢幕的總 px 寬度 / density = 螢幕的總 dp 寬度
螢幕的總 px 寬度 / density = 螢幕的總 dp 寬度 => 當前裝置螢幕總寬度(單位為畫素)/ 設計圖總寬度(單位為 dp) = density
如果我們將一套設計圖的總寬度(dp)作為最終手機螢幕的中寬度(dp), 從而達到修改density的目的,同時又可以保證最終不同解析度手機的螢幕總寬度是相同的,這也就完成了適配。
AndroidAutoSize/今日頭條 就是基於方案二的原理來實現螢幕適配的。
2.3 方案可行性
假設設計圖中寬度為300dp,一個View在設計圖上的尺寸為 100dp * 100dp,那麼這個View的寬度佔整個設計圖寬度的33.3%,那麼接下來我們來驗證下通過方案二的適配方案,這個View能否做到不同解析度的裝置還能保持與設計圖中一致的比例。
- 驗證裝置 1
螢幕總寬度為 1080 px,根據上面公式求出density, 1080/300 = 3.6(density),那麼這個尺寸為 100dp * 100dp的View,系統最後會將都換算成px,也就是 px=100*3.6=360(px), 然後 360/1080=0.333=33.3%,與設計圖上的比例一致。
問題一
某些裝置總寬度為1080px,但裝置的dpi不同,是否對該方案適配產生影響?
答案當然是不會的,其實這個方案根本沒有根據 裝置提供的dpi 求出 density,是根據自己的公式求出的 density,所以這對該方案是沒有影響的。
- 驗證裝置 2-
剛才我們驗證的是寬度為1080px的裝置,現在我們用另外一種解析度的裝置720px來驗證。
螢幕總寬度為 720 px,根據上面公式求出density, 720 /300 = 2.4(density),那麼這個尺寸為 100dp * 100dp的View,系統最後會將都換算成px,也就是 px=100*2.4=24.(px), 然後 240/720=0.333=33.3%,與設計圖上的比例一致。
通過計算,該方案是可行的。
2.4 優缺點
- 優點
-
使用成本非常低,操作非常簡單,使用該方案後在頁面佈局時不需要額外的程式碼和操作,這點可以說完虐其他螢幕適配方案
-
侵入性非常低,該方案和專案完全解耦,在專案佈局時不會依賴哪怕一行該方案的程式碼,而且使用的還是 Android 官方的 API,意味著當你遇到什麼問題無法解決,想切換為其他螢幕適配方案時,基本不需要更改之前的程式碼,整個切換過程幾乎在瞬間完成,會少很多麻煩,節約很多時間,試錯成本接近於 0
-
可適配三方庫的控制元件和系統的控制元件(不止是是 Activity 和 Fragment,Dialog、Toast 等所有系統控制元件都可以適配),由於修改的 density 在整個專案中是全域性的,所以只要一次修改,專案中的所有地方都會受益
-
不會有任何效能的損耗。
- 缺點
-
只需要修改一次 density,專案中的所有地方都會自動適配,這個看似解放了雙手,減少了很多操作,但是實際上反應了一個缺點,那就是隻能一刀切的將整個專案進行適配,但適配範圍是不可控的。
-
這個方案依賴於設計圖尺寸,但是專案中的系統控制元件、三方庫控制元件、等非我們專案自身設計的控制元件,它們的設計圖尺寸並不會和我們專案自身的設計圖尺寸一樣。
其實 AndroidAutoSize開源庫已經很大程度上解決了如上兩個缺點,如前面已經給出Activity的用法,適配粒度可以達到Activity。
3、一些思考
3.1 其他方案對比
- 傳統的dp、layout_weight等做簡單的適配
裝置的dpi值並不是任意指定的,它是通過 sqrt(screenWpx2 + screenHpx2) / 螢幕尺寸 計算出的結果(上面模擬器引數是我特意設定,為了很明顯的演示所需) , 因此在大多數裝置上對View的寬/高設以dp為單位進行設定值,差別並不是十分大,當然這只是大多數裝置,因此要適配每種裝置還是很難做到的。
- smallestWidth 限定符螢幕適配方案
開發者先在專案中根據主流螢幕的 最小寬度 (smallestWidth) 生成一系列 values-swdp 資料夾 (含有 dimens.xml 檔案),當把專案執行到裝置上時,系統會根據當前裝置螢幕的 最小寬度 (smallestWidth) 去匹配對應的 values-swdp 資料夾,而對應的 values-swdp 資料夾中的 dimens.xml 文字中的值,又是根據當前裝置螢幕的 最小寬度 (smallestWidth) 而定製的,所以一定能適配當前裝置。
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-sw320dp
│ ├── ├──values-sw360dp
│ ├── ├──values-sw400dp
│ ├── ├──values-sw411dp
│ ├── ├──values-sw480dp
│ ├── ├──...
│ ├── ├──values-sw600dp
│ ├── ├──values-sw640dp
- 優點
- 非常穩定,極低概率出現意外
- 不會有任何效能的損耗
- 適配範圍可自由控制,不會影響其他三方庫
- 缺點
- 在佈局中引用 dimens 的方式,日常維護修改時較麻煩
- 侵入性高,如果專案想切換為其他螢幕適配方案,因為每個 Layout 檔案中都存在有大量 dimens 的引用,這時修改起來工作量非常巨大,切換成本非常高昂
- 無法覆蓋全部機型,想覆蓋更多機型的做法就是生成更多的資原始檔。
- 如果想使用 sp,也需要生成一系列的 dimens,導致再次增加 App 的體積