本文主要讨论以下几个问题
- 为什么要适配
- 适配方案
为什么要适配
由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,于是导致运行 Android 的设备多种多样,它们有着不同的屏幕尺寸和像素密度。尽管系统可通过基本的缩放和调整大小功能使界面适应不同屏幕,但应做出进一步优化,以确保界面能够在各类屏幕上美观地呈现
几个重要概念
1. 屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等
2. 屏幕分辨率
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素x横向像素,如1960x1080。表示宽度方向上有1080个像素点,在高度方向上有1920个像素点
单位:px(pixel),1px=1像素点
Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920
UI设计师的设计图会以px作为统一的计量单位
3. 像素密度
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:
密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi) |
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
4. 屏幕尺寸、分辨率、像素密度三者关系
5. 密度无关像素
density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。可以保证在不同屏幕像素 密度的设备上显示相同的效果
Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分 辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用 dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
6. dp与px的转换
px = dp * (dpi / 160)
密度类型 | 代表的分辨率(px) | 屏幕密度(dpi) | 换算(px/dp) | 比例 |
低密度(ldpi) | 240x320 | 120 | 1dp=0.75px | 3 |
中密度(mdpi) | 320x480 | 160 | 1dp=1px | 4 |
高密度(hdpi) | 480x800 | 240 | 1dp=1.5px | 6 |
超高密度(xhdpi) | 720x1280 | 320 | 1dp=2px | 8 |
超超高密度(xxhdpi) | 1080x1920 | 480 | 1dp=3px | 12 |
读者可以算下横向dp,前3个是320dp,后面两个是360dp
然而各大手机厂商的分辨率不只是上面的5种,可以说是五花八门,算出来的可能大、可能小,而设计师还是按360dp设计的,这就是我们做适配的原因
下面是自己对适配的理解
1. 我们日常开发为什么用dp、sp、wrap_content、match_parent、weight
看上面表格可以得知手机的机型很多,不同机型的分辨率不同,这也就导致了如果我们用像素(px)做单位只能适用一款机型,开发不可能为每种机型专门写个布局,工作量太大!那有什么办法可以解决这个问题
谷歌爸爸给出了dp这个东西,不论你是720x1280还是1080x1920横向都是360dp,以dp做单位理论上可以完美解决适配问题,不论在哪个手机上都是按比例来的!
计算:px = dp * (dpi / 160) —> dp = px/(dpi/160)
720x1280:dp = 720/(320/160) = 360dp
1080x1920:dp = 1080/(480/160) = 360dp
2. 既然dp已经解决了适配问题,我们为啥还要做适配
谷歌给出的标准是好的,但国内各大厂商心态是放飞的。比如1080x1920的分辨率,但尺寸不一样,尺寸不一样像素密度(dpi)就不一样,dpi变化了算出来的dp也就五花八门了,这是我们做适配的原因!看下面这个例子,算出来的一个是360dp,一个是480dp,布局都是距离左侧20dp,这个时候硬编码就显的俩手机UI不一样!
适配方案
1. 今日头条适配方案
px值 = dp值 * metrics.density ,这里的 density 是指的手机的屏幕密度,由系统提供,不同的手 机的 density 可能不同;所以我们不能直接使用系统的 density ,需要篡改 density 来达到适配的目的
// 系统的Density
private static float sNoncompatDensity;
// 系统的ScaledDensity
private static float sNoncompatScaleDensity;
private static void setCustomDensity(Activity activity, final Application application){
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if(sNoncompatDensity == 0){
// 系统的Density
sNoncompatDensity = appDisplayMetrics.density;
// 系统的ScaledDensity
sNoncompatScaleDensity = appDisplayMetrics.scaledDensity;
// 监听在系统设置中切换字体
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
if(newConfig != null && newConfig.fontScale > 0){
sNoncompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
// 此处以360dp的设计图作为例子
final float targetDensity = appDisplayMetrics.widthPixels / 360;
final float targetScaledDensity = targetDensity * (sNoncompatScaleDensity/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;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 此处调用---一般都是在BaseActivity中
setCustomDensity(this,this.getApplication());
setContentView(R.layout.activity_main);
}
篡改density后只需要一套图,成本最低
为啥是360dp
在众多的主流尺寸手机中,如1920x1080、1280x720等,测试发现,他们的手机宽度dp值是360dp,覆盖范围达到90%。设计师提供的标注图一般也是720px或者1080px的,我们拿来转换为对应的dp值时也是默认基于360的基准值。
2. 百分比适配
- 以某一分辨率为基准,生成所有分辨率对应像素数列表
- 将生成像素数列表存放在res目录下对应的values文件下
- 根据UI设计师给出设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可
这个方案的劣势是会生成一堆文件
3. 使用约束布局ConstraintLayout
ConstraintLayout早在2016年就已问世,因属性颇多用的人不多,但这个约束布局用完后会很爽。层级少,辅助线约束,百分比设置,都是优点;缺点是入门难度稍大
图片适配问题
上面问题理解了,这个就很容易说通了。
根据手机的dpi加载,比如我的手机是1080*1920,为400dpi。android系统会自动优先在drawable-xxhdpi文件夹中找对应的图片,如果找到了就加载,此时图片在手机上显示的就是它本身的大小,也就是270*480像素;如果未找到,就去更高分辨率的文件夹xxxhdpi中找,一直找到最高也没有的话,就会查找drawable-nodpi文件夹,还是没有就开始依次查询低分辨率的文件夹,由高到低一直查到ldpi
dpi范围 | 密度 |
0dpi ~ 120dpi | ldpi |
120dpi ~ 160dpi | mdpi |
160dpi ~ 240dpi | hdpi |
240dpi ~ 320dpi | xhdpi |
320dpi ~ 480dpi | xxhdpi |
480dpi ~ 640dpi | xxxhdpi |
系统在加载图片时,首先去对应的文件夹查找,没找到,又依次按照顺序一直在drawable-mdpi找到,但是系统会认为你这张图是专门为低密度的设备所设计的,如果直接将这张图在当前的高密度设备上使用就有可能会出现像素过低的情况,于是系统自动帮我们做了这样一个放大操作
缩放倍数:以mdpi为基准,drawable-mdpi:drawable-hdpi:drawable-xhdpi:drawable-xxhdpi: drawable-xxxhdpi = 1:1.5:2:3:4 即dpi范围的最大值之比。