杂篇:理一理屏幕尺寸那些事

4,397 阅读11分钟

注:本文的目的在于理清楚一些尺寸关系,如果有表述不当,欢迎指出讨论

本文测试屏幕的长宽像素比为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
oppoR15X1080px2340px6.4in16.256cm6.81cm14.76cm
OPPO R801320px480px3.5in8.89cm4.93cm7.40cm

3.屏幕属性的封装

这么多属性,一个一个算也怪累人的,计算器点着也不爽,封装一下吧ScreenInfo.java

/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/12/1 0001:8:01<br/>
 * 邮箱:1981462002@qq.com<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^2250个/mm^2

那么OPPOR801中1mm的长度排多少个像素,显而易见:576px/163mm =  6.471...  取个整 6.5
那么 1 平方毫米能容下几个像素呢? 6.5*6.5 = 42.25个/mm^242个/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的意思就是每英寸墨滴的个数为300300个点表示一个硬币,和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--github2018-12-4理一理屏幕尺寸那些事
2.更多关于我
笔名QQ微信爱好
张风捷特烈1981462002zdl1994328语言
我的github我的简书我的掘金个人网站
3.声明

1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持


icon_wx_200.png