1. 程式人生 > >Android螢幕適配方案

Android螢幕適配方案

目錄介紹

  • 1.螢幕適配定義
  • 2.相關重要的概念
    • 2.1 螢幕尺寸[物理尺寸]
    • 2.2 螢幕解析度[px]
    • 2.3 螢幕畫素密度[dpi]
    • 2.4 dp、dip、dpi、sp、px
    • 2.5 mdpi、hdpi、xdpi、xxdpi
    • 2.6 獲取螢幕解析度[寬高]
  • 3.Android螢幕適配出現的原因
    • 3.1 什麼是畫素點
    • 3.2 dp與百分比
  • 4.Android適配問題及本質
    • 4.1 尺寸適配
    • 4.2 程式碼適配
    • 4.3 佈局適配
    • 4.4 權重適配
    • 4.5 圖片適配
    • 4.6 百分比適配
  • 5.存在問題和困境
    • 5.1 萬用字元適配困境
    • 5.2 傳統dp適配困境
  • 6.常用解決方案
    • 6.1 今日頭條適配方案
    • 6.2 鴻洋大神庫
    • 6.3 AndroidAutoSize

1.螢幕適配定義

  • 使得某一元素在Android不同尺寸、不同解析度的手機上具備相同的顯示效果

2.相關重要的概念

2.1 螢幕尺寸[物理尺寸]

  • 含義:手機對角線的物理尺寸
  • 單位:英寸(inch),1英寸=2.54cm
  • Android手機常見的尺寸有5寸、5.5寸、6寸等等

2.2 螢幕解析度[px]

  • 含義:手機在橫向、縱向上的畫素點數總和
  • 一般描述成螢幕的”寬x高”=AxB
  • 含義:螢幕在橫向方向(寬度)上有A個畫素點,在縱向方向 (高)有B個畫素點
  • 例子:1080x1920,即寬度方向上有1080個畫素點,在高度方向上有1920個畫素點
  • 單位:px(pixel),1px=1畫素點
  • Android手機常見的解析度:720x1280、1080x1920等等

2.3 螢幕畫素密度[dpi]

  • 含義:每英寸的畫素點數
  • 單位:dpi(dots per ich)
  • 假設裝置內每英寸有160個畫素,那麼該裝置的螢幕畫素密度=160dpi
  • 螢幕畫素密度與螢幕尺寸和螢幕解析度有關,在單一變化條件下,螢幕尺寸越小、解析度越高,畫素密度越大,反之越小。
  • dpi計算公式:舉個例子:螢幕解析度為:1920*1080,螢幕尺寸為5吋的話,那麼dpi為440 image
  • 注意:dpi是根據螢幕真實的解析度和尺寸來計算的,每個裝置都可能不一樣的

2.4 dp[dip]、dpi、sp、px

  • dp(dip):px = dp * 密度比,都是Density Independent Pixels的縮寫,即密度無關畫素
  • dpi:開方(寬度平方 + 高度平方) / 手機的尺寸;dpi是螢幕畫素密度,假如一英寸裡面有160個畫素,這個螢幕的畫素密度就是160dpi
  • sp: 可以根據文字大小首選項進行放縮,是設定字型大小的御用單位。
  • px: 畫素,px是比較熟悉,前面的解析度就是用的畫素為單位,大多數情況下,比如UI設計、Android原生API都會以px作為統一的計量單位,畫素是獲取螢幕寬高等。
  • 問題: dp和px如何換算呢?
px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);

2.5 mdpi、hdpi、xdpi、xxdpi

  • 2.5.1 作用: mdpi、hdpi、xdpi、xxdpi用來修飾Android中的drawable資料夾及values資料夾,用來區分不同畫素密度下的圖片和dimen值。
名稱像                     素密度範圍
ldpi                      0dpi~120dpi
mdpi                      120dpi~160dpi
hdpi                      120dpi~160dpi
xdpi                      160dpi~240dpi
xxdpi                     240dpi~320dpi
xxxdpi                    480dpi~640dpi

Android專案後應該可以看到很多drawable資料夾,分別對應不同的dpi
drawable-ldpi (dpi=120, density=0.75)
drawable-mdpi (dpi=160, density=1)
drawable-hdpi (dpi=240, density=1.5)
drawable-xhdpi (dpi=320, density=2)
drawable-xxhdpi (dpi=480, density=3)
對於五種主流的畫素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。
  • 2.5.2 在進行開發的時候,我們需要把合適大小的圖片放在合適的資料夾裡面。下面以圖示設計為例進行介紹
  • 2.5.3 在設計圖示時,對於五種主流的畫素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。
    • 例如,一個啟動圖示的尺寸為48x48 dp,這表示在 MDPI 的螢幕上其實際尺寸應為 48x48 px,在 HDPI 的螢幕上其實際大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的螢幕上其實際大小是 MDPI 的 2 倍 (96x96 px),依此類推。
  • 2.5.4 下圖為圖示的各個螢幕密度的對應尺寸:
螢幕密度                 圖示尺寸
mdpi                     48X48px
hdpi                     72X72px
xdpi                     96X96px
xxdpi                    144X144px
xxxdpi                   192X192px

2.6 DisplayMetrics解析

  • DisplayMetrics解析
  • 獲取螢幕解析度資訊的三種方法:
//第一種
DisplayMetrics metrics = new DisplayMetrics();
Display display = activity.getWindowManager().getDefaultDisplay();
display.getMetrics(metrics);

//第二種
DisplayMetrics metrics= activity.getResources().getDisplayMetrics();

//第三種
Resources.getSystem().getDisplayMetrics();

3.Android螢幕適配出現的原因

3.1 什麼是畫素點

  • 螢幕解析度是指在橫縱向上的畫素點數,單位是px,1px=1個畫素點。
  • 一般以縱向畫素*橫向畫素,如1960*1080。 由於Android系統的開放性,任何使用者、開發者、OEM廠商、運營商都可以對Android進行定製,修改成他們想要的樣子。 螢幕尺寸這麼多,為了讓我們開發的程式能夠比較美觀的顯示在不同尺寸、解析度、畫素密度(這些概念我會在下面詳細講解)的裝置上,那就要在開發的過程中進行處理,至於如何去進行處理,這就是我們今天的主題。

3.2 dp與百分比 (網頁前端提供百分比,所以無需適配)

  • 只要記住一點dp是與畫素無關的,在實際使用中1dp大約等於1/160inch
  • 那麼dp究竟解決了適配上的什麼問題?可以看出1dp = 1/160inch;那麼它至少能解決一個問題,就是你在佈局檔案寫某個View的寬和高為160dp*160dp,這個View在任何解析度的螢幕中,顯示的尺寸大小是大約是一致的(可能不精確),大概是 1 inch * 1 inch。

    • 1.呈現效果仍舊會有差異,僅僅是相近而已
      1. 當裝置的物理尺寸存在差異的時候,dp就顯得無能為力了。為4.3寸螢幕準備的UI,執行在5.0寸的螢幕上,很可能在右側和下側存在大量的空白。而5.0寸的UI執行到4.3寸的裝置上,很可能顯示不下。
  • 一句話,總結下,dp能夠讓同一數值在不同的解析度展示出大致相同的尺寸大小。但是當裝置的尺寸差異較大的時候,就無能為力了。

4.Android螢幕適配常見方法

4.1 適配常見方法

  • 尺寸適配
  • dimen適配
  • 佈局適配
  • 程式碼適配
  • 圖片適配

4.2 尺寸適配

  • 4.2.1 佈局檔案設定寬高
  • 寬高設定引數:有的時候用dp,有的時候用px,大多數用dp
  • dp:dp(dip):px = dp * 密度比,與螢幕畫素有對應關係,設定成dp後,在不同解析度的手機上有可能尺寸會不一樣 px:畫素,比如小解析度手機上一畫素和大解析度手機上一畫素,所顯示的影象是不一樣的 理解dp和px之間對應的關係,不同解析度的手機用不同的dp值來適配
  • 4.2.2 密度比
  • 密度比是固定的,可以查詢文件Develop—>API Guides—>Best Practices—>Supporting Multiple mdpi手機:160dpi 是基準線,1px = 1dp * 1,其他手機的密度比 = 自己的dpi/160
  • 程式碼獲取密度比:getResources().getDisplayMetrics().density ldip:120px = 160dp * 0.75 mdpi:160px = 160dp * 1 hdpi:240px = 160dp * 1.5 xhdpi:360px = 180dp * 2
  • 4.2.3 dimen適配
1.在預設的values中的dimens檔案下宣告(類似於Strings.xml)
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="harlWidth">160dp</dimen>

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- values-hdpi 480X800 -->
    <dimen name="imagewidth">120dip</dimen>     
</resources>

<resources>
    <!-- values-hdpi-1280x800 -->
    <dimen name="imagewidth">220dip</dimen>     
</resources>


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- values-hdpi  480X320 -->
    <dimen name="imagewidth">80dip</dimen>     
</resources>


2.在佈局檔案中引用
<TextView
    android:layout_width="@dimen/harlWidth"
    android:layout_height="wrap_content"
    android:background="#0ff"
    android:text="@string/hello_world" />

3.新建需要適配的values-XXX(比如values-1280x720,注意規範大值在前)

4.在新建values-1280x720中的dimens.xml檔案中
    * <dimen name="harlWidth">180dp</dimen>

5.所有手機適配找對應的預設的dimens
    * 思考:如何計算dpi?如何計算手機密度比?能夠用dp適配所有手機嗎?
    * dp不能適配所有手機;
    * 舉個例子:按鈕佔螢幕寬度一半,把寬度設定成160dp,120px和160px和240px可以佔螢幕一半,但是360px則小於螢幕一半;
    * 如果把寬度設定成180dp,那麼360dp可以佔螢幕一半,但其他幾個又不行。
    * 如果要適配所有手機的控制元件寬度為螢幕寬度的一半,該怎麼做呢?用dimen

4.2 程式碼適配

4.3 佈局適配,有可能在不同的手機佈局中,控制元件排列的位置不一樣

  • 1.位置不一樣
    • 不同的手機在執行的時候選擇不同的佈局(佈局名稱一樣,類似於dimens),比如:
  • 2.控制元件不一樣
    • 不能用佈局適配了;為什麼?
    • 佈局能夠實現介面效果,但是完成佈局後在程式碼中,由於控制元件都不一樣,所以會找這兩套佈局的id,還要做判斷,根據不同的佈局做兩套程式碼(如果頁面複雜,給控制元件設定引數等十分繁瑣)
  • 3.適用場景
    • 不同的手機的控制元件的位置不一樣,發生了位置變化才會用到佈局適配,實際開發中用的很少

4.4 權重適配

4.5 圖片適配

  • 1.圖片的查詢順序
    • 注意:一般手機 ldpi

5.存在問題和困境

5.1 萬用字元適配困境

5.2 傳統dp適配困境

  • [摘自頭條]一般我們設計圖都是以固定的尺寸來設計的。比如以解析度750px * 1334px來設計,以density為3來標註,也就是螢幕其實是350dp * 667dp。如果想在所有裝置上顯示完全一致,其實是不現實的,因為螢幕高寬比不是固定的,各種寬高比層出不窮,寬高比不同,顯示完全一致就不可能了。但是通常下,我們只需要以寬或高一個維度去適配,比如我們Feed是上下滑動的,只需要保證在所有裝置中寬的維度上顯示一致即可,再比如一個不支援上下滑動的頁面,那麼需要保證在高這個維度上都顯示一致,尤其不能存在某些裝置上顯示不全的情況。同時考慮到現在基本都是以dp為單位去做的適配,如果新的方案不支援dp,那麼遷移成本也非常高。
  • 因此,總結下大致需求如下:
    • 支援以寬或者高一個維度去適配,保持該維度上和設計圖一致;注意是某一個維度
    • 支援dp和sp單位,控制遷移成本到最小。

6.常用適配框架

6.1 今日頭條適配方案

  • 6.1.1 相容突破口
  • 從dp和px的轉換公式 :px = dp * density
  • 可以看出,如果設計圖寬為360dp,想要保證在所有裝置計算得出的px值都正好是螢幕寬度的話,我們只能修改 density 的值。
//在xml中使用何種尺寸單位(dp、sp、pt、in、mm),最後在繪製時都會給我們轉成px!
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;
}
  • 6.1.2 頭條適配方案核心程式碼
public static void setCustomDensity(Activity activity, Application application) {
    DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
    if (sNoncompatDensity == 0) {
        // 系統的Density
        sNoncompatDensity = displayMetrics.density;
        // 系統的ScaledDensity
        sNoncompatScaledDensity = displayMetrics.scaledDensity;
        // 監聽在系統設定中切換字型
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                if (newConfig != null && newConfig.fontScale > 0) {
                    sNoncompatScaledDensity=application.getResources().getDisplayMetrics().scaledDensity;
                }
            }

            @Override
            public void onLowMemory() {

            }
        });
    }
    // 公司UI尺寸是750px-1334px,此處以375dp的設計圖作為例子
    float targetDensity=displayMetrics.widthPixels/375;
    float targetScaledDensity=targetDensity*(sNoncompatScaledDensity/sNoncompatDensity);
    int targetDensityDpi= (int) (160 * targetDensity);
    displayMetrics.density = targetDensity;
    displayMetrics.scaledDensity = targetScaledDensity;
    displayMetrics.densityDpi = targetDensityDpi;

    DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
    activityDisplayMetrics.density = targetDensity;
    activityDisplayMetrics.scaledDensity = targetScaledDensity;
    activityDisplayMetrics.densityDpi = targetDensityDpi;
}
  • 6.1.3 頭條適配方案注意事項
  • 寬度適配就已經完成啦,只需要在Activity中呼叫就行了,必須在setContentView()之前!
  • 如果需要適配高度,頭條指出只要按照同樣的方法做高度適配就可以了!
  • 實現思路:假設設計圖寬度是360dp,以寬維度來適配,那麼適配後的 density = 裝置真實寬(單位px) / 360,接下來只需要把我們計算好的 density 在系統中修改下即可
  • 遇到的問題:

    • 1.如果某個頁面不想適配該方案,該如何處理
    • 2.滾動頁面以寬為維度適配,而某些頁面則是以高為維度適配,這種情況怎麼辦?
    • 3.針對第三方庫有何更好的方案,比如支付寶支付彈窗,或者第三方客服聊天頁面如何處理適配
  • 6.1.4 頭條適配工具類,暫時只是用作測試專案

public class ScreenDensityUtils {


    /*
     * 1.先在application中使用setup()方法初始化一下
     * 2.手動在Activity中呼叫match()方法做適配,必須在setContentView()之前
     * 3.建議使用dp做寬度適配,大多數時候寬度適配才是主流需要
     * 4.個人覺得在寫佈局的時候,可以多用dp,如果是使用px,建議轉化成dp
     * 5.入侵性很低,不需要改動原來的程式碼
     */


    /**
     * 螢幕適配的基準
     */
    private static final int MATCH_BASE_WIDTH = 0;
    private static final int MATCH_BASE_HEIGHT = 1;
    /**
     * 適配單位
     */
    private static final int MATCH_UNIT_DP = 0;
    private static final int MATCH_UNIT_PT = 1;

    // 適配資訊
    private static MatchInfo sMatchInfo;
    // Activity 的生命週期監測
    private static Application.ActivityLifecycleCallbacks mActivityLifecycleCallback;

    private ScreenDensityUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * 初始化
     * @param application                   需要在application中初始化
     */
    public static void setup(@NonNull final Application application) {

        /*
        //獲取螢幕解析度資訊的三種方法
        //第一種
        DisplayMetrics metrics = new DisplayMetrics();
        Display display = activity.getWindowManager().getDefaultDisplay();
        display.getMetrics(metrics);
        //第二種
        DisplayMetrics metrics= activity.getResources().getDisplayMetrics();
        //第三種
        Resources.getSystem().getDisplayMetrics();
        */

        //注意這個是獲取系統的displayMetrics
        final DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (sMatchInfo == null) {
            // 記錄系統的原始值
            sMatchInfo = new MatchInfo();
            sMatchInfo.setScreenWidth(displayMetrics.widthPixels);
            sMatchInfo.setScreenHeight(displayMetrics.heightPixels);
            sMatchInfo.setAppDensity(displayMetrics.density);
            sMatchInfo.setAppDensityDpi(displayMetrics.densityDpi);
            sMatchInfo.setAppScaledDensity(displayMetrics.scaledDensity);
            sMatchInfo.setAppXdpi(displayMetrics.xdpi);
        }
        // 新增字型變化的監聽
        // 呼叫 Application#registerComponentCallbacks 註冊下 onConfigurationChanged 監聽即可。
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                // 字型改變後,將 appScaledDensity 重新賦值
                if (newConfig != null && newConfig.fontScale > 0) {
                    float scaledDensity = displayMetrics.scaledDensity;
                    sMatchInfo.setAppScaledDensity(scaledDensity);
                }
            }

            @Override
            public void onLowMemory() {

            }
        });
    }

    /**
     * 在 application 中全域性啟用適配(也可單獨使用 match() 方法在指定頁面中配置適配)
     */
    @RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static void register(@NonNull final Application application, final float designSize, final int matchBase, final int matchUnit) {
        if (mActivityLifecycleCallback == null) {
            mActivityLifecycleCallback = new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                    if (activity != null) {
                        match(activity, designSize, matchBase, matchUnit);
                    }
                }

                @Override
                public void onActivityStarted(Activity activity) {

                }

                @Override
                public void onActivityResumed(Activity activity) {

                }

                @Override
                public void onActivityPaused(Activity activity) {

                }

                @Override
                public void onActivityStopped(Activity activity) {

                }

                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

                }

                @Override
                public void onActivityDestroyed(Activity activity) {

                }
            };
            application.registerActivityLifecycleCallbacks(mActivityLifecycleCallback);
        }
    }

    /**
     * 全域性取消所有的適配
     */
    @RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static void unregister(@NonNull final Application application, @NonNull int... matchUnit) {
        if (mActivityLifecycleCallback != null) {
            application.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallback);
            mActivityLifecycleCallback = null;
        }
        for (int unit : matchUnit) {
            cancelMatch(application, unit);
        }
    }


    /**
     * 適配螢幕(放在 Activity 的 setContentView() 之前執行)
     *
     * @param context                               上下文
     * @param designSize                            設計圖的尺寸
     */
    public static void match(@NonNull final Context context, final float designSize) {
        match(context, designSize, MATCH_BASE_WIDTH, MATCH_UNIT_DP);
    }


    /**
     * 適配螢幕(放在 Activity 的 setContentView() 之前執行)
     *
     * @param context                               上下文
     * @param designSize                            設計圖的尺寸
     * @param matchBase                             適配基準
     */
    public static void match(@NonNull final Context context, final float designSize, int matchBase) {
        match(context, designSize, matchBase, MATCH_UNIT_DP);
    }

    /**
     * 適配螢幕(放在 Activity 的 setContentView() 之前執行)
     *
     * @param context                               上下文
     * @param designSize                            設計圖的尺寸
     * @param matchBase                             適配基準
     * @param matchUnit                             使用的適配單位
     */
    private static void match(@NonNull final Context context, final float designSize, int matchBase, int matchUnit) {
        if (designSize == 0) {
            throw new UnsupportedOperationException("The designSize cannot be equal to 0");
        }
        if (matchUnit == MATCH_UNIT_DP) {
            matchByDP(context, designSize, matchBase);
        } else if (matchUnit == MATCH_UNIT_PT) {
            matchByPT(context, designSize, matchBase);
        }
    }

    /**
     * 重置適配資訊,取消適配
     */
    public static void cancelMatch(@NonNull final Context context) {
        cancelMatch(context, MATCH_UNIT_DP);
        cancelMatch(context, MATCH_UNIT_PT);
    }

    /**
     * 重置適配資訊,取消適配
     *
     * @param context                       上下文
     * @param matchUnit                     需要取消適配的單位
     */
    private static void cancelMatch(@NonNull final Context context, int matchUnit) {
        if (sMatchInfo != null) {
            final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
            if (matchUnit == MATCH_UNIT_DP) {
                if (displayMetrics.density != sMatchInfo.getAppDensity()) {
                    displayMetrics.density = sMatchInfo.getAppDensity();
                }
                if (displayMetrics.densityDpi != sMatchInfo.getAppDensityDpi()) {
                    displayMetrics.densityDpi = (int) sMatchInfo.getAppDensityDpi();
                }
                if (displayMetrics.scaledDensity != sMatchInfo.getAppScaledDensity()) {
                    displayMetrics.scaledDensity = sMatchInfo.getAppScaledDensity();
                }
            } else if (matchUnit == MATCH_UNIT_PT) {
                if (displayMetrics.xdpi != sMatchInfo.getAppXdpi()) {
                    displayMetrics.xdpi = sMatchInfo.getAppXdpi();
                }
            }
        }
    }


    public static MatchInfo getMatchInfo() {
        return sMatchInfo;
    }


    /**
     * 使用 dp 作為適配單位(適合在新專案中使用,在老專案中使用會對原來既有的 dp 值產生影響)
     * <br>
     * <ul>
     * dp 與 px 之間的換算:
     * <li> px = density * dp </li>
     * <li> density = dpi / 160 </li>
     * <li> px = dp * (dpi / 160) </li>
     * </ul>
     *
     * @param context                       上下文
     * @param designSize                    設計圖的寬/高(單位: dp)
     * @param base                          適配基準
     */
    private static void matchByDP(@NonNull final Context context, final float designSize, int base) {
        final float targetDensity;
        if (base == MATCH_BASE_WIDTH) {
            targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
        } else if (base == MATCH_BASE_HEIGHT) {
            targetDensity = sMatchInfo.getScreenHeight() * 1f / designSize;
        } else {
            targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
        }
        final int targetDensityDpi = (int) (targetDensity * 160);
        final float targetScaledDensity = targetDensity * (sMatchInfo.getAppScaledDensity() / sMatchInfo.getAppDensity());
        final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        displayMetrics.density = targetDensity;
        displayMetrics.densityDpi = targetDensityDpi;
        displayMetrics.scaledDensity = targetScaledDensity;
    }


    /**
     * 使用 pt 作為適配單位(因為 pt 比較冷門,新老專案皆適合使用;也可作為 dp 適配的補充,
     * 在需要同時適配寬度和高度時,使用 pt 來適配 dp 未適配的寬度或高度)
     * <br/>
     * <p> pt 轉 px 演算法: pt * metrics.xdpi * (1.0f/72) </p>
     *
     * @param context                       上下文
     * @param designSize                    設計圖的寬/高(單位: pt)
     * @param base                          適配基準
     */
    private static void matchByPT(@NonNull final Context context, final float designSize, int base) {
        final float targetXdpi;
        if (base == MATCH_BASE_WIDTH) {
            targetXdpi = sMatchInfo.getScreenWidth() * 72f / designSize;
        } else if (base == MATCH_BASE_HEIGHT) {
            targetXdpi = sMatchInfo.getScreenHeight() * 72f / designSize;
        } else {
            targetXdpi = sMatchInfo.getScreenWidth() * 72f / designSize;
        }
        final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        displayMetrics.xdpi = targetXdpi;
    }

    /**
     * 適配資訊
     */
    private static class MatchInfo {

        private int screenWidth;
        private int screenHeight;
        private float appDensity;
        private float appDensityDpi;
        private float appScaledDensity;
        private float appXdpi;

        int getScreenWidth() {
            return screenWidth;
        }

        void setScreenWidth(int screenWidth) {
            this.screenWidth = screenWidth;
        }

        int getScreenHeight() {
            return screenHeight;
        }

        void setScreenHeight(int screenHeight) {
            this.screenHeight = screenHeight;
        }

        float getAppDensity() {
            return appDensity;
        }

        void setAppDensity(float appDensity) {
            this.appDensity = appDensity;
        }

        float getAppDensityDpi() {
            return appDensityDpi;
        }

        void setAppDensityDpi(float appDensityDpi) {
            this.appDensityDpi = appDensityDpi;
        }

        float getAppScaledDensity() {
            return appScaledDensity;
        }

        void setAppScaledDensity(float appScaledDensity) {
            this.appScaledDensity = appScaledDensity;
        }

        float getAppXdpi() {
            return appXdpi;
        }

        void setAppXdpi(float appXdpi) {
            this.appXdpi = appXdpi;
        }
    }

}

6.2 鴻洋大AutoLayout框架

  • 我記得上上一個公司的專案投資界就是用的這個螢幕適配庫……哈哈
  • 該庫的想法非常好:對照設計圖,使用px編寫佈局,不影響預覽;繪製階段將對應設計圖的px數值計算轉換為當前螢幕下適配的大小;為簡化接入,inflate時自動將各Layout轉換為對應的AutoLayout,從而不需要在所有的xml中更改。但是同時該庫也存在以下等問題:
    • 擴充套件性較差。對於每一種ViewGroup都要對應編寫對應的AutoLayout進行擴充套件,對於各View的每個需要適配的屬性都要編寫程式碼進行適配擴充套件;
    • 在onMeasure階段進行數值計算。消耗效能,並且這對於非LayoutParams中的屬性存在較多不合理之處。比如在onMeasure時對TextView的textSize進行換算並setTextSize,那麼玩家在程式碼中動態設定的textSize都會失效,因為在每次onMesasure時都會重新被AutoLayout重新設定覆蓋。
    • issue較多並且作者已不再維護。
    • 個人覺得AutoLayout的設計思想非常優秀,但是將LayoutParams與屬性作為切入口在mesure過程中進行轉換計算的方案存在效率與擴充套件性等方面的問題。那麼Android計算長度的收口在哪裡,能不能在Android計算長度時進行換算呢?如果能在Android計算長度時進行換算,那麼就不需要一系列多餘的計算以及適配,一切問題就都迎刃而解了

6.3 AndroidAutoSize

  • 已經用於現在正式庫,程式碼量多,且註釋也比較專案,作者更新很頻繁,極力維護並解決bug,非常不錯!

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格

03.參考部落格