前言

Android的屏幕适配一直以来都在折磨着我们Android开发者,本文将结合:

Google的官方权威适配文档
郭霖: Android官方提供的支持不同屏幕大小的全部方法
Stormzhang:Android 屏幕适配
鸿洋:Android 屏幕适配方案
凯子: Android屏幕适配全攻略(最权威的官方适配指导)
自身的思考&实践
给你带来一种全新、全面而逻辑清晰的Android屏幕适配思路,只要你认真阅读,保证你能解决Android的屏幕适配问题!

目录

Android屏幕适配解决方案.png
定义

使得某一元素在Android不同尺寸、不同分辨率的手机上具备相同的显示效果

相关重要概念

屏幕尺寸

含义:手机对角线的物理尺寸
单位:英寸(inch),1英寸=2.54cm
Android手机常见的尺寸有5寸、5.5寸、6寸等等
屏幕分辨率

含义:手机在横向、纵向上的像素点数总和
一般描述成屏幕的”宽x高”=AxB
含义:屏幕在横向方向(宽度)上有A个像素点,在纵向方向
(高)有B个像素点
例子:1080x1920,即宽度方向上有1080个像素点,在高度方向上有1920个像素点
单位:px(pixel),1px=1像素点
UI设计师的设计图会以px作为统一的计量单位
Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920
屏幕像素密度

含义:每英寸的像素点数
单位:dpi(dots per ich)
假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi
安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:

密度类型

代表的分辨率(px)

屏幕像素密度(dpi)

低密度(ldpi)

240x320

120

中密度(mdpi)

320x480

160

高密度(hdpi)

480x800

240

超高密度(xhdpi)

720x1280

320

超超高密度(xxhdpi)

1080x1920

480

屏幕尺寸、分辨率、像素密度三者关系

一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是:

三者关系示意图

数学不太差的人应该能懂…..吧?

不懂没关系,在这里举个例子
假设一部手机的分辨率是1080x1920(px),屏幕大小是5寸,问密度是多少?
解:请直接套公式
解答过程
密度无关像素

含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
dp与px的转换
因为ui设计师给你的设计图是以px为单位的,Android开发则是使用dp作为单位的,那么我们需要进行转换:

密度类型

代表的分辨率(px)

屏幕密度(dpi)

换算(px/dp)

比例

低密度(ldpi)

240x320

120

1dp=0.75px

3

中密度(mdpi)

320x480

160

1dp=1px

4

高密度(hdpi)

480x800

240

1dp=1.5px

6

超高密度(xhdpi)

720x1280

320

1dp=2px

8

超超高密度(xxhdpi)

1080x1920

480

1dp=3px

12

在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px

独立比例像素

含义:scale-independent pixel,叫sp或sip
单位:sp
Android开发时用此单位设置文字大小,可根据字体大小首选项进行缩放
推荐使用12sp、14sp、18sp、22sp作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于12sp的字体会太小导致用户看不清
请把上面的概念记住,因为下面讲解都会用到!

为什么要进行Android屏幕适配

由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,于是导致:

Android系统碎片化:小米定制的MIUI、魅族定制的flyme、华为定制的EMUI等等
当然都是基于Google原生系统定制的
Android机型屏幕尺寸碎片化:5寸、5.5寸、6寸等等
Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920
据友盟指数显示,统计至2015年12月,支持Android的设备共有27796种
当Android系统、屏幕尺寸、屏幕密度出现碎片化的时候,就很容易出现同一元素在不同手机上显示不同的问题。

试想一下这么一个场景:
为4.3寸屏幕准备的UI设计图,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白;而5.0寸的UI设计图运行到4.3寸的设备上,很可能显示不下。
为了保证用户获得一致的用户体验效果:

使得某一元素在Android不同尺寸、不同分辨率的手机上具备相同的显示效果
于是,我们便需要对Android屏幕进行适配。

屏幕适配问题的本质

使得“布局”、“布局组件”、“图片资源”、“用户界面流程”匹配不同的屏幕尺寸
使得布局、布局组件自适应屏幕尺寸;
根据屏幕的配置来加载相应的UI布局、用户界面流程
使得“图片资源”匹配不同的屏幕密度
解决方案

问题:如何进行屏幕尺寸匹配?
答:
屏幕尺寸适配解决方案.png
“布局”匹配

本质1:使得布局元素自适应屏幕尺寸

做法
使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout)
开发中,我们使用的布局一般有:

线性布局(Linearlayout)
相对布局(RelativeLayout)
帧布局(FrameLayout)
绝对布局(AbsoluteLayout)
由于绝对布局(AbsoluteLayout)适配性极差,所以极少使用。
对于线性布局(Linearlayout)、相对布局(RelativeLayout)和帧布局(FrameLayout)需要根据需求进行选择,但要记住:

RelativeLayout
布局的子控件之间使用相对位置的方式排列,因为RelativeLayout讲究的是相对位置,即使屏幕的大小改变,视图之前的相对位置都不会变化,与屏幕大小无关,灵活性很强
LinearLayout
通过多层嵌套LinearLayout和组合使
用”wrap_content”和”match_parent”已经可以构建出足够复杂的布局。但是LinearLayout无法准确地控制子视图之间的位置关系,只能简单的一个挨着一个地排列
所以,对于屏幕适配来说,使用相对布局(RelativeLayout)将会是更好的解决方案

本质2:根据屏幕的配置来加载相应的UI布局

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

使用场景:当一款应用显示的内容较多,希望进行以下设置:
在平板电脑和电视的屏幕(>7英寸)上:实施“双面板”模式以同时显示更多内容
在手机较小的屏幕上:使用单面板分别显示内容
因此,我们可以使用尺寸限定符(layout-large)通过创建一个文件

res/layout-large/main.xml
来完成上述设定:

让系统在屏幕尺寸>7英寸时采用适配平板的双面板布局
反之(默认情况下)采用适配手机的单面板布局
文件配置如下:

适配手机的单面板(默认)布局:res/layout/main.xml

”用户界面流程“匹配

  • 使用场景:我们会根据设备特点显示恰当的布局,但是这样做,会使得用户界面流程可能会有所不同。
  • 例如,如果应用处于双面板模式下,点击左侧面板上的项即可直接在右侧面板上显示相关内容;而如果该应用处于单面板模式下,点击相关的内容应该跳转到另外一个Activity进行后续的处理。

本质:根据屏幕的配置来加载相应的用户界面流程
- 做法
进行用户界面流程的自适应配置:

  1. 确定当前布局
  2. 根据当前布局做出响应
  3. 重复使用其他活动中的片段
  4. 处理屏幕配置变化
  • 步骤1:确定当前布局
    由于每种布局的实施都会稍有不同,因此我们需要先确定当前向用户显示的布局。例如,我们可以先了解用户所处的是“单面板”模式还是“双面板”模式。要做到这一点,可以通过查询指定视图是否存在以及是否已显示出来。
    public class NewsReaderActivity extends FragmentActivity {
    boolean mIsDualPane;
@Override 
 public void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.main_layout);
View articleView = findViewById(R.id.article);
mIsDualPane = articleView != null &&
                articleView.getVisibility() == View.VISIBLE;
} 
 }
  • 步骤2:根据当前布局做出响应
    有些操作可能会因当前的具体布局而产生不同的结果。

例如,在新闻阅读器示例中,如果用户界面处于双面板模式下,那么点击标题列表中的标题就会在右侧面板中打开相应报道;但如果用户界面处于单面板模式下,那么上述操作就会启动一个独立活动:

@Override 
 public void onHeadlineSelected(int index) { 
 mArtIndex = index; 
 if (mIsDualPane) { 
 /* display article on the right pane / 
 mArticleFragment.displayArticle(mCurrentCat.getArticle(index)); 
 } else { 
 / start a separate activity */ 
 Intent intent = new Intent(this, ArticleActivity.class); 
 intent.putExtra(“catIndex”, mCatIndex); 
 intent.putExtra(“artIndex”, index); 
 startActivity(intent); 
 } 
 }
  • 步骤3:重复使用其他活动中的片段
    多屏幕设计中的重复模式是指,对于某些屏幕配置,已实施界面的一部分会用作面板;但对于其他配置,这部分就会以独立活动的形式存在。

例如,在新闻阅读器示例中,对于较大的屏幕,新闻报道文本会显示在右侧面板中;但对于较小的屏幕,这些文本就会以独立活动的形式存在。

在类似情况下,通常可以在多个活动中重复使用相同的 Fragment 子类以避免代码重复。例如,在双面板布局中使用了 ArticleFragment:

总结

经过上面的介绍,对于屏幕尺寸大小适配问题应该是不成问题了。


解决方案

  • 问题:如何进行屏幕密度匹配?
  • 答:

“布局控件”匹配

本质:使得布局组件在不同屏幕密度上显示相同的像素效果
- 做法1:使用密度无关像素
由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素(px)定义布局尺寸就会产生问题。
因此,请务必使用密度无关像素 dp独立比例像素 sp 单位指定尺寸。
- 相关概念介绍
密度无关像素
- 含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
- 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果

  1. Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
  2. 场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
  • dp与px的转换
    因为ui给你的设计图是以px为单位的,Android开发则是使用dp作为单位的,那么该如何转换呢?

密度类型

代表的分辨率(px)

屏幕密度(dpi)

换算(px/dp)

比例

低密度(ldpi)

240x320

120

1dp=0.75px

3

中密度(mdpi)

320x480

160

1dp=1px

4

高密度(hdpi)

480x800

240

1dp=1.5px

6

超高密度(xhdpi)

720x1280

320

1dp=2px

8

超超高密度(xxhdpi)

1080x1920

480

1dp=3px

12

在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px

独立比例像素
- 含义:scale-independent pixel,叫sp或sip
- 单位:sp

  1. Android开发时用此单位设置文字大小,可根据用户的偏好文字大小/字体大小首选项进行缩放
  2. 推荐使用12sp、14sp、18sp、22sp作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于12sp的字体会太小导致用户看不清

所以,为了能够进行不同屏幕像素密度的匹配,我们推荐:
- 使用dp来代替px作为控件长度的统一度量单位
- 使用sp作为文字的统一度量单位

可是,请看以下一种场景:

Nexus5的总宽度为360dp,我们现在在水平方向上放置两个按钮,一个是150dp左对齐,另外一个是200dp右对齐,那么中间留有10dp间隔;但假如同样地设置在Nexus S(屏幕宽度是320dp),会发现,两个按钮会重叠,因为320dp<200+150dp

从上面可以看出,由于Android屏幕设备的多样性,如果使用dp来作为度量单位,并不是所有的屏幕的宽度都具备相同的dp长度

再次明确,屏幕宽度和像素密度没有任何关联关系

所以说,dp解决了同一数值在不同分辨率中展示相同尺寸大小的问题(即屏幕像素密度匹配问题),但却没有解决设备尺寸大小匹配的问题。(即屏幕尺寸匹配问题)

当然,我们一开始讨论的就是屏幕尺寸匹配问题,使用match_parent、wrap_content和weight,尽可能少用dp来指定控件的具体长宽,大部分的情况我们都是可以做到适配的。

那么该如何解决控件的屏幕尺寸和屏幕密度的适配问题呢?

从上面可以看出:
- 因为屏幕密度(分辨率)不一样,所以不能用固定的px
- 因为屏幕宽度不一样,所以要小心的用dp

因为本质上是希望使得布局组件在不同屏幕密度上显示相同的像素效果,那么,之前是绕了个弯使用dp解决这个问题,那么到底能不能直接用px解决呢?

即根据不同屏幕密度,控件选择对应的像素值大小

接下来介绍一种方法:百分比适配方法,步骤如下:
1. 以某一分辨率为基准,生成所有分辨率对应像素数列表
2. 将生成像素数列表存放在res目录下对应的values文件下
3. 根据UI设计师给出设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可

步骤1:以某一分辨率为基准,生成所有分辨率对应像素数列表

现在我们以320x480的分辨率为基准:
- 将屏幕的宽度分为320份,取值为x1~x320
- 将屏幕的高度分为480份,取值为y1~y480

然后生成该分辨率对应像素数的列表,如下图:
- lay_x.xml(宽)

步骤2:把生成的各像素数列表放到对应的资源文件

将生成像素数列表(lay_x.xml和lay_y.xml)存放在res目录下对应的values文件(注意宽、高要对应),如下图:

注:
- 分辨率为480x320的资源文件应放在res/values-480x320文件夹中;同理分辨率为1920x1080的资源文件应放在res/values-1920x1080文件夹中。(其中values-480x320是分辨率限定符)
- 必须在默认values里面也创建对应默认lay_x.xml和lay_y.xml文件,如下图
lay_x.xml

步骤3:根据UI设计师给出某一分辨率设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可

如下图:

总结

使用上述的适配方式,应该能进行90%的适配了,但其缺点还是很明显:
- 由于实际上还是使用px作为长度的度量单位,所以和google的要求使用dp作为度量单位会有所背离
- 必须尽可能多的包含所有分辨率,因为这个是使用这个方案的基础,如果有某个分辨率缺少,将无法完成该屏幕的适配
- 过多的分辨率像素描述xml文件会增加软件包的大小和维护的难度


“图片资源”匹配

本质:使得图片资源在不同屏幕密度上显示相同的像素效果
- 做法:提供备用位图(符合屏幕尺寸的图片资源)
由于 Android 可在各种屏幕密度的设备上运行,因此我们提供的位图资源应该始终可以满足各类密度的要求:

密度类型

代表的分辨率(px)

系统密度(dpi)

低密度(ldpi)

240x320

120

中密度(mdpi)

320x480

160

高密度(hdpi)

480x800

240

超高密度(xhdpi)

720x1280

320

超超高密度(xxhdpi)

1080x1920

480

- 步骤1:根据以下尺寸范围针对各密度生成相应的图片。

比如说,如果我们为 xhdpi 设备生成了 200x200 px尺寸的图片,就应该按照相应比例地为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75 尺寸的图片

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

  • 步骤2:将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片
  • 步骤3:通过引用 @drawable/id,系统都能根据相应屏幕的 屏幕密度(dpi)自动选取合适的位图。

注:
- 如果是.9图或者是不需要多个分辨率的图片,放在drawable文件夹即可
- 对应分辨率的图片要正确的放在合适的文件夹,否则会造成图片拉伸等问题。

更好地方案解决“图片资源”适配问题

上述方案是常见的一种方案,这固然是一种解决办法,但缺点在于:
- 每套分辨率出一套图,为美工或者设计增加了许多工作量
- 对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的尺寸以及分辨率都一样)来做原型设计,所有参数请看下图

机型

分辨率(px)

屏幕尺寸(inch)

系统密度(dpi)

iPhone 5s

640X1164

4

332

iPhone 6

1334x750

4.7

326

iPhone 6 Plus

1080x1920

5

400

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

额外小tips

  • ImageView的ScaleType属性
    设置不同的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(); 
 } 
 }

总结

本文根据现今主流Android的适配方法,以逻辑清晰的方式进行了主流Android适配方法的全面整理,接下来我会介绍继续介绍Android开发中的相关知识,有兴趣可以继续关注Carson_Ho的安卓开发笔记

作者:Carson_Ho