1. 程式人生 > >理一理螢幕尺寸那些事

理一理螢幕尺寸那些事

注:本文的目的在於理清楚一些尺寸關係,如果有表述不當,歡迎指出討論


一、科普常識:

0.測試準備

手上有兩個真機: oppoA77(1920*1080 5.5英寸)、 oppoR15X(2340*1080 6.4英寸) 、
再加一臺模擬器(480*320 3.5英寸)仿OPPO R801
輔助:一臺膝上型電腦聯想Y480N(768*1366 14英寸) 和一個iPad_Air_2(2048*1536 9.7英寸)

測試資源.png


1.按照畫素來算引出的問題(反證法):

如果,我說如果一個畫素代表n個物理毫米,當n等於1時
那麼oppoR15X(2340*1080)相當於23.4*10.8cm

,量了一下,大概跟書差不多
OPPO R801(480*320)相當於(4.8*3.2cm),量了一下,差不多跟橡皮一樣大

畫素級截圖比較.jpg

在同一參考系下,玩oppoR15X和OPPO R801,相當於玩一本書和玩橡皮的區別
顯然我並沒有這樣的體驗,這隻能說明,對於兩個不同的手機,它們的n值不同
也就是兩個手機:1個物理毫米中所含的畫素個數是不同的


2.手機英寸的概念

英寸是衡量手機螢幕的真實大小
我們買手機一般關心的是手機是多少多少英寸的,然後懂行的看看解析度,那英寸代表什麼?
1英寸 = 2.54 釐米: 大概和一元硬幣的直徑差不多,不信你拿六個硬幣排在對角線比一下

英寸.png

由於解析度確定了長寬比,我們不難算出oppoR15X和OPPO R801的物理寬高

\ 寬px 高px 對角線in 對角線cm 物理寬cm 物理高cm
oppoR15X 1080px 2340px 6.4in 16.256cm 6.81cm 14.76cm
OPPO R801 320px 480px 3.5in 8.89cm 4.93cm 7.40cm

3.螢幕屬性的封裝

這麼多屬性,一個一個算也怪累人的,計算器點著也不爽,封裝一下吧ScreenInfo.java

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/12/1 0001:8:01<br/>
 * 郵箱:[email protected]<br/>
 * 說明:螢幕尺寸資訊
 */
public class ScreenInfo {
    public static final float INCH_TO_MM = 25.399999961392f;//英寸轉為毫米數
    public String name;//裝置名稱
    public float inchC;//英寸
    public int pxW;//螢幕寬畫素數
    public int pxH;//螢幕高畫素數

    public int pxC;//對角線畫素數
    public float ppi;
    public float dpi;
    public float relW;//實際寬度
    public float relH;//實際高度
    public float relC;//實際對角線長度

    public ScreenInfo() {
    }
    public ScreenInfo(String name, float inchC, int pxH, int pxW) {
        this.name = name;
        this.inchC = inchC;
        this.pxW = pxW;
        this.pxH = pxH;
        pxC();
        ppi();
        dpi();
        relC();
        relW();
        relH();
    }
    private int pxC() {
        pxC = (int) Math.sqrt(pxH * pxH + pxW * pxW);
        return pxC;
    }
    private float ppi() {
        ppi = pxC() / inchC;
        return ppi;
    }
    private float dpi() {
        dpi = pxC() / inchC;
        return dpi;
    }
    private float relW() {
        relW = relC * (pxW * 1.f / pxC());
        return relW;
    }
    private float relH() {
        relH = relC * (pxH * 1.f / pxC());
        return relH;
    }
    private float relC() {
        relC = inchC * INCH_TO_MM;
        return relC;
    }
    @Override
    public String toString() {
        return "ScreenInfo{" +
                "\nname='" + name + '\'' +
                "\n, 螢幕尺寸/英寸=" + inchC +
                "\n, 螢幕橫向畫素數=" + pxW +
                "\n, 螢幕縱向畫素數=" + pxH +
                "\n, 螢幕對角線畫素數=" + pxC +
                "\n, ppi=" + ppi +
                "\n, dpi=" + dpi +
                "\n, 螢幕對角線物理尺寸/mm=" + relC +
                "\n, 螢幕橫向物理尺寸/mm=" + relW +
                "\n, 螢幕縱向物理尺寸/mm=" + relH +
                '}';
    }
}
複製程式碼

尺寸解析.png

好吧,知道大家不喜歡看資料,於是我自定義了一個View,用ScreenInfo資訊畫個圖示,感覺還蠻好的
三行程式碼就能畫一個手機資訊圖,也不是很難,有興趣的可以看看原始碼,或自己畫畫
我是按照物理尺寸畫的,所以現實中它們的螢幕相對大小就是這樣的!

螢幕尺寸.png

如果你想玩,其他的螢幕也可以試試:只要知道解析度和多少英寸

檢視其他螢幕尺寸.png


4.密度:
什麼是密度?----緊密程度?

上學的時候應該聽過線密度,面密度和體密度、或者人口密度吧。
比如一個市的人口密度:合肥市面積為11445.1平方公里,人口為779萬,人口密度為680.6人/平方公里
也就是合肥市平均 1平方公里 有680.6人
複製程式碼
現在把螢幕當做土地,把畫素點當做人,這些人一個一個有秩序地排著 
那麼OPPOR15X中1mm的長度排多少個畫素,顯而易見:2577px/163mm = 15.809...  取個整 15.8
那麼 1 平方毫米能容下幾個畫素呢? 15.8*15.8 = 249.64個/mm^2  約250個/mm^2

那麼OPPOR801中1mm的長度排多少個畫素,顯而易見:2577px/163mm =  6.471...  取個整 6.5
那麼 1 平方毫米能容下幾個畫素呢? 6.5*6.5 = 42.25個/mm^2  約42個/mm^2

相當於在一片等大的土地上,一塊佔了250個人,一塊佔了42個人,神奇的是兩邊都把這塊地佔滿了
於是真相(得出的結論)只有一個:兩塊土地上一塊是小人,一塊是巨人  
複製程式碼

現在把物理尺寸:1mm的螢幕放大

1px.png

記得小時候的手機肉眼就能看到一點一點的畫素,就是因為1mm裡的畫素點少,相對而言一粒畫素就大
現在的手機可是瞅不出來畫素了


5.ppi與dip

現在我們手上的資訊還蠻多的,這些資訊有什麼用?

ppi(Pixel Per Inch),即每英寸的畫素。
我們剛才好像算了每毫米的畫素數,那每英寸的畫素數能難倒你嗎?
OPPO-R15X 的 ppi : 2577px/6.4in = 402.65625 px/in   約402.6ppi
OPPO-A77 的 ppi : 2202px/5.5in = 400.363... px/in   約400.4ppi
OPPO-R801 的 ppi : 576px/3.5in = 164.571... px/in   約164.6ppi
複製程式碼
ppi形象一點的比喻:

一個一元硬幣直徑約1 in,現在讓一元硬幣(包括背景)等大顯示在三個手機上:
OPPO-R15X需要用:402*402=161604 個畫素點
OPPO-A77需要用:400*400=160000 個畫素點
OPPO-R15X需要用:164*164=26896 個畫素點
我們知道畫素組成了顯示的圖片,也就是說用161604個點和26896個點組成相同的畫面
那麼26896的那個看起來效果自然要比161604的差很多,161604更加緊密,所以視覺感好

ppi什麼意思.png

來分析一下膝上型電腦:

ppi=1567/14=111.928...,也就是 1 in 裡有112個點,1 in = 25.4mm
那說明一個畫素的大小是 25.4 / 112 = 0.226mm ,人眼可視長度是0.1mm,所以你近些看可以看到顆粒

筆記本螢幕分析.png


dpi(Dot Per Inch),即每英寸的點數。
dpi又是什麼鬼,點數又是什麼鬼?---dpi稱為列印精度

印表機將[彩色液體油墨]經噴嘴變成細小微粒噴到印紙上,一個顆粒代表1點  
dpi的意思是每英寸墨滴點數,比如300dpi的意思就是每英寸墨滴的個數為300
用300個點表示一個硬幣,和72個點表示一個硬幣,可想而知300的更加精細

大學時做要列印的ps產品效果圖都要把圖片的dpi調到300以上,因為大幅的海報需要細緻的畫素表達
普通的web圖片只要求72dpi就夠了,因為只是顯示在螢幕上而言 
複製程式碼
ppi和dpi在Android
Android又不是印表機,dpi和ppi等價,都是表示 1 in長度對應的px數  
也許谷歌更傾向於用`點(dot)` 來表述螢幕畫素,所以採用dpi的說法而不是ppi
複製程式碼

二、誰動了我的圖片尺寸?

測試圖片.png

1.我的250*200的圖片畫出來怎麼尺寸不對?----Q1

實驗圖片:250*200,300dpi,res\mipmap-xhdpi\wy_250px_200px_300dpi.jpg

mipmap_xhdpi_250px_200px_300dpi.png

實驗圖片:250*200,72dpi,res\mipmap-xhdpi\wy_250px_200px_72dpi.jpg

mipmap_xhdpi_250px_200px_72dpi.png

這是挺糾結的一個問題,我預想的是在小手機上圖片250px應該會很大
為什麼並不是我所預料的那樣?而且自定義的圖片dpi被無視了?----Q2


2.我懷著滿心的疑問將圖片拷貝到drawable裡

實驗圖片:250*200,72dpi,res\drawable\wy_250px_200px_72dpi.jpg

drawable_250px_200px_72dpi.png

實驗圖片:250*200,300dpi,res\drawable\wy_250px_200px_300dpi.jpg

drawable_250px_200px_300dpi.png

貌似對dpi還是免疫,而且OPPO-R15X圖片按比例放大了,分別彈了一個metric.density,一個3,一個1
哥就想都顯示250*200為什麼這麼難?----Q3


3.懷著疑問,分別將圖片在mipmap各個資料夾放一遍

測試結果如下,並畫了一張高度變動的簡單示意圖

高度分析.png

可以看出:
1.OPPO-R15X在xxh的時候顯示原圖,OPPO-R801在m的時候顯示原圖
2.每種情況下OPPO-R15X的高度總是OPPO-R801的3倍
結論:OPPO-R15X自身dpi(ppi)為402,被圈入了xxh的領域,OPPO-R801自身dpi(ppi)為164,被圈入了m的領域
xxh對應的dpi/m對應的dpi = 3
複製程式碼

資料.png

Q1:誰動了我的圖片尺寸
---mipmap的不同資料夾,Android會區分對待

Q2:而且自定義的圖片dpi被無視了?
----圖片自身的dpi對螢幕裝置的顯示並沒有效果,只對列印有影響

Q3:哥就想都顯示250*200為什麼這麼難?
---- 難! 但是也沒有這個必要,你非怎麼想,在所有的mipmap資料夾都放一張圖,
然後所有手機會顯示250*200,不過有人打你不關我事...  
複製程式碼

4.獲取不在mipmap裡的圖片會怎麼樣?

這個問題問的好,程式碼測試走一波
不出所料,從檔案讀取的圖片,沒走mipmap,所以原畫素顯示

load_form_file.png

總結:mipmap會根據圖片的資料夾位置對圖片在不同density裝置上進行不同的縮放,也就是"自動適配"
只限獲取圖片時或使用時warp_content


三、看看那些尺寸

1.dp:

想必大家這個方法都用過

protected float dp(float dp) {
    return TypedValue.applyDimension(
    TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
複製程式碼

來看看它到底幹了什麼:applyDimension

public static float applyDimension(int unit, float value,DisplayMetrics metrics){
    switch (unit) {
    case COMPLEX_UNIT_PX:
        return value;
    case COMPLEX_UNIT_DIP:
        return value * metrics.density;//將值*density返回
    case COMPLEX_UNIT_SP:
        return value * metrics.scaledDensity;
    case COMPLEX_UNIT_PT:
        return value * metrics.xdpi * (1.0f/72);
    case COMPLEX_UNIT_IN:
        return value * metrics.xdpi;
    case COMPLEX_UNIT_MM:
        return value * metrics.xdpi * (1.0f/25.4f);
    }
    return 0;
}
複製程式碼

metrics.png

程式碼追蹤:
--> getResources().getDisplayMetrics()

-->[android.content.res.Resources#getDisplayMetrics]
public DisplayMetrics getDisplayMetrics() {
    return mResourcesImpl.getDisplayMetrics();
}

-->[android.content.res.ResourcesImpl#getDisplayMetrics]
DisplayMetrics getDisplayMetrics() {
    if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
            + "x" + mMetrics.heightPixels + " " + mMetrics.density);
    return mMetrics;
}

-->[現在要看: mMetrics.density被賦值時]
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
    mMetrics.densityDpi = mConfiguration.densityDpi;
    mMetrics.density =
            mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
}

搜尋到:mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
複製程式碼

mMetrics.density.png

我們被奉為靈丹妙藥的dp只不過獲取了DisplayMetrics.DENSITY_DEVICE,再乘以0.00625(即除以160)
而且這個DisplayMetrics.DENSITY_DEVICE也並非螢幕真正的dpi(ppi),400的,402的都算是480,
所以dp的計算方式也是一個滿足大眾需要的約值,雖然有一定的效果,但並不能完美適配。


真的有完美的適配嗎?

除非你能讓長寬是100*180的紙片能夠恰好裝滿長寬是100*200盒子而且沒有變形
或者讓所有的安卓手機廠家生產相同比例的手機,否則無論怎麼配會有瑕疵,
魚和熊掌不可兼得 ,但捨生和取義之間還有平常地活著(儘管並不完美)

我曾經有想過,為什麼手機不是圓形的,如果是圓形的就不需要適配了,永遠等比例  
也許兜裡放著佔地方吧,或者看電影,可用面積會比較少...感覺好可惜
複製程式碼

文字單位:sp
-->[android.util.TypedValue#applyDimension]
 case COMPLEX_UNIT_SP:
     return value * metrics.scaledDensity;

-->[android.content.res.ResourcesImpl#updateConfiguration]
 mMetrics.scaledDensity = mMetrics.density *
    (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
    
//現在焦點移到:mConfiguration.fontScale != 0
//如果是假的,那就和mMetrics.density的值相同,否則是mConfiguration.fontScale
複製程式碼

其中configuration.fontScale是根據系統字號改變的,預設是1,所以會遇到dp和sp混用無影響的情況。但,一旦使用者改變了系統字號,有一定的縮放量,dp的為sp就原形畢露了,所以字型還是乖乖用sp,別沒事找事。


關於適配

個人覺得只要多用控制元件之間進行尺寸依賴關聯,也就是約束佈局
dp雖然不是靈丹妙藥,但是也能治病救人,所以藥不能停,dp不能丟
match_parent是好東西,要懂得合理運用
注意左側和下側,儘量用父去約束,不然跑出去了……可是大忌 儘量避免使用非常大的dp(200+),可通過控制元件間相對位置將過大的dp約束,因為數值越大不同手機的差異性越明顯。


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1--github 2018-12-4 理一理螢幕尺寸那些事
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png