前言
屏幕适配是一款APP的基本素养,需要保证在不同型号的手机上都能正常显示,并且各部件的显示比例和设计图一致。本文我们来介绍一下dp、dpi、density这些名词之间的关系,以及大神 JessYan 的屏幕适配算法AndroidAutoSize的基本用法。
Github导航:AndroidAutoSize官方代码
一、屏幕适配的内在逻辑
1.dp、px、dpi和density
- px:像素;
- dpi:dots per inch,直接来说就是一英寸多少个像素点。常见取值 120(低),160(中),240(高),320(超高),480(超超高)。一般称作像素密度,不同设备的 dpi 往往不同;
- dp:也就是 dip(Density independent pixels),是一种长度单位,与设备的像素密度值无关,因此也称为设备无关像素。dp 和 px 之间存在换算关系:1 dp = 1*(dpi/160)px。
我们可以举个例子具体说明 dpi 是什么:
假设有一部手机,屏幕的物理尺寸为1.5英寸x2英寸,屏幕分辨率为240px * 320px,则我们可以计算出在这部手机的屏幕上:
每英寸包含的像素点的数量为240/1.5=160dpi(横向)或320/2=160dpi(纵向),这里160dpi就是像素密度。
dpi 和 像素密度ppi 都是一种密度值,二者也经常被混着用,但是我查资料发现 dpi 是指点密度,用于打印机; ppi 指像素密度,用于显示器。由于像素是一个个小方格,因此一个像素中可能包含多个点。不过在开发过程中,这俩的含义应该被混淆了,直接把 dpi 当成像素密度就行。
dp 这个单位有什么用呢?
不同的机型,dpi 是不一样的。假如某个组件的宽度以像素 px 为单位,大小为100,那么在 dpi=160 的设备上,它实际显示宽度是 100/160=0.625 英寸;但是在 dpi=320 的设备上,它是实际显示宽度就成了 100/320=0.3125 英寸。这时组件的观感以及屏幕布局会发生很大变化。
因此使用 px 作为单位就非常不灵活,并且泛化性差。dp 则较好地解决了这个问题。dp 不受设备像素密度值 dpi 的影响,它是一个固定的长度,即 1/160=0.00625英寸。
以 dp 为单位可以使组件的实际显示宽度在不同设备上保持为固定值。开发时以160 dpi 为基准(称为标准 dpi),对于 160dpi 的设备,1dp=1px,对于320dpi 的设备,1dp=(320/160)px=2px。其他同理。
Density
Density 直译是密度,但它其实是一种密度比,计算方法是 :
Desity=当前设备的dpi/160dpi
即当前设备的像素密度与标准像素密度的比值。每个设备都有自己的 Density。
2.获取手机屏幕尺寸/dp
如果我们是在一部实验机上开发app,没有设计师给我们设计图纸,部件大小都根据它在实验机上的显示来调整(就如同我的开发过程一样…)这样开发出来的app自然是和实验机的屏幕匹配的,但在其他机型上大概率会显示失常。
这时就需要适配算法的帮助,但在使用适配算法之前,我们需要知道这台实验机显示的长度和宽度是多少,单位是dp(因为我们的组件基本是以 dp 为单位的,为了屏幕适配我们需要知道设计图的dp值,并根据不同屏幕调整组件的dp值,以保证显示比例一致)
有了这两个值,就相当于获得了设计图纸的尺寸,而这个尺寸是适配算法必要的输入。
那么如何获得手机屏幕的显示尺寸呢?
- 可以推导出一个计算公式:
手机宽度dp值 = 手机实际宽度像素px / 手机屏幕密度比density
只要把手机和电脑连接,在Adroid Studio中使用 DisplayMetrics 即可:
DisplayMetrics dm = getResources().getDisplayMetrics();
//手机宽度dp值 = 手机实际宽度像素px / 手机屏幕密度比
float widthDP = dm.widthPixels / dm.density;
// dm.heightPixels 表示手机实际高度像素px
float heightDP = dm.heightPixels / dm.density;
Log.d("MainActivity", "手机屏幕宽度/dp是: "+widthDP);
Log.d("MainActivity", "手机实际宽度像素dp是: "+dm.widthPixels);
Log.d("MainActivity", "手机屏幕密度比是: "+dm.density);
Log.d("MainActivity", "手机屏幕显示dpi是: "+dm.densityDpi);
widthDP 就是我们获得的手机宽度/dp,手机高度可以用同样方式获得。
需要使用 dm.densityDpi 可以获得手机屏幕实际显示的像素密度,不要使用手机官网给出的像素密度 ppi 值,这两个值大概率不一样。
二、AndroidAutoSize使用
1.基本用法
AndroidAutoSize在布局中 dp、sp、pt、in、mm 所有的单位都能支持,唯独不支持 px,可以和之前的一版适配算法AndroidAutoLayout搭配使用。在代码的README中作者详细介绍了如何使用AndroidAutoSize完成适配,并把dp、sp称为主单位,pt、in、mm称为副单位。本文中使用dp进行布局。
AndroidAutoSize的用法可以分为两步:
- 在 build.gradle 中引入
implementation 'me.jessyan:autosize:1.2.1'
- 在 AndroidManifest.xml 中设定设计图纸中使用的屏幕大小(这里的单位是dp),如果是自己拿着实验机开发的话,可以参考第一节中“获取手机屏幕尺寸”的部分来获得伪设计图尺寸,填入程序即可。
下面代码中value后面填入尺寸,width对应宽度,height对应高度。
<application>
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="780"/>
</application>
2.简单自定义方法
由于不同机型的屏幕长宽比不同,因此为了保证组件的形状不发生变化,AndroidAutoSize中只进行宽度适配或高度适配,不能宽度与高度同时适配。
在默认设置下,AndroidAutoSize会进行宽度适配,也就是说之前<meta-data android:name="design_height_in_dp" android:value="780"/>
这个值的填写是不起作用的。
通常情况下,宽度适配的视觉效果较好,此时高度没有适配。那么可能出现在某些屏幕上组件显示不全,我们可以采用 ScrollView 使内容可以上下滑动显示, 这样就相当于完成了高度适配。
当然,如果我们想要高度适配,也是可以利用AndroidAutoSize设置的。方法如下:
假如我们希望对活动CustomAdaptActivity进行自定义设置,就让其实现CustomAdapt接口,并改写 isBaseOnWidth() 与 getSizeInDp() 方法。
public class CustomAdaptActivity extends AppCompatActivity implements CustomAdapt {
@Override
public boolean isBaseOnWidth() {
return false;
}
@Override
public float getSizeInDp() {
return 667;
}
}
其中 isBaseOnWidth() 中 return true 时表示宽度适配,return false 时表示高度适配。getSizeInDp() 用来改变当前 activity 参考的设计图尺寸,可以和全局设计图尺寸不同。若 return 0 则表示和全局保持一致,若return 的是一个非零值,就把当前 Activity 的设计图尺寸改成这个值。
需要注意的是,如果在 isBaseOnWidth() 中选择了宽度适配,那么 getSizeInDp() 的返回值就代表当前 Activity 的设计图宽度,若在 isBaseOnWidth() 中选择了高度适配,则 getSizeInDp() 的返回值代表当前 Activity 的设计图高度。
如果在某一个 Activity 中不想要屏幕适配,可以在该 Activity 中实现 CancelAdapt 接口:
public class CancelAdaptActivity extends AppCompatActivity implements CancelAdapt {
}
关于屏幕宽度适配还有另外一种方法,不过不如 AndroidAutoSize 简单好用,适用性不够强,大家可以看一看:
Android dp方式的屏幕适配
总结
我之前在屏幕适配这里然了好久,没有搞清楚屏幕像素密度 ppi 和开发时用到的 dpi 是两个不同的东西,虽然定义一样,但是不能用手机官方公布的 ppi 或者自己用手机参数计算出来的 ppi 直接当成 dpi 使用。
要想获得一个手机的 dpi, 还是需要把手机连接到电脑,用第一节中“获取手机屏幕尺寸”介绍的方法来获取手机显示的 dpi。
还有个提醒,如果发现在手机上的实验效果和自己预想的不一样,可以看一下是不是手机设置的字体问题 (⊙﹏⊙)
文中若有不正确的地方欢迎大家指出~