AndroidDarkThemeDemo
Android Dark Theme in Action
Install / Use
/learn @shenguojun/AndroidDarkThemeDemoREADME
[toc]

Android Dark Theme in Action (Android深色模式实战)
背景
从Android10(API 29)开始,在原有的主题适配的基础上,Google开始提供了Force Dark机制,在系统底层直接对颜色和图片进行转换处理,原生支持深色模式。到目前为止,我们从用户数据分析**50%**以上的用户已经使用上了Android10系统。深色模式可以节省电量、改善弱势及强光敏感用户的可视性,并能在环境亮度较暗的时候保护视力,更是夜间活跃用户的强烈需求。对深色模式的适配有利于提升用户口碑。
深色模式在安卓上可以分为以下四种场景:
-
强制深色模式
-
强制浅色模式
-
跟随系统
-
低电量自动切换深色
以下将介绍如何设置深色模式以及如何对深色模式进行适配。
资源配置限定符
我们常见的需要设置的资源有drawable、layout、mipmap和values等,对于这些资源,我们可以用一些限定符来表示提供一些备用资源,例如drawable-xhdpi表示超密度屏幕使用的资源,或者layout-land表示横向状态使用的布局。
同样的深色模式可以使用资源的限定符-night来表示在深色模式中使用的资源。如下图所示:
使用了-night限定符的文件夹里面的资源我们称为night资源,没有使用-night限定符的资源我们称为notnight资源。
其中drawable-night-xhdpi可以放置对应超密度屏幕使用的深色模式的图片,values-night可以声明对应深色模式使用的色值和主题。
所有的资源限定符定义以及添加的顺序(例如-night必须在-xhdpi之前)可查看应用资源概览中的配置限定符名称表。
深色模式判断&设置
判断当前是否深色模式
Configuration.uiMode 有三种NIGHT的模式
- UI_MODE_NIGHT_NO 表示当前使用的是
notnight模式资源 - UI_MODE_NIGHT_YES 表示当前使用的是
night模式资源 - UI_MODE_NIGHT_UNDEFINED 表示当前没有设置模式
可以通过以下的代码来判断当前是否处于深色模式:
/**
* 判断当前是否深色模式
*
* @return 深色模式返回 true,否则返回false
*/
fun isNightMode(): Boolean {
return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> true
else -> false
}
}
Tips: 对于一些从网络接口服务获取的需要对深色模式区分的色值或者图片,可以使用上述的判断来获取对应的资源。
判断当前深色模式场景
通过AppCompatDelegate.getDefaultNightMode()可以获取五种深色模式场景:
- MODE_NIGHT_AUTO_BATTERY 低电量模式自动开启深色模式
- MODE_NIGHT_FOLLOW_SYSTEM 跟随系统开启和关闭深色模式(默认)
- MODE_NIGHT_NO 强制使用
notnight资源,表示非深色模式 - MODE_NIGHT_YES 强制使用
night资源 - MODE_NIGHT_UNSPECIFIED 配合 setLocalNightMode(int) 使用,表示由Activity通过AppCompactActivity.getDelegate()来单独设置页面的深色模式,不设置全局模式
模式设置
深色模式设置可以从三个层级设置,分别是系统层、Applcation层以及Activity层。底层的设置会覆盖上层的设置,例如系统设置了深色模式,但是Application设置了浅色模式,那么应用会显示浅色主题。
<img src="https://raw.githubusercontent.com/shenguojun/ImageServer/master/uPic/1*2W_EKdaBH1NQZmIc58eQDA.png" alt="Image for post" style="zoom: 67%;" />系统层是指系统设置中,根据不同产商的手机,可以在设置->显示中修改系统为深色模式。
Application层通过AppCompatDelegate.setDefaultNightMode()设置深色模式。
Activity层通过getDelegate().setLocalNightMode()设置深色模式。
当深色模式改变时,Activity会重建,如果不希望Activity重建,可以在AndroidManifest.xml中对对应的Activity设置android:configChanges="uiMode",不过设置之后页面的颜色改变需要Activity在中通过监听onConfigurationChanged来动态改变。
通过AppCompatDelegate.setDefaultNightMode(int)可以设置深色模式,源码如下:
public static void setDefaultNightMode(@NightMode int mode) {
if (DEBUG) {
Log.d(TAG, String.format("setDefaultNightMode. New:%d, Current:%d",
mode, sDefaultNightMode));
}
switch (mode) {
case MODE_NIGHT_NO:
case MODE_NIGHT_YES:
case MODE_NIGHT_FOLLOW_SYSTEM:
case MODE_NIGHT_AUTO_TIME:
case MODE_NIGHT_AUTO_BATTERY:
if (sDefaultNightMode != mode) {
sDefaultNightMode = mode;
applyDayNightToActiveDelegates();
}
break;
default:
Log.d(TAG, "setDefaultNightMode() called with an unknown mode");
break;
}
}
从源码可以看出设置 MODE_NIGHT_UNSPECIFIED 模式是不会生效的。
Tips:注意,深色模式变化会导致Activity重建,AppCompatDelegate.setDefaultNightMode(int)方法仅对继承自AppCompactActivity的页面有效。
适配方案
自定义适配
1. 主题
将Application和Activity的主题修改为集成自Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight,就可以对于大部分的控件得到较好的深色模式支持。我们看下DayNight主题的定义:

res/values/values.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
<!-- ... -->
<style name="Theme.AppCompat.DayNight" parent="Theme.AppCompat.Light"/>
<style name="Theme.AppCompat.DayNight.DarkActionBar" parent="Theme.AppCompat.Light.DarkActionBar"/>
<style name="Theme.AppCompat.DayNight.Dialog" parent="Theme.AppCompat.Light.Dialog"/>
<style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert"/>
<style name="Theme.AppCompat.DayNight.Dialog.MinWidth" parent="Theme.AppCompat.Light.Dialog.MinWidth"/>
<style name="Theme.AppCompat.DayNight.DialogWhenLarge" parent="Theme.AppCompat.Light.DialogWhenLarge"/>
<style name="Theme.AppCompat.DayNight.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar"/>
<!-- ... -->
</resources>
res/values-night-v8/values-night-v8.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.AppCompat.DayNight" parent="Theme.AppCompat"/>
<style name="Theme.AppCompat.DayNight.DarkActionBar" parent="Theme.AppCompat"/>
<style name="Theme.AppCompat.DayNight.Dialog" parent="Theme.AppCompat.Dialog"/>
<style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Dialog.Alert"/>
<style name="Theme.AppCompat.DayNight.Dialog.MinWidth" parent="Theme.AppCompat.Dialog.MinWidth"/>
<style name="Theme.AppCompat.DayNight.DialogWhenLarge" parent="Theme.AppCompat.DialogWhenLarge"/>
<style name="Theme.AppCompat.DayNight.NoActionBar" parent="Theme.AppCompat.NoActionBar"/>
<style name="ThemeOverlay.AppCompat.DayNight" parent="ThemeOverlay.AppCompat.Dark"/>
</resources>

res/values/values.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2" xmlns:ns2="http://schemas.android.com/tools">
<!-- ... -->
<style name="Theme.MaterialComponents.DayNight" parent="Theme.MaterialComponents.Light"/>
<style name="Theme.MaterialComponents.DayNight.BottomSheetDialog" parent="Theme.MaterialComponents.Light.BottomSheetDialog"/>
<style name="Theme.MaterialComponents.DayNight.Bridge" parent="Theme.MaterialComponents.Light.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.DarkActionBar" parent="Theme.MaterialComponents.Light.DarkActionBar"/>
<style name="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge" parent="Theme.MaterialComponents.Light.DarkActionBar.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.Dialog" parent="Theme.MaterialComponents.Light.Dialog"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.Alert" parent="Theme.MaterialComponents.Light.Dialog.Alert"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.Alert.Bridge" parent="Theme.MaterialComponents.Light.Dialog.Alert.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.Bridge" parent="Theme.MaterialComponents.Light.Dialog.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.FixedSize" parent="Theme.MaterialComponents.Light.Dialog.FixedSize"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.FixedSize.Bridge" parent="Theme.MaterialComponents.Light.Dialog.FixedSize.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.MinWidth" parent="Theme.MaterialComponents.Light.Dialog.MinWidth"/>
<style name="Theme.MaterialComponents.DayNight.Dialog.MinWidth.Bridge" parent="Theme.MaterialComponents.Light.Dialog.MinWidth.Bridge"/>
<style name="Theme.MaterialComponents.DayNight.DialogWhenLarge" parent="Theme.MaterialComponents.Light.DialogWhenLarge"/>
<style name="Theme.MaterialComponents.DayNight.NoActionBar" parent="Theme.MaterialComponents.Light.NoActionBar"/>
<style name="Theme.MaterialComponents.DayNight.NoActionBar.Bridge" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge"/>
<!-- ... -->
</resources>
res/values-night-v8/values-night-v8.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MaterialComponents.DayNight" parent="Theme.MaterialComponents"/>
<style name="Theme.MaterialComponents.DayNight.BottomSheetDialog" parent="Theme.MaterialComponents.BottomSheetDialog"/>
<style name="Theme.MaterialComponents.DayNight.Bridge" parent="Theme.MaterialComponents.
Related Skills
node-connect
348.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
348.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
348.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
