[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 的屏幕像素密度约为316dpiNexus 7的屏幕像素密度为216dpi,相差甚远。
这是因为虽然它们拥有一样的分辨率,但是它们显示尺寸却不一样。
再次说明,屏幕像素密度是分辨率和显示尺寸的比值,两个因素一起决定屏幕像素密度。

例如:计算Nexus5的屏幕像素密度: 屏幕尺寸:4.95inch、分辨率:1920×1080,屏幕像素密度:445

Dip(Device-independent pixel,设备独立像素)

dp,指的是自适应屏幕密度的像素,用于指定控件宽高。就所屏幕密度变大,1dp所对应的px也会变大。
不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGAQVGA推荐使用这个,
不依赖像素。
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以上的大小,
通常可以使用12sp14sp18sp22sp,最好不要使用奇数和小数。

我们可以用下面的部分来解释为什么用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是一比一的计算。
这样这条横线正好占屏幕的一半,而我们将这个横线的视图发布到480
800的手机上时,
虽然也是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的手机上换算成px160*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.这样显示到这两个设备上所占的比例会一样

一个是71280*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,这个不叫屏幕密度,但是在dppx转换的时候要使用到,
这个是屏幕密度的比例值,是用该设备的屏幕密度除以标准的屏幕密度得到的一个比值,
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 320
240 (Quarter VGA)
HVGA 480320 (Half-size VGA)
SVGA 800
600 (Super VGA)

5:3
WVGA 800*480 (Wide VGA)

16:9
FWVGA 854480 (Full Wide VGA)
HD 1920
1080 High Definition
QHD 960540
720p 1280
720 标清
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是有可能会运行在从ldpixxhdpi的各种级别的手机上。
这个时候就需要根据一定的策略去寻找图片了。

Android系统寻找图片的步骤是这样的:

  • 去屏幕密度对应的目录去找。如果找到就拿来用。
  • 如果没找到,就去比这个密度高一级的目录里面去找,如果找到就拿来用。
  • 如果没找到就继续往上找。以此类推。
  • 如果到了xxhdpi目录还没有找到的话,就会去比自身屏幕密度低一级的目录去找,如果低一级的目录>=hdpi,找到了就拿来用。
  • 如果没找到, 就去mdpi目录去找, 如果找到了,就拿来用。
  • 如果没找到,就去默认的drawble目录里去找, 如果找到了就拿来用。
  • 如果没找到,再去最低的ldpi目录里去找。如果找到了,就拿来用。
  • 如果没找到, 那就是没找到了, 图片无法显示。(不过一般不会出现这种现象,因为如果每个目录都没有这个图片的话,你是编译不过的)

这里有两点需要注意:

  • 先会去比自己密度高的目录里去找,这是因为因为系统相信,你在密度更高的目录里会放置分辨率更大的图片,这样的话这个图片会被缩小,但同时显示效果不会有损失,但是如果优先去低一级别的目录去找的话, 找到的图片就会被放大,这样的话这个图片就会被拉扯模糊了。
    e.g. 同一张图片,你在mdpi和xxhdpi目录各放了一份, 这个应用你现在运行在hdpi的手机上, 那应用会选择哪张图片呢。答案是xxhdpi目录里的。即便hdpi离mdpi更近一点!
  • 如果在mdpi里找不到是不会直接去ldpi里找的, 而是先去默认的drawble目录里找,这是drawble目录和drawble-mdpi是一个级别的。

上表几点值得注意的地方:

  • rawable目录和drawable-mdpi目录和dppx的转换关系是一样的。
  • 当你放一个120px*180px的图片到drawable-hdpi目录,如果此应用运行在一个xhdpi的手机上,则这个图片会被拉扯到160px*240px
  • 最后一行dp->px, 说明了在代码或者布局文件中声明一个dp值, 这个值在不同屏幕密度的手机中会被乘以不同的倍数。
    比如你在布局文件中写了一个宽和高分别为120dp180dpLinearLayout, 那么当这个应用运行在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.

以上是[Android开发]Android 屏幕适配的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>