AndroidAutoSize開源庫屏幕適配分析

目錄

1、AndroidAutoSize實戰

1.1 AndroidAutoSize簡介

1.2 代碼實現

1.2.1 依賴

1.2.2 manifest配置

1.2.3 Activity支持

1.2.4 Fragment支持

1.3 不同分辨率屏幕效果對比

2、AndroidAutoSize原理分析

2.1 基本概念

2.1.1 一些重要的單位

2.1.2 單位轉換中涉及到的兩個重要類

2.2 實現原理

2.3 方案可行性

2.4 優缺點

3、一些思考

3.1 其他方案對比

3.2 應用到項目中可能存在的問題

3.3 建議

參考文獻


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支持

  • 默認情況下已經支持適配(寬度)

image

  • 取消適配

取消適配只需要我們的Activity實現CancelAdapt即可,具體如下:

public class TestActivity extends Activity implements CancelAdapt{

}

效果圖如下:

image

取消適配後可以看到寬度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;
    }
}

效果圖如下:

image

通過自定義總寬度值,可以看到寬度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)

image

可以發現,僅僅通過dp來適配,不同的設備顯示的差異非常大。

  • 適配後效果對比(左:手機A, 右:手機B)

爲了與上圖有一個比較,此處將Manifest中meta-data的design_width_in_dp設置爲400。

image

適配過後,不同分辨率的設備顯示非常相似。

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 優缺點

  • 優點
  1. 使用成本非常低,操作非常簡單,使用該方案後在頁面佈局時不需要額外的代碼和操作,這點可以說完虐其他屏幕適配方案

  2. 侵入性非常低,該方案和項目完全解耦,在項目佈局時不會依賴哪怕一行該方案的代碼,而且使用的還是 Android 官方的 API,意味着當你遇到什麼問題無法解決,想切換爲其他屏幕適配方案時,基本不需要更改之前的代碼,整個切換過程幾乎在瞬間完成,會少很多麻煩,節約很多時間,試錯成本接近於 0

  3. 可適配三方庫的控件和系統的控件(不止是是 Activity 和 Fragment,Dialog、Toast 等所有系統控件都可以適配),由於修改的 density 在整個項目中是全局的,所以只要一次修改,項目中的所有地方都會受益

  4. 不會有任何性能的損耗。

  • 缺點
  1. 只需要修改一次 density,項目中的所有地方都會自動適配,這個看似解放了雙手,減少了很多操作,但是實際上反應了一個缺點,那就是隻能一刀切的將整個項目進行適配,但適配範圍是不可控的。

  2. 這個方案依賴於設計圖尺寸,但是項目中的系統控件、三方庫控件、等非我們項目自身設計的控件,它們的設計圖尺寸並不會和我們項目自身的設計圖尺寸一樣。

其實 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

  • 優點
  1. 非常穩定,極低概率出現意外
  2. 不會有任何性能的損耗
  3. 適配範圍可自由控制,不會影響其他三方庫
  • 缺點
  1. 在佈局中引用 dimens 的方式,日常維護修改時較麻煩
  2. 侵入性高,如果項目想切換爲其他屏幕適配方案,因爲每個 Layout 文件中都存在有大量 dimens 的引用,這時修改起來工作量非常巨大,切換成本非常高昂
  3. 無法覆蓋全部機型,想覆蓋更多機型的做法就是生成更多的資源文件。
  4. 如果想使用 sp,也需要生成一系列的 dimens,導致再次增加 App 的體積

3.2 應用到項目中可能存在的問題

  1. 如果使用AndroidAutoSize開源庫,那麼默認就已經使用AndroidAutoSize對應的適配方案來適配了,沒有提供相關API關閉默認適配,這樣會對已有的組件造成不可預估的風險;
  2. 由於AndroidAutoSize 需要設置一個全局的design_width_in_dp和design_height_in_dp(也就是設計圖的dp值),那麼對於我們目前的各個組件是否適合;

3.3 建議

  1. 針對3.2中的問題1,我們可以對AndroidAutoSize進行一定的修改,例如增加相關api,在應用的application執行過程中,關閉AndroidAutoSize的默認開啓全局適配的特性;
  2. 針對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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章