android中新版本的Api 23.0.0后可以使用AppCompatDelegate来实现夜间模式切换:
setDefaultNightMode(@NightMode int mode);
其中 mode is one of AppCompatDelegate.MODE_NIGHT_*
eg: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); or AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
具体实现步骤如下:
1. 主题theme定义
(1) 默认主题: src/main/res/values/themes.xml:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.RuntimeLog" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.RuntimeLog.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.RuntimeLog.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.RuntimeLog.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
(1) 夜间模式主题: src/main/res/values-night/themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.RuntimeLog" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
2. manifest中引用主题
可以再application根节点默认引用,activity默认就可以引用了:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.hulk.runtimelog">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name="com.android.hulk.runtimelog.MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RuntimeLog">
<activity
android:name="com.android.hulk.runtimelog.MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.RuntimeLog.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".NightModeActivity"/>
<service android:name="com.android.hulk.runtimelog.NACService"
android:process=":nac"/>
</application>
</manifest>
3. Activity中切换夜间模式
由于通过AppCompatDelegate实现切换状态, 通常继承 AppCompatActivity
package com.android.hulk.runtimelog;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
public class NightModeActivity extends AppCompatActivity {
private TextView mNightModeTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_night_mode);
//是当前界面的夜间模式
//NightModeUtils.setLocalNightMode(this, AppCompatDelegate.MODE_NIGHT_YES);
//如果直接在onCreate()中调用,FirstActivity直接生效,无需调用recreate()
//AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
mNightModeTv = (TextView)findViewById(R.id.tv_mode);
mNightModeTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取当前的模式,设置相反的模式,这里只使用了,夜间和非夜间模式
NightModeUtils.switchCurrentMode(NightModeActivity.this);
//需要recreate才能生效, 执行后所有界面均切换夜间模式
recreate();
}
});
}
}
4. Application中设置默认夜间模式,保持上次设置的模式一致
package com.android.hulk.runtimelog;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.mobile.emm.debugger.RuntimeLog;
/**
* @author: zhanghao
* @Time: 2021-04-06 16:12
*/
public class MyApp extends Application {
private static final String TAG = "MyApp";
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
String rootDir = RuntimeLog.ROOT_DIR + "/hulk/runtimelog/logs/";
RuntimeLog.initRuntimeLog(base, rootDir);
Log.w(TAG, "attachBaseContext: ");
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "onCreate: ");
//根据app上次退出的状态来判断是否需要设置夜间模式
NightModeUtils.setDefaultNightMode(getApplicationContext());
}
}
5. 夜间模式工具类
package com.android.hulk.runtimelog;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
/**
* 夜间模式工具类
* @author: zhanghao
* @Time: 2021-04-21 14:40
*/
public class NightModeUtils {
private static SharedPreferences mSharedPreference;
private static final String NIGHT_MODE_PREF = "night_mode_pref";
/**
* SharedPreference中存了一个是否是夜间模式的boolean值
*/
public static final String IS_NIGHT_MODE = "is_night_mode";
private static boolean mIsNightMode;
private static SharedPreferences.Editor mEditor;
private static NightModeUtils sModeConfig;
public static SharedPreferences getPref(Context context){
if (mSharedPreference == null) {
mSharedPreference = context.getSharedPreferences(NIGHT_MODE_PREF, Context.MODE_PRIVATE);
}
return mSharedPreference;
}
public static SharedPreferences.Editor getPrefEditor(Context context){
if (mEditor == null) {
mEditor = getPref(context).edit();
}
return mEditor;
}
public static boolean getNightMode(Context context){
mIsNightMode = getPref(context).getBoolean(IS_NIGHT_MODE, false);
return mIsNightMode;
}
public static void setNightMode(Context context, boolean isNightMode){
getPrefEditor(context).putBoolean(IS_NIGHT_MODE, isNightMode);
mEditor.commit();
}
/**
* 根据app上次退出的状态来判断是否需要设置夜间模式
* @param context
*/
public static void setDefaultNightMode(Context context) {
boolean isNightMode = NightModeUtils.getNightMode(context);
if (isNightMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
/**
* 获取当前界面的夜间模式
* @param activity
* @return
*/
public static int getCurrentNightMode(Activity activity) {
int currentMode = activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return currentMode;
}
/**
* 切换默认夜间模式: 获取当前的模式,设置相反的模式: 夜间和非夜间模式
* currentMode取值:
*
* int currentNightMode = getResources().getConfiguration().uiMode
* & Configuration.UI_MODE_NIGHT_MASK;
* switch (currentNightMode) {
* case Configuration.UI_MODE_NIGHT_NO://夜间模式未启用
* case Configuration.UI_MODE_NIGHT_YES://夜间模式启用
* case Configuration.UI_MODE_NIGHT_UNDEFINED://不确定是哪种模式,假设不是夜间模式
* }
* @param activity
*/
public static void switchCurrentMode(Activity activity) {
if (activity == null || activity.isFinishing()) {
return;
}
//获取当前的模式,设置相反的模式
int currentMode = getCurrentNightMode(activity);
if (currentMode != Configuration.UI_MODE_NIGHT_YES) {
//保存夜间模式状态,Application中可以根据这个值判断是否设置夜间模式
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
//ThemeConfig主题配置,这里只是保存了是否是夜间模式的boolean值
NightModeUtils.setNightMode(activity,true);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
NightModeUtils.setNightMode(activity,false);
}
}
/**
* 是当前界面的夜间模式, 其他界面不受影响
* @param activity
* @param mode
*/
public static void setLocalNightMode(AppCompatActivity activity, @AppCompatDelegate.NightMode int mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
activity.getDelegate().setLocalNightMode(mode);
}
}
}
总结:
根据用户的喜好来设置是否启用夜间模式: (1) AppCompatDelegate类中还有一个方法:setLocalNightMode(int mode),这个方法作用在当前组件,AppCompatActivity中使用 getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES)设置。 比如在Application中设置为日间模式,而在当前Activity中调用getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES),该Activity会显示为夜间模式,覆盖原来的模式。 (2) AppCompatDelegate.setDefaultNightMode(int mode);理论上: 只作用于新生成的组件,对原先处于任务栈中的Activity不起作用。实际测试中, 点击按钮切换夜间模式后, 调用recreate(), 返回上一个界面, 自动切换东新的模式. 如果直接在Activity的onCreate()中调用这句代码,那当前的Activity中直接生效,不需要再调用recreate(),但我们通常是在监听器中调用这句代码,那就需要调用recreate(),否则对当前Activity无效(新生成的Activity还是生效的)。 (3) recreate()重建activity,需要提前在onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)中保存好相关属性值,重建时使用