dp


       回头算算,从正式开始做Android,刚好一年时间,在这里所谓的互联网寒冬毅然决然的入了Android的坑。不过话又说回来了,我一直坚信技术的积累与大环境无关,所以关键还是在于提升自己。之前在初入Android坑的工作中,都是使用印象笔记作为知识的积累,但是久而久之变得有点杂乱。因此最近年后一直考虑开始写文章,把一些用到的知识点整理出来。最后选择了简书作为我的笔记本。知识是需要沉淀的,总结出来的东西肯定会有不一样的领悟。如果刚好这些东西能帮助到其他人,那就更好了。

       好了,废话这么多,进入今天的正题吧。相信没有做Android开发的朋友也一定知道一个事实:由于Android开源,因此采用Android系统的厂商非常之多,设备也非常多,而且各种尺寸和各种分辨率的组合也是多如牛毛。下面这张图是从友盟发布的报告上截取的。因此适配就成Android开发中的必不可少的工作。下面我将从几部分来说一说Android中的单位dp。

图-1

1、基本概念

       首先要说的就是px。px是屏幕中可以显示的最小元素单元。我们的手机(或平板)可以显示的东西都是由一个个像素点组成的。在PC上,我们可以通过px这个单位就能把程序打扮的很漂亮,但是手机上却不行。我举个例子,比如我们现在要在界面上画一个button,设置按钮宽度为200px,我分别在1920*1080和800*480的两台设备上预览的效果如下:


图-2(1920*1080)
图-3(800*480)

结果是一目了然,在1920*1080的设备中,300px的像素大于只占屏幕宽度的不到三分之一,而在800*480的设备中,却占了一半还要多。因此,px作为单位肯定不行。google当然也意识到了这个问题,因此引入了dp和sp两个单位作为控件单位和字体单位。

我们先了解几个相关的概念。

dp:也称为dip,device independent pixels,设备独立像素。

dpi:全称是 Dots Per Inch,每英寸所包含的像素数。

density:像素密度。计算公式为dpi/160。

google官方规定,以dpi为160作为基准,即在一台dpi为160的设备上,1px=1dp,此时该设备的density也为1。根据这个规定,你可以对其他设备进行计算。例如一台手机的屏幕宽是2英寸,长是3英寸,分辨率为640*960。那么它的dpi值等于640/2(或者960/3)=320,density等于320/160=2,1dp=1px*2。

这样你应该对dp这个单位有了一定的了解吧,我们最终看dp是实现了什么样的效果。当我把一个控件的宽度设置为1dp是,在google定义的基准设备上,它所表示的真实长度=1/160英寸。在上面定义的640*960的设备上。1dp=2px/320英寸。结果是一样的,都是1/160英寸。也就是在不同的设备上所表现出的物理长度是一样的。也就初步实现了适配。

但是实际情况可能没有这么理想,例如我的手机魅蓝note2,1920*1080,16:9,5.5英寸,通过代码:getResources().getDisplayMetrics().densityDpi;得出的dpi值为480。通过手动计算,勾股定理得:宽为2.7英寸,高为4.8英寸,dpi值等于:1080/2.7=400dpi,1920/4.8=400dpi。咦?怎么两个值不一样,事实是这样的:编译ROM的时候会指定dpi,而且只能指定如下几个值。

ldpi: 120dpi (mdpi x 0.75)

mdpi: 160dpi  (android 基准 dpi, 1dp=1px)

hdpi: 240dpi (mdpi x 1.5)

xhdpi: 320dpi(mdpi x 2)

xxhdpi: 480dpi(mdpi x 3)

xxxhdpi: 640dpi(mdpi x 4)

到目前这个阶段,ldpi的设备已经基本很少了,所以不做考虑。手机厂商应该尽量让手机的实际像素密度与ROM指定的值接近,请注意,是接近,所以才会出现了上面的偏差。假如屏幕 A 实际是 440dpi 的,ROM 被指定为 480 dpi,那么屏幕显示的实际上是 480/440=1.09 倍的真实大小。

好了,上面费这么多话,其实只想说一绝话:

1dp约等于1/160英寸!!

2、Android屏幕适配方案

1、布局适配

做法:尽量使用相对布局

       目前为止,我们在进行布局界面的时候通常使用的布局包括: RelativeLayout、LinearLayout、FrameLayout。推荐优先使用RelativeLayout,RelativeLayout的特点是布局下的子控件之间使用相对位置的方式排列。即使屏幕的大小改变,视图之前的相对位置都不会变化,与屏幕大小无关,灵活性很强。而LinearLayout布局无法控制子视图之间的关系,只能一个挨一个的按照垂直或水平排列。而FrameLayout是把所有的元素都放在屏幕的左上方。如果要进行元素排布的话,只能通过固定的dp值。因此,对于屏幕适配而言,使用RelativeLayout是比较好的方案。

2、布局组件适配

       布局组件适配比较容易理解。即我们最常用的match_parent和wrap_content。设置控件的宽和高时,当我们通过这两个属性值就可以设置控件的宽高为与父控件的宽高一致、适应该控件的宽高,而不需要指定固定的dp值。因此,也是屏幕适配优先使用的控件属性。

3、布局限定符适配

• 使用尺寸限定符

       Android 3.2之前通过small、normal、large、xLarge分别标记小尺寸屏幕、正常尺寸屏幕、大尺寸屏幕、超大尺寸屏幕。同一界面在不同的布局文件路径下创建布局文件:res/layout/my_layout.xml;

res/layout-large/my_layout.xml;

res/layout-xlarge/my_layout.xml;

但是该种方法区分屏幕的边界比较模糊,因此从Android 3.2之后就被弃用。鉴于目前4.0以下的系统基本可以忽略不计,则此种方法目前来说基本是废弃状态。

• 使用最小宽度限定符

       上面说到尺寸限定符已经被废弃掉了,google的替代方案就是smallestWidth,设备的 smallestWidth 是屏幕可用高度和宽度的最小尺寸,smallestWidth 是设备的固定屏幕尺寸特性;设备的 smallestWidth 不会随屏幕方向的变化而改变。如果设备的屏幕上有一些永久性 UI 元素占据沿 smallestWidth 轴的空间,则系统会声明 smallestWidth 小于实际屏幕尺寸。关键字:sw<N>dp。如:sw600dp、sw720dp。同一界面在不同的布局文件路径下创建布局文件:

res/layout/main_activity.xml

res/layout-sw600dp/main_activity.xml

• 使用屏幕方向限定符

       如果我们要求给横屏、竖屏显示的布局不一样。就可以使用屏幕方向限定符来实现。在布局文件夹中增加关键字land、port实现。例如要在平板上实现横竖屏显示不用的布局。同一界面在不同的布局文件路径下创建布局文件:

res/values-sw600dp-land/layouts.xml:横屏

res/values-sw600dp-port/layouts.xml:竖屏

3、图片资源适配

图片资源适配优先使用Android特用的九图格式。

使用场景:当我们需要使图片在拉伸后还能保持一定的显示效果,比如,不能使图片中的重要像素拉伸,不能使内容区域受到拉伸的影响,我们就可以使用.9.png图来实现。

使用方法说明:1、在九图工具中,选择图片的左边和上边,点选一个或多个像素点,即可把这些垂直水平方向选中的像素点进行拉伸处理。2、右边和下边是选择内容区域,在右边和下边画上直线,交叉的区域就是内容区域,即文本显示区域。


9图可以根据屏幕的尺寸进行自由适配,因此是控件背景图片的第一选择。但是并不是所有需要图片的地方都要用9图。其他需要图片的地方,我们要根据前面说到的mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi几种不同的设备进行切图,你如果观察过新建Android项目的app的icon的尺寸的话,你会发现在mipmap相关的几个文件夹中,它的尺寸分别为:48*48、72*72、96*96、144*144、192*192,它们转换后都为48dp*48dp,也就是说它们在设备上表现出的物理尺寸是基本一致的,也就实现了图片的适配。但是实际情况不可能这么完美,公司UI一般不会花这么大精力去做这么多图对我们的设备进行适配,另外也要考虑这样apk的大小会增大不少。

所以一般是给出一种分辨率下的切图,Android系统会自动根据图片所放的文件夹和设备的密度进行相应的缩放显示。这样的方式图片在app上也会有不错的呈现效果。但是这个地方有不少细节需要注意,下一篇我们会在去讲解。

4、用户界面流程适配

       前面有说到根据最小宽度限定符创建不同的布局文件。但是我们怎么在具体Activity中判断究竟当前设备是加载了哪个布局,那些事件可以得到响应。

1、确定当前布局

▪ 通过判断某个控件是否为null。这个控件如果只在一种布局下存在,那个可以使用此方法。

▪ 通过判定某个控件的事件。如这个控件只在一种布局下才生效。

2、根据当前的布局做出响应

       有些操作可能会因当前的具体布局而产生不同的结果。例如,在新闻阅读器示例中,如果用户界面处于双面板模式下,那么点击标题列表中的标题就会在右侧面板中打开相应报道;但如果用户界面处于单面板模式下,那么上述操作就会启动一个独立活动。

3、重复使用其他活动中的片段

       多屏幕设计中的重复模式是指,对于某些屏幕配置,已实施界面的一部分会用作面板;但对于其他配置,这部分就会以独立活动的形式存在。例如,在新闻阅读器示例中,对于较大的屏幕,新闻报道文本会显示在右侧面板中;但对于较小的屏幕,这些文本就会以独立活动的形式存在。

在类似情况下,通常可以在多个活动中重复使用相同的 Fragment 子类以避免代码重复。例如,在双面板布局中使用了 ArticleFragment:

图-4

而在小屏幕上可以直接:

ArticleFragment frag =newArticleFragment();

getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

这种方式与在 XML 布局中声明片段的效果是一样的,但在这种情况下却没必要使用 XML 布局,因为此Fragment是此活动中的唯一组件。

另外,在Fragment中注意,不要针对具体Activity创建强耦合。要做到这一点,通常可以定义一个接口,该接口概括了Fragment与其主Activity交互所需的全部方式,然后让Activity更新界面。这部分的内容具体可参考凯子哥的一篇文章,链接在后面。


好了。关于Android中dp的认识我已经把我所有的理解都放在这里了。如有错误请指正,共同进步。



参考资料:

http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023

http://www.jianshu.com/p/ec5a1a30694b

推荐阅读更多精彩内容