目录
1、AndroidAutoSize实战
1.1 AndroidAutoSize简介
1.2 代码实现
1.2.1 依赖
1.2.2 manifest配置
1.2.3 Activity支持
1.2.4 Fragment支持
1.3 不同分辨率屏幕效果对比
2、AndroidAutoSize原理分析
2.1 基本概念
2.1.1 一些重要的单位
2.1.2 单位转换中涉及到的两个重要类
2.2 实现原理
2.3 方案可行性
2.4 优缺点
3、一些思考
3.1 其他方案对比
3.2 应用到项目中可能存在的问题
3.3 建议
参考文献
1、AndroidAutoSize实战
1.1 AndroidAutoSize简介
基于今日头条屏幕适配方案的一个开源库(Star: 4329);
通过修改Application/Activity等的DisplayMetrics中核心数据,使得在不同分辨率手机上对应的dp相等而达到每个显示的View占用屏幕的比例相同。
https://github.com/JessYanCoding/AndroidAutoSize
1.2 代码实现
1.2.1 依赖
compile 'me.jessyan:autosize:1.0.1'
1.2.2 manifest配置
<manifest>
<application>
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="640"/>
</application>
</manifest>
1.2.3 Activity支持
默认情况下已经支持适配(宽度)
取消适配
取消适配只需要我们的Activity实现CancelAdapt即可,具体如下:
public class TestActivity extends Activity implements CancelAdapt{
}
效果图如下:
取消适配后可以看到宽度360dp 已经不能占满整个宽屏。
自定义宽/高(dp)
如果我们的Activity不能使用Manifest中配置的宽/高,需要个性化配置。Activity只需要实现CustomAdapt 接口并实现isBaseOnWidth和getSizeInDp两个函数即可,具体代码如下:
public class TestActivity extends Activity implements CustomAdapt {
@Override
public boolean isBaseOnWidth() {
// true: 以宽度等比例缩放 false: 以高度等比例缩放
return true;
}
@Override
public float getSizeInDp() {
// 对应的宽/高
return 540;
}
}
效果图如下:
通过自定义总宽度值,可以看到宽度180dp只占有整个屏幕宽度的1/3。
1.2.4 Fragment支持
(略)
1.3 不同分辨率屏幕效果对比
设备介绍
屏幕分辨率 dpi 屏幕宽度dp
手机 A 720x1280 160 720(px=dp*(dpi/160))
手机 B 720x1280 320 360(px=dp*(dpi/160))
适配前效果对比(左:手机A, 右:手机B)
可以发现,仅仅通过dp来适配,不同的设备显示的差异非常大。
适配后效果对比(左:手机A, 右:手机B)
为了与上图有一个比较,此处将Manifest中meta-data的design_width_in_dp设置为400。
适配过后,不同分辨率的设备显示非常相似。
2、AndroidAutoSize原理分析
2.1 基本概念
2.1.1 一些重要的单位
名称 简介
px pixels(像素),屏幕上的实际像素点,无论控件或文字最终都会转化为px单位来显示其大小。
dp 与dip雷同,指的是设备独立像素,在不同分辨率和尺寸的手机上代表了不同的真实像素,计算公式:px = dp(dpi/160)
dpi 像素密度,指的是在系统软件上指定的单位尺寸的像素数量,它往往是写在系统出厂配置文件的一个固定值。
sp 全称scaled pixels,放大像素的缩写,专门用于处理字体的大小。它不仅与屏幕dpi有关,还与系统的默认字体大小有关。
2.1.2 单位转换中涉及到的两个重要类
DisplayMetrics.java
public class DisplayMetrics {
public static final int DENSITY_MEDIUM = 160;
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT;
public static int DENSITY_DEVICE = getDeviceDensity();
public int widthPixels; // 屏幕像素(宽)
public int heightPixels; // 屏幕像素(高)
public float density; // 屏幕密度, density = dpi/160, dp与px之间的转化就是用此参数
public int densityDpi; // dpi
public float scaledDensity; // 字体大小转换会用到此参数 px = sp*scaledDensity
public float xdpi;
public float ydpi;
......
public void setToDefaults() {
widthPixels = 0;
heightPixels = 0;
density = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
densityDpi = DENSITY_DEVICE;
scaledDensity = density;
xdpi = DENSITY_DEVICE;
ydpi = DENSITY_DEVICE;
......
}
}
这里面有几个重要的参数: density、densityDpi、scaledDensity,AndroidAutoSize的原理主要就是修改这几个参数值来实现屏幕的适配。下面还有一个系统提供的单位转化API,系统内部基本上都是调用此API来实现单位转化。
TypedValue.java
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;
}
根据unit,将value转化为px值。
2.2 实现原理
通过上面的单位介绍及之间的转换,我们可以得到如下结论:
p x= dp(dpi/160), density = dpi/160 => px = dp*density => dp = px/density;
明白上面这个结论,下面我们来讨论为什么我们日常对控件设置的宽/高为某一dp时,无法做到各个手机屏幕的适配。
设备 A , 屏幕宽度为 720px, dpi为160,则屏幕总dp为 720/(160/160) = 720 dp
设备 B , 屏幕宽度为 720px, dpi为320,则屏幕总dp为 720/(320/160) = 360 dp
可以看到屏幕的总 dp 宽度在不同的设备上是会变化的,但是我们在布局中填写的 dp 值却是固定不变的,这就导致我们设置的固定宽度在不同的设备上显示的比例不一样。 例如我们布局中有一个View设置固定宽度为180dp,在设备A中会占屏幕宽度的1/4,但是在设备B中只会占屏幕宽度的1/2,这种差别是十分巨大的。
这时我们要想完美适配,那就必须保证这个 View 在任何分辨率的屏幕上,与屏幕的比例都是相同的。
要做到在任何分辨率的屏幕上显示比例相同,我们该怎么做呢?
方案一: 动态改变每个View的dp值
由于每种设备的宽度dp值是不同的,为使得View能够在不同设备上显示的比例一致,可以通过代码计算动态的设置每个View的dp值,这种方式显然是不合适的,工作量太大,太复杂。
方案二: 每个View设置固定的dp值,通过修改density 值而达到每种分辨率手机的宽度dp值相同
由公式:dp = px/density 可知,由于px是屏幕分辨率,这个值有硬件确定,我们是无法改变的,那么我们可以通过修改density 的值使得不同分辨率的手机宽度dp值是相同的,这样当我们对View设置为某一特定的dp宽度时,占总宽度的dp比例是相同的,这样也就达到占屏幕的比例相同。
那么问题来了,我们如何确定density 的值呢?
由dp = px/density => 屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度
屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度 => 当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density
如果我们将一套设计图的总宽度(dp)作为最终手机屏幕的中宽度(dp), 从而达到修改density的目的,同时又可以保证最终不同分辨率手机的屏幕总宽度是相同的,这也就完成了适配。
AndroidAutoSize/今日头条 就是基于方案二的原理来实现屏幕适配的。
2.3 方案可行性
假设设计图中宽度为300dp,一个View在设计图上的尺寸为 100dp * 100dp,那么这个View的宽度占整个设计图宽度的33.3%,那么接下来我们来验证下通过方案二的适配方案,这个View能否做到不同分辨率的设备还能保持与设计图中一致的比例。
验证设备 1
屏幕总宽度为 1080 px,根据上面公式求出density, 1080/300 = 3.6(density),那么这个尺寸为 100dp * 100dp的View,系统最后会将都换算成px,也就是 px=100*3.6=360(px), 然后 360/1080=0.333=33.3%,与设计图上的比例一致。
问题一
某些设备总宽度为1080px,但设备的dpi不同,是否对该方案适配产生影响?
答案当然是不会的,其实这个方案根本没有根据 设备提供的dpi 求出 density,是根据自己的公式求出的 density,所以这对该方案是没有影响的。
验证设备 2-
刚才我们验证的是宽度为1080px的设备,现在我们用另外一种分辨率的设备720px来验证。
屏幕总宽度为 720 px,根据上面公式求出density, 720 /300 = 2.4(density),那么这个尺寸为 100dp * 100dp的View,系统最后会将都换算成px,也就是 px=100*2.4=24.(px), 然后 240/720=0.333=33.3%,与设计图上的比例一致。
通过计算,该方案是可行的。
2.4 优缺点
优点
使用成本非常低,操作非常简单,使用该方案后在页面布局时不需要额外的代码和操作,这点可以说完虐其他屏幕适配方案
侵入性非常低,该方案和项目完全解耦,在项目布局时不会依赖哪怕一行该方案的代码,而且使用的还是 Android 官方的 API,意味着当你遇到什么问题无法解决,想切换为其他屏幕适配方案时,基本不需要更改之前的代码,整个切换过程几乎在瞬间完成,会少很多麻烦,节约很多时间,试错成本接近于 0
可适配三方库的控件和系统的控件(不止是是 Activity 和 Fragment,Dialog、Toast 等所有系统控件都可以适配),由于修改的 density 在整个项目中是全局的,所以只要一次修改,项目中的所有地方都会受益
不会有任何性能的损耗。
缺点
只需要修改一次 density,项目中的所有地方都会自动适配,这个看似解放了双手,减少了很多操作,但是实际上反应了一个缺点,那就是只能一刀切的将整个项目进行适配,但适配范围是不可控的。
这个方案依赖于设计图尺寸,但是项目中的系统控件、三方库控件、等非我们项目自身设计的控件,它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样。
其实 AndroidAutoSize开源库已经很大程度上解决了如上两个缺点,如前面已经给出Activity的用法,适配粒度可以达到Activity。
3、一些思考
3.1 其他方案对比
传统的dp、layout_weight等做简单的适配
设备的dpi值并不是任意指定的,它是通过 sqrt(screenWpx2 + screenHpx2) / 屏幕尺寸 计算出的结果(上面模拟器参数是我特意设置,为了很明显的演示所需) , 因此在大多数设备上对View的宽/高设以dp为单位进行设置值,差别并不是十分大,当然这只是大多数设备,因此要适配每种设备还是很难做到的。
smallestWidth 限定符屏幕适配方案
开发者先在项目中根据主流屏幕的 最小宽度 (smallestWidth) 生成一系列 values-swdp 文件夹 (含有 dimens.xml 文件),当把项目运行到设备上时,系统会根据当前设备屏幕的 最小宽度 (smallestWidth) 去匹配对应的 values-swdp 文件夹,而对应的 values-swdp 文件夹中的 dimens.xml 文字中的值,又是根据当前设备屏幕的 最小宽度 (smallestWidth) 而定制的,所以一定能适配当前设备。
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-sw320dp
│ ├── ├──values-sw360dp
│ ├── ├──values-sw400dp
│ ├── ├──values-sw411dp
│ ├── ├──values-sw480dp
│ ├── ├──...
│ ├── ├──values-sw600dp
│ ├── ├──values-sw640dp
详情可参考: https://juejin.im/post/5ba197e46fb9a05d0b142c62
优点
非常稳定,极低概率出现意外
不会有任何性能的损耗
适配范围可自由控制,不会影响其他三方库
缺点
在布局中引用 dimens 的方式,日常维护修改时较麻烦
侵入性高,如果项目想切换为其他屏幕适配方案,因为每个 Layout 文件中都存在有大量 dimens 的引用,这时修改起来工作量非常巨大,切换成本非常高昂
无法覆盖全部机型,想覆盖更多机型的做法就是生成更多的资源文件。
如果想使用 sp,也需要生成一系列的 dimens,导致再次增加 App 的体积
3.2 应用到项目中可能存在的问题
如果使用AndroidAutoSize开源库,那么默认就已经使用AndroidAutoSize对应的适配方案来适配了,没有提供相关API关闭默认适配,这样会对已有的组件造成不可预估的风险;
由于AndroidAutoSize 需要设置一个全局的design_width_in_dp和design_height_in_dp(也就是设计图的dp值),那么对于我们目前的各个组件是否适合;
3.3 建议
针对3.2中的问题1,我们可以对AndroidAutoSize进行一定的修改,例如增加相关api,在应用的application执行过程中,关闭AndroidAutoSize的默认开启全局适配的特性;
针对3.2中的问题2,目前AndroidAutoSize已经能够做到Activity的粒度,让每个activity的总宽度/高度 dp值个性化(AndroidAutoSize实战已有讲解)。
参考文献
https://juejin.im/post/5b7a29736fb9a019d53e7ee2
https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q
https://juejin.im/post/5b6250bee51d451918537021
————————————————
版权声明:本文为CSDN博主「Sky#boy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010349644/article/details/83619912