一、为什么要做Android手机的适配?
由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子。
通过这张图我们就可以了解到随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深。而我们今天要探讨的,则是对我们开发影响比较大的——屏幕的碎片化。
下面这张图是Android屏幕尺寸的示意图,在这张图里面,蓝色矩形的大小代表不同尺寸,颜色深浅则代表所占百分比的大小。
详细的分析可以去http://opensignal.com/reports/2014/android-fragmentation/中了解。
二、我们应该适配哪些设备呢?
屏幕尺寸这么多,为了让我们开发的程序能够比较美观的显示在不同尺寸、分辨率、像素密度(这些概念我会在下面详细讲解)的设备上,那就要在开发的过程中进行处理,至于如何去进行处理,这就是我们今天的主题了。但是在开始进入主题之前,我们再来探讨一件事情,那就是Android设备的屏幕尺寸,从几寸的智能手机,到10寸的平板电脑,再到几十寸的数字电视,我们应该适配哪些设备呢?
对于具有相同像素密度的设备来说,像素越高,尺寸就越大,所以我们可以换个思路,将问题从单纯的尺寸大小转换到像素大小和像素密度的角度来。
这张图是我从太平洋手机网上截取的部分选购选项,我们可以通过观察发现,市场上主流的手机分辨率和屏幕尺寸并没有第一章中的图表现的那么恐怖。
所以说,我们只要尽量适配这几种分辨率,就可以在大部分的手机上正常运行了。这只是手机的适配,对于平板设备(电视也可以看做是平板),我们还需要一些其他的处理。
三、Android屏幕适配的概念
Q1:什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米,比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等,其中2.4,2.8,3.5都已经比较少见,3.7被部分手机提供商以4.0屏宣称混淆视听。
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素,如1960*1080。
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
Q2:什么是dp、dip、dp、sp、px?他们之间的关系是什么?
px像素单位,大多数情况下,比如UI设计、Android原生API都会以px作为统一的计量单位,像是获取屏幕宽高等。
dip和dp是一个意思,都是Density Independent Pixels的缩写,即密度无关像素,上面我们说过,dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi,那么在这种情况下,dp和px如何换算呢?在Android中,规定以160dpi为基准,1dip=1px,如果密度是320dpi,则1dip=2px,以此类推。
假如同样都是画一条320px的线,在480*800分辨率手机上显示为2/3屏幕宽度,在320*480的手机上则占满了全屏,如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。这也是为什么在Android开发中,写布局的时候要尽量使用dp而不是px的原因。
而sp,即scale-independentpixels,与dp类似,但是可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。
Q3:什么是mdpi、hdpi、xdpi、xxdpi?如何计算和区分?
mdpi、hdpi、xdpi、xxdpi用来修饰Android中的drawable文件夹及values文件夹,用来区分不同像素密度下的图片和dimen值。Google官方指定按照下列标准进行区分:
在设计图标时,对于五种主流的像素密度(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),依此类推。
虽然 Android 也支持低像素密度 (LDPI) 的屏幕,但无需为此费神,系统会自动将 HDPI 尺寸的图标缩小到 1/2 进行匹配。
下图为图标的各个屏幕密度的对应尺寸:
四、解决方案
(1)解决布局问题
使用weight权重来设计布局,可以不用详细的计算dp的值。假设屏幕的宽为L,我们定义两个button 设置它们的属性
android:layout_weight="2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Chenteachar"
/>
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Chenteachar"
在手机上显示的效果为右图所示:
如果我们将数据更改为
效果图如右图所示:
Weight的计算方式: 组件实际高度=补偿高度+剩余空间*权重比
假设屏幕的高为H,通过这个计算公式 我们可以得到第一种情况button1的实际高度=H+(H-2H)*2/3=1/3H;得到第二种情况button1的实际高度=0+H*2/3=2/3H
通常开发者利用weigt属性 和设置宽高的属性wrap_content、match_parent来解决布局问题问题,但文字大小,图片像素问题是无法利用简单的设置来解决的。在《资源文件的合理处理方法》有专门讨论。
(2)根据各种屏幕尺寸选择不同的布局
上面所提到的灵活布局或者是相对布局,带来的优势就只有这么多了。虽然这些布局可以拉伸组件内外的空间以适应各种屏幕,但它们不一定能为每种屏幕都提供最佳的用户体验。因此,应该应针对各种屏幕配置提供一些备用布局。
我们可以通过使用配置限定符,在运行时根据当前的设备配置自动选择合适的资源了,例如根据各种屏幕尺寸选择不同的布局。
Large是Android系统默认的限定符,例如
res/layout/main.xml,单面板(默认)布局:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragmentandroid:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
LinearLayout>
res/layout-large/main.xml,双面板布局:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragmentandroid:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<fragmentandroid:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
LinearLayout>
第二种布局名称目录中的 large 限定符。系统会在属于较大屏幕(例如 7 英寸或更大的平板电脑)的设备上选择此布局。系统会在较小的屏幕上选择其他布局(无限定符)。
但是Android设备的多样性,不可能单单使用large默认限定符就能解决问题的,同时large也因为适用于7寸以上的设备,像5寸这样不大不小的屏幕就显得位置比较尴尬。这时候我们就应该使用最小限定符
res/layout-sw600dp/main.xml,双面板布局:
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
sw600dp的意思是:对于最小宽度大于等于 600 dp 的设备,系统会选择 layout-sw600dp/main.xml(双面板)布局,否则系统就会选择 layout/main.xml(单面板)布局。
我们也可以通过写别名的方法来减少布局文件的编写量,比如上面看到的布局res/layout-sw600dp/main.xml和res/layout-large/main.xml中的代码是一样的,我们可以将布局文件都写在layout文件夹下,命名不同的名字,比如:main或者main_two.在layout-sw600dp和layout-large文件夹中创建一个layout.xml文件,
<resources>
<itemname="main"type="layout">@layout/main_twopanesitem>
resources>
利用上述代码将main_two命名别名,当屏幕大小大于600ddp时系统调用的将不是main布局而是main_twopanes。
屏幕可能会出现的横屏纵屏的情况,这时候我们就应该使用方向限定符。下面将列出几种常见的限定符:
特征 |
限定符 |
描述 |
屏幕尺寸
|
small |
这种屏类似低分辨率的QVGA屏幕。对于小屏的最小布局尺寸大约是320x426dp。例如QVGA低分辨率和VGA高分辨率。 |
normal |
这种屏类似中等分辨率的HVGA屏幕。对于普通屏幕的最小布局尺寸大约是320x470dp。如,WQVGA低分辨率屏、HVGA中等分辨率屏、WVGA高分辨率屏。 |
|
large |
large:这种屏类似中等分辨率的VGA屏幕,对于大屏幕的最小布局尺寸大约是480x640dp。例如VGA和WVGA的中等分辨率屏 |
|
xlarge |
这种屏被认为比传统的中等分辨率的HVGA屏幕大。针对xlarge屏的最小布局尺寸大约是720x960dp。在大多数情况下,这种超大屏幕的设备因为太大而要放到背包中来携带,而且最有可能的是平板样式的设备。 |
|
注意:使用尺寸限定符不意味着资源仅用于这个尺寸的屏幕。如果没有用限定符提供与当前设备配置相匹配的可选资源,那么系统会使用与配置最接近的资源。 |
特征 |
限定符 |
描述 |
|
屏幕方向 |
port |
port:纵向设备(垂直) |
|
land |
land:横向设备(水平) |
||
如果用户旋转屏幕,这个限定能够在应用程序运行期间改变。orientation配置字段指示当前设备的方向。 |
|||
屏幕像素密度 |
ldpi |
针对大约120dpi的低分辨率屏幕 |
|
mdpi |
针对大约160dpi的高分辨率屏幕 |
||
hdpi |
针对大约240dpi的高分辨率屏幕 |
||
xhdpi |
针对大约320dpi的超高分辨率屏幕,被添加在API基本8以后的版本中 |
||
nodpi |
这个限定被用于不想根据匹配的设备分辨率进行缩放的位图资源。 |
||
|
nonight |
白天 |
|
night |
夜间 |
||
屏幕外观 |
long |
长屏幕,如WQVGA、WVGA、FWVGA |
|
notlong |
非长屏幕,如QVGA、HVGA、VGA |
||
最小宽度 |
swdp |
例如之前介绍的sw600dp,以swxxdp的形式来限定最小宽度的,其意义是屏幕大于这个最小值就采用swxxx资源文件夹下的布局和定义的其他人res,当应用程序提供了多个带有不同值的最小宽度限定符资源目录时,系统会使用最接近(不超出)设备最小宽度的那个资源。 |
|
可用宽度 |
wdp |
例如w720dp,它的意义是使用的宽度值要比实际的屏幕尺寸小,因为这些固定UI元素的占用,使得应用程序的可用空间减少,通常使用在虚拟操作栏的设备当中。 |
|
可用高度 |
hdp |
同可用宽度,只是这里变成了高度 |
|
语言和地区 |
En-fr |
语言是用两个字母的ISO 639-1语言代码定义的,紧跟其后的是可选的两个ISO-3166-1-appha-2地区代码字母(前面是小写的“r”)。这个编码不区分大小写,r前缀被用于区分地区部分,不能够单独指定地区。如果用户改变了系统中的语言设置,那么在应用程序的运行期间也能够改变为对应的语言。 |
注意:
1、通常这些限定符不用我们去刻意设置,系统会自动帮我们找到合适得资源来帮我们调整布局,但是在一些特殊的要求下,我们需要去查看官方的api来限定那些适配布局不被考虑使用或者考虑使用。
2、限定符是存在优先级问题得,限定符的匹配是向下匹配,从高向低找。如果同时出现了swxxxdp, large,xxx*xxx,hxxxdp,,几种同时对屏幕做出限定的res,系统会自动找出精度值最高的res进行适配,例如屏蔽实际大小的320*480,如果适配了320*480,和sw300,系统优先使用320*480的res,如果出现了sw300dp和large 系统优先使用sw300dp。
3、命名规则:只需要用横线加限定符的方式即可使用,xx-限定符
4、当我们创建一个项目的时候,系统会自动帮我们生成一部分带限定符的文件夹