天天看點

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 螢幕适配之等比縮放适配

繼續閱讀