深色模式個人采坑記錄

這一期接了一個“適配深色模式”的需求,在做之前過了一遍自己負責的幾個界面,發(fā)現(xiàn)系統(tǒng)的反色基本上把所有的工作都做完了。少了一項需求,心里美滋滋。提測以后測試拿著一堆切了深色模式以后還是白花花的界面來找到我,我一臉不可置信的看著測試。得,系統(tǒng)的反色突然失效了。沒辦法,只好拋開系統(tǒng)自己來做一些針對深色模式的適配。

對于深色模式的適配主要有以下兩方面的工作需要進行

  1. 對于Res的圖片資源進行適配
  2. 對于界面顏色等進行適配

針對圖片等資源文件的深色模式的適配,沒有什么好辦法,只能通過新建drawable-night文件夾來進行適配
針對界面顏色的,正常情況下,我們可以通過以下幾種方式對界面的顏色進行深色模式的適配

  • 依賴系統(tǒng)的反色適配
    這是三種方法里改動最小的方法,系統(tǒng)UI會判斷當前模式是否為深色模式,從而修改View繪制時使用的Paint顏色,以達到反色的目的。但是在廠商定制的系統(tǒng)當中,可能各家對于反色都有著不同的算法,所以最終的結(jié)果是可能會導致相同的顏色在不同廠家的深色模式當中會展現(xiàn)為不同的顏色
  • 設置-night
    和Drawable文件一樣,color資源也可以使用-night來對深色模式進行適配。如果是一個新的項目或者是color資源不多的情況下,使用這種方式顯然會更好一些。但是如果是color資源文件相當多的情況下,這種方式想想就頭大= =
  • 設置Style
    和上面設置color-night類似,甚至還比上面的方式更繁瑣一些- -

在對深色模式的適配過程中,我原本打算只依賴系統(tǒng)的反色來進行適配的。但是在測試過程當中發(fā)現(xiàn),不同的系統(tǒng)版本的反色可能對相同的界面產(chǎn)生不同的效果。在萬般無奈之下,只好引入了一個DarkmodeUtil來對整個頁面的深色模式進行輔助適配,以求完美的效果.

如何得到一個可以在深色模式當中令人舒服的顏色

這是我在做這個工具遇到的第一個問題,在我們?nèi)粘I町斨?,我們通常會使用RGB或者是CMYK的方式來描述一個顏色的具體的數(shù)值。但是根據(jù)我對深色模式下通過系統(tǒng)的反色和原色對比發(fā)現(xiàn)。系統(tǒng)并不是通過RGB方式來計算深色模式到底應該使用哪個顏色的。經(jīng)過查找資料發(fā)現(xiàn),系統(tǒng)使用了一種叫Lab的顏色計量方式來描述一種顏色

什么是Lab顏色
可以參考百度百科對Lab顏色模式的描述
https://baike.baidu.com/item/%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%9E%8B/7558583?fromtitle=Lab&fromid=1514615#2_9

找到了合適的顏色計算方式,接下來就是如何把RGB轉(zhuǎn)化為Lab了,在這里我參考了下面這個博主的一個方法。

https://blog.csdn.net/spring123tt/article/details/7929993

根據(jù)這個博主所言,RGB與Lab不能直接進行轉(zhuǎn)化,所以這個博主引入了XYZ中間量來輔助轉(zhuǎn)化。但是這個博主的方法用在此處仍然有一點點小問題,在我接下來給出方法中將這些問題給修復了

    private double[] sRGB2XYZ(double[] sRGB) {
        double[] xyz = new double[3];

        double sR = sRGB[0] / 255;
        double sG = sRGB[1] / 255;
        double sB = sRGB[2] / 255;

        if (sR <= 0.04045) {
            sR = sR / 12.92;
        } else {
            sR = Math.pow(((sR + 0.055) / 1.055), 2.4);
        }

        if (sG <= 0.04045) {
            sG = sG / 12.92;
        } else {
            sG = Math.pow(((sG + 0.055) / 1.055), 2.4);
        }

        if (sB <= 0.04045) {
            sB = sB / 12.92;
        } else {
            sB = Math.pow(((sB + 0.055) / 1.055), 2.4);
        }

        xyz[0] = 41.24 * sR + 35.76 * sG + 18.05 * sB;
        xyz[1] = 21.26 * sR + 71.52 * sG + 7.2 * sB;
        xyz[2] = 1.93 * sR + 11.92 * sG + 95.05 * sB;

        return xyz;
    }

XYZ轉(zhuǎn)Lab

    private double[] XYZ2Lab(@Size(3) double[] xyz) {
        double[] Lab = new double[3];

        double x = xyz[0], y = xyz[1], z = xyz[2];
        double Xn = 95.04, Yn = 100, Zn = 108.89;

        double XXn, YYn, ZZn;
        XXn = x / Xn;
        YYn = y / Yn;
        ZZn = z / Zn;

        double fx, fy, fz;

        if (XXn > 0.008856) {
            fx = Math.pow(XXn, 0.333333);
        } else {
            fx = 7.787 * XXn + 0.137931;
        }

        if (YYn > 0.008856) {
            fy = Math.pow(YYn, 0.333333);
        } else {
            fy = 7.787 * YYn + 0.137931;
        }

        if (ZZn > 0.008856) {
            fz = Math.pow(ZZn, 0.333333);
        } else {
            fz = 7.787 * ZZn + 0.137931;
        }
        /**
         * 在這里可能會出現(xiàn)Lab[0]超過0-100這個范圍的情況
         * 所以需要這里進行一個限制
         */
        Lab[0] = Math.min(Math.max(116 * fy - 16, 0), 100);
        Lab[1] = Math.min(Math.max((500 * (fx - fy)), -128), 127);
        Lab[2] = Math.min(Math.max((200 * (fy - fz)), -128), 127);

        return Lab;
    }

這樣通過這兩步的轉(zhuǎn)化,就可以把一個RGB顏色轉(zhuǎn)換為Lab進行表示。
在Lab顏色模式當中,L代表當前顏色的亮度。我們可以針對性的對顏色進行設置
譬如#00FF00這個綠色,其L的數(shù)值為87,經(jīng)過變換后,其L的數(shù)值為13,效果如下


上面一行為原色,下面一行為變化后的顏色

可以看到,在經(jīng)過對L的變化后,綠色就顯得不是這么的艷麗,整個效果也更加的符合深色模式的基調(diào)
對顏色進行換算的代碼如下

    private int getDarkColor(int colorNum, boolean isNeedLight) {
        int a = alpha(colorNum);
        int r = red(colorNum);
        int g = green(colorNum);
        int b = blue(colorNum);

        double[] lab = XYZ2Lab(sRGB2XYZ(new double[]{r, g, b}));
        double l = changeL(lab[0]);
        if (isNeedLight) {
            lab[0] = Math.max(l, lab[0]);
        } else {
            lab[0] = Math.min(l, lab[0]);
        }
        int[] rgb = XYZ2sRGB(Lab2XYZ(lab));

        return Color.argb(a, rgb[0], rgb[1], rgb[2]);
    }

在對顏色進行亮度變化完成后需要再將顏色從Lab模式變成RGB模式

    private double[] Lab2XYZ(@Size(3) double[] Lab) {
        double[] xyz = new double[3];
        double l = Lab[0], a = Lab[1], b = Lab[2];
        double Xn = 95.04, Yn = 100, Zn = 108.89;

        double fx, fy, fz;

        fy = (l + 16) / 116;
        fx = a / 500 + fy;
        fz = fy - b / 200;

        if (fx > 0.2069) {
            xyz[0] = Xn * Math.pow(fx, 3);
        } else {
            xyz[0] = Xn * (fx - 0.1379) * 0.1284;
        }

        if ((fy > 0.2069) || (l > 8)) {
            xyz[1] = Yn * Math.pow(fy, 3);
        } else {
            xyz[1] = Yn * (fy - 0.1379) * 0.1284;
        }

        if (fz > 0.2069) {
            xyz[2] = Zn * Math.pow(fz, 3);
        } else {
            xyz[2] = Zn * (fz - 0.1379) * 0.1284;
        }

        return xyz;
    }
    private int[] XYZ2sRGB(@Size(3) double[] xyz) {
        int[] sRGB = new int[3];
        double x = xyz[0], y = xyz[1], z = xyz[2];

        double dr = 0.032406 * x - 0.015371 * y - 0.0049895 * z;
        double dg = -0.0096891 * x + 0.018757 * y + 0.00041914 * z;
        double db = 0.00055708 * x - 0.0020401 * y + 0.01057 * z;

        if (dr <= 0.00313) {
            dr = dr * 12.92;
        } else {
            dr = Math.exp(Math.log(dr) / 2.4) * 1.055 - 0.055;
        }

        if (dg <= 0.00313) {
            dg = dg * 12.92;
        } else {
            dg = Math.exp(Math.log(dg) / 2.4) * 1.055 - 0.055;
        }

        if (db <= 0.00313) {
            db = db * 12.92;
        } else {
            db = Math.exp(Math.log(db) / 2.4) * 1.055 - 0.055;
        }

        dr = dr * 255;
        dg = dg * 255;
        db = db * 255;

        dr = Math.min(255, dr);
        dg = Math.min(255, dg);
        db = Math.min(255, db);
        /**
         * 這里也是,需要對其范圍進行一個限制
         */
        sRGB[0] = (int) Math.min(Math.max((dr + 0.5), 0), 255);
        sRGB[1] = (int) Math.min(Math.max((dg + 0.5), 0), 255);
        sRGB[2] = (int) Math.min(Math.max((db + 0.5), 0), 255);

        return sRGB;
    }

整體思路就是將原來的顏色獲取RGB的值以后將其轉(zhuǎn)換成Lab模式,然后在對其進行一個亮度的換算。在換算完成后再將其轉(zhuǎn)化為RGB模式。在這里有兩個點需要特殊說明一下

  • changeL()函數(shù)是用來換算對應的L的關系的,在這里可以直接使用 y = -x + 100 這個函數(shù)來對L進行簡單的換算。
  • isNeedLight的意義
    這個參數(shù)是用來判斷當前顏色到底是應該變亮還是變黑一些的。在深色模式下,并不是所有的顏色都需要變黑,對于內(nèi)容的呈現(xiàn)部分(文字),是需要對其進行變亮處理的,譬如上圖的文字所示。但是由于可能系統(tǒng)會對某些地方進行反色處理,如果在系統(tǒng)反色之后我們再手動的進行一次反色處理,那么還不如不變色。。。。。所以需要對當前顏色的亮度和變化之后的亮度進行分別判斷取值。如果是需要變亮的區(qū)域,我們就將其和變化后的亮度取最大值,反之取最小值。

通過這個函數(shù)變化后的整體效果為

上面一行為原色,下面一行為變化后的顏色

和經(jīng)過系統(tǒng)反色后的效果進行對比


上面一行為原色,下面一行為變化后的顏色

可以看到,差距仍然是存在的,但是相較而言,已經(jīng)處在一種可以接受的范圍之內(nèi)了。只需要針對性的再調(diào)整一下?lián)Q算亮度L的函數(shù)即可完美對深色模式進行輔助適配,而不用重新寫大量的color

在新建一個函數(shù)來傳入需要變化顏色的View或者是ViewGroup

    public void setParentView(View v) {
        if (v.getBackground() instanceof ColorDrawable) {
            ColorDrawable cd = (ColorDrawable) v.getBackground();
            v.setBackgroundColor(getDarkColor(cd.getColor(), false));
        } else if (v.getBackground() instanceof GradientDrawable) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                GradientDrawable gd = (GradientDrawable) v.getBackground();
                ColorStateList csL = gd.getColor();
                if (csL != null) {
                    int color = csL.getDefaultColor();
                    gd.setColor(getDarkColor(color, false));
                }
                int[] colors = gd.getColors();
                if (colors != null) {
                    for (int i = 0; i < colors.length; i++) {
                        colors[i] = getDarkColor(colors[i], false);
                    }
                    gd.setColors(colors);
                }
                v.setBackground(gd);
            }
        }

        if (v instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) {
                setParentView(((ViewGroup) v).getChildAt(i));
            }
        } else {
            if (v instanceof TextView) {
                int colorNumber = ((TextView) v).getCurrentTextColor();
                ((TextView) v).setTextColor(getDarkColor(colorNumber, true));
            }
        }
    }

這樣就可以針對性的對部分系統(tǒng)反色不起作用的View進行補充適配,以達到一個相對完美適配深色模式的效果

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容