众所周知,Android机型尺寸各种各样,于是屏幕适配就成了Android开发中很重要的一环。Android屏幕适配可能一些开发者都会遇到这样的问题,今天就来分享下屏幕适配,你会发现其实Android屏幕适配也可以很简单。
Android屏幕适配必须要理解的一些概念,这部分可能比较枯燥,但是俗话说的好“工欲善其事,必先利器”,翻译过来就是“有什么样的枪,决定你打什么样的鸟”,一旦这些概念你理解掌握了,屏幕适配你自然而然就觉得简单多了。
是英文单词pixel的缩写,意为像素,屏幕上的点。我们通常所说的分辨率如480X800就是指的像素。
在设计领域中,像素是用来计算数码影像的最小单位。计算机中显示的图像并非连续的线条组成,而是由许多肉眼看不见的小点组成。如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小点所组成,这些小点就是构成影像的最小单位“像素”。由于是最小的独立显示单位,px均为整数,不会出现0.5px的情况。如:
看这个色彩鲜艳的LED灯(原图大小)
你能想象这才是他的本来面目吗?(放大之后)
表示英寸,是屏幕的物理尺寸。每英寸等于2.54厘米。例如我们经常说的手机屏幕大小有,5(英)寸、4(英)寸就是指这个单位。这些尺寸是屏幕的对角线长度。如果手机的屏幕是4英寸,表示手机的屏幕(可视区域)对角线长度是4 X 2.54 = 10.16厘米。
dpi是Dots Per Inch的缩写, 每英寸点数,即每英寸包含像素个数。比如320X480分辨率的手机,宽2英寸,高3英寸, 每英寸包含的像素点的数量为320/2=160dpi(横向)或480/3=160dpi(纵向),160就是这部手机的dpi,横向和纵向的这个值都是相同的,原因是大部分手机屏幕使用正方形的像素点。
屏幕密度,density和dpi的关系为 density = dpi/160
也即dip,设备独立像素,device independent pixels的缩写,Android特有的单位,在屏幕密度dpi = 160屏幕上,1dp = 1px。
和dp很类似,一般用来设置字体大小,和dp的区别是它可以根据用户的字体大小偏好来缩放。
我们新建一个Android项目后应该可以看到很多drawable文件夹,分别对应不同的dpi
drawable-ldpi (dpi=120, density=0.75)
drawable-mdpi (dpi=160, density=1)
drawable-hdpi (dpi=240, density=1.5)
drawable-xhdpi (dpi=320, density=2)
drawable-xxhdpi (dpi=480, density=3)
市面上的一些Android教程大多都是教的是为每种dpi都出一套图片资源,这个固然是一种解决办法,但同时也是一种非常笨的方法,为美工或者设计增加了不少的工作量不说,同时也会让你的apk包变的很大。那么有没有什么好的方法既能保证屏幕适配,又可以最小占用设计资源,同时最好又只使用一套dpi的图片资源呢?下面就来讲解下项目中总结出来的这个方法。
首先必须清楚一个自动渲染的概念,Android SDK会自动屏幕尺寸选择对应的资源文件进行渲染,如SDK检测到你手机dpi是160的话会优先到drawable-mdpi文件夹下找对应的图片资源,注意只是优先,假设你手机dpi是160,但是你只在xhpdi文件夹下有对应的图片资源文件,程序一样可以正常运行。所以理论上来说只需要提供一种规格的图片资源就ok了,如果只提供ldpi规格的图片,对于大分辨率的手机如果把图片放大就会不清晰,所以需要提供一套你需要支持的最大dpi的图片,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。
上面说了只需要提供一套大的dpi的图片就ok了,现在市面手机分辨率最大可达到1080X1920的分辨率,如Nexus5,dpi属于xxhdpi,但是毕竟还没普及,目前市面上最普遍的高端机的分辨率还多集中在720X1080范围,也就是多集中在xhdpi,所以目前来看xhpdi规格的图片成为了首选。当然随着技术规格的提高以后发展,以后可能市场上xxdpi的手机会越来越普遍,但这是后话。
在现在的App开发中,基本都会有iOS和Android版本,有些公司为了保持App不同版本的体验交互一致,还有些公司的设计资源可能比较紧张,这些情况下iOS和Android版本基本是一个设计师主导,而大多数情况下设计师可能更会以iPhone手机为基础进行设计,包括后期的切图之类的。这个时候身为Android开发人员你是否还要求设计师单独为Android端切一套图片资源呢?这会让你们的设计师崩溃的,下面就来告诉一个项目中总结的更棒的方法。
相信设计师们一般都会用最新的iPhone5(5s和5的尺寸以及分辨率都一样)来做原型设计,而iPhone5的屏幕分辨率为640X1164, 屏幕尺寸为4英寸,根据勾股定理(a^2 + b^2 = c^2)640^2+1164^2=1764496, 然后再对其开根号可求出屏幕对角线的分辨率为:1328,除以4可得出iphone5的dpi:1328/4≈332 可以看出iPhone5的屏幕的dpi约等于320, 刚好属于xhdpi,所以你可以很自豪的像你们的设计师说不用专门为Android端切图,直接把iPhone的那一套切好的图片资源放入drawable-xhdpi文件夹里就ok了。
wrap_content和dp都是在Android开发中应该经常用到的,然后它们冥冥中是有关系的。
假设你看了这篇文章后都是统一有xhdpi的资源,那么你用wrap_content完全没有问题,Android会自动为其他规格的dpi屏幕适配,比如你在xhdpi放了一张120X120px大小的图片,那么在在hdpi屏幕上显示的就只有120/2*1.5=90px大小,但是如果你不小心同样把这张图片也放入了mdpi了,这个时候用wrap_content显示就会有问题,具体看下面的例子:
例如假设你只在drawable_xhdpi文件夹下放了test图片,xhdpi的设备会去xhdpi文件夹下找到test图片并直接显示,而mdpi的设备优先会去mdpi文件夹里查找test图片,但是没找到,最后在xhdpi文件夹下找到,然后会自动根据density计算并缩放显示出来,实际显示出来的大小是120/2=60px, 所以整体的显示比例才会看起来比较正常
但是如果你在mdpi文件夹里也放入了同样的图片,那么mdpi的设备会直接去mdpi文件夹里寻找到test图片,并直接显示,而这时候显示不会缩放,实际显示大小就是120X120,在mdpi的屏幕上看起来就会比较大,如图:
通过上面整个过程,大家应该理解了Android加载资源的整个过程, wrap_content同样可以用dp来代替,就拿上面这个例子,在xhdpi文件夹内放入了一张120X120像素的test图片,宽高直接除density就得出dp的数值,即这种情况下以下代码是等同的.
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/test" />
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/test" />
相信通过以上的讲解,对Android UI中的一些基本概念有个很好的理解,实际开发工作中也有一些高效的方法可以参考,应该可以应对大部分的屏幕适配工作。但是项目中仍然有一些比较特殊的适配需求满足不了,以后会针对一些特殊的需求进行示例讲解。
可以写工具类对尺寸单位进行转换,比如:
package com.mengdd.dimen; import android.content.Context; public class DimenUtils { public static int sp2px(Context context, float spValue) { float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } public static int px2sp(Context context, float pxValue) { float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValue / fontScale + 0.5f); } public static int dip2px(Context context, int dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } }
Android中的DisplayMetrics这个类描述了关于显示的各种信息,可以利用它查看设备的状态,上述关于屏幕密度的标准的常量也是从这个类中看到的。
DisplayMetrics的toString()方法如下:
@Override public String toString() { return "DisplayMetrics{density=" + density + ", width=" + widthPixels + ", height=" + heightPixels + ", scaledDensity=" + scaledDensity + ", xdpi=" + xdpi + ", ydpi=" + ydpi + "}"; }
其中各个变量解释如下:
/** * The absolute width of the display in pixels. */ public int widthPixels; /** * The absolute height of the display in pixels. */ public int heightPixels; /** * The logical density of the display. This is a scaling factor for the * Density Independent Pixel unit, where one DIP is one pixel on an * approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen), * providing the baseline of the system's display. Thus on a 160dpi screen * this density value will be 1; on a 120 dpi screen it would be .75; etc. * * <p>This value does not exactly follow the real screen size (as given by * {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of * the overall UI in steps based on gross changes in the display dpi. For * example, a 240x320 screen will have a density of 1 even if its width is * 1.8", 1.3", etc. However, if the screen resolution is increased to * 320x480 but the screen size remained 1.5"x2" then the density would be * increased (probably to 1.5). * * @see #DENSITY_DEFAULT */ public float density; /** * The screen density expressed as dots-per-inch. May be either * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}. */ public int densityDpi; /** * A scaling factor for fonts displayed on the display. This is the same * as {@link #density}, except that it may be adjusted in smaller * increments at runtime based on a user preference for the font size. */ public float scaledDensity; /** * The exact physical pixels per inch of the screen in the X dimension. */ public float xdpi; /** * The exact physical pixels per inch of the screen in the Y dimension. */ public float ydpi;
小米2S,DisplayMetrics中的toString()方法输出如下:
DisplayMetrics{density=2.0, width=720, height=1280, scaledDensity=2.0, xdpi=345.0566, ydpi=342.23157}
只针对手机app适配
目前android 手机分辨率主流有
480*800 480*854 540*960
720*1280
1080*1920
1440*2560
以上不包括屏幕内虚拟机键。一般540*960 加虚拟机 540*800多 720p的是1100多
首先适配光考虑分辨率是不够的,还有考虑分辨率的dpi
android 手机主要朝两个方向发展,一个是手机型 一个是pad型的
先说dpi的不同,同样分辨率的手机 dpi不同
比如,1. 4.3寸 4.6寸720*1280的分辨率的手机他们的dpi 是2 用的是xdpi的图
2. 6.0寸 5.8寸720*1280的分辨率的手机他们的dpi 是 1.5 用的是hdpi的图
这样就导致如果用多套图的话第2套图的hpi 会跟正常的480*800 480*854 540*960
冲突,你可以再加一套图。。但是这样图加下去是无止境的
而且多套图的话貌似 540*960的手机如果带虚拟键的话引用的图有可能有问题。可以拿htc 540*960的带虚拟机的手机的测试下
所以关键适配在布局上做手脚。
布局上适配其实也不难,
主流的
540*960
720*1280
1080*1920
这3个分辨率其实是成比例的,所以以dip为单位的话适配一个其他3个都会适配了前提是手机型的适配。
pad型的适配 其实就是把控件缩小了,或者你可以理解成 布局拉长了,相对的怎么理解都行。
控件缩小是这样理解的 图左
比如你 100dip*100dip的方的布局
在xdpi上面显示的是 200px*200px
在 hpi上面显示的则是 150px*150px
布局拉伸的理解
如图比如你在4.3寸的手机上显示宽占了2/3 但是在大屏上可能占了1/2 感觉布局横向拉长了。
首先要理解了 pad型跟手机型的区别剩下的就好说了
接下来就是实际中布局的应用。
记着一点就是 布局的时候想象着 布局被拉伸后显示的位置
还有就是毋庸置疑的 布局的单位都是dip
举例1. 头部栏
这是两种理解显示的样子,左边的是 假设两个屏幕尺寸一样 分辨率一样 dpi不一样的时候显示, 右侧是 现实中显示出来的 样子
先看左侧的 比如导航栏高是100dip 那么左边第1个图高 就是200px,第2个图高就是150px
这个是合理的 因为你看右边的图的 实际的展示 虽然高是150px 但是实际上他展示的区域跟200px 展示的区域是差不多一样大的。
所以pad型的定义就是为了展示更多的内容。
后面就不花两种图了 太累了,
所以头部栏的布局就是高 dip固定值, 宽用fill, 如果宽不用fill 用固定值的话, 就会出现 比如 左1宽 360dip 刚好 720px 但是左2 360dpi只有360*1.5<720p,
所以适配一般背景什么的 宽是fill 高是固定dip值
然后看具体的控件 icon icon如右图 其实icon大小也是合理的,所以icon的大小也是dip的固定值, 关键是布局。
左边的是左对齐 距左多少像素 右边的是右对齐 距右多少像素, 所以即使控件拉长了 也是对齐的,
如果当前右边的那个布局不是右对齐的话就会出现 按钮右边多出了空白。
所以布局的时候还是前面说的那句话要考虑拉伸之后的位置,然后用距左距右 或者权重
2.随屏幕拉长而拉长的布局 或者不变的布局
这个非常常见 比如
图布局 1 2 3
布局1为正常要做的效果, 2 3为大屏手机显示,
如果1的布局给每个item 布局的宽设了固定的值屏幕是720px 就是360dip 比如设置340dip 居中那天距左距右大概各留了10dip,
如果是让上面的写法居中 宽340dip的话 运行到大屏就是第2个图的效果 明显不是我们想要的
如果是直接写宽fill margin左右10dip的话那就是 图3的效果
根据各自情况,首先要想到布局拉长后的样子!
3. 比较特殊的格子布局 或者瀑布流布局
比如一个方格的gridview 画的不太方= = 那他适配后肯定也是方的。就那gridview来说吧
布局里面是不知道手机屏幕的宽高的,而且如果是用dpi为单位的话那么宽就会变小的,有些人会用权重 weight,宽是可以。但是高呢= = 。。
像这种情况只能通过代码 在adapter里面设置 计算屏幕的宽/列数 就是item的宽高的值。
特别是这种 有列的 肯定是代码里面计算的 布局肯定是做不到的。
那是那句即时你适配了宽 但是高会缩。。
总结就是布局的时候 想象着 布局被拉伸后显示的位置
还有就是 考虑虚拟键盘, 有些高度没虚拟键盘的刚好一屏的 记得外面加scrollview滚动。。 不然适配后高度不够。
补:
至于用哪套图,目前折中用的720p的图,因为要向下兼容,如果直接用1080p的图低分辨率 内存不后, 然后用480 或540的图 1080p 或者后面的2k的显示可能会模糊,
折中的话就用720*1280的图片。
后面2k 1080k普及的话可能就不用720p的图的。用1080的图也有可能