天天看点

Android 屏幕适配之等比缩放适配

新发现 anydpi

今天在创建drawable的时候无意中发现的anydpi选项,测试发现在values上也可以用

这样我们就不再关心屏幕密度了,只需要关心屏幕像素宽度。

我们的适配文件夹可以继续优化成如下结构:

values-anydpi-481x480

values-anydpi-721x720

values-anydpi-769x768

values-anydpi-801x800

values-anydpi-1081x1080

values-anydpi-1201x1200

values-anydpi-1441x1440

values-anydpi-2161x2160

补充1:

1,由于很多机器的虚拟导航栏的存在,在手机获取的DisplayMetrics中,DisplayMetrics.heightPixels可能小于实际的手机尺寸。

如1920x1080的手机,如果底部有导航栏,其DisplayMetrics.heightPixels可能就是1790,如果我们仍然按照1920*1080适配,可能就找不到对应的文件夹,从而进入了默认的xxhdpi中,这个情况还好,因为xxhdpi是1080的,但是如果提供了xxhdpi-1280x720的,那1790x1080必进xxhdpi-1280x720,这样就造成了错误适配。

2,xxhdpi-1790x1080的屏幕,可能进入xxhdpi-100x1600中吗?

测试发现,其可以进入。有人说,那是不是违反了上面的7条呢?是的,原因呢?

我们测试的时候都是高度大于宽度的屏幕,测试给到的文件夹也都是高度大于宽度的。

当我们提供了高度小于宽度的文件夹,发现它还可以进入xxhdpi-1000x1600,xxhdpi-1080x1600,但是无法进入1200x1600,由此可以猜想,当给定的屏幕像素

height>width时,系统会调换高宽,再次计算合法性,如果合法,进入最接近的合法文件夹。

到此我们的适配方案需要改了

1,宽度的定义指的是,高度大于等于宽度,即高宽的最小值,如宽度1080的屏幕,我们指的是高度大于或等于1080,宽度等于1080。

2,在没有land限定符的时候,系统会在匹配失败的情况下,调换高宽,再做一次适配,直到找到最接近的合法文件夹。

最后本方案不是最完美的方案,和swheightdp限定符一样,也有其局限性。

1,没有匹配的宽度,将进入接近的文件夹,如800的没有提供适配,将进入768文件夹

2,同一宽度的需要提供多密度的适配,如1080的如果指提供了xxhdpi,当密度很大的,xxhdpi1080时,发现没有,将进入xxhdpi (这时为1440屏提供的),导致偏大的错误,如果时xhdpi1080的,又会进入xhdpi-1280x720中,(假如720和1080之间没有其他的宽度如768等都没提供),导致偏小的错误

最后补充一句:

屏幕的适配还是更加各公司的UI规范来,如果等比适配,可以考虑本方案

这里给一些特定说明

如768宽的屏幕,建议不需要做等比例,按照720屏来设计,保留一些差异。

Android 屏幕适配的规则

我们常用的适配的dimens是这3种模式

values-dpi
values-HEIGHTxWIDTH
values-dip-HEIGHTxWIDTH
           

相信很多朋友在等比例适配的时候遇到过这样的烦恼,明明是xxhdpi的,为什么进入了xxxhdpi里面。其实那是因为你不知道Android适配的规则。

以下是个人测试后总结的规律。

屏幕比例适配是利用手机的DisplayMetrics参数的density,DisplayMetrics.heightPixels,DisplayMetrics.widthPixels做匹配。

1,当找到了正确的dpi,
	1.1但没有仅dpi的文件夹时,即仅有dpi+像素,
		1.1.1但是没有合法的像素时,优先最接近的dpi,如果有合法像素,选择最接近的合法像素,如果没有,找第二接近的dpi文件夹,以此类推
	1.2有仅dpi的文件夹,
		1.2.1当有dpi+像素的
			1.2.1.1当有对应的合法像素,但没找到完全匹配像素,则进入最接近匹配像素的dpi-文件夹
			1.2.1.2当没有合法的像素,进入正确的仅dpi文件夹 
2,当没有正确的dpi,也没有正确的dpi+像素,找最接近的dpi
3,最接近的dpi优先选择更大的dpi
4,当都没有dpi,优先选择合法像素
5,当即没有合法dpi,也没有合法dpi+像素,也没有合法像素,程序报错
6,合法像素指的是,给定的像素<=实际像素,即给定的宽<=实际的宽,给定的高<=实际的高。
7,完全匹配的像素指给定的像素=实际像素,即给定的宽==实际的宽,给定的高==实际的高。
           

以下是关于第5,6,7条的逻辑整理

static final class DpiHeightWidth {
    int dpi;
    int height;
    int width;

    public DpiHeightWidth() {
    }

    public DpiHeightWidth(int dpi, int height, int width) {
        this.dpi = dpi;
        this.height = height;
        this.width = width;
    }
}

/**
 * 获取合法的size,这是相同dpi情况下的比较
 *
 * @param real
 * @param mode1
 * @param mode2
 * @return
 */
private DpiHeightWidth getLegalDpiHeightWidth(DpiHeightWidth real, DpiHeightWidth mode1, DpiHeightWidth mode2) {
    boolean x1 = real.width >= mode1.width;
    boolean x2 = real.width >= mode2.width;
    boolean y1 = real.height >= mode1.height;
    boolean y2 = real.height >= mode2.height;

    if (x1 && y1) {
        //mode1 合法
        if (x2 && y2) {
            //mode2合法,需要 mode1 和mode2 比较,谁的高大,谁合适
            return getNearestHeightWidth(real, mode1, mode2);
        } else {
            //mode2不合法
            return mode1;
        }
    } else {
        //mode1 不合法,仅考虑mode2
        if (x2 && y2) {
            //mode2 合法
            return mode2;
        } else {
            //mode2 不合法,
            Log.e("SSHSSH", "没有合法的height 和 width");
            return null;
        }
    }
}


/**
 * 比较2个尺寸那个符合要求,这个前提就是mode1和mode2都合法
 *
 * @param real
 * @param mode1
 * @param mode2
 * @return
 */
private DpiHeightWidth getNearestHeightWidth(DpiHeightWidth real, DpiHeightWidth mode1, DpiHeightWidth mode2) {
    if (mode1.height >= mode2.height) {
        if (mode1.height == real.height) {//mode1高匹配
            if (mode2.height == real.height) {//mode1,mode2高都匹配,选择宽更大的
                return mode1.width >= mode2.width ? mode1 : mode2;
            } else {//mode1匹配,mode2高不匹配,优先选择高匹配的
                return mode1;
            }
        } else {//mode1,mode2高都不匹配,此时需要选择宽匹配的,如果都不匹配,选择高最大的
            if (mode1.width == real.width) {
                return mode1;
            } else if (mode2.width == real.width) {
                return mode2;
            } else {
                return mode1;
            }
        }
    } else {
        //mode1.height < mode2.height 了,所以 mode1 高度不匹配
        if (mode2.height == real.height) {
            //mode2高度匹配
            return mode2;
        } else {
            //mode1 和mode2 高度都不匹配
            if (mode1.width == real.width) {
                return mode1;
            } else if (mode2.width == real.width) {
                return mode2;
            } else {
                return mode2;
            }
        }
    }
}
           

以下是完美的适配方案

Android 屏幕适配之等比缩放适配

继续阅读