深色模式个人采坑记录

这一期接了一个“适配深色模式”的需求,在做之前过了一遍自己负责的几个界面,发现系统的反色基本上把所有的工作都做完了。少了一项需求,心里美滋滋。提测以后测试拿着一堆切了深色模式以后还是白花花的界面来找到我,我一脸不可置信的看着测试。得,系统的反色突然失效了。没办法,只好抛开系统自己来做一些针对深色模式的适配。

对于深色模式的适配主要有以下两方面的工作需要进行

  1. 对于Res的图片资源进行适配
  2. 对于界面颜色等进行适配

针对图片等资源文件的深色模式的适配,没有什么好办法,只能通过新建drawable-night文件夹来进行适配
针对界面颜色的,正常情况下,我们可以通过以下几种方式对界面的颜色进行深色模式的适配

  • 依赖系统的反色适配
    这是三种方法里改动最小的方法,系统UI会判断当前模式是否为深色模式,从而修改View绘制时使用的Paint颜色,以达到反色的目的。但是在厂商定制的系统当中,可能各家对于反色都有着不同的算法,所以最终的结果是可能会导致相同的颜色在不同厂家的深色模式当中会展现为不同的颜色
  • 设置-night
    和Drawable文件一样,color资源也可以使用-night来对深色模式进行适配。如果是一个新的项目或者是color资源不多的情况下,使用这种方式显然会更好一些。但是如果是color资源文件相当多的情况下,这种方式想想就头大= =
  • 设置Style
    和上面设置color-night类似,甚至还比上面的方式更繁琐一些- -

在对深色模式的适配过程中,我原本打算只依赖系统的反色来进行适配的。但是在测试过程当中发现,不同的系统版本的反色可能对相同的界面产生不同的效果。在万般无奈之下,只好引入了一个DarkmodeUtil来对整个页面的深色模式进行辅助适配,以求完美的效果.

如何得到一个可以在深色模式当中令人舒服的颜色

这是我在做这个工具遇到的第一个问题,在我们日常生活当中,我们通常会使用RGB或者是CMYK的方式来描述一个颜色的具体的数值。但是根据我对深色模式下通过系统的反色和原色对比发现。系统并不是通过RGB方式来计算深色模式到底应该使用哪个颜色的。经过查找资料发现,系统使用了一种叫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转化为Lab了,在这里我参考了下面这个博主的一个方法。

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

根据这个博主所言,RGB与Lab不能直接进行转化,所以这个博主引入了XYZ中间量来辅助转化。但是这个博主的方法用在此处仍然有一点点小问题,在我接下来给出方法中将这些问题给修复了

    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转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;
        }
        /**
         * 在这里可能会出现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;
    }

这样通过这两步的转化,就可以把一个RGB颜色转换为Lab进行表示。
在Lab颜色模式当中,L代表当前颜色的亮度。我们可以针对性的对颜色进行设置
譬如#00FF00这个绿色,其L的数值为87,经过变换后,其L的数值为13,效果如下


上面一行为原色,下面一行为变化后的颜色

可以看到,在经过对L的变化后,绿色就显得不是这么的艳丽,整个效果也更加的符合深色模式的基调
对颜色进行换算的代码如下

    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的值以后将其转换成Lab模式,然后在对其进行一个亮度的换算。在换算完成后再将其转化为RGB模式。在这里有两个点需要特殊说明一下

  • changeL()函数是用来换算对应的L的关系的,在这里可以直接使用 y = -x + 100 这个函数来对L进行简单的换算。
  • isNeedLight的意义
    这个参数是用来判断当前颜色到底是应该变亮还是变黑一些的。在深色模式下,并不是所有的颜色都需要变黑,对于内容的呈现部分(文字),是需要对其进行变亮处理的,譬如上图的文字所示。但是由于可能系统会对某些地方进行反色处理,如果在系统反色之后我们再手动的进行一次反色处理,那么还不如不变色。。。。。所以需要对当前颜色的亮度和变化之后的亮度进行分别判断取值。如果是需要变亮的区域,我们就将其和变化后的亮度取最大值,反之取最小值。

通过这个函数变化后的整体效果为

上面一行为原色,下面一行为变化后的颜色

和经过系统反色后的效果进行对比


上面一行为原色,下面一行为变化后的颜色

可以看到,差距仍然是存在的,但是相较而言,已经处在一种可以接受的范围之内了。只需要针对性的再调整一下换算亮度L的函数即可完美对深色模式进行辅助适配,而不用重新写大量的color

在新建一个函数来传入需要变化颜色的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));
            }
        }
    }

这样就可以针对性的对部分系统反色不起作用的View进行补充适配,以达到一个相对完美适配深色模式的效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271