1、基础概念
屏幕尺寸
- 含义:指的屏幕对角线的物理长度,单位一般采用英寸(1英寸≈2.53cm)
目前市面常见的有5.0、5.5、5.7等尺寸。
屏幕分辨率
- 含义:屏幕纵横向的像素数量,一般描述成屏幕的"宽x高”=AxB
- 例子:1080x1920,即宽度方向上有1080个像素点,在高度方向上有1920个像素点
- Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920等等
屏幕像素密度
- 含义:每英寸长度包含的像素点数
- 单位:dpi(dots per ich)
- 例子:假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi,这个160dpi在安卓中,也被当做一个基准的屏幕像素密度,此情况下1dp=1px,我们常用的px和dp互相转,用到的一个安卓api中的逻辑密度density在160dpi的时候,density=1,同理如果是320dpi,density=2;以此类推。
- desity = 当前屏幕像素密度/160
- **dp = px/desity **
/**
* px转dp
*
* @param context
* @param pxValue
* @return
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
-
安卓常用的像素密度
dp 介绍
- 含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
- 例子:场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在240dpi手机上设置应为240px;在160dpi的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
sp 介绍
- 含义:scale-independent pixels,与dp类似,但是可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。
- sp 需要注意的事项
1、当修改系统字体大小时,字体大小以dp为单位时,大小不变;
-
2、当修改系统字体大小时,字体大小以sp为单位时,大小跟随变化;
- 为何会有这种差异性。
参考文章关于设置文字大小,最终会调用下面这个方法:
- 为何会有这种差异性。
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP://------->>dp 基于 density
return value * metrics.density;
case COMPLEX_UNIT_SP://------->>sp 基于 scaledDensity
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;
}
从代码上看 dp 和 sp的差别就是 density 和 scaleDensity的区别,关于这两个字段在源码中可以看到注释scaledDensity会收到用户配置的影响,而density是基于屏幕dpi的,不会受到用户配置影响。
这也就是为什么设置sp单位时字体大小会受到用户配置系统字体的影响.
关于字体大小到底什么时候用dp 什么时候用sp
- 使用sp会锁着系统文字的变化而呈现正相关变化,这个设计是符合安卓最初设计思想的,比如一些眼睛不好的人,可能就会把字体放大来看,如果app上的文字没有跟着变大,这可能就会影响用户体验。
- 简单测试了下腾讯系的很多app多数页面都不会跟随系统字体大小变化,知乎会受到系统字体的影响。
- 其中使用apktool简单查看了下qq的一些布局文件和dimens,发现字体大小,dp、sp都有引用。
- 还有就是如果使用sp 写的话,一些注意事项。
- 首先布局高度尽量不要写死,可以通过设置padding或者margin来控制高度,避免文字变大时候导致的展示不全的现象。
- 相对来说对于这种适配,相对布局体验会更好一些,可以定位控件之间的相对关系,避免遮挡问题。
贴几张图:(系统设置文字变大)
比如下面的父控件限死高度的,导致展示不全、文字过大自动换行、没有设置相关关系导致的重叠。
整体来说需要根据实际需求具体调整,尽量在保证业务逻辑正常的情况下,调优展示效果。
屏幕尺寸大小、分辨率、像素密度之间的关系:
<span id="jump1">像素密度计算方法</span>
实际像素密度算法
计算结果不一致?
- 问题:为什么按照上面算法获取的dpi值,和api方法获取的dpi不一致?
比如我的手机360n5,在开发时我使用DisplayMetrics获取手机的densityDpi,这个densityDpi的大小为480,但是我在官网看见的却是PPI为401。他的分辨率为1080*1920,屏幕大小为5.5inch,按照上面的算法计算的话,大约就是401,因此可以肯定sdk上获取的dpi应该不是手机的真实dpi,那么android的dpi的计算方式又是怎样的? - dpi 和 ppi到底有何区别
DPI(dots per inch)和 PPI(pixels per inch)这两个措辞的差别,表面上看来只在于是在谈「dot」还是「pixel」。
* 但实际上 dot 可以指半调印刷的墨点,可以指喷墨打印的墨点,可以指扫描仪的采样点,可以指数字图像的最小单位(即 pixel),可以指屏幕的物理像素,可以指操作系统的抽象像素……在不同的语境下可以指不同的概念。
* 而 pixel 也可以指数字图像的数据 pixel,可以指屏幕物理像素,也可以指代操作系统的抽象像素……在不同语境下的意义也不同。
* 两者经常混用,所以关于dpi和ppi的区别,在不同的用途上面,意思也不同。
结论:在安卓机上面,我们可以理解ppi就是安卓机的真实像素密度,而dpi则是系统的一个内置的值,它接近真实值,但并不是真实的像素密度,它的存在就是为了换算dp、sp的 。
- 安卓的dpi进一步解释
- Android系统目录 /system/build.prop 里面有一行 ro.sf.lcd_density=480,这个就是dpi,这个值是可以变的,改变后通过sdk api拿到的dpi值就变化了,所以觉得dpi是个定义值,同一款设备,最大分辨率已经定了,而dpi其实并不是一定的,但是这个dpi决定了我们dp、sp和px的换算关系,这点很重要,也是屏幕适配的关键。其实,每部安卓手机屏幕都有一个初始的固定密度,这些数值是120、160、240、320、480,这些就是android为不同设备设定的系统密度。 得到实际密度以后,一般会选择一个最近的密度作为系统密度,系统密度是出厂预置的,如440dpi的系统密度就是和它最接近的480dpi;如果是330dpi的设备,它的系统密度就是320dpi。但是,现在很多手机不一定会选择这些值作为系统密度,而是选择实际的dpi作为系统密度,这就导致了很多手机的dpi也不是在这些值内。例如小米Note这样的xxhdpi的设备他的系统密度并不是480,而是它的实际密度440。
- dp到px的转换公式:px = dp * (dpi / 160)
2、适配方案
布局适配
多套布局
针对不同的屏幕尺寸,如果差异过大的话,就需要考虑多套布局的方式来处理
- 首先需要针对不同屏幕写布局
- 然后需要使用尺寸限定符
-
尺寸限定符相关简要介绍:
-
限定符示例:
如图示上面同名文件夹后面标注都属于限定符
限定符使用方法:只需要用横线加限定符的方式即可使用,xx-限定符。
- 常见限定符:
限定符(mdpi,tvdpi,hdpi)可以帮助我们判断屏幕密度 限定符(land,port)可以帮助我们区分屏幕横竖屏状态 限定符(en,fr…)可以帮助我们语言和地区 限定符(v3,v4…)可以帮助我们区分安卓版本 等等
具体参考文章:http://blog.csdn.net/wzy_1988/article/details/52932875
- 关于屏幕适配我们需要重点关注的限定符
- sw(n)dp:(最窄边限制符,不受屏幕方向的影响)
屏幕最小尺寸限定符:就是屏幕可用区域的最小尺寸,是指屏幕可用高度或宽度的最小值,例如,如果你的布局在运行时需要的屏幕最窄边是600dp,则你可以利用这个限定符创建布局资源目录res/layout-sw600dp.只有当屏幕的最小宽度或最小高度大于等于600dp时,系统才会使用这些布局文件或者资源文件 - w(n)dp:(屏幕最小宽度限定符,受到屏幕方向的影响)
指定资源使用时需要的最小宽度.当屏幕方向发生变化时,系统会调整这个值,使其始终为你UI显示的宽度.
- sw(n)dp:(最窄边限制符,不受屏幕方向的影响)
- 怎么计算限定符应该是多少dp
- 计算方法 dp = px/(当前屏幕像素密度/160 )
- 范例:
- 密度:480 dp / xxhdpi / 3.0x 屏幕分辨率:1080 x 1920 px 屏幕尺寸:2.8" x 5.0" / 5.7 英寸
- 1080px /(480/160) = 360dp,所以对应文件夹后缀应该是xxx-w360dp
-
-
定义布局时候注意事项
- 多使用相对关系定义控件之间的关联
- 使用"wrap_content"、"match_parent"和"weight“来控制视图组件的宽度和高度
- 布局中引用的dp sp一定使用引用关系,便于后期可能存在的针对性修改。
一般提前生成一套dimens文件,例如0-400dp,0-30sp,方便使用。
dp sp 适配
dp sp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备差异较大的时候,就无能为力了。适配的问题还需要我们自己去做,一般是生成带标识符的多套dimens文件。
- 适配方案一:dp方法:{"320","360", "384", "400", "411", "533", "640", "720", "768", "820"};
- 缺点:如果没有默认的dimens.xml,那就黄昏依斜阳了,还好有提供。
- 优点:没有枚举全部的item,可以省一些apk空间;有字体sp的适配。
- 适配方案二:px百分比方法
- 优点:针对性适配效果更精确,体验更好。
- 缺点:屏幕px的种类远多于dp的种类,文件数量多;程序for循环枚举item项,有部分用不上的px项;width和height都适配了,给控件写width尺寸时要用dimens_x.xml里面的变量值,写height尺寸时需要用dimens_y.xml里面的变量值。
图片适配
使用点九图
- 9patch图片的作用就是在图片拉伸的时候保证其不会失真,让图片在指定的位置拉伸和在指定的位置显示内容,这样图片的边边角角就不会出现失真了。
范例:
具体参考:点九图的制作
多套图片
- 传统方式切多套图片,放在对应标识符的文件夹里面(缺点:会导致apk包过大)
- 只放一套xhdpi的图(Android会根据屏幕密度自动选择对应的资源文件进行渲染加载)
比如说,SDK检测到你手机的分辨率是320x480(dpi=160),会优先到drawable-mdpi文件夹下找对应的图片资源;但假设你只在xhpdi文件夹下有对应的图片资源文件(mdpi文件夹是空的),那么SDK会去xhpdi文件夹找到相应的图片资源文件,然后将原有大像素的图片自动缩放成小像素的图片,于是大像素的图片照样可以在小像素分辨率的手机上正常显示。
具体请看http://blog.csdn.net/xiebudong/article/details/37040263
所以理论上来说只需要提供一种分辨率规格的图片资源就可以了。
一般选择xhdpi的,这一套可以适配市面上大多数手机,向下缩放,向上扩展表现都不会有太大问题。
注意事项:
- 通过apktool简单分析几个知名的app,看看别人到底使用了几套。
- 微信基本都是xxh为主了
- 拼多多还是xh xxh都有
- 随着安卓设备的发展,分辨率越来越高,可能后期xxh更为合适
- 可以同时放入xxh的图标,避免后期加入的麻烦。
- 为了防止包体过大,打包时候可以临时删除xxh文件夹
- 也可以推广期一套图片资源,用户主动升级时候可以升级携带xxh资源,体验可能更好一些。
代码动态适配
某些布局要求严格宽高比的地方,可以考虑代码的动态设置宽高;或者为了保持美观又防止过高的一些pop、dialog也需要代码动态按照当前屏幕比例动态设置宽高。
- 范例
view.getLayoutParams().height = Utils.getRealHeight(mContext, 720, 300);
/**
* 获取控件准确的高度(针对满屏的情况)
*
* @param context
* @param width 宽度(可以是相对值,仅仅用来计算宽高比例)
* @param height 高度(可以是相对值,仅仅用来计算宽高比例)
* @return 真正的高度
*/
public static int getRealHeight(Context context, int width, int height) {
//宽高比
float aspectRatio = (float) width / (float) height;
return (int) (Utils.getScreenWidth(context) / aspectRatio);
}
/**
* 根据当前宽度基准算真实高度
*
* @param context
* @param height 宽度基准下的高度
* @param totalWidth 宽度基准
* @return
*/
public static int getRealHeightWithBenchmark(Context context, float height, float totalWidth) {
return (int) ((Utils.getScreenWidth(context) / totalWidth) * height);
}
- 范例2:(针对自适应的一些布局,防止过高情况)
//布局渲染完成后回调设置,防止获取不到宽高度
ViewTreeObserver vto = mRecyclerView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mRecyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (mRecyclerView.getHeight() > Utils.getScreenHeight(mContext) / 2) {
mRecyclerView.getLayoutParams().height = Utils.getScreenHeight(mContext) / 2;
}
}
});
3、实际适配过程
参考文章:http://blog.csdn.net/wangwangli6/article/details/63258270