为什么要做适配?Android碎片化
Android系统碎片化:基于Google原生系统,小米定制的MIUI、魅族定制的flyme、华为定制的EMUI等等;
Android机型屏幕尺寸碎片化:5寸、5.5寸、6寸等等;
Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920等
https://screensiz.es/phone 上可以查看市面上流行的手机屏幕尺寸:
基本概念
像素(px)
- 含义:通常所说的像素,就是CCD/CMOS上光电感应元件的数量,一个感光元件经过感光,光电信号转换,A/D转换等步骤以后,在输出的照片上就形成一个点,我们如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。简而言之,像素就是手机屏幕的最小构成单元。
- 单位:px(pixel),1px = 1像素点
- 一般情况下UI设计师的设计图会以px作为统一的计量单位。
- 像素是绝对单位, 1px就代表固定的尺寸,全天下所有的1px都是一样大的
- 那么什么是相对单位?比如1%就是相对的
分辨率(横向像素点*纵向像素点)
- 含义:手机在横向、纵向上的像素点数总和
- 一般描述成: 宽*高 ,即横向像素点个数 * 纵向像素点个数(如1080 x 1920)。
- 单位:px(pixel),1px = 1像素点
- 就相同大小的屏幕而言,当屏幕分辨率低时(例如 640 x 480),在屏幕上显示的像素少,单个像素尺寸比较大。屏幕分辨率高时(例如 1600 x 1200),在屏幕上显示的像素多,单个像素尺寸比较小。
屏幕尺寸(in)
- 含义:手机对角线的物理尺寸
- 单位: 英寸(inch,缩写in),一英寸大约2.54cm
- 常见的屏幕尺寸有4.7寸、5寸、5.5寸、6寸
屏幕像素密度(dpi)
- 含义:每英寸的像素点数。
- 例如每英寸内有160个像素点,则其像素密度为160dpi。
- 单位:dpi(dots per inch)
- 计算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
- 标准屏幕像素密度(mdpi): 每英寸长度上还有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。
xlarge screens are at least 960dp x 720dp
large screens are at least 640dp x 480dp
normal screens are at least 470dp x 320dp
small screens are at least 426dp x 320dp
屏幕尺寸、分辨率、像素密度三者关系
注意:屏幕宽度(屏幕尺寸)和像素密度没有任何关联关系,屏幕大不一定就像素密度大,由各大手机厂商自己生产决定。
一部手机的分辨率是宽像素点个数x高像素点个数,屏幕大小是以寸为单位,那么三者的关系是:
举个例子,假设一部手机的分辨率是1080x1920(px),屏幕大小是5寸,问密度是多少?
密度无关像素(dp或dip)
- 含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关
- 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位。
- 场景例子:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
- dp与px的转换:控件显示像素(px) = 控件相对尺寸(dp) * 设备屏幕的像素密度(dpi)/基准像素密度(dpi),这里基准像素密度选择160dpi。
即:px = density * dp = (dpi / 160) * dp、density = dpi / 160 - 320* 480的屏幕,按照google标准dpi=160,则:1dp = 1px, 160dp =160px
如果是相同大小的屏幕,分辨率改为:640*960,则:160dp = 320px
对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放
dp(dip)与dpi的区别
dp是density-independent pixel,密度无关像素
dpi是dots per inch,屏幕像素密度
既然用了dp,那为什么还要适配?
比如美工在1280 * 1920分辨率的画布上标注一张图片的宽是30px,那么我们在程序中应该写多少dp的宽度?
按照google官方标准1280*1920分辨率是属于480dpi,density=3,所以30px=10dp,那么我们在程序中应该写10dp的宽度就可以了吗?是不可以的,因为市面上的手机太多了,同样的分辨率的手机屏幕尺寸却千差万别,这样写可能在5inch的手机屏幕上显示效果和UI图上的效果一样,但是在3inch或者6inch手机上的显示效果就会和UI图上的效果差别很大,所以要做适配(也就是说如果各个手机厂商都按照google官方标准,多大分辨率就对应相应规定多大的手机屏幕尺寸,那么我们就不用适配了,直接写换算后的dp值就可以了!)。
那既然用dp也要适配,为什么不直接用px?
因为dp是密度无关像素,320 * 480的屏幕上160dp=160px,如果屏幕大小不变,分辨率改为:640 * 960,则160dp=320px,我们只需要适配dp就可以了!
dp解决了同一数值在不同分辨率中展示相同尺寸大小的问题(即屏幕像素密度匹配问题),但却没有解决设备尺寸大小匹配的问题(即屏幕尺寸匹配问题),可以通过屏幕适配方案解决。
独立比例像素(sp)
- 含义:scale-independent pixel,叫sp或sip
- 单位:sp,字体大小专用单位
- Android开发时用此单位设置文字大小,可根据字体大小首选项进行缩放,在安卓手机系统设置调节字体大小的时候,文字跟随改变。
- 推荐使用12sp、14sp、18sp、22sp作为字体大小,不推荐使用奇数和小数,容易造成精度丢失,12sp以下字体太小
sp 与 dp 的区别
- dp只跟屏幕的像素密度有关;
- sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。
获取屏幕密度
获取屏幕的density值:
系统最终绘制视图控件时都是通过下面这个方法把dp转换成px进行绘制的:
开发者在xml中写视图控件的大小时使用的都是dp,而不用px,因为最后界面在展示的时候都会转化为px绘制在屏幕上,如果你将一个view的宽设为30px,在横向分辨率为300px的手机上占屏幕的1/10,在横向分辨率为480px的手机上这个view占1/16,而如果使用dp来做单位,dp会根据手机屏幕的density(density=dpi/160)自动做调整,转换为不同大小的px,同样大小的dp,大屏幕上是大px,小屏幕上是小px。
适配的几个方面
屏幕适配问题的本质是使得布局、布局组件在不同物理尺寸、不同分辨率的Android手机上具备相同的显示效果,下面我将分几个方面来谈谈如何去适配。
- 布局组件的适配
- 布局的适配
- 代码适配: 即接口适配,加载图片的时候,请求图片时附带图片宽高参数,后台返回对应宽高的图片,而不是用到很小的一张图片却请求了很大的一张图,这样会导致很高的流量使用量。
布局组件的适配
指适配布局组件的大小。
- 使用密度无关像素(dip、dp)指定尺寸
- 使用相对布局或线性布局,不要使用绝对布局
- 使用wrap_content、match_parent、权重
- 使用minWidth、minHeight、lines等属性
- dimens使用(根据限定符去找适合的values文件夹下的dimens.xml文件,比如values-sw320dp、values-sw600dp、values-sw720dp)
更多限定符参考:
https://developer.android.google.cn/guide/topics/resources/providing-resources.html
布局的适配
指去适合的布局文件夹下找合适的布局文件。
- 使用Size限定符
- 最小宽度限定符
- 使用屏幕方向限定符
- 使用布局别名
- 多套layout适配
图片的适配
- LOGO 图标
- 普通图片和图标
- 自动拉伸位图:Nine-Patch的图片类型
- 动画、自定义view、shape:一些背景可以用自定义view、shape
ImageView的ScaleType适配
-
android:scaleType=“center”
保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size时,多出来的部分被截掉 -
android:scaleType=“center_inside”
以原图正常显示为目的,如果原图大小大于ImageView的size,就按照比例缩小原图的宽高,居中显示在ImageView中。如果原图size小于ImageView的size,则不做处理居中显示图片 -
android:scaleType=“center_crop”
以原图填满ImageView为目的,如果原图size大于ImageView的size,则与center_inside一样,按比例缩小,居中显示在ImageView上。如果原图size小于ImageView的size,则按比例拉升原图的宽和高,填充ImageView居中显示 -
android:scaleType=“matrix”
不改变原图的大小,从ImageView的左上角开始绘制,超出部分做剪切处理 -
androd:scaleType=“fit_xy”
把图片按照指定的大小在ImageView中显示,拉伸显示图片,不保持原比例,填满ImageView. -
android:scaleType=“fit_start”
把原图按照比例放大缩小到ImageView的高度,显示在ImageView的start(前部/上部) -
android:sacleType=“fit_center”
把原图按照比例放大缩小到ImageView的高度,显示在ImageView的center(中部/居中显示) -
android:scaleType=“fit_end”
把原图按照比例放大缩小到ImageView的高度,显示在ImageVIew的end(后部/尾部/底部)
刘海屏
google官方刘海屏适配方案
https://developer.android.com/reference/android/view/DisplayCutout
各大厂商的方案
华为: https://devcenter-test.huawei.com/consumer/cn/devservice/doc/50114
OPPO: https://open.oppomobile.com/service/message/detail?id=61876
vivo: https://dev.vivo.com.cn/doc/document/info?id=103
折叠屏
华为: https://developer.huawei.com/consumer/cn/devservice/doc/90101
适配方案
AndroidAutoLayout
宽高限定符适配
smallestWidth限定符适配
今日头条屏幕适配方案
github地址
https://github.com/JessYanCoding/AndroidAutoSize
https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md
使用
1.添加Gradle配置
implementation 'me.jessyan:autosize:1.2.1'
2.添加AndroidManifest配置
<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
适配思路
当我们在屏幕上看到view的时候,系统会根据程序员设置的view的dp和手机屏幕的dpi计算出view的px,px是多少view就是多大,现在我们需要适配多种屏幕,最终目的就是使view和屏幕的比例都一样,屏幕的px是厂商固定好的,我们只有改变view计算后显示的px值来保证他们的比例都一样。
实现思路
现在就要考虑如何改变系统最后计算出的px了,px=density*dp,系统是根据这个公式去计算view显示的px的,如果在代码中动态的改变每个view的dp,我们可以实现适配,工作量太大,不现实,那么我们只有改变density的值了,
density解析:density在每个设备上都是固定的,density=dpi/160,意思就是1dp占多少像素,这个值是可以在代码中改变的,今日头条屏幕适配方案就是改变系统的density值。
最终方案
那么我们该考虑如何改变density的值,让他最后计算出来的px是我们想要的px呢,如果我们的设计图宽度是360dp,屏幕宽的px是720px,现在我们在设计图上有一个宽180dp的view(即占屏幕的一半),适配后实际显示的px应该应该是360px,density=px/dp=720px/360dp=2;这个2就是我们在这个宽的px=720的设备上计算出来的density,通过屏幕宽的px/设计图宽的dp来动态的改变每个手机设备的density,从而改变系统最后计算出来各个视图控件的px。
原理解释2
今日头条屏幕适配方案的核心原理在于,根据以下公式算出 density:
当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density
density 的意思就是 1 dp 占当前设备多少像素
然后把计算出来的density设置为系统的density。
为什么要算出 density,这和屏幕适配有什么关系呢?
//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;
}
大家都知道,不管你在布局文件中填写的是什么单位,最后都会被转化为 px,系统就是通过上面的方法,将你在项目中任何地方填写的单位都转换为 px 的。
所以我们常用的 px 转 dp 的公式 dp = px / density,就是根据上面的方法得来的,density 在公式的运算中扮演着至关重要的一步。
要看懂下面的内容,还得明白,今日头条的适配方式,今日头条适配方案默认项目中只能以高或宽中的一个作为基准,进行适配,为什么不像 AndroidAutoLayout 一样,高以高为基准,宽以宽为基准,同时进行适配呢?
因为大部分市面上的 Android 设备的屏幕高宽比都不一致,特别是现在大量全面屏的问世,这个问题更加严重,不同厂商推出的全面屏手机的屏幕高宽比都可能不一致。
这时我们只以高或宽其中的一个作为基准进行适配,就会有效的避免布局在高宽比不一致的屏幕上出现变形的问题。
明白这个后,我再来说说 density,density 在每个设备上都是固定的,dpi / 160 = density,屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度。
假设有两个设备:
- 设备 1,屏幕宽度为 1080px,480DPI,屏幕总 dp 宽度为: 1080 / (480 / 160) = 360dp
- 设备 2,屏幕宽度为 1440px,560DPI,屏幕总 dp 宽度为: 1440 / (560 / 160) = 411dp
可以看到屏幕的总 dp 宽度在不同的设备上是会变化的,但是我们在布局中填写的 dp 值却是固定不变的。
这会导致什么呢?假设我们布局中有一个 View 的宽度为 100dp,在设备 1 中 该 View 的宽度占整个屏幕宽度的 27.8% (100 / 360 = 0.278),但在设备 2 中该 View 的宽度就只能占整个屏幕宽度的 24.3% (100 / 411 = 0.243),可以看到这个 View 在像素越高的屏幕上,dp 值虽然没变,但是与屏幕的实际比例却发生了较大的变化,所以肉眼的观看效果,会越来越小,这就导致了传统的填写 dp 的屏幕适配方式产生了较大的误差。
这时我们要想完美适配,那就必须保证这个 View 在任何分辨率的屏幕上,与屏幕的比例都是相同的。
这时我们该怎么做呢?改变每个 View 的 dp 值?不现实,在每个设备上都要通过代码动态计算 View 的 dp 值,工作量太大
如果每个 View 的 dp 值是固定不变的,那我们只要保证每个设备的屏幕总 dp 宽度不变,就能保证每个 View 在所有分辨率的屏幕上与屏幕的比例都保持不变,从而完成等比例适配,并且这个屏幕总 dp 宽度如果还能保证和设计图的宽度一致的话,那我们在布局时就可以直接按照设计图上的尺寸填写 dp 值。
屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度
在这个公式中我们要保证 屏幕的总 dp 宽度 和 设计图总宽度 一致,并且在所有分辨率的屏幕上都保持不变,我们需要怎么做呢?屏幕的总 px 宽度 每个设备都不一致,这个值是肯定会变化的,这时今日头条的公式就派上用场了
当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density
这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是一样的,只要 density 根据不同的设备进行实时计算并作出改变,就能保证 设计图总宽度 不变,也就完成了适配。
动态权限适配
- 权限分类
Normal Permissions
Dangerous Permission - 查看命令
adb shell pm list permissions -d -g - 注意不能在进入app时就把app用到的所有权限一次性全部申请下来,因为用户在使用app过程中可能关闭动态权限,这样到具体用到权限的页面时还是要写一遍权限申请流程。
参考 :
https://developer.android.google.cn/training/multiscreen/screendensities#java
http://developer.android.com/guide/practices/screens_support.html
https://developer.android.google.cn/guide/practices/screens_support.html
Android 屏幕适配:最全面的解决方案
安卓屏幕完美适配方案——独家秘笈
Android 屏幕适配方案
今日头条适配方案官方文档:一种极低成本的Android屏幕适配方式
骚年你的屏幕适配方式该升级了!-今日头条适配方案
骚年你的屏幕适配方式该升级了!-SmallestWidth 限定符适配方案
今日头条屏幕适配方案终极版正式发布!