Android进阶——一闪而过结合观察者模式灵活利用Framework层自带的“日夜”间模式实现两套皮肤的简单切换

文章大纲

  • 引言
  • 一、系统自带换肤框架概述
  • 二、实现换肤
    • 1、引入support V7的依赖
    • 2、在res目录下新建两套资源目录
    • 3、调用AppCompatDelegate.setDefaultNightMode(int mode)
    • 4、使用观察者模式实现"即时"更新
      • 4.1、创建被观察者角色
      • 4.2、创建观察者角色
      • 4.3、注册观察者
      • 4.4、使用

引言

相信Android的日间/夜间模式切换相信大家在平时使用 APP 的过程中都遇到过,实现日间/夜间模式切换的方案也有许多种(比如说在setContentView之前setTheme、遍历ViewTree动态设置、),在我们Android 5.0版本之后Framework 层下的android.support.v7包中就自带日夜间模式的切换框架,可以帮助我们灵活实现切换,但但仅仅适用于仅仅支持固定两套皮肤的切换,而且需要重启App或者Activity,所以呢,对于用户体验来说可能不那么好,但是在一些特定的需求下是成本最低实现换肤的思路之一,所以了解下也无妨,

一、系统自带换肤框架概述

在Android 5.0之后Framework 在以前原有res 目录的自动适配的基础上,新增了名为values-nightdrawable-night等目录的自动适配支持,并且通过AppCompatDelegate 提供了切换日夜间模式的API——AppCompatDelegate.setDefaultNightMode(int mode),其中mode有四个可选值:

  • MODE_NIGHT_NO—— 使用亮色(light)主题,不使用夜间模式
  • MODE_NIGHT_YES——使用暗色(dark)主题,使用夜间模式
  • MODE_NIGHT_AUTO——根据当前时间自动切换 亮色(light)/暗色(dark)主题
  • MODE_NIGHT_FOLLOW_SYSTEM(默认选项)——设置为跟随系统,通常为MODE_NIGHT_NO

通过调用这个API告知系统当前希望是以何种模式,在App重启或者Activity recreate时系统会像根据适配屏幕分辨率一样,自动去对应的目录下加载资源,从而完成了换肤。

二、实现换肤

Android进阶——一闪而过结合观察者模式灵活利用Framework层自带的“日夜”间模式实现两套皮肤的简单切换_第1张图片

1、引入support V7的依赖

implementation 'com.android.support:appcompat-v7:28.0.0'

2、在res目录下新建两套资源目录

在res目录下,新建名为 values-night 目录,当然还可以新建其他类型的目录,比如说drawable-night等,总之就根据系统预订的规范来命名,也可以直接在styles文件下配置,在加载资源的时候系统就会自动去进行适配。
Android进阶——一闪而过结合观察者模式灵活利用Framework层自带的“日夜”间模式实现两套皮肤的简单切换_第2张图片

3、调用AppCompatDelegate.setDefaultNightMode(int mode)

调用AppCompatDelegate.setDefaultNightMode(int mode)设置模式,调用之后还需要重启App或者recreate Activity。

4、使用观察者模式实现"即时"更新

通过以上三步,就可以完成Activity更新了,但是呢,如何去让多个Activity 都能在我点击换肤的时候,都能让多个Activity同时更新呢,也有很多方案,通过广播、通过监听Activity的周期、通过注解等等,这里我使用观察者模式,但是并不意味着这是最佳的方案。

4.1、创建被观察者角色

package com.crazymo.replaceskin.core;

import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import com.crazymo.replaceskin.MainActivity;

import java.util.Observable;

/**
 * @author : Crazy.Mo
 */
public class SkinManager extends Observable {
    private static SkinManager instance;
    /**
     * Activity生命周期回调
     */
    private MoActivityLifecycleCallbacks skinActivityLifecycle;
    private Application mContext;

    /**
     * 初始化 必须在Application中先进行初始化
     *
     * @param application
     */
    public static void init(Application application) {
        if (instance == null) {
            synchronized (SkinManager.class) {
                if (instance == null) {
                    instance = new SkinManager(application);
                }
            }
        }
    }

    public static SkinManager getInstance() {
        return instance;
    }


    private SkinManager(Application application) {
        mContext = application;
        //注册Activity生命周期
        skinActivityLifecycle = new MoActivityLifecycleCallbacks();
        mContext.registerActivityLifecycleCallbacks(skinActivityLifecycle);
    }

    /**
     * 在需要更新皮肤时, 通知其他观察者,进行更新
     */
    public void updateSkin(){
        setChanged();
        notifyObservers(null);
    }

    /**
     * 直接重启
     */
    public void restart(){
        Intent intent = new Intent(mContext, MainActivity.class);
        PendingIntent restartIntent = PendingIntent.getActivity(
                mContext.getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100,restartIntent);
        /** 1秒钟后重启应用 */
        /** 结束线程,一般与finishAll()一起使用 */
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

4.2、创建观察者角色

package com.crazymo.replaceskin.core;

import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import java.util.Observable;
import java.util.Observer;

/**
 * @author : Crazy.Mo
 */
public class BaseSkinActivity extends AppCompatActivity implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        if((boolean) Util.SharePreference.get(this,"config_skin","isNight",false)) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            Util.SharePreference.save(this,"config_skin","isNight",true);
        }else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
            Util.SharePreference.save(this,"config_skin","isNight",false);
        }
        recreate();
        /////SkinManager.getInstance().restart();
    }
}

4.3、注册观察者

此处我是通过Application.ActivityLifecycleCallbacks监听Activity的生命周期的,并且在onCreate时添加观察者,在onDestroy时移除观察者。

package com.crazymo.replaceskin.core;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

/**
 * @author : Crazy.Mo
 */
public class MoActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        //添加观察者
        SkinManager.getInstance().addObserver((BaseSkinActivity)activity);
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        //移除观察者
        SkinManager.getInstance().deleteObserver((BaseSkinActivity)activity);
    }
}

4.4、使用

  • 初始化
package com.crazymo.replaceskin;

import android.app.Application;
import android.support.v7.app.AppCompatDelegate;
import android.util.Log;

import com.crazymo.replaceskin.core.SkinManager;
import com.crazymo.replaceskin.core.Util;

/**
 * @author : Crazy.Mo
 */
public class MoApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        SkinManager.init(this);
        if((Boolean) Util.SharePreference.get(this,"config_skin","isNight",false)) {
            Log.e("cmskin","日间");
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
            Util.SharePreference.save(this,"config_skin","isNight",false);
        }else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            Log.e("cmskin","夜间");
            Util.SharePreference.save(this,"config_skin","isNight",true);
        }
    }
}
  • 通知观察者
    public void replace(View view) {
        Util.SharePreference.save(this,"config_skin","isNight",false);
        SkinManager.getInstance().updateSkin();
    }

以上就是换肤的核心代码了,不过这种方案有一些缺点,比如说以上代码使用recreate进行刷新的话,只会更新在执行换肤前的打开过且未被销毁回收的Activity(因为我这里偷懒是在Activity注册观察者的,你也可以通过注解来注册观察者),只能接收两套皮肤;如果使用重启App方式的话,可能用户体验不太良好,总之谨慎直接使用,原本不打算写这篇的,但是发现有些文章把换肤说得糊里糊涂的,乱七八糟,就分享下,顺便再一次介绍下观察者模式,至于类似云村、酷狗那种支持皮肤包的换肤,无侵入的原理,也很简单,后面看情况,我也写个系列文章总结并且手写实现吧。源码传送门或者码云

你可能感兴趣的:(Android,进阶)