近日在 QQ 群里,好几个小伙伴都问起 android UI 适配的事。有的是不知道怎么去做适配,有的是对计量单位的概念不清晰。在此拟文,希望能起到抛砖引玉之功效。
问题 1:为什么要做适配?
感觉问题 1,有点二吧。为什么要做适配?因为别人的程序都能在各种机型完美运行。我的也要…。谷歌的 android 是开放的,各个生产厂商可以定制自己的硬件参数,和软件系统,就导致碎片化超级严重。特别是国产的机型,各种尺寸,各种分辨率,有木有?如果谷歌规定了,只能用一种规格。如 手机 是 4 英寸 720x1280 。那么我们只针能一这个规格写程序,就不存在适配的问题了。扯远了……
做 UI 适配置的首要的原则是:"视觉效应" ,其次是操作的简便性,合理性。何为视觉效应,也就是说,你的产品做出来之后,整体的感觉,是可以让你的大部分用户感觉到舒适,美观。视觉效应是一个很主观的东西。比如一个 UI 的字体是 15 ,年轻人可能感觉正合适,而上了年纪人,可能感觉有点偏小。如是一个外星人(这个外星人的视力比人类强 10 倍),那么刚才那个字体,外星人可能会感到太大了。
好吧,回到技术层面。Android 在长度单位,可以是以下几种:px、dp(dip)、sp。其中 px 即像素,dp 和 sp 是 android 特有的单位。是谷歌创造的。在底层的绘制时,最终还是要转成 px。那么为什么要用 dp、sp 作为长度单位呢?原因就是:各个设备硬件的参数的差异化。如果你直接用 px 作为单位,你会发现,在一些机型上效果很好,另一些机型上,就不是那么美观了。下面来个图,说明这种情况:
有小米 2 ( 4.7'720*1280),山寨小米 2(4.7' 480*800) ,有一个应用,就是相片查看,现在要做一个缩略图的查看,点击后,可以看大图。
ImageView 是 200px * 400px , 在上述的设备,效果图如下。
现在把 ImageView 的 px 换成 dp. 效果图如下:
看到差别了。其实,用 dp,sp 的主要目的,是为了让 UI 里的元素,在不同设备的视觉效应,看上去都差不多。
这里只有两个设备,UI 元素也很简单,不就一个 ImageView 吗,这有什么好扯的。是的,没有技术含量。
--------------------------------------------------------------
相关概念
分辨率:整个屏幕的像素数目,为了表示方便一般用屏幕的像素宽度(水平像素数目)乘以像素高度表示,形如 1280x720,反之分辨率为 1280x720 的屏幕,像素宽度不一定为 1280
屏幕密度:表示单位面积内的像素个数,通常用 dpi 为单位,即每英寸多少个像素点.
注意:这里的 dpi 是指物理的密度(即每英寸像素数,如 120dpi,160dpi 等,假设 QVGA(320*240) 分辨率的屏幕物理尺寸是(2 英寸 * 1.5 英寸),dpi=160)一般来说,同样的尺寸的屏,密度越高,分辨率也越高,精细度(视觉效应)也越高。
注意:不要把 dpi 和 dip 搞混了,一个是物理的,一个是逻辑的。dip=dp 在本文中,这不用 dip 作为示例,统一用 dp
px:长度单位,以具体像素为单位
dp:长度单位,与具体屏幕密度无关,显示的时候根据具体平台屏幕密度的不同最终转换为相应的像素长度,具体转换规则是: 1dp = (目标屏幕密度 / 标准密度)*px 。
标准密度为 160dpi,例如,1dp 长度在密度为 160dpi 的平台表示一个像素的长度,而在 240dpi 的平台则表示 1.5 个像素的长度
px 和 dp 的换算公式:px= dp*density 即:px=dp * (设备 DPI / (float) 160)
屏幕尺寸:屏幕的大小,通常用屏幕对角线的长度表示(英寸)
密度比例: density=DENSITY_DEVICE / (float) DENSITY_DEFAULT 即 :设备密度 / 默认密度 (160)
谷歌把屏幕尺寸分为:small,normal,large,xlarge 分别表示小,中,大,超大屏
屏幕密度分为:ldpi,mdpi,hdpi,xhdpi,它们的标准值分别是:120dpi,160dpi,240dpi,320dpi (现在的设备已有 xxhpdi,还有其他一些 如 tvdpi)
以上划分均表示的是一个范围:
PS:谷歌为什么要这么分呢?说白了,还是为了 UI 适配。刚才那两个机型,分别对应 normal 大小的屏(4.7),米 2 的 dpi 是 312 (xhdpi),山寨的 dpi 是 198(tvdpi) (这些值有误差..)
看到这里,请不要感觉到奇怪,为什么手机也会有 198 这样的 dpi (不要纠结实现生活有没有这个东西),这说明了,一样大小的屏,由于生产手机的厂商,做出来的屏,密度一不样,最终设备的像素也会有所差别。谷歌想到这点,就整出上面那个 屏幕尺寸的划分,和一个称为"dp" 的怪东西……
有了这个划分,我们就能以最小的工作量,完成大部分设备的适配。
粗适配
在资源目录后面加上上面的限定就能为资源指定特定的适用平台,如下所示
在实际开发过程中屏幕尺寸不够直观,android 将其转换为分辨率 (dp) 表示,根据屏幕具体分辨率 (dp) 可选择相应的限定符
注意:这里用了 dp ,并非是 px ,就是说你的设备,如果达到了 426dp x 230 dp 就是小屏 。
上面这是针对布局的。如果你偷懒只做一个布局文件资源,并且布局不是那么复杂,那么,你可以在 values 文件夹里,建立多个限定资源的 dimens 文件 (dimesn 定义的是如字体的大小,控件 layout 的高、宽、padding 等等的长度的值 ,这些值,建议用 dp、sp,原因如上述示例。)如下所示:
针对不同资源的 dimens,调整其中定义的值,也可以达定适配置的效果,当然,多个 限定资源的 layout 和 多个限定资源的 dimens 也可以一块使用。
小结:通过加上上述限定可以实现一个 apk 适配几种主流的屏幕尺寸和屏幕密度,这种限定方式比较适用于对外发布应用,不知道终端具体参数的情况,但是不能做到精确适配,对于屏幕尺寸和密度相差不大的两种平台不能很好的区分。
精确适配
为了解决上述问题,自 Android3.2 开始,引入了精确适配,理论上可以适配任意像素宽度,高度,屏幕密度的平台,需用以下方式添加限定符。
其中 w1280dp 表示屏幕宽度为 1280dp,h752dp 表示屏幕高度为 752dp,160dpi 表示屏幕密度,其中屏幕宽,高必须以 dp 为单位,在知道屏幕像素宽高度的情况下可以通过公式:1dp = (目标屏幕密度 / 标准密度)*px 转换成 dp 单位。
例如:某平台屏幕宽,高分别为 1920px,720px,屏幕密度为 240dpi
或者是:
根据公式 1dp=(240/160)px=1.5px, 宽度,高度转为 dp 单位分别是 1280dp 和 480dp.
注意:这里高的值 480dp ,指的不是设备的高度?而是你的 UI 需要的最小的高度?(这里有点迷糊)。高度不是必须的,一般的程序无需指定这个高度的值,因为,竖向一般是可以滚动的,更需要关注的是横向的值。(指定了,你会发现,如果计算错误,将是十分的蛋疼。)
注意:网上的资料说:当 rom 不把虚拟键计算到屏幕尺寸时,你自己计算的高度,和资源的限定符对不上时,可能适配置到下一个资源。
限定符还可以是 sw
,但是你还是要更多关注的是宽度,因为 UI 通常会垂直滚动(别问为什么)
w<N>dp 是指定所需要的最小的宽度,当屏幕方向发生改变时,会以反应当前实际可用的宽度供你的 UI 使用(这可能会导致切换到别的限定符的资源)。h<N>dp 和 w<N>dp 类似,不过,我们一般不需要关注它。
PS:这里是要带单位 (dp) 的。以下是错误的定义:layout-1280x480、layout-w1280-h480、layout-w1280px-h480px、layout-w1280px-h480px-hdpi
关于 drewable-ldpi 、drewable-mpi、drewable-hdpi、drewable-xhdpi、drewable-xxhdpi
这几个目录,是用于存放不同密度的图片。系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片
如 drawable-mdpi 里面存放中等分辨率的图片, 如 HVGA (320x480)。
这里所指的图片,主要是针无法用. 9 处理的图片,如一些程序启动时引导的图,你再怎么用. 9 处理,如果你没有几套对应的图,在不同的设备上,都会产生放大或缩小的情况。
如果是一些控件的 Selector、背景之类的资源图片,用. 9 格式的图,只有一套资源,也是没有问题的。
关于横竖屏切换
有两种情况:1、不切换。2,切换
不切换的,就是一个方向,要么横屏,要么竖屏;切换的,要考虑不同方向的布局视觉效果。如是以横屏为原形设计的,切换成竖屏时,可能会有一些控件出现过小、或是不够位置放,这时,可以利用 HorizontalScrollView 之类作为解决手段,切不可把控件删除,否则切换时,找不到控件,会报错。
关于手机和平板的适置
手机一般来说,都是 4~5 英寸,当然也有个别的,达到 7 英寸。平板一般是 6-12 英寸。如果想要在一个 apk 中,完成手机和平板的适配。按
这种,可以用 Fragment 来解决。(ApiDemo FragmentList 好象实现了这个效果)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
验证计算公式,可以跳过………………
在上面设备的换算分别是(大概):1dp=1.95px ,1dp=1.2px (有误差...)
我们在程序输出一下,验证一下。点击 ImageView 输出一下相关的信息。
代码如下:
米 2 输出如下:
- OnClickListener ivPhotoClick = new OnClickListener() {@Override public void onClick(View v) {
- DisplayMetrics dm = getResources().getDisplayMetrics();
- float density = dm.density;
- int densityDpi = dm.densityDpi;
- int heightPixels = dm.heightPixels;
- int widthPixels = dm.widthPixels;
- float scaledDensity = dm.scaledDensity;
- float xdpi = dm.xdpi;
- float ydpi = dm.ydpi;
- int imageHeight = ivPhoto.getHeight();
- int imageWitth = ivPhoto.getWidth();
- System.out.println("密度比例:density-->" + density);
- System.out.println("密度:densityDpi-->" + densityDpi);
- System.out.println("设备高(像素):heightPixels-->" + heightPixels);
- System.out.println("设备宽(像素):widthPixels-->" + widthPixels);
- System.out.println("字体的密度:scaledDensity-->" + scaledDensity);
- System.out.println("设备x方向dpi:xdpi-->" + xdpi);
- System.out.println("设备y方向dpi-->" + ydpi);
- System.out.println("控件高:imageHeight-->" + imageHeight);
- System.out.println("控制宽:imageWitth-->" + imageWitth);
- }
- };
山寨输出如下:
值有些误差,不要在意这些细节。
---------------------------------------------------
限定符使用示例(个人感觉精确适配这个不好控制,意义不大,不如 layout-larger-mdpi 这种类型来得实际)
如上图所示:分别是 sw360dp-h200dp-xhdpi 对应米 2. layout-w360dp-tvdpi
在之前的 ImageView 下方,加入一个 TextView 控件,值分别为:默认,米 2、山寨。如下图
坚屏
横屏:山寨(M2-Copy)由于没用 sw 所以,当屏幕方向变化时,宽的值发生变化,导致了布局的切换(变成默认)。但是 TextView 的文字位置都错误了。
把限定符再次更改:都成了 sw360 的资源
来源: http://lib.csdn.net/article/android/41945