tags: Tutorial
引言
国庆前在微信群里看到有人在问Android屏幕适配的问题,凑巧自己最近时间
略有闲暇,索性来谈谈Android中屏幕适配相关的一些内容吧,鄙人才疏学浅,
所说的都是自己认知范围以内的,不可能面面俱到,如有疏漏不妥之处,还请
不吝指出,谢谢!配上最近被玩坏的Gif~
Android中的单位与名词
1.相对单位与绝对单位
对于计量单位,人们日常习惯性分为「相对单位」与「绝对单位」两类,前者
根据不同的情景表现出不同的大小,比如Android里的dp,px与sp等;而后者则是
制定了一个标准,cm(厘米),写死了,就那么多,1cm什么情况下都是一样大。
说到这个绝对单位,顺带提下两个单位:
- in(英寸):1 in = 2.54cm
- pt(磅,印刷行业常用单位):1 pt = 1 / 72 in
2.px (pixel,像素)
就是一个个的像素点,图像的最小组成单元
3.dp (dip,密度无关像素)
Density-independent pixel,抽象意义上的像素,与设备的实际物理像素点无关,
可以保证在不同的像素密度的设备上显示相同的效果,也是Android独有的长度单位。
1dp表示在屏幕像素密度为160dpi的屏幕上,1dp = 1px;
类推,在320dpi上,1dp = 2px,不难看出这样的转换公式:px = dp * (dpi / 160)
4.sp (sip,独立比例像素)
scale-independent pixel,字体大小专用单位,会根据系统设置的字体大小进行
缩放,推荐使用12sp以上的字体(12sp以下太小),不推荐用奇数和小数,容易造成
精度丢失问题。
Tip:
尽管官方建议我们字体都用sp作为单位,但是sp字体会根据系统设置的字体大小进行
缩放,假如用户修改了系统字体大小,可能会导致我们APP的UI受到影响,比如字体
大了,然后各种显示不全,个人还是建议使用dp来做为字体单位!
5.手机外观尺寸
整个手机的尺寸,不止屏幕,一般在中关村或者其他评测网站都可以查到。
一般是用mm为单位,依次为长 x 宽 x 厚度,比如我的老掉牙的moto x 的尺寸就是:
159.3mm × 83mm × 10.1mm。
6.屏幕尺寸
屏幕对角线的长度,单位是英寸,计算公式如下:
其实就是勾股定理求对角线长度,比如长4寸,宽3寸的手机,他的屏幕
7.分辨率(Resolution)
屏幕竖直方向与水平方向的像素个数,比如:1280*720 就说明屏幕的
竖直方向上有1280个像素点,而水平方向上有720个像素点,单位px。
8.dpi与ppi
- dpi:dot per inch,点密度,每英寸多少个点,一般用作表示印刷品点密度;
-
ppi:pixels per inch,像素密度,每英寸所包含的像素数目;
一般用作表示显示设备的点密度。
两者的值近乎相等,不用过于纠结!这个值的计算公式如下:
9.density(屏幕密度)
这个单位感觉是用来表示不同dpi的倍数关系,计算公式:density = dpi/160
10.常用单位转换工具类
public class DensityUtil {
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static int px2dp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
11.获取屏幕尺寸与密度的三种方法
public class GetScreenParameter {
//方法一:已过时,可使用,但不建议使用
public static void getResolution1(Context mContext) {
Display mDisplay = ((Activity) mContext).getWindowManager()
.getDefaultDisplay();
int W = mDisplay.getWidth();
int H = mDisplay.getHeight();
}
//方法二:通过getWindowManager来获取屏幕尺寸的
public static void getResolution2(Context mContext) {
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
((Activity) mContext).getWindowManager().getDefaultDisplay()
.getMetrics(mDisplayMetrics);
int W = mDisplayMetrics.widthPixels;
int H = mDisplayMetrics.heightPixels;
// 屏幕密度(0.75 / 1.0 / 1.5)
float density = mDisplayMetrics.density;
// 就是屏幕密度 * 160而已,屏幕密度DPI(120 / 160 / 240)
int densityDpi = mDisplayMetrics.densityDpi;
}
//方法三:通过getResources来获取屏幕尺寸的,大部分用这个
public static void getResolution3(Context mContext) {
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
mDisplayMetrics = mContext.getResources().getDisplayMetrics();
int W = mDisplayMetrics.widthPixels;
int H = mDisplayMetrics.heightPixels;
float density = mDisplayMetrics.density;
int densityDpi = mDisplayMetrics.densityDpi;
}
}
Tip(可以不看,现在基本不会用这么小屏的手机了):
对于屏幕密度很低的小屏手机,比如240320,计算出来的尺寸,可能为:320427
原因是:没有设置多分辨率支持的话,Android系统会将240x320的低密度(120)尺寸
转换为中等密度160dpi对应的尺寸,如果你想获得正确的物理尺寸,需要在
AndroidManifest.xml里添加下述代码:
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:resizeable="true"
android:anyDensity="true"/>
Android适配准备
1.什么是Android适配
答:因为Android系统的开放性,很多厂商都喜欢对Android系统和硬件进行个性化定制,
以达到他们想要的样子,这种结果带来的「Android系统」和「手机屏幕」的碎片化问题。
只对市场占用率较高的720和1080进行适配显然是不够的,为了让我们的Android应用在各种
各样的手机上保证界面效果一致,各种手机屏幕的适配显得非常重要,也是每个开发者
为之头痛的问题。
2.Android中的六种通用屏幕密度
屏幕密度 | 范围(dpi) | 标准分辨率 | dp与px | 图标尺寸 |
---|---|---|---|---|
ldpi(QVGA) | ~ 120 | 240 * 320 | 1dp=0.75px | 36 * 36 |
mdpi(HVGA) | 120 ~ 160 | 320 * 480 | 1dp=1px | 48 * 48 |
hdpi(WVGA) | 160 ~ 240 | 480 * 800 | 1dp=1.5px | 72 * 72 |
xhdpi(720P) | 240 ~ 320 | 720 * 1280 | 1dp=2px | 96 * 96 |
xxhdpi(1080p) | 320 ~ 480 | 1080 * 1920 | 1dp=3px | 144 * 144 |
xxxhdpi(2K) | 480 ~ 640 | 1440 × 2560 | 1dp=4px | 192 * 192 |
Tip:图标大小 = px数 * 4 * 12
3.关于UI设计稿的适配
我们可以通过友盟的 全域罗盘 知道当前国内的移动设备使用情况,
以此了解需要适配的趋势,以八月份的统计为例,截取屏幕尺寸与分辨率占比
拍醒前五的数据如下:
更多详细信息可自行查看,从上面两个表格不难看出这样的趋势:
5.5寸 和 5寸 屏幕尺寸市场占比最高,而分辨率:1920x1080 和 1280x720
依旧是当前主流。
也就是说让设计师按照720p和1080p出两套设计稿就可以适配大部分的
设备了,但是不同的公司因为人员或者时间等外部因素,可能有这几
种情况:
- 三套:720p,1080p,1440p,这是最理想的情况;
- 两套:720p,1080p,主流,大部分公司都或走两套;
-
一套:720p,又或者和iOS共用一套 750x1334,可以当做720p的设计稿
来编写UI,尽管比例和接近16:9,但是还是有些偏差,需要Android对UI进行微调!
UI能做的基本就到这里了,至于其他分辨率的手机则需要我们开通过
相关的手段来适配了。
Android适配开始
1.最简单一些适配技巧
先来说几个烂大街的适配技巧吧
1) 使用dp而非px
dp是像素无关的,而在实际使用中1dp大约等于1/160 in,比如一个160dp * 160dp
的控件,在大多数的屏幕上都能保持1 in * 1 in 的大小。
但是,并不是能解决所有问题的,以下两点要注意:
- 1.实际效果还是会有些差距的,仅仅是相近而已;
- 2.当设备的物理尺寸存在差异的时候,dp就显得无能为力了。
比如,为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在
右侧和下侧存在大量的空白;而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。
2) 少写固定尺寸
「 少写固定尺寸,而使用 wrap_content, match_parent 与 weight 权重 」
3) 使用相对布局,不要使用绝对布局
常识,而且绝对布局基本退出历史舞台了,可以忽略...
4) 自动拉伸的.9图
常识,.9图的作用是:拉伸的时候特定的区域不会发生图片失真,而不失真的区域
可以由我们自己绘制,从而实现图片适配。
5) 使用shape代替纯色图片
常识,一些纯色的矩形,圆角,圆都可以通过编写shape文件来替换,比起png,
xml文件小太多。
6)使用SVG矢量图替换位图
可能有些朋友对SVG矢量图有些陌生,其实和普通的位图最大的区别就是:
SVG是通过「XML文件」来定义一个图形,通过一些特定的语法和规则来绘制
出我们所需的图像,而不是位图那样通过存储图像中每一点的像素值来保存
与使用图形。
SVG是已经定义好怎么画这个图,需要的时候再去画,因为是按照特定的语法
和规则,理论上支持任何级别的缩放,而且不会失真,相比起多套同样的位图
文件,方便太多。
矢量图虽好,但是有几点要注意的:
- 1.适用于Android 5.0以上,尽管官方有兼容包,低版本还是会有些问题的!
- 2.不适合细节过于复杂的图片!
- 3.因为是用到的时候才画,所以加载图片所消耗的时间和资源可能会增加。
至于怎么用这个矢量图,你可以让美工在PS里把图片导出为SVG/PSD格式,然后
AS里右键drawable文件夹:
选中本地文件,确定
然后AS会帮你自动生成一个矢量xml文件,比如我的一个tab图标
右侧可以看到预览图:
然后要用到图片的地方直接引用即可。
至于SVG的规则以及如何自行编写不在本节重点,有兴趣的可自行搜索用法~
2.多套资源文件的套路
常用的套路依旧不能解决大部分的屏幕适配问题,Android给我们提供了
备用资源这个东西,详细可见官网:提供备用资源
简单说就是按照规范,创建屏幕对应的资源文件夹和资源文件,
Android会自动去加载对应文件夹里的资源的值,可能表述得有些不清晰,
举个简单的例子:
比如我们xxhdpi,padding为3dp,而在xxxhdpi下的padding为5dp
我们就可以创建一个对应的values文件夹和dimens.xml文件
相信你看到这里就明了,官网写的这个也就清晰了
而这个qualifier限定符怎么拼接可以看官网,或者看下面的简表:
看到这里,你可能会有问题,我要写多少套?
讲真,我也不知道,只能说看需要,但是想跟你说限定符里一个很常用的:smallestWidth
屏幕区域的最小尺寸,比如 values-sw320dp,只有在屏幕宽度不小于
320dp的才会使用这个文件夹里的dimens.xml文件,我们还可以另外建立一个
values-sw360dp,然后320dp那个文件夹只有在屏幕宽度在320dp到360dp
内才回去调用了,drawable,layout等资源文件也是同样的套路。
3.使用脚本对长度按照不同分辨率进行比例转换
就是拿屏幕宽度,去除以360,得出比例去乘以对应的dp值
比如:sw720dp里一个单位的1 dp = 2dp,而sw800dp里的1 dp = 2.22dp:
Github地址:https://github.com/mengzhinan/PhoneScreenMatch
具体的自己看文档和代码吧,可能有的疑问就是不是win电脑执行不了bat文件
的问题,比如mac,直接命令行,cd到 PhoneScreenMatch/app/src/main
目录下,键入下述命令即可:
java -jar screenMatchPX.jar
项目中还提供了一个px适配的,分开宽高做等值缩放的,对于长宽比很奇葩
的机子可以试试。当然这种适配方式带来的最明显的问题就是资源文件增加
的问题,这需要自行权衡。
类似的方案还有:https://github.com/paulyung541/EasyScreen
4.[过时] 百分比布局库支持库
这个库API 26.0.0 后的版本已经弃用了,在官方仓库可以看到
[DEPRECATED in support lib v26] you should now use ConstraintLayout widget
如果你想使用百分比来编写建议使用约束布局 ConstraintLayout。
所以android-percent-support怎么用并不讲,想通过看看源码的方式
来了解百分比的实现套路,没兴趣的可以跳过了,或者看其他关于
百分比布局支持库的其他使用扩展文章:
源码过一过 (流程参照CSDN张鸿洋的博文)
lib库里的东西不多,就三个核心的类而已
如果让我们来做套路可能是:
- 通过LayoutParams获取布局中设置的与百分比有关的属性
- 父布局拿到这些属性值后进行动态计算,比如宽*宽占百分比
- 子控件调用measure(计算后值)进行绘制。
看下代码具体是怎么做的,首先是获取Layout里子控件的属性,打开
PercentFrameLayout,重写了generateLayoutParams方法,
然后新建了一个LayoutParams对象
看看这个LayoutParams里卖的什么药:
通过PercentLayoutHelper的getPercentLayoutInfo方法构造一个
PercentLayoutInfo对象,而getPercentLayoutInfo里做的事则
是把attrs里的东东都拿出来,然后塞到PercentLayoutInfo对象用于返回:
而这个PercentLayoutInfo里定义了一堆百分比相关的属性,
还有一些方法,字面上大概可以知道是设置LayoutParams相关
的方法,就是把onMeasure里的逻辑操作抽取到了这里。
好的,属性拿到了,接着就是跟onMeasure方法了,调用了
PercentLayoutHelper对象里的adjustChildren方法
方法里做的东西也比较简单,拿到布局的宽高,然后
循环遍历布局里的子控件,如果params是百分比类型的
取出PercentLayoutInfo里info,然后去设置宽,高和margin。
再跟下fillLayoutParams方法,保存原本宽高后(后面用来重置),重新设置宽高,
fillMarginLayoutParams也是类似,百分比的基本套路就已经完了。
不过项目中还多了两个细节,计算值过小的情况以及重置原布局尺寸
onMeasure最后还调用handleMeasuredStateTooSmall方法,而方法做的
事是出去容器中所有的子控件,如果params是百分比类型的,则调用
shouldHandleMeasuredWidthTooSmall方法判断值是不是太小,太小则
把宽或高设置为ViewGroup.LayoutParams.WRAP_CONTENT(图中 = -2那里)
needsSecondMeasure设置为true,代表需要二次重绘。
而重置布局尺寸的代码则是写在onLayout方法里(onMeasure后会回调)
一样是遍历子控件,然后把参数都重置为之前存mPreservedParams里的值,
而这个值是布局里设置的0dp。(感觉没什么用处...)
因为onMeasure的关键代码都写到Helper类里,扩展一个百分比的线性
布局也很简单,拷贝下PercentFrameLayout改点东西就好。
另外,因为依赖于父容器,导致ScrollView,ListView等容器内高度无法使用百分比。
而鸿洋还另外对官方的百分比进行了扩展,写了一个android-percent-support-extend,
有兴趣可以移步到他的博客阅读:Android 增强版百分比布局库 为了适配而扩展
5.[作者停止维护] AndroidAutoLayout
又是鸿洋大神的库,不过作者已经不维护了,使用之前还是三思!!!
博客链接:http://blog.csdn.net/lmj623565791/article/details/49990941
大概套路是:
定好设计稿尺寸,直接选择对应分辨率的预览,AndroidAutoLayout 库会根据
占屏比例,自动计算转换当前屏幕下适配的大小。加载布局其实是把各自的
Layout都转换为对应的AutoLayout,从而不用在所有的xml中进行更改。
思路很gay,但是也有一些弊端,在onMeasure的时候进行数值计算,存在性能问题;
扩展性差,用到的ViewGroup都需要自行对AutoLayout进行扩展。
最重要的是:
issues较多,而且作者不维护,应用到实际项目中还需自行权衡...
6.ConstraintLayout 约束布局
使用约束的方式来指定各个控件的位置和关系的,可以看做更加强大的
RelativeLayout,通过图形化界面拖拽的方式来编写界面,当然也可以
直接用XML进行编写,毕竟拖拽的背后也是XML来实现的,不过属性比较
多,直接编写显得有些繁琐,建议还是拖拽后自己在手动修改参数。
ConstraintLayout约束布局除了支持百分比外,相比传统布局一层
套一层的嵌套布局结构,编写的XML元素层次结构更简单(一个外层
ConstraintLayout包全部),而且性能更优。
至于问题的话,可能就是布局中的元素过于复杂时,拖拽可能会有少许问题;
其他的话暂时没发现,关于ConstraintLayout与传统布局方式的性能对比可见:
解析ConstraintLayout的性能优势
而关于图形化拖拽的,郭霖大神有篇详细讲解的博文:
Android新特性介绍,ConstraintLayout完全解析
而关于属性讲解和具体实例的,可以翻看鸿洋的博文:
ConstraintLayout 完全解析 快来优化你的布局吧
附:记下关键点,方便自己日后回忆
-
Inspector:右侧Properties区域的上半部分,竖直和水平的轴用于确定
位置;图中的四个16确定的是间距;中间的四个 >>> 的箭头确定的是大小
有三种可选的模式:
用于填充满当前控件的约束规则,和match_parent是不同的!
-
Guidelines:参照线
两种,用来实现一些百分比的效果很赞,比如下图这种两个控件并排居中的布局:
-
自动添加约束:拖拽控件后,一个个去添加约束非常繁琐,ConstraintLayout中
支持自动添加约束,有两种自动添加约束的方式:
AutoConnect:默认关闭,启用后拖拉控件到布局里会自动生成约束
Infer Constraints:控件拖拉得差不多后,点击后会为所有控件生成约束
当然,这两种自动生成的约束可能有些问题,我们可以自己另外调整下~
7.等比例缩放的另一种实现方案
同样是等比例缩放的套路,原文可见:一种粗暴快速的Android全屏幕适配方案
上面也说了AndroidAutoLayout在onMeasure中进行数值运算可能会有性能问题。
而这个方案则是规避了这个问题,直接在Android进行长度计算的时候就进行换算;
系统长度计算的入口是TypedValue里的applyDimension方法:
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
}
而作者选择比较冷门的pt单位,从而避免对正常的px,dp,sp造成影响,
PT的计算为:value * metrics.xdpi * (1.0f/72),我们可以动前面
的metrics.xdpi,这个值应改为:缩放比例72,72的原因是想转px,
而metrics可以通过context.getResources().getDisplayMetrics()
拿到。作者另外写了一个Helper类,通过构造方法传入设计稿的宽度除以
当前设备的分辨率得出比例,然后乘以72即可得出对应分辨率下的尺寸。
调用也很简单,直接在Application类启用即可:
new RudenessScreenHelper(this, 750).activate();
等比缩放套路换汤不换药,不过很机智的规避了复杂的计算问题,想法是挺赞的,
有兴趣的可以试试。
小结
在本节中,笔者对于自己所知的Android原生适配套路都一一进行了复述,相信会对
你在开发中的屏幕适配有所帮助,当然具体要怎么适配还是看需求吧,比如我在的
小作坊,也是只是适配720p,2333;当然有其他的套路也欢迎告知,万分感激~
(PS:说来惭愧,本来想着国庆能发的,后面因为各种琐事拖到今天...)