前一段時間無心中看到今日頭條的適配方案,使用到項目中,感受真的是無比絲滑。因此特地寫一篇文章分享給小夥伴們!java
作Android開發的都瞭解,因爲Android屏幕碎片化嚴重,雖然Android官方提供了dp爲單位的適配方案,可是因爲各類千奇百怪的機型,因此變現每每不盡如人意。因此須要進行屏幕適配。說白了就是讓全部機型都進行保持UI的設計原貌!android
終於到了本文的重點了。爲了你們能深入理解其中的含義,這裏從最基本的開始提及。web
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
其實,當咱們拿到設計圖的時候,通常都是根據蘋果的6進行設計的,每每在Android中,存在16:9和4: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爲基準的適配,固然這個值你是能夠修改爲任何尺寸的!
//這裏widthPixels表明屏幕的寬度
activityDm.density = activityDm.widthPixels / 360;
複製代碼
//這裏經過一個比例肯定activity字體的density
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;
複製代碼
總體代碼以下:
/** * 適配的主要代碼 * * @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判斷。上面是主要代碼。
進行上面的適配以後,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什麼的都會出現上面的狀況,因此解決辦法是同樣的
因爲 WebView 初始化的時候會還原 density 的值致使適配失效,繼承 WebView,重寫以下方法:
@Override
public void setOverScrollMode(int mode) {
super.setOverScrollMode(mode);
ScreenUtils.restoreAdaptScreen();
}
複製代碼