转自:Android夜间模式最佳实践

由于Android的设置中并没有夜间模式的选项,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体验。目前越来越多的应用开始把夜间模式加到自家应用中,没准不久google也会把这项功能添加到Android系统中吧。

业内关于夜间模式的实现,有两种主流方案,各有其利弊,我较为推崇第三种方案:
1、通过切换theme来实现夜间模式。
2、通过资源id映射的方式来实现夜间模式。
3、通过修改uiMode来切换夜间模式。

值得一提的是,上面提到的几种方案,都是资源内嵌在Apk中的方案,像新浪微博那种需要通过下载方式实现的夜间模式方案,网上有很多介绍,这里不去讨论。

下面简要描述下几种方案的实现原理:

一、通过切换theme来实现夜间模式


首先在attrs.xml中,为需要随theme变化的内容定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources><attr name="textColor" format="color|reference" /><attr name="mainBackground" format="color|reference" />
</resources>

其次在不同的theme中,对属性设置不同的值,在styles.xml中定义theme如下

<?xml version="1.0" encoding="utf-8"?>
<resources><!-- 默认 --><style name="ThemeDefault" parent="Theme.AppCompat.Light.DarkActionBar"><item name="mainBackground">#ffffff</item><item name="textColor">#000000</item></style><!-- 夜间 --><style name="ThemeNight" parent="Theme.AppCompat.Light.DarkActionBar"><item name="mainBackground">#000000</item><item name="textColor">#ffffff</item></style>
</resources>

在布局文件中使用对应的值,通过?attr/属性名,来获取不同theme对应的值。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android="@+id/main_screen"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="?attr/mainBackground"><Button
        android:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="改变Theme"android:onClick="changeTheme"android:textColor="?attr/textColor"/>
</LinearLayout>

在Activity中调用如下changeTheme方法,其中isNightMode为一个全局变量用来标记当前是否为夜间模式,在设置完theme后,还需要调用restartActivity或者setContentView重新刷新UI。

public void changeTheme() {if (isNightMode) {setTheme(R.style.ThemeDefault);isNightMode = false;} else {setTheme(R.style.ThemeNight);isNightMode = true;}setContentView(R.layout.activity_main);
}

到此即完成了一个夜间模式的简单实现,包括Google自家在内的很多应用都是采用此种方式实现夜间模式的,这应该也是Android官方推荐的方式。

但这种方式有一些不足,规模较大的应用,需要随theme变化的属性会很多,都需要逐一定义,有点麻烦,另外一个缺点是要使得新theme生效,一般需要restartActivity来切换UI,会导致切换主题时界面闪烁。

不过也可以通过调用如下updateTheme方法,只更新需要更新的部分,规避闪烁问题,只是需要写上一堆updateTheme方法。

private void updateTheme() {TypedValue typedValue = new TypedValue();Resources.Theme theme = getTheme();theme.resolveAttribute(R.attr.textColor, typedValue, true);findViewById(R.id.button).setBackgroundColor(typedValue.data);theme.resolveAttribute(R.attr.mainBackground, typedValue, true);findViewById(R.id.main_screen).setBackgroundColor(typedValue.data);
}

二、通过资源id映射的方式实现夜间模式


通过id获取资源时,先将其转换为夜间模式对应id,再通过Resources来获取对应的资源。

public static Drawable getDrawable(Context context, int id) {return context.getResources().getDrawable(getResId(id));
}public static int getResId(int defaultResId) {if (!isNightMode()) {return defaultResId;}if (sResourceMap == null) {buildResourceMap();}int themedResId = sResourceMap.get(defaultResId);return themedResId == 0 ? defaultResId : themedResId;
}

这里是通过HashMap将白天模式的resId和夜间模式的resId来一一对应起来的。

private static void buildResourceMap() {sResourceMap = new SparseIntArray();sResourceMap.put(R.drawable.common_background, R.drawable.common_background_night);// ...
}

这个方案简单粗暴,麻烦的地方和第一种方案一样:每次添加资源都需要建立映射关系,刷新UI的方式也与第一种方案类似,貌似今日头条,网易新闻客户端等主流新闻阅读应用都是通过这种方式实现的夜间模式。

三、通过修改uiMode来切换夜间模式


首先将获取资源的地方统一起来,使用Application对应的Resources,在Application的onCreate中调用ResourcesManager的init方法将其初始化。

public static void init(Context context) {sRes = context.getResources();
}

切换夜间模式时,通过更新uiMode来更新Resources的配置,系统会根据其uiMode读取对应night下的资源,同时在res中给夜间模式的资源添加-night后缀,比如values-night,drawable-night。

public static void updateNightMode(boolean on) {DisplayMetrics dm = sRes.getDisplayMetrics();Configuration config = sRes.getConfiguration();config.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;config.uiMode |= on ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO;sRes.updateConfiguration(config, dm);
}

至于Android的资源读取,我们可以参考老罗的博客《Android应用程序资源的查找过程》,分析看看资源是怎么被精准找到的。这种方法相对前两种的好处就是资源添加非常简单清晰,但是UI上的更新还是无法做到非常顺滑的切换。

我是怎么找到第三种方案的?


在Android开发文档中搜索night发现如下,可以通过UiModeManager来实现

night: Night time
notnight: Day time
Added in API level 8.This can change during the life of your application if night mode is left in auto mode (default), in which case the mode changes based on the time of day. You can enable or disable this mode using UiModeManager. See Handling Runtime Changes for information about how this affects your application during runtime.

不幸的是必须在驾驶模式下才有效,那是不是打开驾驶模式再设置呢,实际上是不可行的,驾驶模式下系统UI有变动,这样是不可取的。

/**
* Sets the night mode. Changes to the night mode are only effective when
* the car or desk mode is enabled on a device.
*
* The mode can be one of:
* {@link #MODE_NIGHT_NO}- sets the device into notnight
* mode.
* {@link #MODE_NIGHT_YES} - sets the device into night mode.
* {@link #MODE_NIGHT_AUTO} - automatic night/notnight switching
* depending on the location and certain other sensors.
*/
public void setNightMode(int mode)

从源码开始看起,UiModeManagerService.java的setNightMode方法中:

if (isDoingNightModeLocked() && mNightMode != mode) {Settings.Secure.putInt(getContext().getContentResolver(), Settings.Secure.UI_NIGHT_MODE, mode);mNightMode = mode;updateLocked(0, 0);
}boolean isDoingNightModeLocked() {return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
}

在 isDoingNightModeLocked中判断了DockState和mCardMode的状态,如果满足条件实际上只修改了mNightMode的值,继续跟踪updateLocked方法,可以看到在updateConfigurationLocked中更新了Configuration的uiMode。

让我们转向Configuration的uiMode的描述:

/**
* Bit mask of the ui mode. Currently there are two fields:
*
The {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the
* device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED},
* {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK},
* {@link #UI_MODE_TYPE_CAR}, {@link #UI_MODE_TYPE_TELEVISION},
* {@link #UI_MODE_TYPE_APPLIANCE}, or {@link #UI_MODE_TYPE_WATCH}.
*
*
The {@link #UI_MODE_NIGHT_MASK} defines whether the screen
* is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED},
* {@link #UI_MODE_NIGHT_NO} or {@link #UI_MODE_NIGHT_YES}.
*/
public int uiMode;

uiMode为public可以直接设置,既然UiModeManager设置nightMode只改了Configuration的uiMode,那我们是不是可以直接改其uiMode呢?

实际上只需要上面一小段代码就可以实现了,但如果不去查看UiModeManager的夜间模式的实现,不会想到只需要更新Configuration的uiMode就可以了。

Android夜间模式最佳实践相关推荐

  1. android+夜间模式开发,高质量Android开发系列之(一)-Android夜间模式最佳实现

    由于Android的设置中并没有夜间模式的开关,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体检.当前越来越多的应用开始把夜间模式加到自家应用中,相信不久google也会把这项功能用到A ...

  2. Android夜间模式实践

    前言 由于项目需要,近段时间开发的夜间模式功能.主流的方案如下: 1.通过切换theme实现 2.通过resource id映射实现 3.通过Android Support Library的实现 方案 ...

  3. 基于阿里云移动推送的移动应用推送模式最佳实践

    摘要: ### 一.概念 以下概念对应系统设计时的语义,对于如何合理使用移动推送有借鉴意义 #### 1.1 设备 安装并使用开发者移动应用的装置 #### 1.2 设备ID 阿里云移动推送为设备分配 ...

  4. android自动夜间模式吗,Android夜间模式的实现方案

    原标题:Android夜间模式的实现方案 作者简介 本篇来自 Sunlight1024的投稿,详细地讲解了关于Android应用的夜间模式的实现,希望大家喜欢! Sunlight1024的博客地址: ...

  5. android自动夜间模式,夜晚的故事(android夜间模式实现)

    夜幕降临,他走在马路上,回想着今天发生的一切,他不敢相信事情就这样发生了.他最终还是决定拨打那个电话,掏出手机,解锁屏幕,突然一道强光从屏幕里毫无预兆的发射出来.他一个踉跄倒在了马路中央.而他身后伴随 ...

  6. Spring体系下单例策略模式,java策略模式最佳实践

    java springboot 策略模式最佳实践 本文将以最常用最好理解的一个业务场景--支付进行模拟 将不同支付场景业务拆分,后续增加.修改业务,逻辑解耦. 简单的逻辑流程如下: 业务类型为支付 i ...

  7. android 夜间模式 框架,Android 夜间模式的三种实现

    实现夜间模式有很多种方式,经过多次尝试,算是找到了一种性价比较高的方式. 主题方式 这是最正统的方式,但工作量巨大,因为要全局替换 xml 布局中所有硬编码的色值,将其换成主题色.然后通过换主题达到换 ...

  8. android os夜间,Android夜间模式实现

    查看我的全部开源项目[开源实验室] 欢迎加入我的QQ群:[201055521],本博客客户端下载[请点击] 本文原创,转载请注明地址:http://blog.kymjs.com/ 最近在做一个Andr ...

  9. android 夜间模式设置

    夜晚的故事(android夜间模式实现) 字数2003  阅读1393  评论11  喜欢18 夜幕降临,他走在马路上,回想着今天发生的一切,他不敢相信事情就这样发生了.他最终还是决定拨打那个电话,掏 ...

最新文章

  1. 在线作图|2分钟绘制三维PCA图
  2. 海贼王热血航线正在连接服务器,航海王热血航线连接服务器失败?解决方法一览...
  3. 认识JVM--第一篇-对象分配回收算法
  4. c# ioc 单例模式_Spring-IOC
  5. css3小球坠落,CSS3 圆球体内的小球碰撞运动
  6. jdbctemplate 开启事务_浅入浅出 Spring 事务传播实现原理
  7. python selenium使用JS新建标签(new tab)与切换标签
  8. Interop type 'jmail.POP3Class' cannot be embedded. Use the applicable interface instead.
  9. 《深入剖析Android系统》第9章RIL补充配图
  10. try catch finally return之间的关系
  11. 【华为机试题 HJ22】汽水瓶
  12. Java 枚举和泛型
  13. android 视频插件下载,轻视频动态壁纸插件
  14. 如何在Word中创建一个符合自己要求的样式
  15. 有苦有乐的算法 --- 使用栈结构实现队列结构
  16. 2021年电工(中级)试题及解析及电工(中级)模拟试题
  17. C++使用技巧(二十四):回顾vector用法及多维数组使用
  18. Servlet - 九大对象和四个作用域
  19. 什么是SEM,标准误
  20. html css 矩形边框,html – CSS:在纯CSS箭头上创建边框

热门文章

  1. Unity中AssetBundle打包文件大致解读
  2. java 线程管理_Java提供的线程池来创建多线程,进一步优化线程管理。
  3. 北京林业大c语言程序设计考试试题,2016年北京林业大学信息学院程序设计语言、数据结构(上机操作)复试笔试最后押题五套卷...
  4. swift 地图定位(五)指南针
  5. 温度补偿计算公式_热补偿计算实例
  6. 【每天1分钟】MarkDown语法学习之插入表格
  7. 1046. Shortest Distance
  8. 2-3文件+结构体实现实用系统
  9. iOS播放/渲染/解析MIDI
  10. 异形滚动轮播图---jquery实现