相信Android的日间/夜间模式切换相信大家在平时使用 APP 的过程中都遇到过,实现日间/夜间模式切换的方案也有许多种(比如说在setContentView之前setTheme、遍历ViewTree动态设置、),在我们Android 5.0版本之后Framework 层下的android.support.v7包中就自带日夜间模式的切换框架,可以帮助我们灵活实现切换,但但仅仅适用于仅仅支持固定两套皮肤的切换,而且需要重启App或者Activity,所以呢,对于用户体验来说可能不那么好,但是在一些特定的需求下是成本最低实现换肤的思路之一,所以了解下也无妨,
在Android 5.0之后Framework 在以前原有res 目录的自动适配的基础上,新增了名为values-night、drawable-night等目录的自动适配支持,并且通过AppCompatDelegate 提供了切换日夜间模式的API——AppCompatDelegate.setDefaultNightMode(int mode),其中mode有四个可选值:
通过调用这个API告知系统当前希望是以何种模式,在App重启或者Activity recreate时系统会像根据适配屏幕分辨率一样,自动去对应的目录下加载资源,从而完成了换肤。
implementation 'com.android.support:appcompat-v7:28.0.0'
在res目录下,新建名为 values-night 目录,当然还可以新建其他类型的目录,比如说drawable-night等,总之就根据系统预订的规范来命名,也可以直接在styles文件下配置,在加载资源的时候系统就会自动去进行适配。
调用AppCompatDelegate.setDefaultNightMode(int mode)设置模式,调用之后还需要重启App或者recreate Activity。
通过以上三步,就可以完成Activity更新了,但是呢,如何去让多个Activity 都能在我点击换肤的时候,都能让多个Activity同时更新呢,也有很多方案,通过广播、通过监听Activity的周期、通过注解等等,这里我使用观察者模式,但是并不意味着这是最佳的方案。
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());
}
}
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();
}
}
此处我是通过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);
}
}
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方式的话,可能用户体验不太良好,总之谨慎直接使用,原本不打算写这篇的,但是发现有些文章把换肤说得糊里糊涂的,乱七八糟,就分享下,顺便再一次介绍下观察者模式,至于类似云村、酷狗那种支持皮肤包的换肤,无侵入的原理,也很简单,后面看情况,我也写个系列文章总结并且手写实现吧。源码传送门或者码云