Android暗黑模式适配

Android暗黑模式适配
最近好久没写了,懒的要死。暗黑模式的适配也做完了,目前来看运行很稳定,算是比较成熟了,就来分享下暗黑模式的适配。其实我更倾向于深色模式和浅色模式切换,因为国内的手机普遍就是深色模式开关,我们可以把我们常用的模式称之为浅色模式,在Android10中我们Android开始支持深色模式,主要的原因还是为了省电,AMOLED屏在显示黑色时还是毕竟省电的。
闲话少说,进入主题,如何实现Android的暗黑模式,首先我们需要修改我们的主题,即我们常用的Theme.AppCompat.Light改为Theme.AppCompat.DayNight,这里其实已经实现了我们的第一步,有的厂商如果你把你的主题修改了以后他们会帮我们实现暗黑模式的适配,而有的需要我们来主动适配,毕竟我们需要保证我们的App在多个手机上是一致的。
对于暗黑模式适配来说我们需要做到的就是把对应的浅色模式下颜色值和深色模式下的颜色值和icon一一对应,保证我们在深色模式的情况下能够在深色模式下找到对应的属性值。既然我们需要保证在深色模式下的色值和图片能够对应上浅色模式上的相应的值,那么我们就需要一个文件夹去保存我们深色模式下的文件。首先我们需要建一个文件夹即vlaues-night这里我们存放2个文件就够了即styles.xml和colors,这里colors存放对应的颜色值,styles可以放我们在深色模式下的style,因为有的时候Button和TextView等这些控件我们不想使用系统的默认值会进行全局的定义,styles里就可以存放这些值了。这里再说一句,那就是我们在values-night下所有的值必须在values下有对应的属性值,否则是编译不了的。
上述问题有个最难受的地方就是colors的处理,其实很多人在xml中是直接填写色值的即#FFFFFFFF,针对这种情况我们需要先把这些色值在values下先定义好在xml中引用@color/bg_color_ffffffff,这个引用只是个大概的说明,在实际的开发中我们遇到的问题就是有些页面我们不会对这个色值进行修改,那么这个色值我们就要定义2套。这里建议大家在values下和values-night下都进行定义,方便后续的更改,我们在命名时做上区分,这样后续的修改也会更方便。
写到这里我们已经可以看到我们的App已经基本上实现了,这是基于系统的自动改变的。如果我们需要自己控制请继续往下看。先附个图,大家先看下。


res结构图.png

现在很多App是可以在设置中控制是否跟随系统变动的。如果我们需要我们的App也同样实现该功能应该怎么办呢,那么首先我们需要监听系统的改变,这里我们必须要在我们的AndroidManifest中的Activity中configChanges加上uiMode。


加上uiMode,我们就可以在Activity的onConfigurationChanged()方法中进行监听系统深色模式开关的变化。

   /**
     *  newConfig.uiMode == 17 代表是浅色  newConfig.uiMode == 33代表深色
     * @param newConfig
     */
 @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        int mSysThemeConfig = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
        switch (mSysThemeConfig) {
            // 浅色主题
            case Configuration.UI_MODE_NIGHT_NO:
                break;
            // 深色主题
            case Configuration.UI_MODE_NIGHT_YES:
                break;
            default:
                break;
        }
    }

这样我们就可以监听到系统的深色模式的开关,就可以实现自己控制是否跟随系统开启深色模式了。
还有就是我们需要判断当前页面是否是暗黑模式,根据上述方法我们可以得到下述方法

 public boolean isDarkMode() {
        int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return mode == Configuration.UI_MODE_NIGHT_YES;
    }

下面就是我们可以主动开启深色模式了,当系统是浅色模式时,我们是可以主动开启深色模式的。这里我们需要调用以下方法:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
同理我们可以也可以在适配暗黑模式的情况下强制开启浅色模式,来满足我们的需求:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
需要注意的是,我们在设置完后通常是需要调用recreate()方法来刷新View的。
下面我们对setDefaultNightMode()的mode类型进行下讲解。

MODE_NIGHT_AUTO  //根据时间来自动变换需要定位。
MODE_NIGHT_NO //浅色模式
MODE_NIGHT_YES //深色模式
MODE_NIGHT_FOLLOW_SYSTEM //跟随系统
MODE_NIGHT_UNSPECIFIED //未指定,默认值

讲到这里,我们已经可以实现了暗黑模式适配的进阶,也就是说我们可以根据这些来满足我们的日常开发需求了。这里页面会因为加载的view过于复杂页面有明显的闪烁,这是由于在代码执行的时候会走recreate()重新加载view。

系统源码相关

 @Override
    public boolean applyDayNight() {
        boolean applied = false;

        @NightMode final int nightMode = getNightMode();
        @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
        if (modeToApply != MODE_NIGHT_FOLLOW_SYSTEM) {
            applied = updateForNightMode(modeToApply);
        }

        if (nightMode == MODE_NIGHT_AUTO) {
            // If we're already been started, we may need to setup auto mode again
            ensureAutoNightModeManager();
            mAutoNightModeManager.setup();
        }

        mApplyDayNightCalled = true;
        return applied;
    }

在执行onConfigurationChanged()的时候会主动调用这个执行我们的深色模式处理,最终会执行下述代码实现我们的Acitivity更新操作。

private boolean updateForNightMode(@ApplyableNightMode final int mode) {
        final Resources res = mContext.getResources();
        final Configuration conf = res.getConfiguration();
        final int currentNightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK;

        final int newNightMode = (mode == MODE_NIGHT_YES)
                ? Configuration.UI_MODE_NIGHT_YES
                : Configuration.UI_MODE_NIGHT_NO;

        if (currentNightMode != newNightMode) {
            if (shouldRecreateOnNightModeChange()) {
                if (DEBUG) {
                    Log.d(TAG, "applyNightMode() | Night mode changed, recreating Activity");
                }
                // If we've already been created, we need to recreate the Activity for the
                // mode to be applied
                final Activity activity = (Activity) mContext;
                activity.recreate();
            } else {
                if (DEBUG) {
                    Log.d(TAG, "applyNightMode() | Night mode changed, updating configuration");
                }
                final Configuration config = new Configuration(conf);
                final DisplayMetrics metrics = res.getDisplayMetrics();

                // Update the UI Mode to reflect the new night mode
                config.uiMode = newNightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
                res.updateConfiguration(config, metrics);

                // We may need to flush the Resources' drawable cache due to framework bugs.
                if (!(Build.VERSION.SDK_INT >= 26)) {
                    ResourcesFlusher.flush(res);
                }
            }
            return true;
        } else {
            if (DEBUG) {
                Log.d(TAG, "applyNightMode() | Skipping. Night mode has not changed: " + mode);
            }
        }
        return false;
    }

实际遇到的问题附解决方案。

以下问题都是基于App有跟随系统开关并且代码设置主动跟随系统
1.如果我们走了某个方法导致我们回到了主页面,这个时候我们对Activity栈进行了清空,就可能会出现设置无效的情况,这个跟清空了栈有关系,可以在主页面做特殊处理的设置。
2.在Activity、Fragment或自定义View中使用getResources().getColor(),这个颜色值也要在night下有对应的色值,另外是不要使用Application中的Context,因为这个Context的uiMode是跟随App打开时的状态的并不随代码的改动而修改,就会显示错误和错乱,这里包括图片的引用。另外就是这里尽量不要使用0XFFFFFFFF这种色值。
3.在自定义View中如果引起了多次刷新问题或者显示错误的问题,可以修改View所持有的Resource,使用以下代码:

if (深色模式){
            Resources resources = context.getResources();
            Configuration configuration = resources.getConfiguration();
            configuration.uiMode = 33;
            resources.updateConfiguration(configuration,resources.getDisplayMetrics());
        }else {
            Resources resources = context.getResources();
            Configuration configuration = resources.getConfiguration();
            configuration.uiMode = 17;
            resources.updateConfiguration(configuration,resources.getDisplayMetrics());
        }

这样就可以在初始化的时候正常引用正确的Resource保证适配的效果了,如果有其他问题也可以参考这个问题。
先这样了,大概就这些了吧,以后想起来什么就接着写,源码稍后附上。

你可能感兴趣的:(Android暗黑模式适配)