前言
其實(shí)網(wǎng)上已經(jīng)有很多人總結(jié)了Andorid 屏幕適配的知識. 這里總結(jié)了適配的主流方案, 通過分析思考適配的本質(zhì), 再來思考各個適配方案的優(yōu)劣. 弄清楚為什么有適配問題.
一.什么是屏幕適配?
這里說的屏幕適配就是在Android眾多機(jī)型上,能有一個相對一致的顯示表現(xiàn).Android機(jī)型分辨率,尺寸,寬高比太多了,如果不做適配后果就是顯示效果差異比較大. 舉個栗子
xml布局:
<TextView
android:layout_width="300px"
android:layout_height="50px"
android:background="@color/colorAccent"
android:gravity="center_vertical"
android:text="文本"/>
左側(cè)是nexus4 右側(cè)是nexus5
屏幕都是等分十等分的, 便于我們觀察差異.
二. 為什么需要做屏幕適配?
剛才可以看到是用px做單位的. 其實(shí)谷歌有默認(rèn)的適配方案,就是采用dp做單位來適配. 我們來看看使用dp做單位的情況是什么樣的.
從圖中可以看到其實(shí)使用dp做適配,基本解決了問題, 只有一些小差異,如果公司要求不高,使用dp做適配其實(shí)也可以的. 沒有問題, 甚至還有些優(yōu)勢 這個后面再分析.
現(xiàn)在主要分析為什么dp適配產(chǎn)生了一些差異, 谷歌這么牛x的公司為什么設(shè)計的方案是這這個樣子 ,好像并沒有完全解決問題, 還需要我們繼續(xù)操心適配問題. 要了解這些,就先需要了解一下基本概念.
三. 基本概念
相信各位大佬肯定猜到我要說什么了. 老生長談的幾個概念, 但是我還是說下,便于之后的分析理解
- 像素 px: 像素就是對應(yīng)屏幕的分辨率上的像素, 比如手機(jī)是分辨率是1080*1920的,那么手機(jī)橫向就是1080個像素點(diǎn). 我們看圖一,右側(cè)的手機(jī)就是這個分辨率, 在布局中寫300px自然顯示效果大約橫向屏幕的30%寬度.不管什么適配, 其實(shí)最后都是轉(zhuǎn)為px, 因?yàn)閜x是對應(yīng)的屏幕物理像素.
- 密度無關(guān)像素dp: 這個是谷歌為Android定制的, px = dpi/160 *dp 看到轉(zhuǎn)化公式, 問題來了,dpi是什么.
- 屏幕像素真實(shí)密度: 就是用屏幕對象線的像素點(diǎn)數(shù)量/對角線英寸 就可以計算得出.
- 邏輯像素密度 dpi : 這個也是密度, 和上面的密度的區(qū)別就是這個密度是廠家rom定義的. 和屏幕像素真實(shí)密度有一定的差異. getResources().getDisplayMetrics().densityDpi 可以獲取到該值;
我們看看nexus5 nexus5x 手機(jī)參數(shù)
實(shí)際運(yùn)行效果 如下圖: 這個顯示效果和上面表格計算出來的一致
可以看出使用dp做單位影響因素最大的就是dpi這個值了
適配問題的產(chǎn)生核心本質(zhì)就一句話:
- 因?yàn)閐pi和實(shí)際像素密度的差異導(dǎo)致使用dp做單位,沒有很好的適配.
四. Android各個適配方案對比
一. dp 適配
dp適配是谷歌原始的適配方案, 上文也做了分析, 大家再來思考一下之前提出的問題 為什么谷歌這樣設(shè)計. 設(shè)個一個dpi的概念, 而不使用真實(shí)像素密度的?
其實(shí)dpi是為了給手機(jī)廠商靈活配置顯示效果的. 有兩個手機(jī)一個是5寸 一個是6寸 , 如果直接采用真實(shí)像素密度那么6寸手機(jī)就是5寸手機(jī)的完全放大版本.很多時候我們想著的是大屏手機(jī)可以看到更多的內(nèi)容. 而不是單純的等比例放大.
缺點(diǎn): 在不同的手機(jī)上表現(xiàn)不同, 不符合公司設(shè)計圖標(biāo)準(zhǔn).
二. 直接資源引用適配
我們在布局中寫的單位可以引用dimens資源文件. 配置不同的分辨率然后手機(jī)運(yùn)行的時候可以根據(jù)不同分辨率去對應(yīng)不同的單位. 參考鏈接:
http://blog.csdn.net/lmj623565791/article/details/45460089
優(yōu)點(diǎn): 可以很好的控制不同分辨率的顯示效果.
缺點(diǎn): Andorid手機(jī)分辨率越來越多, 維護(hù)這套多分辨率的文件十分繁瑣. 更糟糕的是, 如果沒有對應(yīng)的分辨率 不會去自動匹配相似的分辨率.
這種方案不做推薦
三. 最小寬度限定符
和方案一類似, 但是不是指定分辨率的,而是指定屏幕寬度的, 這樣一來文件就要少很多了
目標(biāo)屏幕的最小寬度都大于480dp時,屏幕就會自動到帶sw480dp后綴的資源文件里去尋找相關(guān)資源文件桂塞,這里的最小寬度是指屏幕寬高的較小值谋币,每個屏幕都是固定的富纸,不會隨著屏幕橫向縱向改變而改變昆稿。
參考使用鏈接: http://www.reibang.com/p/759375113de9
優(yōu)點(diǎn): 可以很好的控制不同分辨率的顯示效果. 缺少對應(yīng)寬度的引用資源文件 ,可以默認(rèn)去匹配相近的文件.
缺點(diǎn): 還是要去維護(hù)較多的文件. 略繁瑣.
四. 谷歌退出的新的百分比設(shè)置支持庫
這個庫提供了PercentRelativeLayut percentFrameLayout 支持常用的屬性. 使用的時候設(shè)置百分比就可以用了.
實(shí)現(xiàn)原理: 通過Layoutparams 獲取child設(shè)置的百分比. 通過計算獲取這百分比應(yīng)該是多少. 測量出控件的大小.
缺點(diǎn): 使用起來比較麻煩. 以為設(shè)計圖標(biāo)注的是px.每次設(shè)置百分比的時候需要去計算.
設(shè)置百分比還是依賴父容器的.導(dǎo)致scrollView ListView 內(nèi)的高度無法使用百分比.
這種方案用的人比較少 不推薦使用.
五. 張鴻洋提出的 AutoLayout 全新適配方式.
使用px單位并不是像素.內(nèi)部經(jīng)過處理.變成相應(yīng)的百分比. 寬和高都是百分比. 寬的1px和高的1px不相同;
https://github.com/hongyangAndroid/AndroidAutoLayout
鴻洋大神的這套方案用的很多 . 雖然最后停止維護(hù)了,
優(yōu)點(diǎn): 配置簡單, 使用起來可以直接用px對著設(shè)計稿直接寫.
缺點(diǎn): 在一些復(fù)雜布局, 帶一些自定義控件的布局時候容易出現(xiàn)適配問題.
而且在實(shí)際項(xiàng)目使用的時候 有偶發(fā)失效的情況. 失效后, 界面由于px做單位. 界面元素變的很小.
這方案可以考慮使用. 畢竟在項(xiàng)目中運(yùn)用廣泛. 需要注意的是一些自定義布局的處理, 在列表中itemview需要通過工具類額外處理一下.
六. 今日頭條方案:
其實(shí)以上幾種方案都不是很好, 在項(xiàng)目中有各自缺點(diǎn). 目前最推薦的還是頭條的方案;
參考鏈接: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
工具類如下:
/**
* @param activity
* @param application
* @param isLandscape 是否是橫屏
*/
public class ScreenUtils {
/** 設(shè)計稿標(biāo)準(zhǔn) */
private static final float width = 750f;
private static final float high = 1334f;
private static float textDensity = 0;
private static float textScaledDensity = 0;
/**
* 今日頭條的屏幕適配方案
* 根據(jù)當(dāng)前設(shè)備物理尺寸和分辨率去動態(tài)計算density巡李、densityDpi黄琼、scaledDensity
* 同時也解決了用戶修改系統(tǒng)字體的情況
* @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); //當(dāng)前UI標(biāo)準(zhǔn)750*1334
} else {
targetDensity = displayMetrics.widthPixels / (width / 2); //當(dāng)前UI標(biāo)準(zhǔn)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);
//在設(shè)置布局之前調(diào)用工具類方法
ScreenUtils.setCustomDensity(this);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
int densityDpi = getResources().getDisplayMetrics().densityDpi;
tv.setText("邏輯像素密度 dpi " + densityDpi);
}
}
看看實(shí)際運(yùn)行效果:
實(shí)際效果中可以看到很好的完成了適配
之前我們分析了dp不太適配的原因. 所以今日頭條方案簡答暴力.
既然rom中設(shè)置的dpi不是實(shí)際像素密度. 那么我直接把dpi給你改成實(shí)際像素密度.
果然夠簡單暴力的...
工具類比較簡單,復(fù)制就可以用. 就不上傳github了.
五. 最后總結(jié)和思考:
實(shí)際開發(fā)中我們直接使用今日頭條方案就可好了, 可以滿足需求.
似乎我們已經(jīng)找到了完美的適配方案了.
但是? 但是了. 但是這個所謂的適配的真的就是谷歌的個各個手機(jī)廠商想看到的么?
廠商定義dpi沒有按照實(shí)際的來定義 而是做了調(diào)整, 調(diào)整之后的好處就是就是希望在大屏手機(jī)上可以看更多的內(nèi)容. 在圖三中我們發(fā)現(xiàn)nexus-5x 比nexus-5 屏幕大一些 dpi值廠商設(shè)置的也diy低一些, 更具公式px=dpi/160*dp 得知這樣px就變小, 內(nèi)容占的地方就小, 可以容納更多內(nèi)容. 這就是為了讓大屏手機(jī)看到更多的內(nèi)容. 為了驗(yàn)證這個猜想我們再看看更大手機(jī)的顯示情況
圖五中左側(cè)是通過工具類轉(zhuǎn)換后的顯示, 完成了所謂的適配.
右側(cè)是本來的樣子. 可以看到右側(cè)中textview占據(jù)的屏幕空間確實(shí)要小一些.
圖六中的表格看到越是大屏手機(jī), 廠商對dpi的標(biāo)定比實(shí)際小的越多, 這樣就可以讓屏幕顯示更多的內(nèi)容.特別是看電子書之類的內(nèi)容時候體驗(yàn)更明顯, 大屏手機(jī)一頁可以顯示更多的內(nèi)容. 大屏幕的優(yōu)勢就凸顯出來了.
但是我們開發(fā)的普通應(yīng)用為了在大屏小屏上一致的體驗(yàn). 就不得不去修改dpi為實(shí)際值了.
以上是我的對Android中適配的一些思考和總結(jié), 希望對大家有幫助~