###1. android 屏幕适配

由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子, 而随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深

android 设备的屏幕尺寸这么多,为了让我们开发的程序能够比较美观的显示在不同尺寸、分辨率、像素密度(这些概念我会在下面详细讲解)的设备上,那就要在开发的过程中进行处理, 但是从几寸的智能手机,到10寸的平板电脑,再到几十寸的数字电视,我们应该适配哪些设备呢?

其实这个问题不应该这么考虑,因为对于具有相同像素密度的设备来说,像素越高,尺寸就越大,所以我们可以换个思路,将问题从单纯的尺寸大小转换到像素大小和像素密度的角度来考虑

###2. 屏幕尺寸 / 屏幕分辨率 / 屏幕像素密度

**屏幕尺寸: **指屏幕的对角线的长度, 单位是英寸 (inch),1英寸=2.54厘米, 常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0 等

*屏幕分辨率: **指在横纵向上的像素点数单位是px (pixel),1px=1个像素点,一般以纵向像素横向像素,如1960*1080

**屏幕像素密度: **是指每英寸上的像素点数, 单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小

###3. dp / dip

Android原生API都会以px作为统一的计量单位,像是获取屏幕宽高等, 但是, 编写布局时使用 px 为单位则存在局限性, 例如, 假如同样都是画一条320px的线,在480800分辨率手机上显示为2/3屏幕宽度,在320480的手机上则占满了全屏

dp 和 dip 是同一个意思, 都是 Density Independent Pixels的缩写,即密度无关像素, 那么 dp 和 物理像素的是如何对应的呢?

Android中,规定以160dpi为基准,1dp = 1px, 即

px = dp * (dpi / 160)

例如, 320dpi 时, 1dp = 2px

dp 能够让同一数值在不同的分辨率下展示出大致相同的尺寸 (可以近似认为 1dp = 1/160inch), 但是当设备的物理尺寸差异较大时, 就无能为力了, 例如为 4.3 寸设备准备的 UI , 在 5.0 的设备上运行时, 可能在屏幕下侧和右侧存在大量的空白, 而为5.0寸设备准备的UI, 在4.3寸上可能显示不下

###4. layout的屏幕适配

####4.1 支持不同的屏幕尺寸

#####4.1.1 使用灵活布局

要确保布局的灵活性并适应各种尺寸的屏幕,应使用 “wrap_content” 和 “match_parent” 控制某些视图组件的宽度和高度。

使用 “wrap_content”,系统就会将视图的宽度或高度设置成所需的最小尺寸以适应视图中的内容,而 “match_parent”(在低于 API 级别 8 的级别中称为 “fill_parent”)则会展开组件以匹配其父视图的尺寸。

如果使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需的空间或展开以填满可用空间。此方法可让布局正确适应各种屏幕尺寸和屏幕方向

“weight” 是线性布局的一个独特的属性,我们可以使用这个属性来按照比例对界面进行分配,完成一些特殊的需求, android:layout_weight的真实含义是:如果View设置了该属性并且有效,那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比

#####4.1.2 使用相对布局

在开发中,我们大部分时候使用的都是线性布局、相对布局和帧布局,绝对布局由于适配性极差,所以极少使用。

由于各种布局的特点不一样,所以不能说哪个布局好用,到底应该使用什么布局只能根据实际需求来确定。我们可以使用 LinearLayout 的嵌套实例并结合 “wrap_content” 和 “match_parent”,以便构建相当复杂的布局。不过,我们无法通过 LinearLayout 精确控制子视图的特殊关系;系统会将 LinearLayout 中的视图直接并排列出。

如果我们需要将子视图排列出各种效果而不是一条直线,通常更合适的解决方法是使用 RelativeLayout,这样就可以根据各组件之间的特殊关系指定布局了。例如,我们可以将某个子视图对齐到屏幕左侧,同时将另一个视图对齐到屏幕右侧

#####4.1.3 使用不同的布局

上面所提到的灵活布局或者是相对布局,可以为我们带来的优势就只有这么多了。虽然这些布局可以拉伸组件内外的空间以适应各种屏幕,但它们不一定能为每种屏幕都提供最佳的用户体验。因此,我们的应用不仅仅是只实施灵活/相对布局来适应不同的屏幕尺寸,还应该应针对各种屏幕配置提供不同的布局设计, 然后通过使用配置限定符,在运行时根据当前的设备配置自动选择合适的资源了

######4.1.3.1 屏幕尺寸限定符

很多应用会在较大的屏幕上实施“双面板”模式,即在一个面板上显示项目列表,而在另一面板上显示对应内容。平板电脑和电视的屏幕已经大到可以同时容纳这两个面板了,但手机屏幕就需要分别显示。因此,我们可以根据屏幕的物理尺寸选择不同的布局文件

android 支持 “small” “large” 等尺寸限定符, 因此, 可以根据屏幕尺寸, 准备几套布局文件, 放置在对应的目录中:

  • layout : 放置一些通用布局文件, 比如顶部和底部的布局, 在不同的屏幕大小下使用相同的布局
  • layout-small : 屏幕尺寸小于3英寸左右的布局
  • layout-normal : 屏幕尺寸小于4.5英寸左右的布局
  • layout-large : 屏幕尺寸4 ~ 7 英寸左右的布局
  • layout-xlarge : 屏幕尺寸7 ~ 10 英寸左右的布局

例如

//单面板(默认)布局
res/layout/main.xml

//双面板布局
res/layout-large/main.xml

######4.1.3.2 最小宽度限定符

屏幕尺寸限定符的限定范围比较宽泛, android 3.2 之后, 引入了一个更细粒度的的限定符 : “最小宽度限定符”

最小宽度限定符可让您通过指定某个最小宽度(以 dp 为单位)来定位屏幕。例如,标准 7 英寸平板电脑的最小宽度为 600 dp,因此如果您要在此类屏幕上的用户界面中使用双面板(但在较小的屏幕上只显示列表),您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符

例如:

//单面板(默认)布局
res/layout/main.xml

//双面板布局
res/layout-sw600dp/main.xml

也就是说,对于最小宽度大于等于 600 dp 的设备,系统会选择 layout-sw600dp/main.xml(双面板)布局,否则系统就会选择 layout/main.xml(单面板)布局

######4.1.3.3 屏幕方向限定符

若希望在横屏和竖屏模式下, 使用不同的布局, 则可以使用屏幕方向限定符

  • land : 横屏
  • port : 竖屏

例如

res/layout/main.xml
res/layout-land/main.xml
res/layout-port/main.xml

#####.4.1.4 布局别名

如果在不同的限定符下的几个布局是相同的, 则可以使用布局别名来引用相同的布局文件, 避免重复定义相同的布局

例如, 有一个layout文件在 “sw600dp-port” “large-port” 2种限定下, 都会用到这同一份layout 文件

res/layout/main.xml

在 “sw600dp-port” “large-port” 2种限定下, 都会用到这同一份layout 文件

res/values-sw600dp-port/layouts.xml 文件
<resources>
    <item name="main_layout" type="layout">@layout/main<item>
</resources>

res/values-large-port/layouts.xml 文件
<resources>
    <item name="main_layout" type="layout">@layout/main<item>
</resources>

则使用 “@layout/main_layout” 或者 “R.layout.main_layout” 事实上最终引用的都是 res/layout/main.xml 文件

####4.1.5 支持不同的屏幕密度

由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素定义布局尺寸就会产生问题。因此,请务必使用 dp 或 sp 单位指定尺寸。dp 是一种非密度制约像素,其尺寸与 160 dpi 像素的实际尺寸相同。sp 也是一种基本单位,但它可根据用户的偏好文字大小进行调整(即尺度独立性像素)

###5. 图片的屏幕适配

####5.1 使用像素密度限定符

android 支持使用如下的限定符来限定在不同的像素密度下, 使用不同的 drawable 和 value 资源

  • ldpi : 120dpi, 这个像素密度的设备已经很罕见了,所在现在适配时不需考虑
  • mdpi : 120dpi ~ 160dpi
  • hdpi : 160dpi ~ 240dpi
  • xhdpi : 240dpi ~ 320dpi
  • xxhdpi : 320dpi ~ 480dpi
  • xxxhdpi : 480dpi ~ 640dpi

在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面 (res/drawable 等价于 res/drawable-mdpi 目录)

在设计图标时,对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放

例如, 个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推, 下面为图标的各个屏幕密度的对应尺寸

  • mdpi : 48 x 48 px, 放置在 res/drawable-mdpi/ 中
  • hdpi : 72 x 72 px, 放置在 res/drawable-hdpi/ 中
  • xhdpi : 96 x 96 px, 放置在 res/drawable-xhdpi/ 中
  • xxhdpi : 144 x 144 px, 放置在 res/drawable-xxhdpi/ 中
  • xxxhdpi : 192 x 192 px, 放置在 res/drawable-xxxdpi/ 中

这样一来,只要我们引用 @drawable/id,系统都能根据相应屏幕的 dpi 选取合适的位图

在运行时,如果在对应的 drawable 目录中没有找到图片资源, 则按照如下的规则寻找其它 drawable 目录的图片进行替代:

  1. 依次向更高分辨率的 drawable 目录中寻找图片资源, 直到找到图片为止 (例如在 mdpi 设备上, drawbale-mdpi 目录中未找到对应的图片, 则依次以 drawable-hdpi, drawable-xhdpi … 的顺序来寻找 )
  2. 若上一步仍为找到图片资源, 则依次向更低分辨率的 drawbale 目录中寻找图片资源 ((例如在 mdpi 设备上, drawbale-mdpi 目录中未找到对应的图片, 则依次以 drawable, drawable-ldpi … 的顺序来寻找 ) )

最终, 如果图片没找到, 那就是没找到了, 图片无法显示, 如果找到了图片, 也并不是原样显示, 还需要找比例进行缩放 (mdpi、hdpi、xhdpi、xxhdpi 和 xxxhdpi 按照 2:3:4:6:8 的比例进行缩放), 这一步在将图片装在进内存时, 由系统自动完成

####5.2 使用自动拉伸位图(.9图)

自动拉伸位图(.9图)是一种格式特殊的 PNG 文件,其中会指明可以拉伸以及不可以拉伸的区域, 典型的例子就是 手机QQ 中的消息背景, 每一条消息都是一张不规则的图片, 无论消息多长, 都会自动拉升,并且显示成相同的样式, 这就是利用 .9 图实现的

.9图的制作,实际上就是在原图片上添加1px的边界(即图片添加了一个1px宽度的边框),然后按照我们的需求,把对应的位置设置成黑色线,系统就会根据我们的实际需求进行拉伸

  • 顶部黑线: 指定横向拉伸区域
  • 左侧黑线: 指定纵向拉伸区域
  • 顶部黑线: 指定前景区横向范围 (android 里面的view既有背景又有前景, 例如 TextView, 前景就是文字的显示区域)
  • 右侧黑线: 指定前景区纵向范围 (android 里面的view既有背景又有前景, 例如 TextView, 前景就是文字的显示区域)

.9 图只需要放置在 drawable 目录中即可

####5.3 高清设计图的尺寸

Google官方给出的高清设计图尺寸有两种方案,一种是以mdpi设计,然后对应放大得到更高分辨率的图片,另外一种则是以高分辨率作为设计大小,然后按照倍数对应缩小到小分辨率的图片。

更推荐第二种方法,因为小分辨率在生成高分辨率图片的时候,会出现像素丢失,我不知道是不是有方法可以阻止这种情况发生。

而分辨率可以以1280720或者是19601080作为主要分辨率进行设计