看了这篇文章,我的心得有几个:

1、之所以使用dp,是为了保证控件的长度一致

2、像素一样,dpi不一样,那么长度不同

3、dp一样,dpi不一样,长度相同


所以相同的dp在不同的手机上看起来长度是一样的,而我在设置间隔的时候如果设置为具体数值的dp,那么其间隔长度也是一样的,这样比较小尺寸的手机就会出问题了



屏幕分辨率
首先要了解Android屏幕分辨率,从density来看,常见的分辨率对应关系有:
hdpi = 480x800 (如Nexus One/Nexus S)
mdpi = 320x480 (如HTC Wildfire S G13)
ldpi = 240x320 (如HTC G8)
除此之外,还有xhdpi = 960x640 (如魅族M9)
但以上只是常见的而已,实际分辨率像素数量与density无关,例如SonyEricsson X10分辨率是480x854也是hdpi,Kindlefire 的分辨率是 1024x600但实际是mdpi(large-mdpi)。另外还有各种山寨诡异的屏幕分辨率,暂且不说。

更多的参数关系可以参考:
http://developer.android.com/guide/practices/screens_support.html

什么是DP
有了以上知识之后,再看看dp。dp的意义是按照mdpi下px:dp = 1:1换算,其他分辨率按照density比例算出来(xhdpi=320,hdpi=240,mdpi=160,ldpi=120),如果不想记density值,则按照标注屏幕宽度比例换算也可以(对large-*dpi无效),也就是说:
mdpi下屏幕宽度为320px,所以就是320dp,半个屏幕自然就是160dp
hdpi下dp:px = 1:1.5 (按照屏幕宽480/320=1.5算出来),所以160dp也等于半个屏幕宽
ldpi下dp:px = 3:4 = 1:0.75 (按照240/320=0.75算出来),所以160dp也等于半个屏幕
……
其他算法也一致,所以对于常见的标准分辨率来说半个屏幕宽度就是160dp。

其他可能的问题
但仍需要考虑很多特殊情况,例如:对于像Kindlefire这种large-mdpi来说,160dp=160px,而人家实际宽度为600,就肯定是不对的。如果横屏,横屏对于480x800的hdpi的一半是400px/1.5 = 267dp,而对于480x854的hdpi的一般则是427px/1.5 = 285dp,所以会出现很多问题。

动态代码处理
所以,最为保险的方式,是在代码中,调用:


DisplayMetrics metrics = new DisplayMetrics();


 

getWindowManager().getDefaultDisplay().getMetrics(metrics);


 

int screenHalfWidth = metrics.widthPixels / 2;




然后修改view的大小:


view.setLayoutParams(new LayoutParams(screenHalfWidth, LayoutParams.WRAP_CONTENT));




关于DisplayMetrics的使用,可以参考http://developer.android.com/reference/android/util/DisplayMetrics.html 它m里面可以获取很多与屏幕相关的内容,包括density比例,当前density等





——————————————————————————————————————————————————————————————————————

今天偶然间问了同事一个关于dp单位的问题,然后由这个问题引发的一连串的问题彻底颠覆了我关于dp的理论体系。


我那个问题是这样的:既然dp的本质是物理尺寸,为什么不用cm或者mm等传统长度单位替代?


然后他回答我dp是和像素密度无关的。。。我对这个回答不屑一顾,不过他接下来的一句话把我彻底震惊了,那句话是这样的:在你的手机上320dp就刚好满屏了,310dp就差一点点满屏。


我的手机是HTC Desire,这个理论我闻所未闻,然后马上做了个小实验,事实确实是这样,把一个TextView背景设成红色,宽度设成320dp,能看到满屏,310dp就差那么一点点。


看到这个测试结果的时候,我再一次崩溃了,我希望同事第二句话是一个美丽的错误,我无法接受这么久以来我理解的东西是错误的,可是事实是残酷的。


Android Developers关于dp的文档我看过N次,那个px和dp的转换公式我记得很清楚: px = dp * (dpi / 160),可是今天翻了源码了才发现,原来这里的dpi是归一化后的dpi,可能值只有120(low)、160(medium)、240(high)、320(xhigh)四种,而我之前理解的竟然是实际设备真实的dpi!


G7的真实dpi是252,根据我以前的理解,310dp换算成px应该是:310 * (252 / 160) = 488像素,而G7水平方向是480px,310dp在这上面绝对满屏都不止了,事实上Android系统并没有拿252作为dpi来计算,而是将G7视作hdpi设备,然后使用240dpi来计算最终像素,所以在G7上320dp刚好是:320 * (240 / 160) = 480像素,刚好满屏了,310dp确实要差一点点。


搞清楚这个问题后,我心里稍微好受点了,可是另一个问题接踵而来:
dp的本质还是物理尺寸,难道不是吗?尽管Android系统对待dp这事上,将所有设备视为四种:120(low)、160(medium)、240(high)、320(xhigh),在160dpi上,160dp就是1英寸,在240dpi上,160dp还是1英寸,120dpi和320dpi也还是1英寸,虽然他们占用的像素数不一样,但是最终显示出来的效果都是占用了屏幕上1英寸的范围。这套体系其实是非常合理的,一个宽为160dp的按钮,它在所有设备上占用的物理尺寸应该是一样大才合理,这样他们对人眼所形成的张角才一样大,观看或者阅读的感觉才一致(姑且不考虑按钮的背景是否一样细致)。这应该是Android系统引入dp概念的原因,因为手机屏幕的像素密度相差实在太大了,web那套东西已经完全不适用,你想电脑屏幕的像素密度能相差多大?


终极问题来了,现实生活中真的只有以上四种不同像素密度的设备吗?不可能。虽然所有这些设备都可以粗略地划分为low、medium、high、xhigh四种密度,可是对于划在同一范围内但具有不同像素密度的两个设备来说,同样的dp最终所占用的物理尺寸是不一样的。举个例子,G7(HTC Desire)的屏幕尺寸是3.7英寸,分辨率是480*800,像素密度是252dpi,G10(HTC Desire HD)的屏幕尺寸是4.3英寸,分辨率同为480*800,像素密度是217dpi。假设现在有一个按钮,它的宽度设为100dp,由于G7和G10同被划分为hdpi,那么在这两个设备上,这个按钮的实际宽度是:100 * (240 / 160) = 150像素,可由于这两个设备的实际像素密度是不一样的,所以真实的显示效果是:这个按钮在两个设备上的实际物理尺寸是不一样大的。而这,和Android进入dp的概念是相悖的。


你可以说对于同属于hdpi的设备而言,这个差别很小,但是在ldpi和hdpi之间这个差别就很明显了。我非常认同,可是如果在将dp转化为px的时候,不是使用归一化dpi(也就是120(low)、160(medium)、240(high)、320(xhigh)这四种),而是使用设备真实的像素密度,那么得出的像素数目虽然各不一样,但是最终显示出来的物理尺寸确实一样大的,而这种计算方法,我认为是忠于像素密度无关的理论的。


最后我还是想说,如果Android希望一个宽度为160dp的按钮在任何设备上都是1英寸大,那为什么不直接使用英寸作为度量单位呢?

如果你有好的想法,欢迎留言。



UPDATE:

今天下午在回答factar网友的问题的时候,我上面那个“终极问题”终于找到了一个合理的答案。在factar贴的网址里,我发现一句重要的话:

“However, bitmap scaling can result in blurry or pixelated bitmaps, which you might notice in the above screenshots.”

这句话的意思是说,图片资源在缩放的时候会造成图像模糊。按照我以上的分析,如果为了保证相同的图片资源在不同像素密度的设备上保持完全一样的尺寸大小(这完全可以做到,在dp转化成px的时候使用设备的物理像素密度参数),那图片在不同设备上的缩放因子必然不一样,而这会导致图像模糊!所以我猜想Google为了保证了图像不会模糊退了一步,让相同dp在不同设备上“差不多一样大”。


还有,这个答案也纠正了我的一个误区,现在有很多应用程序开发商为了降低安装包的大小,只使用一套hdpi资源或者一套xhdpi资源,而不提供mdpi资源或ldpi资源,希望在mdpi和ldpi设备上有系统完成缩放适应,虽然可行,但是我们不应忽视因为缩放带来的图像模糊、显示效果不佳的现象。