移动开发中“单位”的那些事儿

peixiaopao 2016-09-27

前言

在移动开发中我们常常会考虑度量单位的问题,在传统pc的web开发中用的比较多的是px(css像素),在Android开发中一般则用dp、sp、px(物理像素)等,而移动web开发中同样也是用px(css像素)做单位,通常会结合viewport进行缩放。本篇blog就梳理一下在移动开发中用到的各种单位以及他们之间的联系和区别。

绝对单位和相对单位

如题,首先来看看什么是绝对单位和相对单位:

  • 绝对单位:即值在任何环境下都不会变化的常量,如我们日常生活中经常会用到的in(inch英寸)、cm(厘米)、mm(毫米)、pt(磅,1磅等于1/72英寸)等,pt常用于标准印刷,这些绝对长度单位在界面设置中很少用到,仅作了解。

  • 相对单位:主要就是我们最熟悉的px,之所以是相对的因为它会随着设备和环境的变化而变化,后面再具体说明,还包括在css中常用到的em、rem等。

屏幕尺寸、分辨率与像素密度

这是第二个话题,首先来看看屏幕尺寸,屏幕尺寸实际指的是矩形屏幕对角线的长度,单位是英寸(in),1英寸=2.54厘米,例如常见的笔记本电脑尺寸是14英寸,而在手机中大屏幕普遍是5.5英寸以上,而小屏手机的代表iphone4s则是3.5英寸。

接下来看一下分辨率,屏幕分辨率指的是横纵向上的像素点数,单位是px,1px=1个像素点,一般以纵向像素*横向像素来表示完整分辨率,例如魅族最新的旗舰手机pro6的分辨率就是:1920*1080,也就意味着pro6的屏幕是由2073600个像素点组成的,也就是说即使屏幕尺寸一样,而像素不一样的话(例如:iphone3和iphone4都是3.5英寸,而前者的分辨率是480*320,后者则是960*640),所能容纳的像素点也是不一样的,这一点就充分说明了android开发中控件单位不能用px的原因,比如将一个TextView设置为500px*500px,在相同的屏幕尺寸情况下,分辨率为480*320的设备上的占屏比要比960*640的设备上的占屏比大一倍,我们来举个例子看看。

首先准备两台android模拟器,一个是Google Nexus 4,4.7寸屏,分辨率为1280*768:
移动开发中“单位”的那些事儿

另一台是HTC One XL,为了演示我将它的分辨率修改为1920*1080 - 480dpi,这样刚好能保证屏幕尺寸也为4.7,而且分辨率也高Nexus 4很多(关于为什么修改成这个分辨率以及dpi的概念稍后再说):
移动开发中“单位”的那些事儿

接下来双开这两台模拟器跑一个简单的android程序,仅仅是在一个线性布局中放了一个TextView,并将它的width和height都设置为500px,并设置了背景色方便观察效果:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="500px"
        android:layout_height="500px"
        android:text="abc" 
        android:background="#FFFF00"
        />

</LinearLayout>

接下来看一下这两台虚拟机的运行效果:
移动开发中“单位”的那些事儿

如上图,左边是高分辨率的HTC(1920*1080),右边是低分辨率的Nexus(1280*768),它们的尺寸都是4.7英寸,但通过观察发现明显左边的TextView占屏面积小,而又边的占屏面积大的多,这就是android中不能用px做单位的原因,好了,通过这个问题我们又引入了一个新的概念,就是屏幕像素密度(pixels per inch),也就是ppi/dpi

首先看一下概念,屏幕像素密度指的是屏幕上每英寸可以显示的像素点的数量,屏幕像素密度和屏幕尺寸以及屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,则像素密度就越大。计算密度的公式是:屏幕对角线分辨率/屏幕尺寸。以上面的两台虚拟机设备为例,4.7寸的HTC One XL的屏幕分辨率是1920*1080,那么它的屏幕像素密度就是:

 

x=1920移动开发中“单位”的那些事儿2移动开发中“单位”的那些事儿+1080移动开发中“单位”的那些事儿2移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿4.7移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿

 

通过勾股定理先算出对角线的像素,再除以屏幕尺寸,结果约等于470dpi,现在再回头看我刚才为什么要选1080*1920 - 480dpi呢?显而易见就是为了选一个约等于4.7英寸屏幕的设备,这样才方便和同是4.7英寸的Nexus进行比较,同理再计算一下Nexus 4的屏幕像素密度:

y=1280移动开发中“单位”的那些事儿2移动开发中“单位”的那些事儿+768移动开发中“单位”的那些事儿2移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿4.7移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿移动开发中“单位”的那些事儿

 

得到的结果是317.6,和设备参数中的320dpi基本一致:
移动开发中“单位”的那些事儿

现在再结合像素密度的概念想一下,我们在上面写的android程序中,之所以高分辨率的HTC中的TextView占屏比较小,正是因为它的像素密度大,所以固定的500*500px尺寸的TextView在它的屏幕里面就显得占的地方小,而Nexus 4的像素密度小,所以500*500px这个像素对于它来说占的地方就会更大。最后总结一句话就是:屏幕尺寸相同的情况下,分辨率越高,像素密度就越大

再看下面这张图应该就彻底清楚了它们的换算关系:
移动开发中“单位”的那些事儿

到这里这三个概念就算介绍完毕了,虽然本篇blog的重点不是介绍android中的单位,但说到这里,不得不提几个相关的单位,例如:dp、sp、dip等等。

android中的单位

如题,依次来看看google为android引入的单位:

  • dp:又称dip(ios中叫ppi),density independent pixels,即密度独立像素,它和px有一个根据dpi按比例的换算关系,是一个android标准,即:以160dpi为基准,1dp=1px。结合上一小节的内容再想想,用dp做单位的话就不会再出现上面的窘境,因为分辨率不同,dpi也就不同,那么换算的px也就不一样了,这样即使在不同分辨率的设备上也可以让控件看起来一样。
  • sp:与dp用法类似,只不过是android中专门用来定义文字大小的单位,即所有的fontSize都应用sp来度量。

android中主要就用到上面这两种单位,还有一点值得提一下就是做过android开发的话都知道在项目工程中有这样的目录:
移动开发中“单位”的那些事儿

理论上我们是应该在每个目录中放一套不同分辨率的图片,app会根据设备的分辨率自动进行加载,那么这些不同分辨率的数值标准又是什么呢?google官方给出了以下标准:
移动开发中“单位”的那些事儿

没错,依然是通过像素密度(dpi)来做划分,目前市场上的主流机型基本都在xxhdpi范围内,所以一般情况下在xxhdpi放一套图就能满足需求了。苹果手机则比较简单的分为非高清屏、高清屏和超高清屏,对应的机型有iphone3(163ppi)、iphone6(326ppi)和iphone6 plus(401ppi),都是类似的道理。梳理了一遍这些单位之后,接下来就是要介绍移动页面相关(mobile page)的单位以及viewpoint。

移动web单位和viewport

在移动web网页开发中用到的单位也是px,但和上面谈到的px要完全区分开,因为在安卓程序中谈到的px指的就是设备的物理像素,而在移动网页开发中css里的px像素只是一个抽象单位,在不同的设备或不同的环境中,css中的1px所代表的设备物理像素是不同的,它的名字叫“设备独立像素”,它是浏览器中使用的抽象单位,也叫“css逻辑像素”,而设备物理像素和设备独立像素的比值叫devicePixelRatio,它是移动浏览器中的window对象的一个属性。例如:在Retina屏的iphone上,devicePixelRatio的值为2,也就是说1个css像素相当于2个物理像素,同样的用一个例子来说明。

首选准备一个简单的页面,只放一个大小为1280px*768px的div:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Test 01</title>
        <style>
            .pic{
                width: 768px;
                height: 1280px;
                background-color: #FFE872;
                color: white;
                font-size: 150px;
                text-align: center;
                line-height: 1280px;
                font-family: cursive;
            }
        </style>
    </head>
    <body>
        <div class="pic"> Yellow </div>
    </body>
</html>

在PC浏览器中的效果图如下:
移动开发中“单位”的那些事儿

由于我们的PC显示器的分辨率为1366*768,所以在浏览器中显示纵向肯定会出现滚动条的,但之所以将这个div设置为1280*768是因为假如没有上述的“屏幕独立像素(css逻辑像素)”的概念,那么这个div应当在一个1280*768像素的手机上满屏显示,我们在上面创建的Google Nexus 4正好是这个分辨率:移动开发中“单位”的那些事儿,所以我们就在这个设备上看一下该页面的效果:
移动开发中“单位”的那些事儿

如上图所示,有留边,很明显并没有占满屏幕,这里我们先明确一点就是物理像素和逻辑像素存在明显差异,其次再看一下上面提到的dpr(缩放比)的问题,我们使用的虚拟设备Google Nexus 4的dpr也为2(看下图),所以说它实际的分辨率为640*384,那么问题来了,我的图片是768*1280px,那么在640*384的小屏幕上为什么还显示不全呢?理论上应该不但能全显示,而且只能显示图片总面积的1/4(经纬度分别为1/2,所以总面积为1/4)呢?这里明确两点,首先我们说的这一点没错,看下面这幅图:
移动开发中“单位”的那些事儿

首先有一个小问题就是chrome的手机调试工具中的Nexus 4 为384*567而并非384*640,这个不用在意,安卓模拟器中的Nexus 4是768*1280px,版本问题而已。观察上面的chrome手机调试工具中的效果(page sacle=1.0表示正常页面比例,即0缩放),C、D标注是横纵向的两个滚动条,也就是说我滚动到刚好能看到这个div的右下部分,同时再看A、B标注的标尺部分,可视范围内,横向是400到800,纵向是750到1350,也就是说横纵差不多都显示了1/2,也就是说图中的可视面积正是div总面积的1/4,符合我们上面的计算,这是第一点,接下来再考虑安卓模拟器中为什么没有和上面一样,而是没有占满屏幕呢?原因正是因为viewport的存在,接下来具体介绍一下viewport的概念。

介绍viewport的概念之前再看一个例子,我们在手机浏览器中打开一个PC网站时我们发现可以看到网页的全貌,而并非出现了滚动条:
移动开发中“单位”的那些事儿

如上图,左边是chrome的手机调试工具,右边的是Nexus 4的安卓模拟器,之所以能看到1000多像素宽的PC网页的全貌是因为viewport的存在,手机浏览器将PC页面渲染在一个比较大的viewport里面(android4.x普遍为980px,ios也为980px),然后再进行缩放,就能看到整个页面的全貌了。所以明确一下手机浏览器的工作分为两步:

  1. 将页面渲染在一个比较大的viewport里面并进行缩放(这个viewport也叫layout viewport),保证页面排版以及css布局不会乱。
  2. 通过大小等于手机浏览器可视区域的viewport(这个viewport也叫visual viewport)选取可视区域进行展示。

OK,接下来就具体介绍一下viewport的分类,viewport分为layout viewport和visual viewport,layout viewport可以理解为一个大的窗体,处在底层,包含整个PC浏览器页面,如下图所示:
移动开发中“单位”的那些事儿

layout viewport的值可以通过以下js代码来获取:

document.documentElement.clientWidth

而visual viewport则代表手机浏览器当前可视区域的窗体,处在上层,如下图所示:
移动开发中“单位”的那些事儿

visual viewport的值可以通过以下js代码来获取:

window.innerWidth

接下来就分别看一下Nexus 4下的layout viewport和visual viewport的值:
移动开发中“单位”的那些事儿

如上图所示,都是980px,至此就可以解释最开始的那个问题——1280*768px的div为什么不能在Nexus 4上全屏显示。答案很简单,默认的layout viewport的值是980px,而这个图片的宽度是768px,所以无法铺满屏幕。然而在实际开发中我们并不会用默认的980px的viewport,原因很简单,上面显示pc页面的效果虽然没有滚动条可以看到页面的全貌,但是可以发现缩放的太小了以至于有些看不清,这样明显是不合适的,所以在实际开发中我们都会自定义layout viewport的值从而得到合适的缩放效果,那么该如何自定义layout viewport的值?很简单,都过设置Meta标签就可以完成。

下面就介绍一下如何通过Meta标签来设置layout viewport的值,Meta viewport有以下6个属性值:

Content属性value
width设置layout viewport 的宽度,可以是固定值或“width-device”
initial-scale设置页面的初始缩放值
minimum-scale允许用户的最小缩放值
maximum-scale允许用户的最大缩放值
user-scalable是否允许用户进行缩放,值为“no”或“yes”

再看一下Meta标签的格式:

<meta name="viewport" content="width=device-width">

如上所述,设置width的值为device-width(即设备宽度)就表示将layout viewport的值设置为设备宽度,比如Nexus 4就是384px,加上这个Meta标签后我们再看看上面Yellow的效果,看之前首先要理清,div的宽度依旧是1280*768px,而我们chrome手机调试工具选择的设备是Nexus 4,也就是说此时的layout viewport的值是384px,在html源文件中添加<meta name="viewport" content="width=device-width"> 后再看看渲染的效果:
移动开发中“单位”的那些事儿

如上图所示,layout viewport的值变成了384px,和Nexus 4的设备宽度保持一致,而visual viewport的值变成了776px,因为我们页面的div给的是768px,所以为了容纳下这个div我们的visual viewport的值就为776px,如标尺上的一致,注意第三行js:

window.innerWidth/document.documentElement.clientWidth

用visual viewport除以layout viewport得到的就是页面的缩放比,这个缩放比是手机浏览器通过页面大小和设备宽度自动计算的,通俗的讲,上面这一情况可以理解为:为了在384px的Nexus 4中完全显示1280*768的这个div,那么只有通过缩放,缩放比是2.02,也就是说当我们的div的像素更大时,这个缩放比也会越大,在实际开发中显然这样任由它缩放是不合理的,我们应该控制这个缩放系数,通常指定值为1:

<meta name="viewport" content="width=device-width,initial-scale=1">

在html源文件中添加如上修改,再次看一下渲染后的效果:
移动开发中“单位”的那些事儿

如上图,我们指定了initial-scale为1,可以看到layout viewport和visual viewport的值都为384了,这也就回归了我们最最原始的预期,在这个384*567的Nexus 4上只能显示1280*768像素的这个div总面积的1/4,所以我们在移动web开中关于viewport最佳的设置方案就是:layout viewport=设备宽度=visual viewport,对应的Meta标签代码就是:

<meta name="viewport" 
      content="width=device-width,
               initial-scale=1,
               user-scalable=no">

最后一点,为了让这个div能在Nexus 4上正常铺满显示,我们应该合理设置它的宽度和高度,即将它设置为384*567px,如下图所示就完美的刚好铺满了屏幕:
移动开发中“单位”的那些事儿

至此关于viewport以及移动开发中的单位就全部介绍完毕了。

总结

简单记录一下移动开发中用到的各种单位以及移动web开发中viewport的相关概念和应用,希望对遇到类似问题的同学有所帮助,The End。

相关推荐