简介

Android 10(API级别29)版本开始,Google提供了深色模式。

官方介绍详见:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme

深色模式的优势

  • 更好的用户体验
  • 减少耗电量
  • 为弱视以及对强光敏感的用户提高可视性

深色模式的启用

  • 设置 -> 显示 -> 深色模式
  • 下拉通知栏中开启
  • Pixel 手机开启省电模式时会自动激活

适配

通过应用主题设配

要直接支持深色主题背景,可以将应用的主题设置为

<style name="AppTheme" parent="Theme.AppCompat.DayNight">

或者

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

DayNight主题会将应用主要的主题背景与系统控制的深色模式相关联,跟随系统的深色模式开启关闭而变化。

Theme.AppCompat.DayNight

res\values\values.xml

res\values-night-v8\values-night-v8.xml

以上截图可以看出,DayNight主题就是在values和values-night中分别定义了浅色和深色的主题,如果应用主题是直接继承DayNight主题,就不需要重复地声明对应的night主题资源了。

验证如下

主题继承DayNight,没有设置颜色(包括背景颜色及字体颜色),开启深色模式,系统会强制将背景及字体设置为深色模式,例如:

tips:控件如果设置了背景颜色或者字体颜色,不管系统是否已经开启深色模式,均显示为代码里设置的颜色。

应用内资源文件适配

常见的需要设置的资源有animdrawablelayoutvalues等,对于这些资源,可以使用限定符来提供一些备用资源;例如深色模式可以使用限定符-night来表示在深色模式中使用的资源,如下图:


tips:-night文件夹里只放置在深色模式中需要改变的资源,若浅色主题与深色主题使用的资源一样,则不需要在-night中新增;只在-night文件夹定义的资源而普通模式文件夹内没有的资源,能编译通过但是运行会崩溃。

网络图片适配

适配网络图片可以通过判断当前是否是应用深色主题,加载显示不同的图片。代码如下:

 /*** 判断当前是否是深色模式 * @param mContext 上下文* @return true:深色,false:浅色*/public static boolean isNightMode(Context mContext){return (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;}

Configuration.uiMode有三种Night的模式

  • UI_MODE_NIGHT_NO 表示当前使用的是notnight模式资源
  • UI_MODE_NIGHT_YES 表示当前使用的是night模式资源
  • UI_MODE_NIGHT_UNDEFINED 表示当前没有设置模式

直接设置使用深色模式

应用内可以直接调用AppCompatDelegate.setDefaultNightMode(),设置使用深色模式;共有以下几种场景:

  • 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层;底层的设置会覆盖上层的设置。

  • 系统层:是指在系统设置中开启深色模式

  • Applcation层:通过调用AppCompatDelegate.setDefaultNightMode()设置深色模式

    static {AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
    }
    
  • Activity层:通过调用getDelegate().setLocalNightMode()设置当前页面的深色模式。

    getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
    

setDefaultNightMode源码如下

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;}
}

其中主要是调用了applyDayNightToActiveDelegates()这个方法

private static void applyDayNightToActiveDelegates() {synchronized (sActivityDelegatesLock) {for (WeakReference<AppCompatDelegate> activeDelegate : sActivityDelegates) {final AppCompatDelegate delegate = activeDelegate.get();if (delegate != null) {if (DEBUG) {Log.d(TAG, "applyDayNightToActiveDelegates. Applying to " + delegate);}delegate.applyDayNight();}}}
}

通过遍历sActivityDelegates,调用了AppCompatDelegate的applyDayNight()方法

getDelegate().setLocalNightMode()主要也是调用了applyDayNight()方法

@SuppressWarnings("deprecation")
private boolean applyDayNight(final boolean allowRecreation) {if (mIsDestroyed) {if (DEBUG) {Log.d(TAG, "applyDayNight. Skipping because host is destroyed");}// If we're destroyed, ignore the callreturn false;}@NightMode final int nightMode = calculateNightMode();@ApplyableNightMode final int modeToApply = mapNightMode(mContext, nightMode);final boolean applied = updateForNightMode(modeToApply, allowRecreation);if (nightMode == MODE_NIGHT_AUTO_TIME) {getAutoTimeNightModeManager(mContext).setup();} else if (mAutoTimeNightModeManager != null) {// Make sure we clean up the existing managermAutoTimeNightModeManager.cleanup();}if (nightMode == MODE_NIGHT_AUTO_BATTERY) {getAutoBatteryNightModeManager(mContext).setup();} else if (mAutoBatteryNightModeManager != null) {// Make sure we clean up the existing managermAutoBatteryNightModeManager.cleanup();}return applied;
}

这里很多都是状态的重置计算等,其中有个updateForNightMode()的方法需要注意

/*** Updates the {@link Resources} configuration {@code uiMode} with the* chosen {@code UI_MODE_NIGHT} value.** @param mode The new night mode to apply* @param allowRecreation whether to attempt activity recreate* @return true if an action has been taken (recreation, resources updating, etc)*/
private boolean updateForNightMode(@ApplyableNightMode final int mode,final boolean allowRecreation) {boolean handled = false;final Configuration overrideConfig =createOverrideConfigurationForDayNight(mContext, mode, null);final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode();final int currentNightMode = mContext.getResources().getConfiguration().uiMode& Configuration.UI_MODE_NIGHT_MASK;final int newNightMode = overrideConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;if (DEBUG) {Log.d(TAG, String.format("updateForNightMode [allowRecreation:%s, currentNightMode:%d, "+ "newNightMode:%d, activityHandlingUiMode:%s, baseContextAttached:%s, "+ "created:%s, canReturnDifferentContext:%s, host:%s]",allowRecreation, currentNightMode, newNightMode, activityHandlingUiMode,mBaseContextAttached, mCreated, sCanReturnDifferentContext, mHost));}if (currentNightMode != newNightMode&& allowRecreation&& !activityHandlingUiMode&& mBaseContextAttached&& (sCanReturnDifferentContext || mCreated)&& mHost instanceof Activity&& !((Activity) mHost).isChild()) {// If we're an attached, standalone Activity, we can recreate() to apply using the// attachBaseContext() + createConfigurationContext() code path.// Else, we need to use updateConfiguration() before we're 'created' (below)if (DEBUG) {Log.d(TAG, "updateForNightMode. Recreating Activity: " + mHost);}ActivityCompat.recreate((Activity) mHost);handled = true;}if (!handled && currentNightMode != newNightMode) {// Else we need to use the updateConfiguration pathif (DEBUG) {Log.d(TAG, "updateForNightMode. Updating resources config on host: " + mHost);}updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode, null);handled = true;}if (DEBUG && !handled) {Log.d(TAG, "updateForNightMode. Skipping. Night mode: " + mode + " for host:" + mHost);}// Notify the activity of the night mode. We only notify if we handled the change,// or the Activity is set to handle uiMode changesif (handled && mHost instanceof AppCompatActivity) {((AppCompatActivity) mHost).onNightModeChanged(mode);}return handled;
}
  1. 根据传入的mode调用createOverrideConfigurationForDayNight()方法创建新的Configuration;
  2. 如果新模式!=旧模式、manifest中没有处理uiMode变化等条件满足时,调用ActivityCompat.recreate()重建activity;
  3. 如果没有处理第2点,并且新模式!=旧模式,调用updateResourcesConfigurationForNightMode()方法,主要是为了更新Configuration;并且判断是否需要回调onConfigurationChanged()方法通知activity;
  4. 如果处理了第2点或者第3点,此时深色模式有改动,会回调onNightModeChanged()方法通知activity。

综上可知

  • manifest中没有配置android:configChanges=“uiMode”,

直接调用AppCompatDelegate.setDefaultNightMode()或者getDelegate().setLocalNightMode(),

activity会重建,会回调onNightModeChanged()方法。

  • manifest中配置了android:configChanges=“uiMode”,

直接调用AppCompatDelegate.setDefaultNightMode()或者getDelegate().setLocalNightMode(),

activity不会重建,会回调onConfigurationChanged()和onNightModeChanged()方法。

forceDarkAllowed

针对已有的项目,除了对所有的颜色和图片定义night模式资源的自定义适配方法外,还可以使用forceDarkAllowed进行强制转换,但是效果比较生硬,可能没有自己适配那么完美。

可以分别对Theme及View设置强制深色模式,具体如下:

  • Theme:在主题中设置android:forceDarkAllowedtrue,这样系统在深色模式时会强制改变应用控件颜色,自动进行适配;

    <style name="AppStyle" parent="@android:style/Theme.Light.NoTitleBar"><item name="android:forceDarkAllowed">true</item>
    </style>
    
  • view:如果不希望某个view被强制夜间模式,可以直接调用view.setForceDarkAllowed(false)或者在xml布局中给view添加android:forceDarkAllowed="false"

<LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:forceDarkAllowed="false"/>

需要注意的是:

  1. 要强制深色模式生效必须开启硬件加速(默认开启);
  2. 在主题添加android:forceDarkAllowed仅对Light主题生效,对非Light的主题无效;
  3. 主题中若声明android:forceDarkAllowedfalse的话,无论view是否开启forceDarkAllowed都不会显示深色模式;
  4. 主题中若声明android:forceDarkAllowedtrue的话,view可以通过设置forceDarkAllowedfalse来关闭view的深色模式;
  5. 父view如果设置了forceDarkAllowedfalse的话,子view就算是设置forceDarkAllowedtrue,也无法开启深色模式;
  6. view中的forceDarkAllowed属性默认为true

综上可得出:forceDarkAllowed设置为false的优先

直接使用forceDarkAllowed强制开启深色模式效果较生硬,forceDarkAllowed一般在view上使用,避免已经适配好深色模式的view被系统强制修改了。

总结

深色模式适配总体上来说,并不复杂,主要是细致,并且前期就确定好适配方案。以下是个人总结的三种适配方案:

  • 直接跟随系统开启或关闭深色模式

    优点

    1. 直接跟随系统切换模式,只需要配置好notnight模式及night模式对应的资源即可,适配性较好,工作量小;
    2. 在系统层做颜色处理,比Java效率高。

    缺点

    1. 若应用在运行中,栈内所有activity会重建;
    2. 针对用户已操作的比如选择框、列表滑动等,activity重建相当于重新渲染此页面,用户之前操作内容不会保存,用户体验不好;
    3. 应用运行中切换深色模式状态,activity重建,可能会导致意想不到的崩溃,需要每个页面都进行排查,工作量较大。
  • manifest配置android:configChanges=“uiMode”,通过监听onConfigurationChanged(),直接通过代码逻辑更改显示样式

    优点

    1. 页面保持当前状态,activity不会重建,用户体验较好。

    缺点

    1. 不会跟随系统切换显示样式,需要在onConfigurationChanged回调中通过一系列代码逻辑更改显示样式,工作量较大。
  • manifest配置android:configChanges=“uiMode”,通过onConfigurationChanged()监听在深色模式改变时给予提示,类似弹窗告知用户模式改变,需要重启才能生效,用户点击同意了再重启app

    优点

    1. 具有跟随系统切换模式的全部优点;
    2. 能避免activity重建带来的不可预料的崩溃以及减少重新渲染页面深/浅色的代码量。

    缺点

    1. 每次深色模式切换时,都需要用户重启应用才能生效。

Android 10 深色模式适配相关推荐

  1. Android 10深色主题适配踩坑记录

    1. 问题简述 Android 10 推出了深色主题,便于用户根据白天和夜晚自由切换合适的主题.在适配的过程中,要特别注意,切换主题会导致当前activity被重建,也就是会重新走一遍Activity ...

  2. android自动切换暗色,超实用!Android 深色模式适配(可定时开启的APP内主题切换管理工具)...

    前言 前面分享了一篇"黑白化主题"的文,主要适用场景是不久就要到来的"清明"等时节或者是其他的国家公祭日什么的(一名成熟的程序员,要学会自己提产品需求). 今天 ...

  3. Android深色模式适配原理分析,android应用开发

    return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI ...

  4. Android Q暗色模式适配踩坑—状态栏

    暗色模式已经不是什么新鲜玩意了,大家最近看到关于暗色模式最多的内容可能就是iOS版本微信未适配暗色模式面临被AppStore下架的风险.然后今天早上一醒来,发现Android的微信也黑了(因为我手机一 ...

  5. AndroidQ(10)黑暗模式适配

    前言:作为一个Android程序员,每年最期待就是Google的发布会啦!!这不,今年的AndroidQ如期而至.这里简单介绍一下Android的新特性: AndroidQ全局暗黑模式 隐私权限的更新 ...

  6. H5页面系统深色模式适配

    1. 在css中设置CSS 媒体查询 PC web模式适配,在css中通过media查询进行适配即可生效. /*日间模式*/ @media (prefers-color-scheme: light) ...

  7. Web前端深色模式适配方案

    众所周知,GitHub 网站在前不久支持了深色模式.我在看 GitHub 的时候发现浏览器默认的滚动条也变成了深色样式: 我当时猜测应该有一个属性可以声明配色方案,然后浏览器根据声明的配色方案采用相应 ...

  8. Android切换深色模式导致布局字体变小的解决方案

    切换深色模式导致布局字体变小问题困扰了我很久,一直排查自身代码问题却没发现并非自身代码导致,而是使用了今日头条屏幕适配方案AndroidAutoSize导致的,目前暂时在小米手机安卓11系统发现,切换 ...

  9. android 分屏模式适配,安卓适配分屏注意事项

    分屏模式下可强制应用横屏,所以注意应用内强制竖屏的页面适配横屏显示 分屏模式下,获取应用所占用的宽高 getResources().getConfiguration().screenWidthDp g ...

  10. Android 10 录屏适配

    Android 8.0以后android得权限有所更改,但是影响录屏得得权限目前只影响Android Q版本,具体原理请看下面这篇博客,是他人所写,很是详细. https://blog.csdn.ne ...

最新文章

  1. php的封装继承多态,PHP面向对象深入理解之二(封装、继承、多态、克隆)
  2. android 自定义频谱,android – 如何从实时音频开发频谱分析仪?
  3. 本人使用abapgit遇到的一些错误
  4. 【转】系统缓存全解析二:动态缓存(4)-第三方分布式缓存解决方案 Memcache(2)...
  5. kotlin函数式编程_我最喜欢的Kotlin函数式编程示例
  6. git 修改默认分支为main_Git:基本操作
  7. mysql 安全问题_浅谈MySQL数据库的Web安全问题
  8. 在fedora20下面手动为自己的安装程序创建桌面图标
  9. 搜狗开源srpc:自研高性能通用RPC框架
  10. 手机wifi java_Android中使用WIFI来连接ADB
  11. Profinet Commander下载方法
  12. 小心!第三方支付最常遇到的 6 大骗局!
  13. 人工智能成热门 苹果谷歌等科技公司竞相涌入改变神经科学研究
  14. 知识 | 四种渲染到底是啥?终于有人讲明白了(下)
  15. uni-app 杂记
  16. 【DuerOS开发日记】2.打造属于自己的小度(1):使用PythonSDK
  17. Windows 10 IDM 下载play.kth.se上面的网课视频
  18. 综述 | 最新双曲深度神经网络综述论文
  19. matplotlib基础绘图命令之pie
  20. 论文投稿指南——中文核心期刊推荐(原子能技术)

热门文章

  1. 大数据如何可以推动员工敬业度
  2. docker批量导出导入全部镜像
  3. ThinkPHP5.0 查询条件where()使用
  4. Redis高级特性之神奇的HyperLoglog解决统计问题
  5. 微信程序开发之小程序入门
  6. mac 当前位置打开终端
  7. Stack Frame JAVA运行时数据区域之栈帧
  8. 用Photoshop进行icon的制作或将其它格式图片转成icon
  9. Android 设备接入扫码枪,Android 设备接入扫码枪
  10. 蛋白质组学与转录组学联合分析