版权声明:本文为博主原创文章(部分引用他人博文,已加上引用说明),未经博主允许不得转载。https://www.jianshu.com/p/7aa34434ad4d
转载请标明出处:
https://www.jianshu.com/p/7aa34434ad4d
本文出自 AWeiLoveAndroid的博客
前言
Android适配是一个老生常谈的问题,很多程序员觉得很恶心,不愿意做适配,但是又不得不做。然后老板说,这位兄弟,做好了,今天晚饭给你加个鸡腿,然后程序员开始找各种资料,忙活起来了,最终在苦逼的煎熬中做完了。
好了,言归正传,根据多年开发经验,总结一下Android适配主要表现在以下 3个方面:
1、屏幕适配。(网上讲的最多的就是这个。) 由于Android碎片化严重,导致开发中一套代码在不同手机上运行起来效果不是很好,兼容性不是很好,这就需要对不同分辨率,不同屏幕大小的手机做屏幕适配。
2、版本适配。 不同的系统版本api有所变更,既要适配高版本,也要做到兼容低版本。
具体讲解请看链接: http://www.jianshu.com/p/49fa8ebc0105
3、ROM适配。(这个是最难的,工作量也是最大的,如果没有不同版本手机适配的积累,遇到问题都不知道怎么解决。) 由于Android是开源的,不同的手机厂商有自己定制的ROM,对系统的api可能有变更,也有可能新增一些api,所以在开发中,要针对不同厂商的手机做一些特殊适配。
具体讲解请看链接: http://www.jianshu.com/p/f9c67a4b908e
废话少说,开始进入正题。这篇文章我们先讲解第一个问题 ---- 屏幕适配。
一、屏幕适配是啥(可能有人不懂,我在此简单解释一下)?
程序猿把设计狮制作的效果图应用到不同的手机,对不同的屏幕进行界面调整的过程,确保界面不变形,呈现效果图的位置、尺寸、比例。
二、需要掌握的几个知识点。
(1)屏幕物理尺寸:
屏幕对角线的尺寸。单位是英寸,1英寸 ≈ 2.54厘米
比如常见的屏幕尺寸有5.0、5.1、5.2、5.5、5.7、5.9、6.0等
(2)屏幕分辨率:
-
定义:
确定计算机屏幕上显示多少信息的设置,以水平和垂直像素来衡量。
计算公式:
屏幕分辨率 = 横向像素*纵向像素(或者 宽x高),如 1080*1920
-
单位:
单位是px,1px=1个像素点。
常见分辨率:
720x1280、1080x1920(当然还有480x800,这个很少见了)
(3)屏幕像素密度(dots per inch,简称dpi)
-
含义:每英寸上的像素点数。
屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
dpi和ppi:
dpi最初用于衡量打印物上每英寸的点数密度。DPI值越小图片越不精细。当DPI的概念用在计算机屏幕上时,就应称之为ppi。同理: PPI就是计算机屏幕上每英寸可以显示的像素点的数量。因此,在电子屏幕显示中提到的ppi和dpi是一样的,可认为dpi=ppi
-
单位:dpi(dots per inch)
假设设备内每英寸有240个像素,那么该设备的屏幕像素密度=240dpi
不同手机屏幕大小对应的屏幕像素密度关系表:
密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi) |
---|---|---|
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
- 当然与像素有关的还有一个单位ppi,这个我们安卓中用不到,有兴趣的可以百度一下。
(4)以上三者(屏幕尺寸、分辨率、像素密度)之间的关系
(5)密度无关像素(dp 或 dip)
单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
Android开发设置布局和控件宽高,用dp而不是px,dp是Android特有的单位
-
dp与px的转换
因为ui给的图是以 px 为单位的,Android开发则是使用 dp 作为单位的,那么我们需要进行转换:
在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px
【特大误区】:网上关于px和sp的换算公式全部都写反了,我在这里纠正一下。如下图所示:
我创建了一个模拟器。
转换过程 | 转换公式 | 示例 |
---|---|---|
dp单位转换成px单位 | px = dp* (devicePixelRatio)= dp*(density/160) | 假如density(像素密度)为320,把360dp转换成px,那么最终像素=360dp*(320/160)=720px |
px单位转换成dp单位 | dp = px * (devicePixelRatio)=px * (density/160) | 假如density(像素密度)为320,把640dp转换成px,那么最终像素=640dp*(320/160)=1280px |
我们通过换算公式可以得知:假如devicePixelRatio为2
,那么1px=2dp
,网上那些1dp=2px的都是扯淡,都是没有做过实践的,乱写的。
(6)独立比例像素(sp)
- 单位:sp
- Android开发时用sp设置文字大小,使用它可以根据文字大小首选项进行放缩。
- 不推荐使用奇数和小数,容易造成精度的丢失问题;小于12sp的字体会太小导致用户看不清。
下面给一个实例说明,让你更能明白这几个单位:
看下图你可以知道:为什么使用了dp作为单位,两个手机分辨率也是一样的,可是按钮显示的宽度还是不一样?
三、屏幕适配的本质
总结一下,主要有以下两点:
(1)使得“布局”、“布局组件”、“图片资源”、“用户界面流程”匹配不同的屏幕尺寸
(2)使得“图片资源”匹配不同的屏幕密度
四、屏幕适配具体的解决方案
首先看一张图:
4.1 屏幕尺寸适配
4.1.1.布局适配
- 4.1.1.1. 使得布局元素自适应屏幕尺寸
解决方案:使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout)。这个很基础,就不多说了。
- 4.1.1.2. 根据屏幕的配置来加载相应的UI布局。为不同屏幕尺寸的设备设计不同的布局。
解决方案:使用限定符。通过配置限定符使得程序在运行时根据当前设备的配置(屏幕尺寸)自动加载合适的布局资源。
限定符分类:
(1)尺寸(size)限定符(这种方式只适合Android 3.2版本之前
)
res目录新建一个layout-large文件夹,布局名字和res/layout里面的同名。在平板电脑和电视的屏幕(>7英寸)上:实施 双面板
模式以同时显示更多内容,它会加载res/layout-large里面的布局,在手机较小的屏幕上:使用 单面板
分别显示内容,加载的是res/layout里面的同名布局。
(2)最小宽度(Smallest-width)限定符。
通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕从而加载不同的UI资源。(适用于
Android 3.2及之后版本
)最小宽度限定符可让您通过指定某个最小宽度(以 dp 为单位)来定位屏幕。例如,标准 7 英寸平板电脑的最小宽度为 600 dp,因此如果您要在此类屏幕上的用户界面中使用双面板(但在较小的屏幕上只显示列表),您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符。
(3)布局别名
为了解决文件名的重复从而带来一些列后期维护的问题,我们使用 布局别名 方案。
通过以上两点,大家也会发现一个问题:
适配手机没问题。但是适配平板发现Android3.2前后的这两个文件内容是一样的,只是文件名不同而已。
适配手机的单面板(默认)布局:res/layout/main.xml
适配尺寸>7寸平板的双面板布局(Android 3.2前):res/layout-large/main.xml
适配尺寸>7寸平板的双面板布局(Android 3.2后)res/layout-sw600dp/main.xml
解救方案:取一个别名就好了,示例如下:
这样两个layout.xml都只是引用了@layout/main_twopanes,就避免了重复定义布局文件的情况
(4)屏幕方向(Orientation)限定符。
根据屏幕方向进行布局的调整。
某些布局会同时支持横向模式和纵向模式,但我们可以通过调整优化其中大部分布局的效果。每种屏幕尺寸和屏幕方向下的布局行为方式如下所示:
- 小屏幕,纵向:单面板,带徽标
- 小屏幕,横向:单面板,带徽标
- 7 英寸平板电脑,纵向:单面板,带操作栏
- 7 英寸平板电脑,横向:双面板,宽,带操作栏
- 10 英寸平板电脑,纵向:双面板,窄,带操作栏
- 10 英寸平板电脑,横向:双面板,宽,带操作栏
- 电视,横向:双面板,宽,带操作栏
解决方案:
第一步:先定义类别:单/双面板、是否带操作栏、宽/窄
定义在 res/layout/ 目录下的某个 XML 文件中
第二步:再进行相应的匹配:屏幕尺寸(小屏、7寸、10寸)、方向(横、纵)
使用布局别名进行匹配
示例代码如下图所示:
这里没有完全把全部尺寸匹配类型的代码贴出来,大家可以自己去尝试把其补充完整。
4.1.2. 布局组件适配
使得布局组件自适应屏幕尺寸。
- 解决方案:使用"wrap_content"、"match_parent"和"weight“来控制视图组件的宽度和高度。这个很基础,这几个的用法大家应该都经常用的,就不多说了。
4.1.3. 图片资源适配
使得图片资源在不同屏幕密度上显示相同的像素效果。
在实际开发中一个按钮的背景图片必须能够随着按钮大小的改变而改变。使用普通的图片将无法实现这个效果,因为运行时会对图片均匀地拉伸或压缩。
解决方案:使用自动拉伸位图(
nine-patch图片
),后缀名是.9.png
,它是一种被特殊处理过的PNG图片,设计时可以指定图片的拉伸区域和非拉伸区域;使用时,系统就会根据控件的大小自动地拉伸你想要拉伸的部分。-
注意事项:
- 1.必须使用UI给的图片格式(.9.png后缀),随意更改后缀使用在项目中会报错,因为系统就是根据这个来区别nine-patch图片和普通的PNG图片的。
- 2.部分nine-patch图片在Android Studio项目中不能识别,会报错,需要谨慎使用。
下面一张图看看使用
nine-patch图片
的效果:
nine-patch图片
制作请参考我的博客:
nine-patch图片的制作
4.1.4. 用户界面流程适配
根据屏幕的配置来加载相应的用户界面流程。
使用场景:我们会根据设备特点显示恰当的布局,但是这样做,会使得用户界面流程可能会有所不同。
例如:如果应用处于双面板模式下,点击左侧面板上的项即可直接在右侧面板上显示相关内容;而如果该应用处于单面板模式下,点击相关的内容应该跳转到另外一个Activity进行后续的处理。解决方案(最终目的是进行用户界面流程的自适应配置,其实就是用java代码动态加载):
① 确定当前布局。示例如下:
由于每种布局的实施都会稍有不同,因此我们需要先确定当前向用户显示的布局。
例如,我们可以先了解用户所处的是“单面板”模式还是“双面板”模式。
② 根据当前布局做出响应。示例如下:
有些操作可能会因当前的具体布局而产生不同的结果。
例如,在新闻阅读器示例中,如果用户界面处于双面板模式下,那么点击标题列表中的标题就会在右侧面板中切换到相应报道(Fragment);但如果用户界面处于单面板模式下,那么上述操作就会启动一个独立Activity:
③ 重复使用其他 Activity 中的 Fragment。示例如下:
例如,在新闻阅读器示例中,对于较大的屏幕,新闻报道文本会显示在右侧 Fragment 面板中;但对于较小的屏幕,这些文本就会以独立 Activity 的形式存在。
④ 处理屏幕配置变化。示例如下:
如果我们使用独立Activity实施界面的独立部分,那么请注意,我们可能需要对特定配置变化(例如屏幕方向的变化)做出响应,以便保持界面的一致性。
例如,在运行 Android 3.0 或更高版本的标准 7 英寸平板电脑上,如果新闻阅读器示例应用运行在纵向模式下,就会在使用独立Activity 显示新闻报道;但如果该应用运行在横向模式下,就会使用双面板布局。
4.2 屏幕密度适配
4.2.1.布局控件适配
使得布局组件在不同屏幕密度上显示相同的像素效果。
解决方案有以下两种:
-
(1)使用 密度无关像素 ( dp ) 或 独立比例像素( sp ) 作为计量单位。
- 使用场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
- dp 和 px 的转换在前面有介绍,这里就不说了。
- 为了能够进行不同屏幕像素密度的匹配,应该这样做:
1. 使用dp来代替px作为控件宽高的统一度量单位。 2. 使用sp作为文字的统一度量单位。
有下面一种场景:
RelativeLayout布局里面,水平方向上放置两个按钮,一个是150dp左对齐,另外一个是200dp右对齐。在屏幕总宽度为360dp的Nexus5上中间有10dp的间隙。但同样地设置在Nexus S(屏幕宽度是320dp),会发现,两个按钮会重叠,因为320dp<200+150dp。
如图:
从上面可以看出,由于Android屏幕设备的多样性,如果使用dp来作为度量单位,并不是所有的屏幕的宽度都具备相同的dp长度。
dp解决了同一数值在 不同分辨率 中展示 相同尺寸大小 的问题(即屏幕像素密度匹配问题),但却没有解决设备 尺寸大小匹配 的问题。(即屏幕尺寸匹配问题)。
注意:屏幕宽度和像素密度没有任何关联关系。
-
(2)使用像素作为计量单位,采用百分比布局。
从上面案例看出,因为屏幕密度(分辨率)不一样,所以不能用固定的px;因为屏幕宽度不一样,所以要小心的用dp。
因为本质上是希望使得布局组件在不同屏幕密度上显示相同的像素效果,那么,之前是绕了个弯使用dp解决这个问题,那么到底能不能直接用px解决呢?当然是可以的。根据不同屏幕密度,控件选择对应的像素值大小。解决方法是: 百分比适配。
先说一下缺点:
使用像素作为计量单位的适配方式,应该能进行90%的适配了,但其 缺点 还是很明显:
1.由于实际上还是使用px作为长度的度量单位,所以和google的要求使用dp作为度量单位会有所背离
2.必须尽可能多的包含所有分辨率,因为这个是使用这个方案的基础,如果有某个分辨率缺少,将无法完成该屏幕的适配
3.过多的分辨率像素描述xml文件会增加软件包的大小和维护的难度
★ 1.以某一分辨率为基准,生成所有分辨率对应像素数列表
现在我们以320x480的分辨率为基准:
将屏幕的宽度分为320份,取值为x1x320,将屏幕的高度分为480份,取值为y1y480
然后生成该分辨率对应像素数的列表,如下图:
找到基准后,是时候把其他分辨率补全了,以下是以1080x1920的分辨率为例:
关于自动生成values文件夹,这里推荐两个工具:
① AndroidPixelDimenGenerator ,使用方式可以百度一下,这不是本文的重点。
② 张鸿洋大神写的autolayout.jar这个工具,下载地址请点此 ,使用方法如下图所示:
★ 2.将生成像素数列表存放在res目录下对应的values文件下,这个步骤上面的动态图已经做了。
注意事项:
- (1)对应分辨率的资源文件应放在res/values对应的文件夹中。比如分辨率为1920x1080的资源文件应放在res/values-1920x1080文件夹中。
- (2)必须在默认values里面也创建对应默认lay_x.xml和lay_y.xml文件,只是单位是dp。如果默认values文件夹没有(即没有对应的分辨率、没有对应dimen)就会报错,从而无法进行屏幕适配。
★ 3.根据UI设计师给出设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可。如下图:
<FrameLayout >
<Button
android:layout_gravity="center"
android:gravity="center"
android:text="@string/hello_world"
android:layout_width="@dimen/x160"
android:layout_height="@dimen/y160"/>
</FrameLayout>
4.2.2.图片资源适配
使得图片资源在不同屏幕密度上显示相同的像素效果。
解决方案:提供备用位图(符合屏幕尺寸的图片资源)
-
常见做法步骤如下:
★ 1.根据以下尺寸范围针对各密度生成相应的图片:(一套分辨率设计一套位图资源)
谷歌官方给出的屏幕密度对应的图标尺寸对照表
比如说,如果我们为 xxhdpi 设备生成了144144 px尺寸的图片,就应该按照相应比例地为 hdpi、xhdpi 和 xxhdpi 设备分别生成 7272 px 、9696 px 和 192192 px 尺寸的图片。
★ 2.将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片。
★ 3.通过引用 @drawable/id,系统都能根据相应屏幕的 屏幕密度(dpi)自动选取合适的位图。
- 注意事项:
1.如果是.9图或者是不需要多个分辨率的图片,放在drawable文件夹即可。
2.对应分辨率的图片要正确的放在合适的文件夹,否则会造成图片拉伸等问题。
- 图片资源适配的 缺点:
1. 每套分辨率出一套图,为美工或者设计增加了许多工作量
2. 对Android工程文件的apk包变的很大
稍微优化一下:有没有一种方法,保证屏幕密度适配,可以最小占用设计资源,使得apk包不变大(只使用一套分辨率的图片资源)?下面我们就来介绍这个方法:
只需选择唯一一套分辨率规格的图片资源。
xhdpi 应该是首选。目前市面上最普遍的高端机的分辨率还多集中在720X1080范围内(xhdpi),所以目前来看xhpdi规格的图片资源成为了首选。-
动态设置属性,做到完美适配:
① 给ImageView设置不同的ScaleType属性会得到不同的显示效果,一般情况下,设置为centerCrop能获得较好的适配效果。
② 有些情况下,我们需要动态的设置控件大小或者是位置,比如说popwindow的显示位置和偏移量等。这时我们可以动态获取当前的屏幕属性,然后设置合适的数值。
public class ScreenSizeUtil {
public static int getScreenWidth(Activity activity) {
return activity.getWindowManager().getDefaultDisplay().getWidth();
}
public static int getScreenHeight(Activity activity) {
return activity.getWindowManager().getDefaultDisplay().getHeight();
}
}
③.使用第三方的屏幕适配框架。
`
本文参考文章:
- Carson_Ho:Android 屏幕适配:最全面的解决方案
- cocopeng:Android屏幕适配全攻略(最权威的官方适配指导)
- 郭霖: Android官方提供的支持不同屏幕大小的全部方法
- Stormzhang:Android 屏幕适配
- 鸿洋:Android 屏幕适配方案
- 凯子: Android屏幕适配全攻略(最权威的官方适配指导)