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

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

學習今日頭條技術團隊-->一種極低成本的Android屏幕適配方式
JessYan->騷年你的屏幕適配方式該升級了!-今日頭條適配方案
今日頭條屏幕適配方案終極版正式發(fā)布!

文章中剛開的的計算供著幾個參數就沒弄懂,搜了2遍不錯的文章
兩分鐘理解Android中PX、DP、SP的區(qū)別
Android 中 px、dp、dip、sp詳解

再回來看計算公式:

  • px = density * dp;
  • density = dpi / 160;
  • px = dp * (dpi / 160);

而為什么除以160,在Google官方文檔也有說明,密度無關像素(dp)等于 160 dpi 屏幕上的一個物理像素,這是 系統(tǒng)為“中”密度屏幕假設的基線密度。
了解了是怎么一回事。

下表格出處Google官方文檔

屏幕特性 限定符 說明
尺寸 small 適用于小尺寸屏幕的資源。
normal 適用于正常尺寸屏幕的資源。(這是基線尺寸。)
large 適用于大尺寸屏幕的資源。
xlarge 適用于超大尺寸屏幕的資源。
密度 ldpi 適用于低密度 (ldpi) 屏幕 (~120dpi) 的資源。
mdpi 適用于中密度 (mdpi) 屏幕 (~160dpi) 的資源。(這是基線 密度。)
hdpi 適用于高密度 (hdpi) 屏幕 (~240dpi) 的資源。
xhdpi 適用于超高密度 (xhdpi) 屏幕 (~320dpi) 的資源。
xxhdpi 適用于超超高密度 (xxhdpi) 屏幕 (~480dpi) 的資源。
xxxhdpi 適用于超超超高密度 (xxxhdpi) 屏幕 (~640dpi) 的資源。此限定符僅適用于 啟動器圖標,請參閱上面的注。
nodpi 適用于所有密度的資源。這些是密度獨立的資源。不管當前屏幕的密度如何,系統(tǒng)都不會 縮放以此限定符標記的資源。
tvdpi 適用于密度介于 mdpi 和 hdpi 之間屏幕(約為 213dpi)的資源。它并不是 “主要”密度組,主要用于電視,而大多數應用都不 需要它 — 對于大多數應用而言,提供 mdpi 和 hdpi 資源便已足夠,系統(tǒng)將根據需要對其進行 縮放。如果發(fā)現(xiàn)必須提供 tvdpi 資源,應以 1.33*mdpi 的系數 調整其大小。例如,mdpi 屏幕的 100px x 100px 圖像應該相當于 tvdpi 的 133px x 133px。
方向 land 適用于橫屏(長寬比)的資源。
port 適用于豎屏(高寬比)的資源。
縱橫比 long 適用于縱橫比明顯高于或寬于(分別在豎屏 或橫屏時)基線屏幕配置的屏幕的資源。
notlong 適用于使用縱橫比類似于基線屏幕 配置的屏幕的資源。

關鍵

px = dp * density
從上面公式中,我們可以了解到我在布局文件設置相同的dp,但在不同的設備顯示不同,唯一可以變化的就是desity所以我們只要修改destiny,來滿足我們需要平米顯示的px值。

destinyDisplayMetrics中的成員變量,我們可以通過Resources#getDisplayMetrics獲取我們需要的數據。

我們需要關注的幾個變量:

  • DisplayMetrics#density 就是上述的density
  • DisplayMetrics#densityDpi 就是上述的dpi
  • DisplayMetrics#scaledDensity 字體的縮放因子,正常情況下和density相等,但是調節(jié)系統(tǒng)字體大小后會改變這個值

然后,可以了解到

  1. 布局文件中dp的轉換,最終都是調用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來進行轉換

    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
            return 0;
        }
    
  2. 圖片中的decode,BitmapFactory#decodeResourceStream方法:

    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {
        //省略不想干的代碼
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }
    

通過上面2個我們了解到了,基本都是通過 DisplayMetrics來計算的。所以了解了上面的到下面的最終方案。

最終方案

今日頭條用的是360dp(360*640),以寬維度來適配的。

    private static float sComponentDensity;
    private static float sComponentScaledDensity;
    /**
     * 設置自定義的屏幕密度
     * 在BaseActivity中的onCreate()方法中調用
     */
    private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application){
        final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

        if (sComponentDensity == 0) {
            sComponentDensity = appDisplayMetrics.density;
            sComponentScaledDensity = appDisplayMetrics.scaledDensity;
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        sComponentScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }

        final float targetDensity = appDisplayMetrics.widthPixels / 360F;//以360dp(360\*640)和寬維度來適配的
        final float targetScaledDensity = targetDensity * (sComponentScaledDensity / sComponentDensity);
        final int targetDensityDpi = (int) (160 * targetDensity);

        //賦值到application中的density中
        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaledDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        //下面賦值到activity中的density中
        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi= targetDensityDpi;
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容