暗色模式已经不是什么新鲜玩意了,大家最近看到关于暗色模式最多的内容可能就是iOS版本微信未适配暗色模式面临被AppStore下架的风险。然后今天早上一醒来,发现Android的微信也黑了(因为我手机一直用的暗色模式),然后最近也遇到了一个暗色模式适配的一个坑,就拿出来讲一讲。
适配暗色模式
在开始之前还是提一下,暗色模式的一个适配方式。这个谷歌官方讲的很清楚,方式有两种:
这种方式较为复杂,需要在style下定义正常模式和暗色模式两套app_theme,且必须继承自Theme.AppCompat.DayNight.DarkActionBar,然后提取出需要适配暗色模式的属性,最后在BaseActivity的onCreate方法中,根据当前模式设置不同的主题即可。
判断系统当前是否暗色模式:
public boolean isDarkMode() { int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; return mode == Configuration.UI_MODE_NIGHT_YES;}
这种方式简单粗暴,只需要app_theme中声明
<item name="android:forceDarkAllowed">true</item>
应用会在系统切换暗色时,自动适配,这个前提就是不要使用硬编码颜色值。同样需要准备两套资源,暗色模式需要的资源文件,放在以values-night命名的资源目录下,在不同模式下,会自动读取对应目录下的资源。
forceDrakAllowed不仅可以用在App主题级别,也可以直接使用在View上。如果仅需某个View适配暗色模式,直接在view属性声明即可。同理,如果某个View在暗色模式下,不需要适配,通过设置forceDrakAllowed为false即可,或者通过view.setForceDarkAllowed(false)。
遇到的bug
暗色模式下,状态栏没有反色,导致看不清。
这个很好定位,肯定是StatusBar状态写死了,去代码里面看看
private void setStatusBarColor() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);}
可以看到,之前应该是因为某种业务需要,所以将状态栏设置了LIGHT_STATUS_BAR这个flag。
方案一:
我们知道,如果不认为去设置SystemUI的Visibility,系统会自动根据当前主题颜色来适配状态栏是否进行反色,那么我们如果去掉这个这个人为设置的flag, 是否就可以解决这个问题。
private void setStatusBarColor() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); //去掉LIGHT_STATUS_BAR这个flag //window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);}
结果如下:
暗色模式虽然状态栏反色了,但是正常模式下,又看不到了。也就是说,暗色模式下的状态栏,需要自己适配。并且,Activity的内容与状态栏出现了重叠。
方案二:
既然无法自动反色,那就适配咯,原本逻辑咱们不改动,加个判断在暗色模式时,咱们设置一个DRAK_STATUS_BAR属性是不是就可以了。开玩笑哈,View属性里面并没有这个flag,需要通过位运算来处理
private void setStatusBarColor() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); if (isDarkMode()) { int uiOption = window.getDecorView().getSystemUiVisibility(); //没有DARK_STATUS_BAR属性,通过位运算将LIGHT_STATUS_BAR属性去除 window.getDecorView().setSystemUiVisibility(uiOption & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } else { window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); }}
结果如下:
正常模式和暗色模式,状态栏都已经正常反色,但是暗色模式下,Activity内容依然与状态栏重叠。
方案三:
通过对比不难发现,只有暗色模式重叠,无非就是因为我们保留了之前所设置的FLAG,这里要注意,这里的FLAG是通过set方法来设置的,也就是说,后面的只会覆盖前面的,而不像我们平时所使用的addFlags,这个是叠加的。
再来回顾一下,没有修改前的代码:
private void setStatusBarColor() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //第一次set了两个属性 window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); //这里又一次set,也就是前面的e两个属性根本没有使用 window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);}
而我们出现重叠的原因,就是因为保留了之前的属性,其中SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN就是导致重叠的真凶,作用是在不隐藏StatusBar的情况下,将view所在window的显示范围扩展到StatusBar下面。之所以正常模式下,不会出现重叠,是因为二次设置LIGHT_STATUS_BAR会覆盖前面的属性。
很明显,我们的内容并不需要延伸至状态栏下,所以前面的代码就是无用的,删除即可。
private void setStatusBarColor() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE // | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); window.setStatusBarColor(Color.TRANSPARENT); int uiOption = window.getDecorView().getSystemUiVisibility(); if (isDarkMode()) { //没有DARK_STATUS_BAR属性,通过位运算将LIGHT_STATUS_BAR属性去除 window.getDecorView().setSystemUiVisibility(uiOption & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } else { //这里是要注意的地方,如果需要补充新的FLAG,记得要带上之前的然后进行或运算 window.getDecorView().setSystemUiVisibility(uiOption | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); }}
最后的真凶,并不是暗色模式导致了重叠,而是原代码作者留下的坑。主要还是对于SystemUI Flag的一些属性不熟导致。OK,修改完效果如下。
对于SystemUI的一些FLAG作用不清楚的同学,可以参考下面这个文章: https://www.jianshu.com/p/e6656707f56c