[Android开发]Android 屏幕适配
基本概念
Px
不同设备显示效果相同。这里的“相同”是指像素数不会变,比如指定UI
长度是100px
,那不管分辨率是多少UI
长度都是100px
。
也正是因为如此才造成了UI
在小分辨率设备上被放大而失真,在大分辨率上被缩小。
Screen Size(屏幕尺寸)
一般所说的手机屏幕大小如5寸、5.5英寸,都是指的屏幕对角线的长度,而不是手机面积。我们可以根据勾股定理获取手机的宽和长,当然还有面积。单位为inch
,1英寸=2.54厘米
Resolution(分辨率)
指手机屏幕垂直和水平方向上的像素个数。比如分辨率是480*320
,则指设备垂直方向有480个像素点,水平方向有320个像素点。单位px * px
,指的是一屏显示多少像素点。
Dpi(dots per inch屏幕密度)
单位dpi
,指的是每inch
上可以显示多少像素点即px
。(每英寸中的像素数)如160dpi
指手机水平或垂直方向上每英寸距离有160
个像素点。dpi
越高,每个像素点越小也就越清晰。
简单地说,dpi
越高就意味着每英寸显示的细节越多,而不是直接取决于高分辨。举个例子,Galaxy Nexus
(对角线 4.65”)分辨率为:720×1280
,Nexus 7
(对角线 7”)分辨率为:800×1280
.
常见的错误观点是认为他们拥有相同的屏幕像素密度,因为它们的分辨率几乎一样。
然而,Galaxy Nexus
的屏幕像素密度约为316dpi
,Nexus 7
的屏幕像素密度为216dpi
,相差甚远。
这是因为虽然它们拥有一样的分辨率,但是它们显示尺寸却不一样。
再次说明,屏幕像素密度是分辨率和显示尺寸的比值,两个因素一起决定屏幕像素密度。
例如:计算Nexus5
的屏幕像素密度: 屏幕尺寸:4.95inch
、分辨率:1920×1080
,屏幕像素密度:445
Dip(Device-independent pixel,设备独立像素)
同dp
,指的是自适应屏幕密度的像素,用于指定控件宽高。就所屏幕密度变大,1dp所对应的px也会变大。
不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA
和QVGA
推荐使用这个,
不依赖像素。dip
和具体像素值的对应公式是dip值 =设备密度/160* pixel
值,可以看出在dpi
(像素密度)为160dpi
的设备上1px=1dip
,
在Android
中,规定以160dpi
为基准,1dip=1px
,如果密度是320dpi
,则1dip=2px
,
以此类推。
Sp(ScaledPixels放大像素)
主要用于字体显示(best for textsize)
。根据google
的建议,TextView
的字号最好使用sp
做单位,
而且查看TextView
的源码可知Android
默认使用sp
作为字号单位。Google
推荐我们使用12sp
以上的大小,
通常可以使用12sp
,14sp
,18sp
,22sp
,最好不要使用奇数和小数。
我们可以用下面的部分来解释为什么用dip代替px作单位:
设备最终会以px
作为长度单位。
如果我们直接用px
作为单位会造成UI
在不同分辨率设备上出现不合适的缩放。
因此我们需要一种新的单位,这种单位要最终能够以合适的系数换算成px
使UI
表现出合适的大小。Dip
符合这种要求吗?
由dip
和具体像素值的对应公式dip值 =设备密度/160* pixel
值可以知
也就是说UI
最终的pixel值只受像素密度dip的影响,
这个dip
就相当于那个换算系数,这个系数的值是多少有设备商去决定。因此dip
符合这种要求。
android为什么引进dip
The reason for dip to exist is simple enough. Take for instance the T-Mobile G1. It has a pixel resolution of 320×480 pixels.
Now image another device, with the same physical screen size, but more pixels, for instance 640×480.
This device would have a higher pixel density than the G1.—If you specify, in your application, a button with a width of 100 pixels, it will look at lot smaller on the 640×480 device than on the 320×480 device.
Now, if you specify the width of the button to be 100 dip, the button will appear to have exactly the same size on the two devices.—The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen, the baseline density assumed by the
platform (as described later in this document). At run time, the platform transparently handles any scaling of the dip units needed,
based on the actual density of the screen in use. The conversion of dip units to screen pixels is simple: pixels = dips * (density / 160).
For example, on 240 dpi screen, 1 dip would equal 1.5 physical pixels.Using dip units to define your application’s UI is highly recommended,
as a way of ensuring proper display of your UI on different screens.
dip
是一种能自适应屏幕密度的像素,这个屏幕密度就是屏幕中一英寸里面含有的像素数,打个比方说,现在有两个4寸的手机,一个分辨率是320*480
,
它的屏幕密度是1,
而另一个是480*800
的分辨率,这样他的屏幕密度就是1.5,如果我们在320480的手机中弄了一个160dp的横线,
由于他的屏幕密度是1,所以他里面dp和px是一比一的计算。
这样这条横线正好占屏幕的一半,而我们将这个横线的视图发布到480800的手机上时,
虽然也是160dp,
但是由于他的屏幕密度是1.5.按照dp与px的计算公式pixel值=dip值/(设备密度/160),
可以得到将160dp要乘以1.5倍,这样得到的px就是240px,
而这个手机的分辨率正好是480*800,所以看起来仍然是占屏幕的一半。
注意:不论多少dp
最后都是要通过转换成px
来显示的手机的屏幕上的,
至于这个dp
的大小在屏幕上到底能占多少位置,就要看这个转换后的px
与屏幕的宽度像素的比值了。
另一个例子: 打个比方一个是4寸480*800
的手机,屏幕密度为1.5. 另一个是7寸的1280*800
的平板。屏幕密度也为1.
这样用160dp的横线,在480*800
的手机上换算成px
是160*1.5 = 240
,这样横向正好占屏幕的一半,
但是在1280*800
的平板上,160dp
换算后的px
为,160*1 = 160px
,
而屏幕的宽是1280
.这样这条横线显示在1280*800
的平板上后,是160/1280
正好是八分之一。
所以引入dp解决的是什么问题呢?同一个屏幕尺寸下,对于不同的分辨率,由于尺寸相同,分辨率不同,所以它的屏幕密度就会不一样,
这样在同一尺寸的不同分辨率下的设备上,显示出来会看到所占的比例基本一样。如两个7寸的平板,一个是1280*800
,一个是2560*1600
,
这样用dp
后,虽然2560*1600
的分辨率比那个要大一倍,但是它尺寸一样啊,
所以屏幕的分辨率会是另一个的2倍,这样dp转成px后会乘以2.这样显示到这两个设备上所占的比例会一样
一个是7
寸1280*800
的手机,密度为1,一个是5寸1024*600的
手机,密度为1.
这样你用160dp的线,在前面是8分之一,在后面是六分之一,你就悲剧了,所以要做屏幕适配了
代码
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
Log.e("@@@", "density:"+displayMetrics.density); //1
Log.e("@@@", "densityDpi:"+displayMetrics.densityDpi); //160
Log.e("@@@", "widthPixels:"+displayMetrics.widthPixels); //1024
Log.e("@@@", "heightPixels:"+displayMetrics.heightPixels); //600
displayMetrics.densityDpi
是真正的屏幕密度,可以为160dpi
(mdpi
标准的屏幕密度),240dpi
(hdpi
),320dpi(xhdpi)
.displayMetrics.density
,这个不叫屏幕密度,但是在dp
和px
转换的时候要使用到,
这个是屏幕密度的比例值,是用该设备的屏幕密度除以标准的屏幕密度得到的一个比值,
如240dpi/160dpi = 1.5
屏幕适配文件夹命名规则
- 命名不区分大小写;
- 命名形式:资源名-属性1-属性2-属性3-属性4-属性5…..
资源名就是资源类型名,包括:drawable, values, layout, anim, raw, menu, color, animator, xml;
属性1-属性2-属性3-属性4-属性5…..就是上述的属性集内的属性,如:-en-port-hdpi;
注意:各属性的位置顺序必须遵守优先级从高到低排列!否则编译不过 -
实例说明
- 把全部属性都用上的例子(各属性是按优先级先后排列出来的)
values-mcc310-en-sw320dp-w720dp-h720dp-large-long-port-car-night-ldpi-notouch-keysexposed-nokeys-navexposed-nonav-v7 - 上述例子属性的中文说明
values-mcc310(sim卡运营商)-en(语言)-sw320dp(屏幕最小宽度)-w720dp(屏幕最佳宽度)-h720dp(屏幕最佳高度)-large(屏幕尺寸)-long(屏幕长短边模式)
-port(当前屏幕横竖屏显示模式)-car(dock模式)-night(白天或夜晚)-ldpi(屏幕最佳dpi)-notouch(触摸屏模类型)-keysexposed(键盘类型)-nokey(硬按键类型)
-navexposed(方向键是否可用)-nonav(方向键类型)-v7(android版本)
- 把全部属性都用上的例子(各属性是按优先级先后排列出来的)
手机常见分辨率
4:3
VGA 640480 (Video Graphics Array)
QVGA 320240 (Quarter VGA)
HVGA 480320 (Half-size VGA)
SVGA 800600 (Super VGA)
5:3
WVGA 800*480 (Wide VGA)
16:9
FWVGA 854480 (Full Wide VGA)
HD 19201080 High Definition
QHD 960540
720p 1280720 标清
1080p 1920*1080 高清
分辨率对应DPI | 像素密 | 通常的分辨率 | 比例大小 |
---|---|---|---|
HVGA | mdpi | 160dpi | 320*480 | 1 |
WVGA | hdpi | 240dpi | 480*800 | 1.5 |
720P | xhdpi | 320dpi | 720*1280 | 2 |
1080P | xxhdpi | 480dpi | 1080*1920 | 3 |
xx | xxxhdpi | 640dpi | | 4 |
为了解决适配的问题有时候还会使用9patch图,这里随便说一下。
-
Stretchable area
代表拉伸的部分。 -
Padding box
代码内容所在区域。外面的就是padding
部分。
Android图片选择策略
上面说到,如果屏幕所对应的文件夹没有要找的图片怎么办。
这是很常见的,我们开发项目时一般不会去为每一个级别的屏幕去切一套图片。
那样做只会让apk
很大。所以一般性的图片我们只切一两个典型密度屏幕的图片。
但是apk
是有可能会运行在从ldpi
到xxhdpi
的各种级别的手机上。
这个时候就需要根据一定的策略去寻找图片了。
Android
系统寻找图片的步骤是这样的:
- 去屏幕密度对应的目录去找。如果找到就拿来用。
- 如果没找到,就去比这个密度高一级的目录里面去找,如果找到就拿来用。
- 如果没找到就继续往上找。以此类推。
- 如果到了xxhdpi目录还没有找到的话,就会去比自身屏幕密度低一级的目录去找,如果低一级的目录>=hdpi,找到了就拿来用。
- 如果没找到, 就去mdpi目录去找, 如果找到了,就拿来用。
- 如果没找到,就去默认的drawble目录里去找, 如果找到了就拿来用。
- 如果没找到,再去最低的ldpi目录里去找。如果找到了,就拿来用。
- 如果没找到, 那就是没找到了, 图片无法显示。(不过一般不会出现这种现象,因为如果每个目录都没有这个图片的话,你是编译不过的)
这里有两点需要注意:
- 先会去比自己密度高的目录里去找,这是因为因为系统相信,你在密度更高的目录里会放置分辨率更大的图片,这样的话这个图片会被缩小,但同时显示效果不会有损失,但是如果优先去低一级别的目录去找的话, 找到的图片就会被放大,这样的话这个图片就会被拉扯模糊了。
e.g. 同一张图片,你在mdpi和xxhdpi目录各放了一份, 这个应用你现在运行在hdpi的手机上, 那应用会选择哪张图片呢。答案是xxhdpi目录里的。即便hdpi离mdpi更近一点! -
如果在mdpi里找不到是不会直接去ldpi里找的, 而是先去默认的drawble目录里找,这是drawble目录和drawble-mdpi是一个级别的。
上表几点值得注意的地方:
-
rawable
目录和drawable-mdpi
目录和dp
到px
的转换关系是一样的。 - 当你放一个
120px*180px
的图片到drawable-hdpi
目录,如果此应用运行在一个xhdpi
的手机上,则这个图片会被拉扯到160px*240px
。 - 最后一行
dp->px
, 说明了在代码或者布局文件中声明一个dp
值, 这个值在不同屏幕密度的手机中会被乘以不同的倍数。
比如你在布局文件中写了一个宽和高分别为120dp
和180dp
的LinearLayout
, 那么当这个应用运行在xhdpi
的手机上时
它的实际像素就会被转换为240px*360px
。 如果运行在ldpi
的手机上,就变成了90px*135px
。 但是在这两个手机中显示的区域大小从肉眼看,是一模一样大的。
UI给工程师切多大图是合适的。
直接基于720*1280
的视觉稿切一版图片就可以了。 将图片只放到xhdpi
目录中,
这样系统会在不同密度屏幕的手机中对图片进行合理的缩放,如果想在xxhdpi
的手机上显示的很好, 也可以基于1080P
的屏幕设计然后放到xxhdpi
中,
这样的话就兼容所有低密度屏幕的手机, 而且也不会出现图片被拉扯的现象。
Note:The mipmap-xxxhdpi qualifier is necessary only to provide a launcher icon that can appear larger than usual on an xxhdpi device. You do not need to provide xxxhdpi assets for all your app's images.