在本篇文章中给出了三种实现日间/夜间模式切换的方案,三种方案综合起来可能导致文章的篇幅过长,请耐心阅读。

1、使用 setTheme的方法让 Activity重新设置主题;

2、设置 Android Support Library中的 UiMode来支持日间/夜间模式的切换;

3、通过资源 id 映射,回调自定义 ThemeChangeListener接口来处理日间/夜间模式的切换。

一、使用 setTheme 方法

我们先来看看使用 setTheme方法来实现日间/夜间模式切换的方案。这种方案的思路很简单,就是在用户选择夜间模式时,Activity 设置成夜间模式的主题,之后再让 Activity调用 recreate() 方法重新创建一遍就行了。

那就动手吧,在 colors.xml 中定义两组颜色,分别表示日间和夜间的主题色:

#3F51B5

#303F9F

#FF4081

#3b3b3b

#383838

#a72b55

之后在 styles.xml 中定义两组主题,也就是日间主题和夜间主题:

@color/colorPrimary

@color/colorPrimaryDark

@color/colorAccent

@android:color/black

@android:color/white

@color/nightColorPrimary

@color/nightColorPrimaryDark

@color/nightColorAccent

@android:color/white

@color/nightColorPrimaryDark

在主题中的 mainBackground属性是我们自定义的属性,用来表示背景色:

接下来就是看一下布局 activity_main.xml:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="?attr/mainBackground"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.yuqirong.themedemo.MainActivity">

android:id="@+id/btn_theme"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="切换日/夜间模式" />

android:id="@+id/tv"

android:layout_below="@id/btn_theme"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center_horizontal"

android:text="通过setTheme()的方法" />

在 的 android:background属性中,我们使用 "?attr/mainBackground" 来表示,这样就代表着 RelativeLayout的背景色会去引用在主题中事先定义好的 mainBackground属性的值。这样就实现了日间/夜间模式切换的换色了。

最后就是 MainActivity的代码:

public class MainActivity extends AppCompatActivity {

// 默认是日间模式

private int theme = R.style.AppTheme;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// 判断是否有主题存储

if(savedInstanceState != null){

theme = savedInstanceState.getInt("theme");

setTheme(theme);

}

setContentView(R.layout.activity_main);

Button btn_theme = (Button) findViewById(R.id.btn_theme);

btn_theme.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

theme = (theme == R.style.AppTheme) ? R.style.NightAppTheme : R.style.AppTheme;

MainActivity.this.recreate();

}

});

}

@Override

protected void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

outState.putInt("theme", theme);

}

@Override

protected void onRestoreInstanceState(Bundle savedInstanceState) {

super.onRestoreInstanceState(savedInstanceState);

theme = savedInstanceState.getInt("theme");

}

}

在 MainActivity中有几点要注意一下:

1、调用 recreate()方法后 Activity 的生命周期会调用 onSaveInstanceState(Bundle outState) 来备份相关的数据,之后也会调用 onRestoreInstanceState(Bundle savedInstanceState) 来还原相关的数据,因此我们把 theme的值保存进去,以便 Activity 重新创建后使用。

2、我们在 onCreate(Bundle savedInstanceState)方法中还原得到了 theme值后,setTheme()方法一定要在 setContentView()方法之前调用,否则的话就看不到效果了。

3、recreate() 方法是在 API 11 中添加进来的,所以在 Android 2.X 中使用会抛异常。

贴完上面的代码之后,我们来看一下该方案实现的效果图:

二、使用 Android Support Library 中的 UiMode 方法

使用 UiMode 的方法也很简单,我们需要把 colors.xml 定义为日间/夜间两种。之后根据不同的模式会去选择不同的 colors.xml 。在 Activity 调用 recreate()之后,就实现了切换日/夜间模式的功能。

说了这么多,直接上代码。下面是 values/colors.xml :

#3F51B5

#303F9F

#FF4081

#FF000000

#FFFFFF

除了 values/colors.xml 之外,我们还要创建一个 values-night/colors.xml 文件,用来设置夜间模式的颜色,其中 的 name 必须要和 values/colors.xml 中的相对应:

#3b3b3b

#383838

#a72b55

#FFFFFF

#3b3b3b

在 styles.xml 中去引用我们在 colors.xml 中定义好的颜色:

@color/colorPrimary

@color/colorPrimaryDark

@color/colorAccent

@color/textColor

@color/backgroundColor

activity_main.xml 布局的内容和上面 setTheme()方法中的相差无几,这里就不贴出来了。之后的事情就变得很简单了,在 MyApplication 中先选择一个默认的 Mode :

public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

// 默认设置为日间模式

AppCompatDelegate.setDefaultNightMode(

AppCompatDelegate.MODE_NIGHT_NO);

}

}

要注意的是,这里的 Mode 有四种类型可以选择:

1、MODE_NIGHT_NO: 使用亮色(light)主题,不使用夜间模式;

2、MODE_NIGHT_YES:使用暗色(dark)主题,使用夜间模式;

3、MODE_NIGHT_AUTO:根据当前时间自动切换 亮色(light)/暗色(dark)主题;

4、MODE_NIGHT_FOLLOW_SYSTEM(默认选项):设置为跟随系统,通常为 MODE_NIGHT_NO

当用户点击按钮切换日/夜间时,重新去设置相应的 Mode :

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button btn_theme = (Button) findViewById(R.id.btn_theme);

btn_theme.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;

getDelegate().setLocalNightMode(currentNightMode == Configuration.UI_MODE_NIGHT_NO

? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);

// 同样需要调用recreate方法使之生效

recreate();

}

});

}

}

我们来看一下 UiMode方案实现的效果图:

就前两种方法而言,配置比较简单,最后的实现效果也都基本上是一样的。但是缺点就是需要调用 recreate() 使之生效。而让 Activity 重新创建就必须涉及到一些状态的保存。这就增加了一些难度。所以,我们一起来看看第三种解决方法。

通过资源 id 映射,回调接口

第三种方法的思路就是根据设置的主题去动态地获取资源 id 的映射,然后使用回调接口的方式让 UI 去设置相关的属性值。我们在这里先规定一下:夜间模式的资源在命名上都要加上后缀 “_night” ,比如日间模式的背景色命名为 color_background ,那么相对应的夜间模式的背景资源就要命名为 color_background_night 。好了,下面就是我们的 Demo 所需要用到的 colors.xml :

#3F51B5

#3b3b3b

#303F9F

#383838

#FF4081

#a72b55

#FF000000

#FFFFFF

#FFFFFF

#3b3b3b

可以看到每一项 color 都会有对应的 “_night” 与之匹配。

看到这里,肯定有人会问,为什么要设置对应的 “_night” ?到底是通过什么方式来设置日/夜间模式的呢?下面就由 ThemeManager 来为你解答:

public class ThemeManager {

// 默认是日间模式

private static ThemeMode mThemeMode = ThemeMode.DAY;

// 主题模式监听器

private static List mThemeChangeListenerList = new LinkedList<>();

// 夜间资源的缓存,key : 资源类型, 值

private static HashMap> sCachedNightResrouces = new HashMap<>();

// 夜间模式资源的后缀,比如日件模式资源名为:R.color.activity_bg, 那么夜间模式就为 :R.color.activity_bg_night

private static final String RESOURCE_SUFFIX = "_night";

/**

* 主题模式,分为日间模式和夜间模式

*/

public enum ThemeMode {

DAY, NIGHT

}

/**

* 设置主题模式

*

* @param themeMode

*/

public static void setThemeMode(ThemeMode themeMode) {

if (mThemeMode != themeMode) {

mThemeMode = themeMode;

if (mThemeChangeListenerList.size() > 0) {

for (OnThemeChangeListener listener : mThemeChangeListenerList) {

listener.onThemeChanged();

}

}

}

}

/**

* 根据传入的日间模式的resId得到相应主题的resId,注意:必须是日间模式的resId

*

* @param dayResId 日间模式的resId

* @return 相应主题的resId,若为日间模式,则得到dayResId;反之夜间模式得到nightResId

*/

public static int getCurrentThemeRes(Context context, int dayResId) {

if (getThemeMode() == ThemeMode.DAY) {

return dayResId;

}

// 资源名

String entryName = context.getResources().getResourceEntryName(dayResId);

// 资源类型

String typeName = context.getResources().getResourceTypeName(dayResId);

HashMap cachedRes = sCachedNightResrouces.get(typeName);

// 先从缓存中去取,如果有直接返回该id

if (cachedRes == null) {

cachedRes = new HashMap<>();

}

Integer resId = cachedRes.get(entryName + RESOURCE_SUFFIX);

if (resId != null && resId != 0) {

return resId;

} else {

//如果缓存中没有再根据资源id去动态获取

try {

// 通过资源名,资源类型,包名得到资源int值

int nightResId = context.getResources().getIdentifier(entryName + RESOURCE_SUFFIX, typeName, context.getPackageName());

// 放入缓存中

cachedRes.put(entryName + RESOURCE_SUFFIX, nightResId);

sCachedNightResrouces.put(typeName, cachedRes);

return nightResId;

} catch (Resources.NotFoundException e) {

e.printStackTrace();

}

}

return 0;

}

/**

* 注册ThemeChangeListener

*

* @param listener

*/

public static void registerThemeChangeListener(OnThemeChangeListener listener) {

if (!mThemeChangeListenerList.contains(listener)) {

mThemeChangeListenerList.add(listener);

}

}

/**

* 反注册ThemeChangeListener

*

* @param listener

*/

public static void unregisterThemeChangeListener(OnThemeChangeListener listener) {

if (mThemeChangeListenerList.contains(listener)) {

mThemeChangeListenerList.remove(listener);

}

}

/**

* 得到主题模式

*

* @return

*/

public static ThemeMode getThemeMode() {

return mThemeMode;

}

/**

* 主题模式切换监听器

*/

public interface OnThemeChangeListener {

/**

* 主题切换时回调

*/

void onThemeChanged();

}

}

上面 ThemeManager 的代码基本上都有注释,想要看懂并不困难。其中最核心的就是 getCurrentThemeRes方法了。在这里解释一下 getCurrentThemeRes的逻辑。参数中的 dayResId 是日间模式的资源id,如果当前主题是日间模式的话,就直接返回 dayResId 。反之当前主题为夜间模式的话,先根据 dayResId 得到资源名称和资源类型。比如现在有一个资源为 R.color.colorPrimary ,那么资源名称就是 colorPrimary ,资源类型就是 color 。然后根据资源类型和资源名称去获取缓存。如果没有缓存,那么就要动态获取资源了。这里使用方法的是

context.getResources().getIdentifier(String name, String defType, String defPackage)

name 参数就是资源名称,不过要注意的是这里的资源名称还要加上后缀 “_night” ,也就是上面在 colors.xml 中定义的名称;

defType 参数就是资源的类型了。比如 color,drawable等;

defPackage 就是资源文件的包名,也就是当前 APP 的包名。

有了上面的这个方法,就可以通过 R.color.colorPrimary 资源找到对应的 R.color.colorPrimary_night资源了。最后还要把找到的夜间模式资源加入到缓存中。这样的话以后就直接去缓存中读取,而不用再次去动态查找资源 id 了。

ThemeManager 中剩下的代码应该都是比较简单的,相信大家都可以看得懂了。

现在我们来看看 MainActivity 的代码:

public class MainActivity extends AppCompatActivity implements ThemeManager.OnThemeChangeListener {

private TextView tv;

private Button btn_theme;

private RelativeLayout relativeLayout;

private ActionBar supportActionBar;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ThemeManager.registerThemeChangeListener(this);

supportActionBar = getSupportActionBar();

btn_theme = (Button) findViewById(R.id.btn_theme);

relativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout);

tv = (TextView) findViewById(R.id.tv);

btn_theme.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

ThemeManager.setThemeMode(ThemeManager.getThemeMode() == ThemeManager.ThemeMode.DAY

? ThemeManager.ThemeMode.NIGHT : ThemeManager.ThemeMode.DAY);

}

});

}

public void initTheme() {

tv.setTextColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.textColor)));

btn_theme.setTextColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.textColor)));

relativeLayout.setBackgroundColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.backgroundColor)));

// 设置标题栏颜色

if(supportActionBar != null){

supportActionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.colorPrimary))));

}

// 设置状态栏颜色

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

Window window = getWindow();

window.setStatusBarColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.colorPrimary)));

}

}

@Override

public void onThemeChanged() {

initTheme();

}

@Override

protected void onDestroy() {

super.onDestroy();

ThemeManager.unregisterThemeChangeListener(this);

}

}

在 MainActivity 中实现了 OnThemeChangeListener接口,这样就可以在主题改变的时候执行回调方法。然后在initTheme()中去重新设置 UI 的相关颜色属性值。还有别忘了要在 onDestroy()中移除 ThemeChangeListener 。

最后就来看看第三种方法的效果吧:

也许有人会说和前两种方法的效果没什么差异啊,但是仔细看就会发现前面两种方法在切换模式的瞬间会有短暂黑屏现象存在,而第三种方法没有。这是因为前两种方法都要调用 recreate()。而第三种方法不需要 Activity 重新创建,使用回调的方法来实现。

三个方法对比

到了这里,按照套路应该是要总结的时候了。那么就根据上面给的三种方法来一个简单的对比吧:

setTheme方法:可以配置多套主题,比较容易上手。除了日/夜间模式之外,还可以有其他五颜六色的主题。但是需要调用 recreate() ,切换瞬间会有黑屏闪现的现象;

UiMode方法:优点就是 Android Support Library 中已经支持,简单规范。但是也需要调用 recreate(),存在黑屏闪现的现象;

动态获取资源 id ,回调接口:该方法使用起来比前两个方法复杂,另外在回调的方法中需要设置每一项 UI 相关的属性值。但是不需要调用 recreate(),没有黑屏闪现的现象。

总结

以上就是这篇文章的全部内容了,希望能对各位Android开发者们有所帮助。

android自动夜间模式,Android实现日夜间模式的深入理解相关推荐

  1. android 自动 键盘,关于Android中的软键盘

    InputMethodService为我们的输入法创建了一个Dialog,并且将该Dialog的Window的某些参数(如Gravity)进行了设置,使之能够在底部或者全屏显示.当我们点击输入框时,系 ...

  2. android自动添加包,android nfc写入应用程序包名与网址 自动打开应用程序与网址...

    安卓手机大部分手机提供了NFC芯片   可以根据NFC传递数据   NFC卡与手机  手机与手机数据等交互  非常好的功能,这篇文章教大家如何向NFC卡或标签写入功能. 用法 :  点击包名   将N ...

  3. android自动更新demo,Android程序自动更新功能模块的实现方法【附完整demo源码下载】...

    本文实例讲述了Android程序自动更新功能模块的实现方法.分享给大家供大家参考,具体如下: 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程序启动的时候首先调用更 ...

  4. android自动创建快捷方式,Android开发之生成桌面快捷方式细则(原创)

    本文已独家授权 郭霖 ( guolin_blog) 公众号发布! 申明,标题里的快捷方式不是指开发人员使用频率极高的Ctrl+C和Ctrl+V:也不是IDE里Ctrl+D.Ctrl+F等常用快捷键.这 ...

  5. android自动切换图片,Android应用中图片浏览时实现自动切换功能的方法详解

    先给最终效果图: 当我们在最下边的gallery中切换图片时,上面的大图片会自动切换,切换时有动画效果哦,很简单的一个程序,有待完善更多的功能! activity代码: package cn.com. ...

  6. Android 自动搜索频道,Android自定义收音机搜台控件RadioRulerView

    前言:像这类的自定义控件有非常多的开源项目,但还是没有找到我项目想要的,所以简单实现了一个,下面简单讲讲实现原理. 效果图: 实现思路: 首先画固定背景尺子,而实现这个则要计算刻度线的宽度.刻度线间的 ...

  7. android 自动挂断,android实现接通和挂断电话

    本文实例为大家分享了android实现接通和挂断电话的具体代码,供大家参考,具体内容如下 关键代码:[PhoneUtils类] package com.ebupt.phonerecorddemo.se ...

  8. android 自动挂断,android 来电自动接听和自动挂断(2.3以上)

    转自http://stephen830.iteye.com/blog/1181786java android 来电自动接听和自动挂断android 注意:android2.3版本不支持下面的自动接听方 ...

  9. android 自动播放 幻灯片,Android自动播放Banner图片轮播效果

    本文实例为大家分享了Android自动播放Banner图片轮播的具体代码,供大家参考,具体内容如下 先看一下效果图 支持本地图片以及网络图片or本地网络混合. 使用方式: android:id=&qu ...

  10. Android自动伸展动画,android – 如何实现平滑的展开/折叠动画

    我指的是在这里找到的扩展/折叠动画代码. 虽然这样做不好,动画不顺利. 我做一些登录代码. public static void expand(final View v) { v.measure(Me ...

最新文章

  1. BAPI_PO_CHANGE修改NETPRICE
  2. window中osmnx包的详细安装过程
  3. 和ajax区别_AJAX、Fetch和Axios的细微区别
  4. C++之queue和dequeu用法
  5. xshell连接Linxu系统乱码
  6. python参考手册下载_python学习手册下载|
  7. 阿里云盾证书服务助力博客装逼成功
  8. Axure 9软件功能区域、界面介绍
  9. 【干货】Chrome插件(扩展)开发全攻略
  10. 有限元:什么是有限元分析法
  11. 如何批量实现通过MP3标题重命名文件名
  12. MySQL基于位置的恢复
  13. 鼠标滚轮乱跳解决方法
  14. excel如何拆分表格?
  15. Twitter Inc.(TWTR)2020年第三季度收益电话会议记录
  16. 基础内网信息探针思路
  17. 尼尔森十大易用性原则驱动的测试
  18. 这些线上峰会为何不约而同选择亿联视讯方案?云端协同、流畅安全是关键!
  19. ESP01S CH340 一键下载电路设计
  20. Elasticsearch 6.4 ingest-attachment对文件IK分词器全文检索

热门文章

  1. Vue项目首页_热销推荐组件、周末游组件开发
  2. Tryhackme-Windows Exploitation Basics
  3. Vero tempora officia nesciunt unde consequuntuIncidunt voluptatem minus earum.r.
  4. win10系统快速进入bios的设置方法
  5. 纵向表格转为横向表格
  6. win10浏览器闪退_win10系统ie打不开闪退怎么办
  7. 计算机专业高数学科难度,大学里极其有难度的4个专业,挂科率很高,不是学霸学不明白...
  8. 小孟5w接了个盲盒小程序,三周开发完毕
  9. 安装向日葵远程后电脑亮度和夜间模式不能使用了——故障笔记
  10. Google 人工智能基本原则