android 今日頭條的屏幕適配理解

前一段時間無心中看到今日頭條的適配方案,使用到項目中,感受真的是無比絲滑。因此特地寫一篇文章分享給小夥伴們!java

本文知識點:

  • 爲何要作屏幕適配
  • 今日頭條的適配方案(劃重點)
  • 今日頭條的適配方案的一些問題

1. 爲何要作屏幕適配

作Android開發的都瞭解,因爲Android屏幕碎片化嚴重,雖然Android官方提供了dp爲單位的適配方案,可是因爲各類千奇百怪的機型,因此變現每每不盡如人意。因此須要進行屏幕適配。說白了就是讓全部機型都進行保持UI的設計原貌!android

2. 今日頭條的適配方案

終於到了本文的重點了。爲了你們能深入理解其中的含義,這裏從最基本的開始提及。web

2.1 傳統的dp適配的流程

android中的dp在渲染前會將dp轉爲px,計算公式:app

px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);
複製代碼

而dpi是根據屏幕真實的分辨率和尺寸來計算的,每一個設備均可能不同的。那麼dpi是怎麼計算的呢?ide

上面圖片說明dpi是怎麼計算得來的。舉個例子,當屏幕分辨率爲1920 * 1080屏幕尺寸爲5寸的手機。計算得來的dpi爲440。不信的話能夠計算一下!post

那麼問題來了?字體

假設咱們UI設計圖是按屏幕寬度爲360dp來設計的,那麼在上述設備上,屏幕寬度其實爲1080/(440/160)=392.7dp,也就是屏幕是比設計圖要寬的。這種狀況下, 即便使用dp也是沒法在不一樣設備上顯示爲一樣效果的。 同時還存在部分設備屏幕寬度不足360dp,這時就會致使按360dp寬度來開發實際顯示不全的狀況。this

並且上述屏幕尺寸、分辨率和像素密度的關係,不少設備並無按此規則來實現, 所以dpi的值很是亂,沒有規律可循,從而致使使用dp適配效果差強人意。spa

3.2 今日頭條的適配方式說明

其實,當咱們拿到設計圖的時候,通常都是根據蘋果的6進行設計的,每每在Android中,存在16:9和4:3的一些機型,那麼這些機型中的寬高比不一樣,若是想徹底按照設計圖進行適配是不可能的,也是不現實的。可是若是咱們以一個維度,也就是寬這個維度來進行適配的話,若是高度超出了屏幕咱們就使用可滑動的控件進行展現。這就是今日頭條的適配方案。設計

所以,採用以寬度爲標準去進行適配,保持該維度上和設計圖一致

2.3 今日頭條的適配方案

先科普幾個內容,

  • dp和px的轉換公式爲:px = dp * density
  • dp轉換的場景都是經過DisplayMetrics來進行計算的,
  • DisplayMetrics#density 就是上述的density
  • DisplayMetrics#densityDpi 就是上述的dpi
  • DisplayMetrics#scaledDensity 字體的縮放因子,正常狀況下和density相等,可是調節系統字體大小後會改變這個值

由於全部關於dp的計算都是經過DisplayMetrics這個類進行的。因此只須要針對這個類進行操做就能夠了。

我簡單把DisplayMetrics類分爲三個層面,第一個是System(能夠理解成初始分配)的,第二個是APP(能夠理解成Application)的,第三個是Activity的。當你適配的時候,儘可能不要去修改第一個System中的Displaymetris的,由於可能第三方的庫不會按照你的方式去適配,因此這裏只修改後面兩個就能夠了。第一個不修改是便於以後的還原!!!

如下是三個層面獲取DisplayMetrics中的代碼:

// 系統的屏幕尺寸
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
// app總體的屏幕尺寸
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
// activity的屏幕尺寸
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
複製代碼

接下來咱們看看須要怎麼適配,這裏就只以屏幕寬度爲基準進行相應的適配了。這裏模擬360dp爲基準的適配,固然這個值你是能夠修改爲任何尺寸的!

  1. 先計算一下屏幕的寬度
//這裏widthPixels表明屏幕的寬度
activityDm.density = activityDm.widthPixels / 360;
複製代碼
  1. 計算一下字體的density
//這裏經過一個比例肯定activity字體的density
activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density);
複製代碼
  1. 計算相應的dpi
//上面有相應的公式
activityDm.densityDpi = (int) (160 * activityDm.density);
複製代碼
  1. 複製相應的內容
//進行相應的賦值操做
appDm.density = activityDm.density;
appDm.scaledDensity = activityDm.scaledDensity;
appDm.densityDpi = activityDm.densityDpi;
複製代碼

總體代碼以下:

/** * 適配的主要代碼 * * @param activity 上下文 * @param sizeInPx 你要適配的相應尺寸 * @param isVerticalSlide 水平仍是垂直爲參考 */
 private static void adaptScreen(final Activity activity, final int sizeInPx, final boolean isVerticalSlide) {
        // 系統的屏幕尺寸
        final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
        // app總體的屏幕尺寸
        final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
        // activity的屏幕尺寸
        final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
        if (isVerticalSlide) {
            activityDm.density = activityDm.widthPixels / (float) sizeInPx;
            Log.e(TAG, "adaptScreen: "+activityDm.widthPixels );
        } else {
            activityDm.density = activityDm.heightPixels / (float) sizeInPx;
        }
        // 字體的縮放因子,這個是經過一個比例計算得來的!
        activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density);
        // 計算獲得相應的dpi
        activityDm.densityDpi = (int) (160 * activityDm.density);

        //進行相應的賦值操做
        appDm.density = activityDm.density;
        appDm.scaledDensity = activityDm.scaledDensity;
        appDm.densityDpi = activityDm.densityDpi;
    }
複製代碼

由於上面涉及到橫豎屏的問題,因此這裏有個if判斷。上面是主要代碼。

3 今日頭條的適配方案的一些問題

3.1 適配以後Toast的問題?

進行上面的適配以後,Toast會變得很小。其實也不難理解,由於你修改了APP的density,因此整個圖片的界面都會發生相應的變化也就很好理解了。那麼怎麼解決呢?其實就想上面說的,使用System的density對App和Activity進行還原。怎麼說呢?其實就是在show()方法以前還原,在以後在進行適配。

怎麼取消呢?看下面的代碼。

public static void cancelAdaptScreen(final Activity activity) {
        final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
        final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
        final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
        activityDm.density = systemDm.density;
        activityDm.scaledDensity = systemDm.scaledDensity;
        activityDm.densityDpi = systemDm.densityDpi;

        appDm.density = systemDm.density;
        appDm.scaledDensity = systemDm.scaledDensity;
        appDm.densityDpi = systemDm.densityDpi;
    }
複製代碼

其實就是使用System的density把APP和Activity的density修改回來就能夠了!

而後在show()方法以後使用下面方法從新對界面進行適配!

public static void restoreAdaptScreen(Activity activity, boolean isVerticalSlide, int sizeInPx) {
        final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
        final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
        final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
        if (isVerticalSlide) {
            activityDm.density = activityDm.widthPixels / (float) sizeInPx;
        } else {
            activityDm.density = activityDm.heightPixels / (float) sizeInPx;
        }
        activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density);
        activityDm.densityDpi = (int) (160 * activityDm.density);

        appDm.density = activityDm.density;
        appDm.scaledDensity = activityDm.scaledDensity;
        appDm.densityDpi = activityDm.densityDpi;
    }
複製代碼

調用代碼就變成了這個樣子

//取消適配
ScreenUtils.cancelAdaptScreen(this);
//彈出Toast
Toast.makeText(this, "點擊了第一個內容", Toast.LENGTH_SHORT).show();
//從新適配
ScreenUtils.restoreVerticalAdaptScreen(this, 720);
複製代碼

像什麼Toast、dialog什麼的都會出現上面的狀況,因此解決辦法是同樣的

3.2 webview加載後發現density復原

因爲 WebView 初始化的時候會還原 density 的值致使適配失效,繼承 WebView,重寫以下方法:

@Override
public void setOverScrollMode(int mode) {
    super.setOverScrollMode(mode);
    ScreenUtils.restoreAdaptScreen();
}
複製代碼

特別感謝: blankj的Android 屏幕適配從未如斯簡單

相關文章
相關標籤/搜索