前言

一个月前看了今日头条新的屏幕适配方案,这是传送门,对此不禁拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码即可适配,如今最新 1.19.0 版 AndroidUtilCode 已有其最新的适配方案,其相关函数在 ScreenUtils 中,相关 API 如下所示:

adaptScreen4VerticalSlide  : 适配垂直滑动的屏幕
adaptScreen4HorizontalSlide: 适配水平滑动的屏幕
cancelAdaptScreen          : 取消适配屏幕
isAdaptScreen              : 是否适配屏幕
复制代码

效果

UtilApk 中的 ScreenAdaptActivity 以设计图为 360dp 宽度 来做适配,我们设置两个 view 宽度为 180dp,代码如下所示:

public class ScreenAdaptActivity extends BaseActivity {private TextView tvUp;private TextView tvDown;public static void start(Context context) {Intent starter = new Intent(context, ScreenAdaptActivity.class);context.startActivity(starter);}@Overridepublic void initData(@Nullable Bundle bundle) {if (ScreenUtils.isPortrait()) {ScreenUtils.adaptScreen4VerticalSlide(this, 360);} else {ScreenUtils.adaptScreen4HorizontalSlide(this, 360);}}@Overridepublic int bindLayout() {return R.layout.activity_screen_adapt;}@Overridepublic void initView(Bundle savedInstanceState, View contentView) {}@Overridepublic void doBusiness() {}@Overridepublic void onWidgetClick(View view) {}public void toggleFullScreen(View view) {ScreenUtils.toggleFullScreen(this);}@Overrideprotected void onDestroy() {ScreenUtils.cancelAdaptScreen(this);super.onDestroy();}
}
复制代码

其在 1080x1920 420dpi(xxhdpi) 下的效果如下所示:

其在 768x1280 320dpi(xhdpi) 下的效果如下所示:

其在 480x800 240dpi(hdpi) 下的效果如下所示:

其在 320x480 160dpi(mdpi) 下的效果如下所示:

如上就是竖屏以 360dp 为宽度和横屏以 360dp 为高度的适配效果。

原理

如果看了上面今日头条的那篇适配文章,那么你可能已经知道其原理了,不明白的话可以继续看下我的解释: 我们知道 px = dp * density,我们要适配的话需要确保 dp 不变去修改 density,而安卓默认 density = dpi / 160,其意思就是 1dp 有多少 px,也就是像素密度,我们开发是按照一份设计稿来做的,那么有没有什么办法来让 density 和设计稿尺寸做联系呢?假设我们设计稿是宽度是 1080px,资源放在 xxhdpi,那么我们宽度转换为 dp 就是 1080 / 3 = 360dp,要在不同设备上宽度都表现为 360dp,那么就需要修改其 density = screenWidthPx / 360,这样就满足了上述条件,而和 density 相关的还有 densityDpi、scaledDensity,我们根据 density 等比修改 densityDpi、scaledDensity 即可。

由于 API 26 及以上的 Activity#getResources()#getDisplayMetrics()Application#getResources()#getDisplayMetrics() 是不同的引用,所以在 API 26 及以上适配是没有影响的,但在 API 26 以下 Activity#getResources()#getDisplayMetrics()Application#getResources()#getDisplayMetrics() 是相同的引用,导致适配有问题,这里要感谢 @MirkoWu 提出的问题,后面会有解决方案。

如果我们以 xxhdpi 的 360dp 来适配的话,首先在 AS 中预览是个问题,在接入第三方 SDK 带有界面或者 View 的话会导致它的尺寸全然不对,因为我们那样适配后界面宽度只有 360dp,而第三方 SDK 中很有可能写的布局会超出 360dp,这便会引发新的问题,当然这也是有响应的解决之道,比如暂时取消适配,但我们有更好的方式,着重看下面介绍。

我着重推荐以 mdpi 为特例来适配,比如前面说到的 xxhdpi 的 360dp,那么在 mdpi 下就是 360 * 3 = 1080dp,这样我们新建一个宽为 1080px 的 mdpi 设备(可以通过修改设备尺寸来达到 mdpi),然后切换为该设备来预览布局就完美解决了以上问题,我们在写布局的时候设计图是 36px,那么我们直接就写 36dp 即可,设计图字体是 24px, 我们直接就写 24sp 即可,这样便可达到和设计图一致的效果。另外,图片资源放在需要适配的最高 dpi 下面即可,比如 drawable-xxhdpi 或者 drawable-xxxhdpi,这样在高清屏上也不会导致失真。

但是这样会导致获取状态栏和导航栏高度有问题,其获取状态栏高度代码为如下所示:

public static int getStatusBarHeight() {Resources resources = Utils.getApp().getResources();int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");return resources.getDimensionPixelSize(resourceId);
}
复制代码

由于使用的是 Application#getResources,这会导致最后计算状态栏高度使用的是修改过后的 density,在这里也要感谢 @magic0908 无意间提到的 Resources.getSystem() 来获取系统的 Resources,果不其然可以获取到正确高度的状态栏高度,代码如下所示:

public static int getStatusBarHeight() {Resources resources = Resources.getSystem();int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");return resources.getDimensionPixelSize(resourceId);
}
复制代码

同理获取导航栏高度也可以这样。

考虑到了 Resources.getSystem(),那么我们在适配上岂不是可以更方便,不用区分版本什么的 Activity#getResources()#getDisplayMetrics()Application#getResources()#getDisplayMetrics(),也不需要什么中间变量来记录适配前的值,那些值我们直接在 Resources#getSystem()#getDisplayMetrics() 中获取 densitydensityDpiscaledDensity 即可,而且在修改系统字体的时候,Resources#getSystem()#getDisplayMetrics() 也会相应地改变,这样也就不需要注册 registerComponentCallbacks 来监听系统字体的改变,所以最终的源码很是简洁,但其中间遇到的问题很是复杂,光工具类我这些天就更新了很多版本来解决其问题,从1.18.01.18.7,有六个版本都是和这个适配有关系,但最终还是完美地找到了解决方案,也要感谢大家的帮助,其最终源码如下所示:

/*** Adapt the screen for vertical slide.** @param activity        The activity.* @param designWidthInPx The size of design diagram's width, in pixel.*/
public static void adaptScreen4VerticalSlide(final Activity activity,final int designWidthInPx) {adaptScreen(activity, designWidthInPx, true);
}
/*** Adapt the screen for horizontal slide.** @param activity         The activity.* @param designHeightInPx The size of design diagram's height, in pixel.*/
public static void adaptScreen4HorizontalSlide(final Activity activity,final int designHeightInPx) {adaptScreen(activity, designHeightInPx, false);
}
/*** Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA*/
private static void adaptScreen(final Activity activity,final int sizeInPx,final boolean isVerticalSlide) {final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();if (isVerticalSlide) {activityDm.density = activityDm.widthPixels / (float) sizeInPx;} else {activityDm.density = activityDm.heightPixels / (float) sizeInPx;}activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.densactivityDm.densityDpi = (int) (160 * activityDm.density);appDm.density = activityDm.density;appDm.scaledDensity = activityDm.scaledDensity;appDm.densityDpi = activityDm.densityDpi;
}
/*** Cancel adapt the screen.** @param activity The activity.*/
public static void cancelAdaptScreen(final Activity activity) {final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();activityDm.density = systemDm.density;activityDm.scaledDensity = systemDm.scaledDensity;activityDm.densityDpi = systemDm.densityDpi;appDm.density = systemDm.density;appDm.scaledDensity = systemDm.scaledDensity;appDm.densityDpi = systemDm.densityDpi;
}
/*** Return whether adapt screen.** @return {@code true}: yes<br>{@code false}: no*/
public static boolean isAdaptScreen() {final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();return systemDm.density != appDm.density;
}
复制代码

坑点

在原理里都已经说完了哈。

建议

新老项目都可以用这套方案,老项目中如果有新的 Activity 加进来,那么可以对其使用该方案来适配,然后在启动其他老的 Activity 时候 cancelAdaptScreen 即可。新项目我建议采用我工具类中的使用,可以让你爽到极致,在 BaseActivitysetContentView(xx) 之前调用适配代码即可,记得第二个参数一定要传入设计图的实际像素尺寸,不再是曾经的 dp 尺寸了。

有了固定的尺寸,那么我们百分比是不是就很好实现了,计算后直接写 xxdp 即可,这样在所有设备上也都是一定的比例,哪里还需要什么百分比布局什么的来做?是不是 so easy,更多风骚的操作可待你解锁。

结语

如果我的工具类对你的适配造成了影响,欢迎到 AndroidUtilCode 提 issue,感谢今日头条的方案,让我可以站在巨人的肩膀上装一次 13。

最后

记得屏幕适配一定要用 1.19.0 版本及以上

记得屏幕适配一定要用 1.19.0 版本及以上

记得屏幕适配一定要用 1.19.0 版本及以上

给大家带来了麻烦,sorry。

GitHub issue

屏幕适配问题汇总

Android 屏幕适配从未如斯简单(8月10日最终更新版)相关推荐

  1. Android 屏幕适配终结者

    本文作者: 布兰柯基 本文链接: https://blankj.com/2018/12/18/android-adapt-screen-killer/ 文末有彩蛋 背景 之前基于头条的适配方案写了篇文 ...

  2. Android 屏幕适配

    一.适配方式之dp 名词解释 分辨率:480*800,1280*720.表示物理屏幕区域内像素点的总和.(切记:跟屏幕适配没有任何关系) 因为我们既可以把1280*720 的分辨率做到4.0 的手机上 ...

  3. Android屏幕适配全攻略(最权威的官方适配指导)(转),共大家分享。

    Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因.重要概念.解决方案及最佳实践,我相信如果你能认真的学习 ...

  4. Android屏幕适配全攻略(最权威的官方适配指导) (转)

    招聘信息: Cocos2d-X 前端主程 [新浪微博]手机客户端iOS研发工程师 20k-40k iOS 开发工程师 iOS高级开发工程师(中国排名第一的企业级移动互联网云计算公司 和创科技 红圈营销 ...

  5. android 屏幕分辨率 屏幕密度,Android屏幕适配——多分辨率多屏幕密度

    为什么要适配,适配的好处等等这里就不说了,直接说我们要怎么适配,请看下面的内容. 1.重要概念 px:pixel,像素Android原生API,UI设计计量单位,如获取屏幕宽高. 屏幕分辨率:指在纵向 ...

  6. 【收藏】Android屏幕适配全攻略(最权威的Google官方适配指导)

    来源:http://blog.csdn.net/zhaokaiqiang1992 更多:Android AutoLayout全新的适配方式, 堪称适配终结者 Android的屏幕适配一直以来都在折磨着 ...

  7. Android 系统(186)---最易懂的Android屏幕适配解决方案--总结版

    最易懂的Android屏幕适配解决方案--总结版 本文参考自: Google的官方权威适配文档 郭霖:Android官方提供的支持不同屏幕大小的全部方法 Stormzhang:Android 屏幕适配 ...

  8. 2021年最详细的Android屏幕适配方案汇总

    1 Android屏幕适配的度量单位和相关概念 建议在阅读本文章之前,可以先阅读快乐李同学写的文章<Android屏幕适配的度量单位和相关概念>,这篇文章包含了阅读本文的一些基础知识,推荐 ...

  9. Android屏幕适配全方位解析与指导

    Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因.重要概念.解决方案及最佳实践,我相信如果你能认真的学习 ...

最新文章

  1. java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderL,spring获取context...
  2. LeetCode-动态规划基础题-343. 整数拆分
  3. Xamarin效果第八篇之视频监控
  4. LeetCode —— 深搜水题记录
  5. tf.nn.embedding_lookup()函数
  6. item 12: 把重写函数声明为“override”的
  7. 多服务器消息推送消息,多浏览器窗口接收websocket服务器推送消息问题
  8. java 获取数组(二维数组)长度实例程序
  9. 使用Flash,HTML5和Unity开发网页游戏的对比
  10. 企业外贸出口业务流程图 进出口贸易流程细节
  11. 2017又是新的一年
  12. 《用 Python 处理 Excel 数据之正则表达式视频教程》 曾贤志
  13. 《剑指Offer》51. 二叉搜索树的第k个结点
  14. 移动式护栏巡逻机器人_重磅!移动式护栏巡逻执法机器人上岗!专盯高速乱停乱行!...
  15. Android中的单元测试
  16. 爬虫君子协议-robots.txt协议
  17. Oracle中的子查询
  18. Vue-change和input事件
  19. TF girls系列(1)搭建二层全连接神经网络
  20. 圣经书||《强化学习导论(2nd)》原书、代码、习题答案、课程视频大全

热门文章

  1. 探索 ConcurrentHashMap 高并发性的实现机制
  2. GoldenGate复制单表开并行
  3. python 奇偶拆分list,python拆分list,得到一个原来list的奇数list,一个原来list的偶数list...
  4. MySQL数据库备份命令
  5. 《我也能做CTO之.程序员职业规划》新书出版推举
  6. 【直播回顾】云栖社区特邀专家徐雷Java Spring Boot开发实战系列课程(第19讲):Java Spring Cloud微服务架构模式与开发实战...
  7. 在IIS8.5的环境下配置WCF的Restful Service
  8. Xamarin.Android 调用本地相册
  9. 《网络维护》MAC地址
  10. 《数据库技术原理与应用教程》一3-5信息世界与逻辑模型