前言
其实网上已经有很多人总结了Andorid 屏幕适配的知识. 这里总结了适配的主流方案, 通过分析思考适配的本质, 再来思考各个适配方案的优劣. 弄清楚为什么有适配问题.
一.什么是屏幕适配?
这里说的屏幕适配就是在Android众多机型上,能有一个相对一致的显示表现.Android机型分辨率,尺寸,宽高比太多了,如果不做适配后果就是显示效果差异比较大. 举个栗子
xml布局:
<TextView
android:layout_width="300px"
android:layout_height="50px"
android:background="@color/colorAccent"
android:gravity="center_vertical"
android:text="文本"/>
左侧是nexus4 右侧是nexus5
屏幕都是等分十等分的, 便于我们观察差异.
二. 为什么需要做屏幕适配?
刚才可以看到是用px做单位的. 其实谷歌有默认的适配方案,就是采用dp做单位来适配. 我们来看看使用dp做单位的情况是什么样的.
从图中可以看到其实使用dp做适配,基本解决了问题, 只有一些小差异,如果公司要求不高,使用dp做适配其实也可以的. 没有问题, 甚至还有些优势 这个后面再分析.
现在主要分析为什么dp适配产生了一些差异, 谷歌这么牛x的公司为什么设计的方案是这这个样子 ,好像并没有完全解决问题, 还需要我们继续操心适配问题. 要了解这些,就先需要了解一下基本概念.
三. 基本概念
相信各位大佬肯定猜到我要说什么了. 老生长谈的几个概念, 但是我还是说下,便于之后的分析理解
- 像素 px: 像素就是对应屏幕的分辨率上的像素, 比如手机是分辨率是1080*1920的,那么手机横向就是1080个像素点. 我们看图一,右侧的手机就是这个分辨率, 在布局中写300px自然显示效果大约横向屏幕的30%宽度.不管什么适配, 其实最后都是转为px, 因为px是对应的屏幕物理像素.
- 密度无关像素dp: 这个是谷歌为Android定制的, px = dpi/160 *dp 看到转化公式, 问题来了,dpi是什么.
- 屏幕像素真实密度: 就是用屏幕对象线的像素点数量/对角线英寸 就可以计算得出.
- 逻辑像素密度 dpi : 这个也是密度, 和上面的密度的区别就是这个密度是厂家rom定义的. 和屏幕像素真实密度有一定的差异. getResources().getDisplayMetrics().densityDpi 可以获取到该值;
我们看看nexus5 nexus5x 手机参数
实际运行效果 如下图: 这个显示效果和上面表格计算出来的一致
可以看出使用dp做单位影响因素最大的就是dpi这个值了
适配问题的产生核心本质就一句话:
- 因为dpi和实际像素密度的差异导致使用dp做单位,没有很好的适配.
四. Android各个适配方案对比
一. dp 适配
dp适配是谷歌原始的适配方案, 上文也做了分析, 大家再来思考一下之前提出的问题 为什么谷歌这样设计. 设个一个dpi的概念, 而不使用真实像素密度的?
其实dpi是为了给手机厂商灵活配置显示效果的. 有两个手机一个是5寸 一个是6寸 , 如果直接采用真实像素密度那么6寸手机就是5寸手机的完全放大版本.很多时候我们想着的是大屏手机可以看到更多的内容. 而不是单纯的等比例放大.
缺点: 在不同的手机上表现不同, 不符合公司设计图标准.
二. 直接资源引用适配
我们在布局中写的单位可以引用dimens资源文件. 配置不同的分辨率然后手机运行的时候可以根据不同分辨率去对应不同的单位. 参考链接:
http://blog.csdn.net/lmj623565791/article/details/45460089
优点: 可以很好的控制不同分辨率的显示效果.
缺点: Andorid手机分辨率越来越多, 维护这套多分辨率的文件十分繁琐. 更糟糕的是, 如果没有对应的分辨率 不会去自动匹配相似的分辨率.
这种方案不做推荐
三. 最小宽度限定符
和方案一类似, 但是不是指定分辨率的,而是指定屏幕宽度的, 这样一来文件就要少很多了
目标屏幕的最小宽度都大于480dp时,屏幕就会自动到带sw480dp后缀的资源文件里去寻找相关资源文件,这里的最小宽度是指屏幕宽高的较小值,每个屏幕都是固定的,不会随着屏幕横向纵向改变而改变。
参考使用链接: https://www.jianshu.com/p/759375113de9
优点: 可以很好的控制不同分辨率的显示效果. 缺少对应宽度的引用资源文件 ,可以默认去匹配相近的文件.
缺点: 还是要去维护较多的文件. 略繁琐.
四. 谷歌退出的新的百分比设置支持库
这个库提供了PercentRelativeLayut percentFrameLayout 支持常用的属性. 使用的时候设置百分比就可以用了.
实现原理: 通过Layoutparams 获取child设置的百分比. 通过计算获取这百分比应该是多少. 测量出控件的大小.
缺点: 使用起来比较麻烦. 以为设计图标注的是px.每次设置百分比的时候需要去计算.
设置百分比还是依赖父容器的.导致scrollView ListView 内的高度无法使用百分比.
这种方案用的人比较少 不推荐使用.
五. 张鸿洋提出的 AutoLayout 全新适配方式.
使用px单位并不是像素.内部经过处理.变成相应的百分比. 宽和高都是百分比. 宽的1px和高的1px不相同;
https://github.com/hongyangAndroid/AndroidAutoLayout
鸿洋大神的这套方案用的很多 . 虽然最后停止维护了,
优点: 配置简单, 使用起来可以直接用px对着设计稿直接写.
缺点: 在一些复杂布局, 带一些自定义控件的布局时候容易出现适配问题.
而且在实际项目使用的时候 有偶发失效的情况. 失效后, 界面由于px做单位. 界面元素变的很小.
这方案可以考虑使用. 毕竟在项目中运用广泛. 需要注意的是一些自定义布局的处理, 在列表中itemview需要通过工具类额外处理一下.
六. 今日头条方案:
其实以上几种方案都不是很好, 在项目中有各自缺点. 目前最推荐的还是头条的方案;
参考链接: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
工具类如下:
/**
* @param activity
* @param application
* @param isLandscape 是否是横屏
*/
public class ScreenUtils {
/** 设计稿标准 */
private static final float width = 750f;
private static final float high = 1334f;
private static float textDensity = 0;
private static float textScaledDensity = 0;
/**
* 今日头条的屏幕适配方案
* 根据当前设备物理尺寸和分辨率去动态计算density、densityDpi、scaledDensity
* 同时也解决了用户修改系统字体的情况
* @param activity
*/
public static void setCustomDensity(@NonNull Activity activity) {
setCustomDensity(activity, false);
}
/**
* @param activity
* @param isLandscape 是否是横屏
*/
public static void setCustomDensity(@NonNull final Activity activity, boolean isLandscape) {
final Application application = activity.getApplication();
final DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (textDensity == 0) {
textDensity = displayMetrics.density;
textScaledDensity = displayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
if (configuration != null && configuration.fontScale > 0) {
textScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDensity;
if (isLandscape) {//横屏
targetDensity = displayMetrics.widthPixels / (high / 2); //当前UI标准750*1334
} else {
targetDensity = displayMetrics.widthPixels / (width / 2); //当前UI标准750*1334
}
final float targetScaledDensity = targetDensity * (textScaledDensity / textDensity);
final int targetDpi = (int) (160 * targetDensity);
displayMetrics.density = targetDensity;
displayMetrics.scaledDensity = targetScaledDensity;
displayMetrics.densityDpi = targetDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDpi;
}
}
使用如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在设置布局之前调用工具类方法
ScreenUtils.setCustomDensity(this);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
int densityDpi = getResources().getDisplayMetrics().densityDpi;
tv.setText("逻辑像素密度 dpi " + densityDpi);
}
}
看看实际运行效果:
实际效果中可以看到很好的完成了适配
之前我们分析了dp不太适配的原因. 所以今日头条方案简答暴力.
既然rom中设置的dpi不是实际像素密度. 那么我直接把dpi给你改成实际像素密度.
果然够简单暴力的...
工具类比较简单,复制就可以用. 就不上传github了.
五. 最后总结和思考:
实际开发中我们直接使用今日头条方案就可好了, 可以满足需求.
似乎我们已经找到了完美的适配方案了.
但是? 但是了. 但是这个所谓的适配的真的就是谷歌的个各个手机厂商想看到的么?
厂商定义dpi没有按照实际的来定义 而是做了调整, 调整之后的好处就是就是希望在大屏手机上可以看更多的内容. 在图三中我们发现nexus-5x 比nexus-5 屏幕大一些 dpi值厂商设置的也diy低一些, 更具公式px=dpi/160*dp 得知这样px就变小, 内容占的地方就小, 可以容纳更多内容. 这就是为了让大屏手机看到更多的内容. 为了验证这个猜想我们再看看更大手机的显示情况
图五中左侧是通过工具类转换后的显示, 完成了所谓的适配.
右侧是本来的样子. 可以看到右侧中textview占据的屏幕空间确实要小一些.
图六中的表格看到越是大屏手机, 厂商对dpi的标定比实际小的越多, 这样就可以让屏幕显示更多的内容.特别是看电子书之类的内容时候体验更明显, 大屏手机一页可以显示更多的内容. 大屏幕的优势就凸显出来了.
但是我们开发的普通应用为了在大屏小屏上一致的体验. 就不得不去修改dpi为实际值了.
以上是我的对Android中适配的一些思考和总结, 希望对大家有帮助~