最近陆陆续续被一些android屏幕适配的文章刷屏了,我发现有些问题在看别人写的文章时候还是比较不容易理解的,在这里我们把这些东西捋一捋,来讲讲android屏幕适配的原理,还有之前用过的屏幕适配框架,以及目前比较火的适配框架,欢迎探讨...

为什么要适配?

目前,很多厂商都推出了自己的全面屏手机,例如小米mix系列,蓝绿兄弟的 find-x,nex等等,不仅用了很多新的技术,而且体验方面也是流畅到没朋友(吃鸡必备,视角广),遥想当年用htc卡的不行也是美滋滋,在惊叹科技进步的同时,也不由感. 慨. 万.千.啊!

好了我们言归正传..

安卓屏幕适配从很久以前到现在一直是开发从业人员比较头疼的问题,源于google亲爸爸对他的定位--开源,所以任何厂商都可以对这个系统进行定制及修改,这就导致了国内各个型号分辨率,各种尺寸手机层出不穷,碎片化非常严重,再到现在全面屏即将成为街机,除了以往16比9 的手机之外,又有了18比9等其他屏占比手机,所以我们在开发的过程当中,需要进行屏幕上的适配调整.

image

对于这张图,很多人都不陌生,这是2014年表示安卓手机碎片化的一张图,仅仅是2014年的,更别说4年后的今天了 (没找到2018年的图),所以,对于安卓这个打败了塞班划时代的系统,让我们又爱又恨.一方面我们可以在不重复造轮子的情况下,在巨人的肩膀上登高望远,一方面又受困于整个市场没有一个统一的规范,所以这张图很清晰的告诉大家这就是我们所要面临的适配问题

一些概念

碎片虽然多,但也不是一个一个进行适配的,我们只需找出碎片中的共性,然后开发一些通用的手法,让每一个机型都达到我们的要求,当然,我们要进行屏幕适配,有几个概念要了解一下

DPI(屏幕密度):为单位英寸的像素点(每一英寸有多少个像素点).

这个概念非常重要,这个参数一般是在手机出厂时候写死在配置文件中的,之前我以为它是真正的屏幕密度,其实不是,它是在参考了物理密度之后,根据物理密度的区间,设定了一个统一的值.

PPI:这个是真正的物理密度,如果你自己想算一下,那么假如我们的手机是1080x1920分辨率的,尺寸为5寸(对角线长度),那么我们可以根据勾股定理求出对角线的像素数,再除以5,便是当前手机的密度,那我们举的例子密度为440,当然如果相同分辨率手机,尺寸越大那么密度就小,尺寸越小,密度就越大,这个也很好理解

Tips:以上面两个概念举个例子,看张图

QQ20180827-170339@2x.png

我们可以看到相同分辨率的手机,尺寸不一样,密度肯定也不一样,但是dpi同为420,说明ppi在一个区间之内 ,统一给到了dpi为420,这样会方便我们适配,也就是说我们写在项目中的dp,在两台分辨率一致,尺寸不一致的手机上,表现是一致的.

PX:像素点,1px=1像素点

DIP(等于dp):这是google提供的一种单位,方便我们去适配,

如果我们在开发时候将px直接写死在项目里,那么不同分辨率的手机显示比例肯定不一致,比如宽为1080px(像素单位)的手机中与一条横线是800px,那么他在720px的手机上肯定会占满整个屏幕,导致显示不全,所以为了避免这种情况,我们在项目中写的都是dp,在安卓系统中,会以160dpi为基准 也就是说手机密度为160dpi ,1dp=1px;320dpi,1dp=2px 以此类推,那我们写在项目中的dp也会因屏幕密度不同转化成不同的px呈现出来,这样可以解决大部分适配问题,根据之前的例子所以这里便有两个公式

1. density=dpi/160,   2. dp=px/density

density:上文说到屏幕密度,是定死的,那肯定是不变的,那么density也是不变的,我们可以通过这个density用第二个公式去获取我们应该把我们的控件到底写成多少dp,当然这要依设计图而定,通常情况下如果设计图是1080P的 那么密度为480,density便为3,那么UI标出来的像素/3,即为dp数值,简单提一下,今日头条屏幕适配方案的核心思想就是通过修改这个density值,来进行屏幕适配的

小结:通常情况下我们用写dp这个单位去做适配,确实可以解决大部分问题,那如果有一台手机分辨率为1080x1920,dpi为440,并不是480,那么相同的dp所求出来的px,440就比480小(上面俩公式),如果你的控件写的是dp,那么肯定会一个长,一个短,所以接下来的几种方案都是为了解决这个问题而做的

各种适配方案

1.宽高限定符适配

当我们平常用as去开发的时候,在资源文件res下面,有一个vaules文件夹,我们写的各种宽度跟高度,通常会写在这个文件夹中的一个叫dimens.xml文件中,然后在布局中引用,当然,默认是所有分辨率的手机都会引用这个文件夹,限定符适配方案就是在这里,新建市面上所有分辨率的的vaules文件夹,那么不同分辨率的手机会寻找它所在分辨率所在的文件夹中的dimens文件,并引用,例如:

├── src/main

│ ├── res

│ ├── ├──values

│ ├── ├──values-800x480

│ ├── ├──values-860x540

│ ├── ├──values-1024x600

│ ├── ├──values-1024x768

│ ├── ├──...

│ ├── ├──values-2560x1440

通常情况下,我们会将1dp=1px的480x320分辨率的文件夹作为基准,那么我们所定义的vaules-480x320中的dimens文件肯定以1px为单位逐次递增:(以宽度举例)

1px

2px

3px

4px

5px

...

那么如果是800x480分辨率的dimens 就应该为(宽度)

480(现在的)/320(基准的)=1.5 那么对应的dimens为:

1.5px

3px

4.5px

6px

7.5px

...

同理:如果是1920x1080

3px

6px

9px

12px

15px

...

好,那如果设计图是按照1080x1920设计的,UI小姐姐在其中一张图上标了一个100x100的按钮,那么我们只需要在这个1080x1920分辨率的dimens文件中找到100的值,并引用就可以了.

小结:这是一个比较古老的方案,是安卓屏幕适配的先驱者,很多开源的适配项目都是根据这个这个原理进一步填充跟扩展的,我们发现其完全摒弃了dp(虽然写dp也是会被转成px),使用起来非常简单,通过工具也可以批量生成各种文件,能保证绝大部分手机比例正常显示,也是比较成熟的,但是这个方案比较依赖于精准限定,也就是说你的手机分辨率如果没有出现在我的项目中,比如1080x2140,那对不起,他会使用默认的valus,这样我们的界面肯定会显示不正常,还有就是市面上手机分辨率层出不穷,那我到底要建多少个文件夹,当我每次用到res,看到几十个vaules,项目看着臃肿不说,dimens多也会增大apk体积,不过总体来说,这确实是一种良好适配方式

2.smallestWidth适配

顾名思义,最小宽度适配,也属于限定符适配的一种,只不过是按照手机宽,高的最小值为基准进行适配,不同的是,我们需要在项目中新建这个样子的文件夹:

├── src/main

│ ├── res

│ ├── ├──values

│ ├── ├──values-sw320dp

│ ├── ├──values-sw360dp

│ ├── ├──values-sw400dp

│ ├── ├──values-sw410dp

│ ├── ├──...

文件夹中的数字320dp,360dp等就是我们所说的屏幕宽高最小值所代表的dp值,比如宽度为1080px的手机,dpi为480,那么根据第前面两个公式

density=480/160=3

dp=1080/3=360dp

那么宽度为360dp的手机都会从sw360dp这个文件中去读取dimens数值,其他同理;那每一个文件夹中的dimens文件应该怎么写呢?通过文件夹名称我们就会发现,我们是用dp去做单位的,它主要进行了两个步骤

1.将我们的手机屏幕宽度根据dpi转换成了dp

2.然后计算设计图宽度每一像素占多少dp

举个例子:

假如我们设计图宽度为750px(ios通用设计宽度),如果屏幕是360dp,1px占多少dp怎么算?

0.48=360/750

360dp

0.00dp

0.48dp

0.96dp

1.44dp

1.92dp

...

这样我们算出的dp跟设计图所标注的px完全是一个比例,也就是说设计图写的是多少像素,我们在项目里直接@dimen/qb_px_像素值

简单到没朋友...

当然生成这些文件也是有工具的,工具戳这里,用ide直接import就可以

使用:

├── DimenTypes.class

//适配Android 3.2以上 大部分手机的sw值集中在 300-460之间

DP_sw__300(300), // values-sw300

DP_sw__310(310),

DP_sw__320(320),

DP_sw__360(360);

// 想生成多少自己以此类推

1.换成自己要用的文件夹

├── DimenGenerator.class

1.首先根据设计图尺寸修改

/**

* 设计稿尺寸(将自己设计师的设计稿的宽度填入)

*/

private static final int DESIGN_WIDTH = 750;

/**

* 设计稿的高度 (将自己设计师的设计稿的高度填入)

*/

private static final int DESIGN_HEIGHT = 2150;

2.修改输出路径,执行main方法

public static void main(String[] args) {

int smallest = DESIGN_WIDTH>DESIGN_HEIGHT? DESIGN_HEIGHT:DESIGN_WIDTH; // 求得最小宽度

DimenTypes[] values = DimenTypes.values();

for (DimenTypes value : values) {

MakeUtils.makeAll(smallest, value, "/Users/gaox/Desktop/smallestwidth/dimens_sw");

}

}

小结:smallestWidth方案还是比较友好的,因为它不会像宽高限定符适配方案需要精准命中,假如我们生成的文件夹并没有匹配到,那么它会向下进行匹配,这样就不存在匹配不到脱机显示,那为了适配精准,一般手机宽度dp值在300-450,我们可以以10dp位单位去生成文件夹,这样即使个别手机没有精准匹配到,相差也基本上可以忽略不计,这套适配方案用于新项目上非常简单,只需要在项目初始阶段,生成配套文件夹就可以了,那如果有老项目想用,设计图尺寸又不一样,那改起来就费劲了,而且很多人还是比较排斥建很多个vaules文件夹的,总体来看,如果没有更好的方案,还是比较推荐使用的

3.今日头条适配方案

通过上面两个方案,很明显我们可以发现,适配的核心,就是把设计图中所标注的px,根据手机dpi不同,分别换算成相应的px(宽高限定符),和相应的dp值(smallestwidth),也就是说我们写在layout中的dp(px),会根据手机dpi不同而改变,那假如现在有一个控件,我就想在布局里写死,我心情不好,就想这么写:

android:layout_width="100dp"

android:layout_height="100dp"

android:text="任性"

android:textColor="@color/black"/>

那么能不能有办法进行适配一下呢?那我们就要说一下今日头条团队的适配方案了,他们把我们的适配思路转换了一下

之前我们说过手机的dpi是固定的,那么根据公式density=dpi/160我们的density是可以算出的,你也可以理解为固定的,我们平常写在布局中的dp也可以根据公式dp=px/density算出来,button中的100dp也许你就是这么算的,但是注意,如果我们想要用头条的方案去适配:

density=dpi/160这个公式要替换成

density=当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp)

也就是说这个density不是通过dpi计算出来的,而是我们根据设计图自己算的,那么算出这个density的最终目的就是告诉你1dp等于多少px,没错,跟smallestWidth刚好完全相反!

这里不得不说android的一个机制,不管我们写在布局中的单位是px,dp,sp等等什么也好最终都会转换为px,原因在这里:

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;

}

我们可以发现,写在项目中的dp会乘以density最终转换成px,在不动任何代码的情况下,可以在app初始化的时候,比如说application中获取屏幕的宽度,计算出density,然后修改系统的density为你算的就可以了,方法如下(解决了字体显示不正确等issues):

private static float sNoncompatDensity;

private static float sNoncompatScaledDensity;

private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {

final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

if (sNoncompatDensity == 0) {

sNoncompatDensity = appDisplayMetrics.density;

sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;

application.registerComponentCallbacks(new ComponentCallbacks() {

@Override

public void onConfigurationChanged(Configuration newConfig) {

if (newConfig != null && newConfig.fontScale > 0) {

sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;

}

}

@Override

public void onLowMemory() {

}

});

}

final float targetDensity = appDisplayMetrics.widthPixels / 360;

final float targetScaledDensity=targetDensity*(sNoncompatScaledDensity/sNoncompatDensity);

final int targetDensityDpi = (int) (160 * targetDensity);

appDisplayMetrics.density=targetDensity;

appDisplayMetrics.scaledDensity = targetScaledDensity;

appDisplayMetrics.densityDpi = targetDensityDpi;

final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();

activityDisplayMetrics.density = targetDensity;

activityDisplayMetrics.scaledDensity=targetScaledDensity;

activityDisplayMetrics.densityDpi = targetDensityDpi;

}

有两个问题需要特别说明一下:

①以上举例是以宽度进行density计算的,却不是一定要以宽度算,我们要根据页面的实际情况选取是按照宽度计算还是按照高度计算,通常一个不支持上下滑动的页面,内容又比较短,我们便要在高度上让其维持在一定比例显示,所以要按高度算,只是把上面方法,宽度改成高度就行了

②不仅仅能在application中进行全局修改,该方案也支持在每一个activity中修改,这样会更加灵活,哪怕每一个页面的设计尺寸都不一样,都能进行合理适配,最关键的是几乎没有工作量

小结:这套方案以设计图的宽,高(dp)为基准,计算出的density含义其实是,设计图中的1dp占屏幕宽/高中的多少个像素,那么在用density乘以我们layout中的dp值,再除以整个屏幕的宽/高,这个比例在不同分辨率的手机上绝壁是相等的,举个例子:

①宽1080px,设计图为360dp,控件宽:100dp

0.2777=1080/360*100/1080

②宽720px,设计图为360dp,空间宽:100dp

0.2777=720/360*100/720

这套方案,我个人是比较推荐的,不管是新项目,还是老项目,只要你想用,代码几乎不用任何修改,全是系统的api,侵入性非常之低,使用上又非常灵活,如果哪天在使用过程中觉得哪里有问题,更换其他更好的适配方案又非常安全,删掉方法就行了,不过我们修改density,修改的是本项目尺寸按照设计图来计算,一些三方库并不是按照我们的这个设计图尺寸设计的,这样难免会有问题,比如一些dialog,toast,popwindow等库显示不正确,那么我们便需要调整,我们可以调整当前activity不用,或者调整成跟三方库一致的尺寸,也有大佬针对这些问题专门做了调整,传送门在这:大佬的头条适配究极方案,使用起来更简单.

综上所述

安卓手机的碎片化在可以预见到的将来会越来越严重,这个问题完全没有办法解决,现在的流行的适配方案,可以根据自身的项目需求进行选取,当然,鱼和熊掌不能兼得,我们往往再得到了我们想要的同时,也会失去一些东西,比如我们适配是要保证每一台手机显示的一致性,那大屏跟小屏显示一致,那大屏还有什么意义?有人会说,大屏手机就是为了让用户在一屏内看到更多的信息而设计,这个很难说谁在本末倒置,只能仁者见仁智者见智,方案永远没有最好的,只有符合设计需求的适配才是最好的.

那如果不用上面说的方案是不是就没法适配了呢?

当然是不是,

1.一般情况下dp是可以满足的.

2.布局尽量不要写死 ,wrap_content ,match_parent,weight等等都是有意义的

3.看到设计图时,要想想如果这个出现在大屏手机上哪里会有问题,写的时候可以用代码进行比例换算(屏幕的宽或者高),通常情况下需要算高

4.可以考虑用constraintlayout进行布局编写

android屏幕适配的目的,剖析Android屏幕适配及各方案相关推荐

  1. android 图片闪光动画_剖析Android动画(图片闪烁、左右摇摆、上下晃动等效果) | 学步园...

    图片闪烁,类似这样. 2011-11-22 16:18 上传 左右摇摆: 2011-11-22 17:07 上传 一.续播  (不知道取什么名字好,就是先播放动画A, 接着播放动画B) 有两种方式. ...

  2. android 最新pdf下载,深入剖析Android系统 (杨长刚) 中文pdf扫描版[127MB]

    <深入剖析Android系统>以Android Jelly Bean(4.1)的代码为蓝本,对Android的部分关键代码进行了注释分析,并辅以大量插图,讲述了Android大部分子系统中 ...

  3. android屏幕适配的目的,Android 不同分辨率下屏幕适配的实战方案与经验总结

    Android 开发中,屏幕适配是一大考点,几乎每一场面试,都不会落下这个问题,这个问题说简单也简单,说难也难,当然对于有过真实的适配经验的人来说,这个根本不算什么问题,从坑里爬过的人,自然知道这其中 ...

  4. android 从服务端获取的图片怎么适配不同分屏幕的手机,移动端的适配|切图|标注...

    年前最后一个工作日,完成这篇干货- 这篇文章的目的是想从更深的原理层去找到一些设计规范的原因,比如为什么要选用@2x的750*1334做基础设计稿? 开发拿到设计基础稿是如何操作适配的? 基础稿是如何 ...

  5. 高级Android研发面试必问:Android屏幕适配全方位解析

    前言 前面我们已经将android的绘制基础已经讲完,那么现在我们下面的两个内容点是事件分发问题,和屏幕适配相关.这篇我们主要来进android但中的各种屏幕适配问题 1.屏幕适配概念 而随着支持An ...

  6. android学习笔记---49_屏幕适配,根据不同手机屏幕大小适配软件界面

    2013/5/12 49_屏幕适配 ----------------------- 1.根据手机屏幕的大小自动显示软件界面的大小 2.这里用480x320和320x240这两种屏幕大小举例. ---- ...

  7. android 平板的 dpi,Android平板上开发应用的一点心得——精确适配不同的dpi和屏幕尺寸...

    一.适配可行性 早在Android设计之初就考虑到了这一点,为了让app适应标准or山寨屏幕,google已经有一套成熟的解决方案.其中,有这么几个指标需要注意: (1)屏幕尺寸:单位inch,指的是 ...

  8. android图片适配(图片大小与屏幕密度)Bitmap占用内存计算

    目录 概述 dpi 计算公式 dp与px换算公式: android系统适配图片规律: 同名图片放在不同密度的文件夹下,系统选择图片规律 同一张图片,放在不同密度的Drawable文件夹下,ImageV ...

  9. 关于适配市面上的Android手机的刘海屏幕

    一大波 Android 刘海屏来袭,全网\Maybe/最全适配技巧! 一.序 Hi,大家好,我是承香墨影! Apple 一直在引领设计的潮流,自从 iPhone X 发布之后,"刘海屏&qu ...

最新文章

  1. ACMNO.46 A+B问题 问题描述 输入A、B,输出A+B。(别被数值范围所局限)
  2. 小蠢笔记:从继承特性来看构造函数
  3. patch是什么意思啊_学 Vue 看这个就够了 - 什么是 Vue.js
  4. kibana操作elasticsearch:新增数据(随机生成id)
  5. Eclipse 中隐藏的 5 个非常有用的功能
  6. 我来告诉你优秀的产品经理是如何管理需求的
  7. SVG格式的Icon,用了你就知道有多香
  8. java shareable_spring中@Resource和@Autowired理解详解_编程语言_IT虾米网
  9. c# datagridview 中DataSource的使用总结
  10. Machine Learning - Coursera week5 cost function and backpropagation 1
  11. [GZOI2017]取石子游戏
  12. 经典网络建设方案案例
  13. javascript 去掉html标签,js怎么去掉html标签
  14. python 检测键盘按键,随时停止程序
  15. 快速入门Kotlin
  16. 为 Kubernetes 提供 CI/CD 服务 Jenkins X
  17. JSON对象转java对象 JSON数组转LIST数组
  18. 终于等到你,最强 IDE Visual Studio 2017 正式版发布
  19. [某人的题解]徒步旅行(travel)
  20. 从0到1搭建电商营销数据分析平台(一)

热门文章

  1. 2023江西理工大学考研择校
  2. Android实现箭头无限循环上升的简单动画
  3. 深度学习模型压缩与优化加速(Model Compression and Acceleration Overview)
  4. 电脑数据丢失如何恢复
  5. Lattice ECP5UM5G 踩过的坑
  6. 基于SpringBoot 2.x开发的简易版图书管理系统(实现对图书的CRUD)
  7. fcpx如何用光流法_熟悉这些fcpx剪辑技巧 快速提高你的剪辑效率
  8. Linux项目实战C++轻量级Web服务器源码分析TinyWebServer
  9. 泉州计算机英语ppt,介绍泉州的英文.ppt
  10. Threes.js入门篇之4 - World View Projection