Android夜间模式开发总结

本文介绍了如何在Android中使用AppCompatDelegate的setDefaultNightMode方法来切换应用的夜间模式,包括创建主题、在Manifest中引用、在Activity中切换、在Application中设置默认模式以及使用夜间模式工具类进行管理。同时,详细解析了不同方法的作用和场景,如setLocalNightMode仅影响当前组件,而setDefaultNightMode会影响新生成的组件。最后,强调了recreate()方法在切换模式中的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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)中保存好相关属性值,重建时使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值