目錄
1、AndroidAutoSize實戰
1.1 AndroidAutoSize簡介
- 基於今日頭條屏幕適配方案的一個開源庫(Star: 4329);
- 通過修改Application/Activity等的DisplayMetrics中核心數據,使得在不同分辨率手機上對應的dp相等而達到每個顯示的View佔用屏幕的比例相同。
https://github.com/JessYanCoding/AndroidAutoSize
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
詳情可參考: https://juejin.im/post/5ba197e46fb9a05d0b142c62
- 優點
- 非常穩定,極低概率出現意外
- 不會有任何性能的損耗
- 適配範圍可自由控制,不會影響其他三方庫
- 缺點
- 在佈局中引用 dimens 的方式,日常維護修改時較麻煩
- 侵入性高,如果項目想切換爲其他屏幕適配方案,因爲每個 Layout 文件中都存在有大量 dimens 的引用,這時修改起來工作量非常巨大,切換成本非常高昂
- 無法覆蓋全部機型,想覆蓋更多機型的做法就是生成更多的資源文件。
- 如果想使用 sp,也需要生成一系列的 dimens,導致再次增加 App 的體積
3.2 應用到項目中可能存在的問題
- 如果使用AndroidAutoSize開源庫,那麼默認就已經使用AndroidAutoSize對應的適配方案來適配了,沒有提供相關API關閉默認適配,這樣會對已有的組件造成不可預估的風險;
- 由於AndroidAutoSize 需要設置一個全局的design_width_in_dp和design_height_in_dp(也就是設計圖的dp值),那麼對於我們目前的各個組件是否適合;
3.3 建議
- 針對3.2中的問題1,我們可以對AndroidAutoSize進行一定的修改,例如增加相關api,在應用的application執行過程中,關閉AndroidAutoSize的默認開啓全局適配的特性;
- 針對3.2中的問題2,目前AndroidAutoSize已經能夠做到Activity的粒度,讓每個activity的總寬度/高度 dp值個性化(AndroidAutoSize實戰已有講解)。
參考文獻
https://juejin.im/post/5b7a29736fb9a019d53e7ee2
https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q
https://juejin.im/post/5b6250bee51d451918537021