前言
其實網上已經有很多人總結了Andorid 螢幕適配的知識. 這裡總結了適配的主流方案, 通過分析思考適配的本質, 再來思考各個適配方案的優劣. 弄清楚為什麼有適配問題.
一.什麼是螢幕適配?
這裡說的螢幕適配就是在Android眾多機型上,能有一個相對一致的顯示錶現.Android機型解析度,尺寸,寬高比太多了,如果不做適配後果就是顯示效果差異比較大. 舉個栗子 xml佈局:
<TextView
android:layout_width="300px"
android:layout_height="50px"
android:background="@color/colorAccent"
android:gravity="center_vertical"
android:text="文字"/>
複製程式碼
左側是nexus4 右側是nexus5
螢幕都是等分十等分的, 便於我們觀察差異.
二. 為什麼需要做螢幕適配?
剛才可以看到是用px做單位的. 其實谷歌有預設的適配方案,就是採用dp做單位來適配. 我們來看看使用dp做單位的情況是什麼樣的.
從圖中可以看到其實使用dp做適配,基本解決了問題, 只有一些小差異,如果公司要求不高,使用dp做適配其實也可以的. 沒有問題, 甚至還有些優勢 這個後面再分析. 現在主要分析為什麼dp適配產生了一些差異, 谷歌這麼牛x的公司為什麼設計的方案是這這個樣子 ,好像並沒有完全解決問題, 還需要我們繼續操心適配問題. 要了解這些,就先需要了解一下基本概念.三. 基本概念
相信各位大佬肯定猜到我要說什麼了. 老生長談的幾個概念, 但是我還是說下,便於之後的分析理解
- 畫素 px: 畫素就是對應螢幕的解析度上的畫素, 比如手機是解析度是1080*1920的,那麼手機橫向就是1080個畫素點. 我們看圖一,右側的手機就是這個解析度, 在佈局中寫300px自然顯示效果大約橫向螢幕的30%寬度.不管什麼適配, 其實最後都是轉為px, 因為px是對應的螢幕物理畫素.
- 密度無關畫素dp: 這個是谷歌為Android定製的, px = dpi/160 *dp 看到轉化公式, 問題來了,dpi是什麼.
- 螢幕畫素真實密度: 就是用螢幕物件線的畫素點數量/對角線英寸 就可以計算得出.
- 邏輯畫素密度 dpi : 這個也是密度, 和上面的密度的區別就是這個密度是廠家rom定義的. 和螢幕畫素真實密度有一定的差異. getResources().getDisplayMetrics().densityDpi 可以獲取到該值;
我們看看nexus5 nexus5x 手機引數
實際執行效果 如下圖: 這個顯示效果和上面表格計算出來的一致
可以看出使用dp做單位影響因素最大的就是dpi這個值了
適配問題的產生核心本質就一句話:
- 因為dpi和實際畫素密度的差異導致使用dp做單位,沒有很好的適配.
四. Android各個適配方案對比
一. dp 適配
dp適配是谷歌原始的適配方案, 上文也做了分析, 大家再來思考一下之前提出的問題 為什麼谷歌這樣設計. 設個一個dpi的概念, 而不使用真實畫素密度的?
其實dpi是為了給手機廠商靈活配置顯示效果的. 有兩個手機一個是5寸 一個是6寸 , 如果直接採用真實畫素密度那麼6寸手機就是5寸手機的完全放大版本.很多時候我們想著的是大屏手機可以看到更多的內容. 而不是單純的等比例放大.
缺點: 在不同的手機上表現不同, 不符合公司設計圖示準.
二. 直接資源引用適配
我們在佈局中寫的單位可以引用dimens資原始檔. 配置不同的解析度然後手機執行的時候可以根據不同解析度去對應不同的單位. 參考連結: blog.csdn.net/lmj62356579… 優點: 可以很好的控制不同解析度的顯示效果. 缺點: Andorid手機解析度越來越多, 維護這套多解析度的檔案十分繁瑣. 更糟糕的是, 如果沒有對應的解析度 不會去自動匹配相似的解析度. 這種方案不做推薦
三. 最小寬度限定符
和方案一類似, 但是不是指定解析度的,而是指定螢幕寬度的, 這樣一來檔案就要少很多了 目標螢幕的最小寬度都大於480dp時,螢幕就會自動到帶sw480dp字尾的資原始檔裡去尋找相關資原始檔,這裡的最小寬度是指螢幕寬高的較小值,每個螢幕都是固定的,不會隨著螢幕橫向縱向改變而改變。
參考使用連結: www.jianshu.com/p/759375113… 優點: 可以很好的控制不同解析度的顯示效果. 缺少對應寬度的引用資原始檔 ,可以預設去匹配相近的檔案. 缺點: 還是要去維護較多的檔案. 略繁瑣.四. 谷歌退出的新的百分比設定支援庫
這個庫提供了PercentRelativeLayut percentFrameLayout 支援常用的屬性. 使用的時候設定百分比就可以用了. 實現原理: 通過Layoutparams 獲取child設定的百分比. 通過計算獲取這百分比應該是多少. 測量出控制元件的大小. 缺點: 使用起來比較麻煩. 以為設計圖示註的是px.每次設定百分比的時候需要去計算. 設定百分比還是依賴父容器的.導致scrollView ListView 內的高度無法使用百分比. 這種方案用的人比較少 不推薦使用.
五. 張鴻洋提出的 AutoLayout 全新適配方式.
使用px單位並不是畫素.內部經過處理.變成相應的百分比. 寬和高都是百分比. 寬的1px和高的1px不相同; github.com/hongyangAnd… 鴻洋大神的這套方案用的很多 . 雖然最後停止維護了, 優點: 配置簡單, 使用起來可以直接用px對著設計稿直接寫. 缺點: 在一些複雜佈局, 帶一些自定義控制元件的佈局時候容易出現適配問題. 而且在實際專案使用的時候 有偶發失效的情況. 失效後, 介面由於px做單位. 介面元素變的很小. 這方案可以考慮使用. 畢竟在專案中運用廣泛. 需要注意的是一些自定義佈局的處理, 在列表中itemview需要通過工具類額外處理一下.
六. 今日頭條方案:
其實以上幾種方案都不是很好, 在專案中有各自缺點. 目前最推薦的還是頭條的方案; 參考連結: mp.weixin.qq.com/s/d9QCoBP6k…
工具類如下:
/**
* @param activity
* @param application
* @param isLandscape 是否是橫屏
*/
public class ScreenUtils {
/** 設計稿標準 */
private static final float width = 750f;
private static final float high = 1334f;
private static float textDensity = 0;
private static float textScaledDensity = 0;
/**
* 今日頭條的螢幕適配方案
* 根據當前裝置物理尺寸和解析度去動態計算density、densityDpi、scaledDensity
* 同時也解決了使用者修改系統字型的情況
* @param activity
*/
public static void setCustomDensity(@NonNull Activity activity) {
setCustomDensity(activity, false);
}
/**
* @param activity
* @param isLandscape 是否是橫屏
*/
public static void setCustomDensity(@NonNull final Activity activity, boolean isLandscape) {
final Application application = activity.getApplication();
final DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (textDensity == 0) {
textDensity = displayMetrics.density;
textScaledDensity = displayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
if (configuration != null && configuration.fontScale > 0) {
textScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDensity;
if (isLandscape) {//橫屏
targetDensity = displayMetrics.widthPixels / (high / 2); //當前UI標準750*1334
} else {
targetDensity = displayMetrics.widthPixels / (width / 2); //當前UI標準750*1334
}
final float targetScaledDensity = targetDensity * (textScaledDensity / textDensity);
final int targetDpi = (int) (160 * targetDensity);
displayMetrics.density = targetDensity;
displayMetrics.scaledDensity = targetScaledDensity;
displayMetrics.densityDpi = targetDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDpi;
}
}
複製程式碼
使用如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在設定佈局之前呼叫工具類方法
ScreenUtils.setCustomDensity(this);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
int densityDpi = getResources().getDisplayMetrics().densityDpi;
tv.setText("邏輯畫素密度 dpi " + densityDpi);
}
}
複製程式碼
看看實際執行效果: 實際效果中可以看到很好的完成了適配
之前我們分析了dp不太適配的原因. 所以今日頭條方案簡答暴力. 既然rom中設定的dpi不是實際畫素密度. 那麼我直接把dpi給你改成實際畫素密度. 果然夠簡單暴力的... 工具類比較簡單,複製就可以用. 就不上傳github了.
五. 最後總結和思考:
實際開發中我們直接使用今日頭條方案就可好了, 可以滿足需求.
似乎我們已經找到了完美的適配方案了.
但是? 但是了. 但是這個所謂的適配的真的就是谷歌的個各個手機廠商想看到的麼?
廠商定義dpi沒有按照實際的來定義 而是做了調整, 調整之後的好處就是就是希望在大屏手機上可以看更多的內容. 在圖三中我們發現nexus-5x 比nexus-5 螢幕大一些 dpi值廠商設定的也diy低一些, 更具公式px=dpi/160*dp 得知這樣px就變小, 內容佔的地方就小, 可以容納更多內容. 這就是為了讓大屏手機看到更多的內容. 為了驗證這個猜想我們再看看更大手機的顯示情況
圖六中的表格看到越是大屏手機, 廠商對dpi的標定就比實際越偏小. 這樣就可以讓螢幕顯示更多的內容.特別是看電子書之類的內容時候體驗更明顯, 大屏手機一頁可以顯示更多的內容. 大螢幕的優勢就凸顯出來了.
但是我們開發的普通應用為了在大屏小屏上一致的體驗. 就不得不去修改dpi為實際值了. 以上是我的對Android中適配的一些思考和總結, 希望對大家有幫助~