一、Android屏幕适配出现的原因

由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子。
但是这种“碎片化”到底到达什么程度呢?
  • Android系统碎片化:小米定制的MIUI、魅族定制的flyme、华为定制的EMUI等等,当然都是基于Google原生系统定制的
  • Android机型屏幕尺寸碎片化:5寸、5.5寸、6寸等等
  • Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920 
据友盟指数显示,统计至2015年12月,支持Android的设备共有27796种
在2012年,OpenSignalMaps(以下简称OSM)发布了第一份Android碎片化报告,统计数据表明,
2012年,支持Android的设备共有3997种。
2013年,支持Android的设备共有11868种。
2014年,支持Android的设备共有18796种。
而随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深。而我们今天要探讨的,则是对我们开发影响比较大的——屏幕的碎片化。


二、屏幕适配的本质:

使得“布局”、“布局组件”、“图片资源”、“用户界面流程”匹配不同的屏幕尺寸 


三、重要概念

  • 屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等。
  • 屏幕分辨率
屏幕分辨率是指在横纵向上的像素点数,一般描述为屏幕的“宽x高”,单位是px,1px=1个像素点,一般以纵向像素*横向像素,如1080*1920。
  • 屏幕像素密度
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
  • px

px我们应该是比较熟悉的,前面的分辨率就是用的像素为单位,大多数情况下,比如UI设计、Android原生API都会以px作为统一的计量单位,像是获取屏幕宽高等。

  • dip和dp

dip和dp是一个意思,都是Density Independent Pixels的缩写,即密度无关像素,上面我们说过,dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi,那么在这种情况下,dp和px如何换算呢?在Android中,规定以160dpi为基准,1dip=1px,如果密度是320dpi,则1dip=2px,以此类推。

从而得出dp与px的换算关系:px=dp*dpi/160;dp=px*160/dpi。

举一个列子假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
  • sp

sp,即scale-independent pixels,与dp类似,但是可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。
推荐使用12sp、14sp、18sp、22sp作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于12sp的字体会太小导致用户看不清。

  • density
屏幕密度,density和dpi的关系为 density = dpi/160


四、解决方案

  1. “布局”适配
本质1:使得布局元素自适应屏幕尺寸 
尽量减少XML的布局层次,减少布局层次可以减少系统解析代码所做的工作,让界面的渲染速度变快
尽量使用RelativeLayout,使用RelativeLayout相比使用多个LinearLayout来说可以减少布局的层次,所以,对于屏幕适配来说,使用相对布局(RelativeLayout)将会是更好的解决方案。
本质2:根据屏幕的配置来加载相应的UI布局

应用场景:需要为不同屏幕尺寸的设备设计不同的布局

做法:使用限定符
作用:通过配置限定符使得程序在运行时根据当前设备的配置(屏幕尺寸)自动加载合适的布局资源
限定符类型:
    1. 尺寸(size)限定符
    2. 最小宽度(Smallest-width)限定符
    3. 布局别名
    4. 屏幕方向(Orientation)限定符

尺寸(size)限定符

使用场景:当一款应用显示的内容较多,希望进行以下设置:

在平板电脑和电视的屏幕(>7英寸)上:实施“双面板”模式以同时显示更多内容
在手机较小的屏幕上:使用单面板分别显示内容
因此,我们可以使用尺寸限定符(layout-large)通过创建一个文件
适配手机的单面板(默认)布局:res/layout/main.xml
适配尺寸>7寸平板的双面板布局::res/layout-large/main.xml
两个布局名称均为main.xml,只有布局的目录名不同:第一个布局的目录名为:layout,第二个布局的目录名为:layout-large,包含了尺寸限定符(large)
被定义为大屏的设备(7寸以上的平板)会自动加载包含了large限定符目录的布局,而小屏设备会加载另一个默认的布局

但要注意的是,这种方式只适合Android 3.2版本之前。


最小宽度(Smallest-width)限定符

背景:上述提到的限定符“large”具体是指多大呢?似乎没有一个定量的指标,这便意味着可能没办法准确地根据当前设备的配置(屏幕尺寸)自动加载合适的布局资源

例子:比如说large同时包含着5寸和7寸,这意味着使用“large”限定符的话我没办法实现为5寸和7寸的平板电脑分别加载不同的布局
于是,在Android 3.2及之后版本,引入了最小宽度(Smallest-width)限定符
定义:通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕从而加载不同的UI资源
代码展示:
适配手机的单面板(默认)布局:res/layout/main.xml
适配尺寸>7寸平板的双面板布局:res/layout-sw600dp/main.xml
sw xxxdp,即small width的缩写,其不区分方向,即无论是宽度还是高度,只要大于 xxxdp,就采用次此布局
对于最小宽度≥ 600 dp 的设备 
系统会自动加载 layout-sw600dp/main.xml(双面板)布局,否则系统就会选择 layout/main.xml(单面板)布局 

(这个选择过程是Android系统自动选择的)


使用布局别名

当你需要同时为Android 3.2版本前和Android 3.2版本后的手机进行屏幕尺寸适配的时候,由于尺寸限定符仅用于Android 3.2版本前,最小宽度限定符仅用于Android 3.2版本后,所以这会带来一个问题,为了很好地进行屏幕尺寸的适配,你需要同时维护layout-sw600dp和layout-large的两套main.xml平板布局,如下:

适配手机的单面板(默认)布局:res/layout/main.xml
适配尺寸>7寸平板的双面板布局(Android 3.2前):res/layout-large/main.xml
适配尺寸>7寸平板的双面板布局(Android 3.2后)res/layout-sw600dp/main.xml
最后的两个文件的xml内容是完全相同的,这会带来:文件名的重复从而带来一些列后期维护的问题
于是为了要解决这种重复问题,我们引入了“布局别名”
还是上面的例子,你可以定义以下布局:
适配手机的单面板(默认)布局:res/layout/main.xml
适配尺寸>7寸平板的双面板布局:res/layout/main_twopanes.xml
然后加入以下两个文件,以便进行Android 3.2前和Android 3.2后的版本双面板布局适配:

res/values-large/layout.xml(Android 3.2之前的双面板布局):

@layout/main_twopanes

res/values-sw600dp/layout.xml(Android 3.2及之后的双面板布局):

@layout/main_twopanes

注: 

最后两个文件有着相同的内容,但是它们并没有真正去定义布局,它们仅仅只是将main设置成了@layout/main_twopanes的别名 
由于这些文件包含 large 和 sw600dp 选择器,因此,系统会将此文件匹配到不同版本的>7寸平板上: 
a. 版本低于 3.2 的平板会匹配 large的文件 
b. 版本高于 3.2 的平板会匹配 sw600dp的文件

这样两个layout.xml都只是引用了@layout/main_twopanes,就避免了重复定义布局文件的情况


屏幕方向(Orientation)限定符

使用场景:根据屏幕方向进行布局的调整

取以下为例子:
小屏幕, 竖屏: 单面板
小屏幕, 横屏: 单面板
7 英寸平板电脑,纵向:单面板,带操作栏
7 英寸平板电脑,横向:双面板,宽,带操作栏
10 英寸平板电脑,纵向:双面板,窄,带操作栏
10 英寸平板电脑,横向:双面板,宽,带操作栏
电视,横向:双面板,宽,带操作栏
方法是: 
    • 先定义类别:单/双面板、是否带操作栏、宽/窄
定义在 res/layout/ 目录下的某个 XML 文件中
再进行相应的匹配:屏幕尺寸(小屏、7寸、10寸)、方向(横、纵)
使用布局别名进行匹配
在 res/layout/ 目录下的某个 XML 文件中定义所需要的布局类别 
(单/双面板、是否带操作栏、宽/窄) 
res/layout/onepane.xml:(单面板)
res/layout/onepane_with_bar.xml:(单面板带操作栏)
res/layout/twopanes.xml:(双面板,宽布局)

res/layout/twopanes_narrow.xml:(双面板,窄布局)

    •   使用布局别名进行相应的匹配 
(屏幕尺寸(小屏、7寸、10寸)、方向(横、纵)) 

res/values/layouts.xml:(默认布局)

@layout/onepane_with_barfalse

可为resources设置bool,通过获取其值来动态判断目前已处在哪个适配布局
res/values-sw600dp-land/layouts.xml 

(大屏、横向、双面板、宽-Andorid 3.2版本后)

@layout/twopanestrue

res/values-sw600dp-port/layouts.xml 

(大屏、纵向、单面板带操作栏-Andorid 3.2版本后)

@layout/onepanefalse


    2.“布局组件”匹配
本质:使得布局组件自适应屏幕尺寸
做法:

使用”wrap_content”、”match_parent”和”weight“来控制视图组件的宽度和高度


    3.“图片资源”匹配
本质:使得图片资源在不同屏幕密度上显示相同的像素效果
    • 做法1:使用自动拉伸位图:Nine-Patch的图片类型

使用自动拉伸位图(nine-patch图片),后缀名是.9.png,它是一种被特殊处理过的PNG图片,设计时可以指定图片的拉伸区域和非拉伸区域;使用时,系统就会根据控件的大小自动地拉伸你想要拉伸的部分

    • 做法2:提供备用位图(符合屏幕尺寸的图片资源)

由于 Android 可在各种屏幕密度的设备上运行,因此我们提供的位图资源应该始终可以满足各类密度的要求:

即一套分辨率=一套位图资源(这个当然是Ui设计师做了)

上述方案是常见的一种方案,这固然是一种解决办法,但缺点在于:

  • 每套分辨率出一套图,为美工或者设计增加了许多工作量
  • 对Android工程文件的apk包变的很大

那么,有没有一种方法:

  • 保证屏幕密度适配
  • 可以最小占用设计资源
  • 使得apk包不变大(只使用一套分辨率的图片资源)

下面我们就来介绍这个方法: 

方法介绍

1. 先来理解下Android 加载资源过程 

Android SDK会根据屏幕密度自动选择对应的资源文件进行渲染加载(自动渲染)

比如说,SDK检测到你手机的分辨率是320x480(dpi=160),会优先到drawable-mdpi文件夹下找对应的图片资源;但假设你只在xhpdi文件夹下有对应的图片资源文件(mdpi文件夹是空的),那么SDK会去xhpdi文件夹找到相应的图片资源文件,然后将原有大像素的图片自动缩放成小像素的图片,于是大像素的图片照样可以在小像素分辨率的手机上正常显示。

所以理论上来说只需要提供一种分辨率规格的图片资源就可以了。 

那么应该提供哪种分辨率规格呢?

如果只提供ldpi规格的图片,对于大分辨率(xdpi、xxdpi)的手机如果把图片放大就会不清晰

所以需要提供一套你需要支持的最大dpi分辨率规格的图片资源,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。那么这一套最大dpi分辨率规格应该是哪种呢?是现在市面手机分辨率最大可达到1080X1920的分辨率(dpi=xxdpi=480)吗?

2. xhdpi应该是首选

原因如下:

xhdpi分辨率以内的手机需求量最旺盛 

目前市面上最普遍的高端机的分辨率还多集中在720X1080范围内(xhdpi),所以目前来看xhpdi规格的图片资源成为了首选

节省设计资源&工作量 

在现在的App开发中(iOS和Android版本),有些设计师为了保持App不同版本的体验交互一致,可能会以iPhone手机为基础进行设计,包括后期的切图之类的。 

设计师们一般都会用最新的iPhone6和iPhone5s(5s和5的尺寸以及分辨率都一样)来做原型设计,所有参数请看下图:

Android屏幕适配方案_第1张图片

iPhone主流的屏幕dpi约等于320, 刚好属于xhdpi,所以选择xhdpi作为唯一一套dpi图片资源,可以让设计师不用专门为Android端切图,直接把iPhone的那一套切好的图片资源放入drawable-xhdpi文件夹里就好,这样大大减少的设计师的工作量!

    4.如何进行屏幕密度匹配?

本质:使得布局组件在不同屏幕密度上显示相同的像素效果

    • 做法1:以某一分辨率为基准,生成所有分辨率对应像素数列表
比如480*320的分辨率为基准
宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320
高度为480,将任何分辨率的高度分为480份,取值为y1-y480
如下提供自动生成各分辨率对对应像素数列表:
autolayout.jar包下载地址
内置了常用的分辨率,默认基准为320*480,当然对于特殊需求,通过命令行指定即可:
java -jar xx.jar width height width,height_width,height
到此,我们通过编写一个工具,根据某基准尺寸,生成所有需要适配分辨率的values文件。在UI给出的设计图,可以快速的按照其标识的px单位进行编写布局。基本解决了适配的问题。
使用上述的适配方式,应该能进行90%的适配了,但其缺点还是很明显:
由于实际上还是使用px作为长度的度量单位,所以和google的要求使用dp作为度量单位会有所背离
必须尽可能多的包含所有分辨率,因为这个是使用这个方案的基础,如果有某个分辨率缺少,将无法完成该屏幕的适配
过多的分辨率像素描述xml文件会增加软件包的大小和维护的难度。
    • 做法2:使用dp

在任何分辨率的屏幕中,显示的尺寸大小是大约是一致的

但是,这样并不能够解决所有的适配问题:

呈现效果仍旧会有差异,仅仅是相近而已

当设备的物理尺寸存在差异的时候,dp就显得无能为力了。为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。

我们同样举之前的例子,想要画一条长度是屏幕一半的线,我们之前说设定为160dp,如果在320*480和480*800的设备上都是我们理想的效果,但是在1080*1920的设备上,我们根据公式计算出px值为480px,并不是1080的一半。如下图:

Android屏幕适配方案_第2张图片

直接设定成固定的dp值不能完全适配所有的机型,在这时候我们就要多考虑使用布局的属性。


五、总结

        综合以上所有的分析和解决方案,最严谨的一种方案就是选择生成各分辨率的像素列表并且使用备用位图,如果对适配没有那么精确可以直接使用dp(尽量多使用布局文件自带的属性),图片则选择主流xhdpi图片即可。

更多相关文章

  1. Android之TabLayout布局的使用
  2. Android Studio App设置线性布局LinerLayout控件垂直/水平方向排
  3. Android屏幕适配-终结者
  4. 最新res索引讲解(drawable、layout、values等目录的分辨率和layou
  5. 【Android】Android中两种常用布局(LinearLayout和RelativeLayout

随机推荐

  1. 【Android】viewpager banner 广告 自动
  2. android skia 使用实例
  3. Android Makefile example
  4. android 下载文件图片圆形进度条
  5. android RadioButton 点击时候出现点击声
  6. android 屏幕适配之自动生成多重values
  7. Android开发之自定义PopupWindow记录
  8. android 入门demo 进度条
  9. Ubuntu下android真机调试Using Hardware
  10. Android下的OpenGl ES