問題
通常在寫布局的時候,我們用相對布局宴咧、權(quán)重比來解決因為屏幕尺寸大小不一帶來的控件擺放問題,而大控件的寬高度量使用dp毕源、文字大小使用sp它抱,通常情況下dp和sp是一樣的秕豫、例如10dp和10sp在屏幕上顯示效果一樣,只不過當系統(tǒng)修改文字大小時观蓄,使用sp標量的控件都跟隨系統(tǒng)發(fā)生變化混移。不論是dp還是sp,最終要在頁面上渲染出來前都會被轉(zhuǎn)成像素單位px侮穿。
1歌径、哪為什么一開始不使用像素作為控件的寬高的單位呢?
2亲茅、dp和px換算關(guān)系呢沮脖?
3金矛、同等dp在不同設(shè)備上能保證控件大小一致嗎?
解決這些疑問前先來復(fù)習(xí)一下Android屏幕的幾個概念勺届。
概念
屏幕分辨率:1920*1080標識高上有1920個像素點、寬度上有1080個像素點
屏幕尺寸 屏幕對角線的長度(單位inch)
屏幕像素密度dpi 計算公式
dip為160娶耍,則剛好 1dp = 1px免姿。
接下來就可以解決上訴提出的三個問題
1、dp和px換算關(guān)系
px = dp * (dip / 160)
安卓中定義了一個系數(shù)density
density = (dip / 160)
px = density * dp
2榕酒、假設(shè)屏幕大小相同胚膊,若屏幕分別率越高,那么在相同的區(qū)域內(nèi)就得放下更多的像素點想鹰、意味著屏幕密度越大紊婉,像素點就得越小,反之辑舷,像素點就越大喻犁,密度越小,像素點就越大何缓;所以同樣畫一段長度100px的線段在高分辨率下肢础,看上去就比在低分辨率屏幕下短,所以在寫布局時不會采取像素來度量控件寬高碌廓。
3传轰、最后一個問題 “同等dp在不同設(shè)備上能保證控件大小一致嗎?”
理想狀態(tài)狀態(tài)下是可以基本保持一致谷婆。但是安卓手機碎片化嚴重慨蛙,有很多奇怪尺寸出現(xiàn)。通過對比纪挎,可以明顯發(fā)現(xiàn)同樣的相同的dp華為設(shè)備上UI顯示比較粗大
由于屏幕尺寸期贫、分辨率和像素密度的關(guān)系,很多設(shè)備并沒有按此規(guī)則來實現(xiàn)廷区, 因此dpi的值非常亂
我現(xiàn)在手上有兩臺pad設(shè)備,屏幕分辨率1920*1200唯灵,一臺是華為C5pad,另一臺是三星pad隙轻。三星是9寸多埠帕,華為是8寸多。
今日頭條對此給出分析和解決方案玖绿,造成這樣問題是UI設(shè)計假設(shè)按寬度360dp這一尺寸設(shè)計的敛瓷,而有的設(shè)備實際上寬度比360dp還大,有的比360小斑匪。這種情況下呐籽, 即使使用dp也是無法在不同設(shè)備上顯示為同樣效果的。 同時還存在部分設(shè)備屏幕寬度不足360dp,這時就會導(dǎo)致按360dp寬度來開發(fā)實際顯示不全的情況狡蝶。
梳理需求
首先來梳理下我們的需求庶橱,一般我們設(shè)計圖都是以固定的尺寸來設(shè)計的。比如以分辨率1920px * 1080px來設(shè)計贪惹,以density為3來標注苏章,也就是屏幕其實是640dp * 360dp。如果我們想在所有設(shè)備上顯示完全一致奏瞬,其實是不現(xiàn)實的枫绅,因為屏幕高寬比不是固定的,16:9硼端、4:3甚至其他寬高比層出不窮并淋,寬高比不同,顯示完全一致就不可能了珍昨。但是通常下县耽,我們只需要以寬或高一個維度去適配,比如我們Feed是上下滑動的曼尊,只需要保證在所有設(shè)備中寬的維度上顯示一致即可酬诀,再比如一個不支持上下滑動的頁面,那么需要保證在高這個維度上都顯示一致骆撇,尤其不能存在某些設(shè)備上顯示不全的情況瞒御。同時考慮到現(xiàn)在基本都是以dp為單位去做的適配,如果新的方案不支持dp神郊,那么遷移成本也非常高肴裙。
因此,總結(jié)下大致需求如下:
支持以寬或者高一個維度去適配涌乳,保持該維度上和設(shè)計圖一致蜻懦;
支持dp和sp單位,控制遷移成本到最小夕晓。
解決突破口
從dp和px的轉(zhuǎn)換公式 :px = dp * density
可以看出宛乃,如果設(shè)計圖寬為360dp,想要保證在所有設(shè)備計算得出的px值都正好是屏幕寬度的話蒸辆,我們只能修改 density 的值征炼。
通過閱讀源碼,我們可以得知躬贡,density 是 DisplayMetrics 中的成員變量谆奥,而 DisplayMetrics 實例通過 Resources#getDisplayMetrics 可以獲得,而Resouces通過Activity或者Application的Context獲得拂玻。
先來熟悉下 DisplayMetrics 中和適配相關(guān)的幾個變量:
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi
DisplayMetrics#scaledDensity 字體的縮放因子酸些,正常情況下和density相等宰译,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值
下面給出代碼,只有一個方法魄懂,對dp進行修改沿侈。
//三星pad寬度1280dp (是dp,是不px) 市栗,SCREEN_WIDTH_DP根據(jù)不同的設(shè)計圖修改肋坚,手機一般是360
private final static int SCREEN_WIDTH_DP = 1280;
private static float sNoncompatDensity;
private static float sNoncompatScaleDensity;
public static void setCusomDensity(final Activity activity,final Application application){
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sNoncompatDensity == 0){
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaleDensity = appDisplayMetrics.scaledDensity;
//監(jiān)聽系統(tǒng)改變字體的大小
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig !=null &&newConfig.fontScale>0){
sNoncompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
Log.e("tag","sNoncompatScaleDensity:"+sNoncompatScaleDensity);
}
}
@Override
public void onLowMemory() {
}
});
}
//根據(jù)參考的適配寬度 計算新的Density、ScaleDensity肃廓、DensityDpi
float targetDensity = (float) appDisplayMetrics.widthPixels/SCREEN_WIDTH_DP;
float targetScaleDensity = (float)targetDensity*(sNoncompatScaleDensity/sNoncompatDensity);
int targetDensityDpi = (int) (160*targetDensity);
//修改全局的
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaleDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
//修改當前activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
在要在UI布局渲染前調(diào)用即可
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ScreanAdapterUtils.setCusomDensity(this,MyAplication.getApplication());
setContentView(R.layout.activity_main);
}
今日頭天適配方案https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
問題
但是這個方法會導(dǎo)致一些比較大的對話框出現(xiàn)暫時問題,對話框不能全名展示诲泌。需要從新計算對話寬的寬度和高度盲赊,當然一些小對話的的展示不受影響。
直接給出代碼
public static void dialogAdapter(WindowManager windowManager, Dialog dialog,float heightScale,float widthScale){
Point point = new Point();
//獲得代表當前window屬性的對象
Window window = dialog.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
//獲取window的寬高信息
Display display = windowManager.getDefaultDisplay();
display.getSize(point);
// 將設(shè)置后的大小賦值給window的寬高
if (widthScale!=0){
params.width = (int) (point.x * widthScale);
}
if (heightScale!=0){
params.height = (int) (point.y * heightScale);
}
//設(shè)置屬性
window.setAttributes(params);
}
在UI渲染之后調(diào)用