如何在不重新启动活动的情况下切换主题(夜间模式)?

76

我做了几个支持多主题的应用程序,但每当用户切换主题时,我总是不得不重新启动应用程序,因为setTheme()需要在setContentView()之前调用。

直到我发现了这个应用程序,它可以无缝地在两个主题之间切换,并且带有过渡/动画效果!

enter image description here

请给我一些关于如何实现这个功能(包括动画)的提示。谢谢!


1
这个视频是一个真实的应用程序吗? - Blackbelt
1
@Blackbelt 是的,我录制了视频/动图。这个应用程序叫做“知乎”。 - WSBT
2
嘿朋友,你找到像知乎一样更改主题的解决方案了吗? - John Error
1
@JohnError 还没有。你有解决方案吗?如果你正在寻找一个,可以点赞该问题以提高可见度。 - WSBT
@user1032613,你找到解决方案了吗?现在很多应用程序都在这样做。 - Mateen Chaudhry
7个回答

64

@Alexander Hanssen的回答基本上已经回答了这个问题...不知道为什么没有被接受...可能是因为finish()/startActivity()的原因。 我投了票,但我试图发表评论却无法...

无论如何,就样式而言,我会完全按照他所描述的去做。

<style name="AppThemeLight" parent="Theme.AppCompat.Light">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<!-- This will set the fade in animation on all your activities by default -->
<style name="WindowAnimationTransition">
    <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
    <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>

但是,不是以新的意图结束/开始:

Intent intent = new Intent(this, <yourclass>.class);
startActivity(intent);
finish();

我会做:

@Override
protected void onCreate(Bundle savedInstanceState) {

    // MUST do this before super call or setContentView(...)
    // pick which theme DAY or NIGHT from settings
    setTheme(someSettings.get(PREFFERED_THEME) ? R.style.AppThemeLight : R.style.AppThemeDark);

    super.onCreate(savedInstanceState);
}

// Somewhere in your activity where the button switches the theme
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

        // decide which theme to use DAY or NIGHT and save it
        someSettings.save(PREFFERED_THEME, isDay());

        Activity.this.recreate();
    }
});

效果如视频所示...


2
杀死创建的活动和重新创建活动之间有什么区别?两者都会重新创建活动,只是动画不可见...如果我希望在切换主题时保留我的editText数据,这段代码将无法工作。 - Android Geek
2
这是一个老问题,但我在使用这个解决方案时遇到了问题。更准确地说,除了动画之外,一切都按预期工作。我还尝试使用自定义动画,但无论我在xml中设置的持续时间是多少,动画都会在20-30ms后停止。你建议的原始动画也是如此。我在一个用于设置的片段中使用getActivity()重新创建了活动。 - m.i.n.a.r.
1
@KishanSolanki 我在我的主活动中这样做。我认为这个主题适用于整个应用程序。 - GKA
2
我在这个解决方案中遇到了问题,我在活动中使用了片段。当我重新启动活动时,我会失去片段中的数据,例如滚动列表位置。 - Majid Sadeghi
2
@MajidSadeghi 这是一个标准的Android生命周期问题。在活动的生命周期钩子中保存和恢复所需的信息。 - GKA
显示剩余2条评论

17

当您重新启动活动时,转换/动画使主题更改无缝,可以通过将项目"android:windowanimationStyle"添加到您的主题中,并引用一个样式,在其中指定Activity进入和退出时应如何进行动画处理来实现这一点。 请注意,这会使动画适用于使用该主题的所有活动。

<style name="AppThemeLight" parent="Theme.AppCompat.Light">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<!-- This will set the fade in animation on all your activities by default -->
<style name="WindowAnimationTransition">
    <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
    <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>

然后,当你想要改变主题时,你可以在点击按钮时执行以下操作:

AppSettings settings = AppSettings.getInstance(this);
settings.set(AppSettings.Key.USE_DARK_THEME,
!settings.getBoolean(AppSettings.Key.USE_DARK_THEME));
Intent intent = new Intent(this, <yourclass>.class);
startActivity(intent);
finish();

然后在您的onCreate方法中,使用setTheme()将当前在AppSettings中设置的主题应用于此:

AppSettings settings = AppSettings.getInstance(this);
setTheme(settings.getBoolean(AppSettings.Key.USE_DARK_THEME) ? R.style.AppThemeDark : R.style.AppThemeLight);
super.onCreate(savedInstanceState);
setContentView(<yourlayouthere>);

查看此gist以供参考:https://gist.github.com/alphamu/f2469c28e17b24114fe5


2
虽然这个理论上回答了问题,但最好在这里包含答案的关键部分,并提供参考链接。 - Enamul Hassan
2
感谢您的反馈,我编辑了我的答案并包含了必要的部分。 - Alexander Hanssen
“finish” 只是退出应用程序吗? - user5306470

5

针对那些正在寻找 Android 10 及以上版本解决方案的人。

使用以下代码设置深色/浅色模式:

AppCompatDelegate.setDefaultNightMode(state) //state can be AppCompatDelegate.MODE_NIGHT_YES or AppCompatDelegate.MODE_NIGHT_NO

它会改变您的应用程序的显示,但会出现闪烁。

为了避免活动重建时的闪烁(以实现平滑过渡),请在您的活动中添加以下方法。

@Override
    public void recreate() {
        finish();
        overridePendingTransition(R.anim.anime_fade_in,
                                  R.anim.anime_fade_out);
        startActivity(getIntent());
        overridePendingTransition(R.anim.anime_fade_in,
                                  R.anim.anime_fade_out);
    }

3
虽然这对于多活动应用程序看起来很好,但对于单活动/多片段应用程序并不总是有效。在这种情况下,像这样重新创建活动并不能将用户带回到他们原来的位置。 - m_katsifarakis

4
在GKA的答案中,在super.onCreate(savedInstanceState)之前调用setTheme()是完美的方法,而且效果很好,感谢GKA。
但这会再次为所有资源创建新实例,包括活动、片段和回收站视图。我认为这可能是一项繁重的工作,并导致某些保存的数据(如本地变量)丢失。
根据Google文档所述:https://developer.android.com/reference/android/app/Activity#recreate(),这将导致此Activity重新创建一个新实例。这基本上与由于配置更改而创建Activity时相同的流程相同——当前实例将通过其生命周期到onDestroy(),然后创建一个新实例。
还有另一种方法,您可以使用代码(Java或Kotlin)以编程方式更改主题,在这种方法中,您不需要重新创建所有资源,而且您还可以使用自定义动画,如涟漪效果。
请检查我的GitHub库:https://github.com/imandolatkia/Android-Animated-Theme-Manager 在这个库中,您可以创建自定义主题并在不重新创建任何资源的情况下使用涟漪动画动态更改它们。 enter image description here

2

在片段中简单高效的一行代码:

requireActivity().recreate();

针对此次活动:

recreate();

1

没有任何东西阻止你调用 setTheme() 然后再次调用 setContentView()。 但是,您需要重新构建您的应用程序,以便如果更改主题,您需要重新初始化可能持有对 View 对象引用的任何成员变量。


你认为视频中的应用程序开发人员使用了这个吗?从动画来看,我觉得他们可能没有走这个方向。 - WSBT
这绝对是最简单的方法。我已经构建了类似这样的应用程序。只要您的布局不太复杂,这并不是什么大问题。我能想到的唯一另一个选择是编写代码来遍历整个“View”树,并在每个“View”上设置“Theme”。 - David Wasser

0

以上所有的答案并不能真正防止活动被重新创建! 正确的解决方案应该是这样的:

  1. AndroidManifest.xml文件的configChanges字段中添加"uiMode",以避免在切换到暗黑模式时重新创建活动。

android:configChanges="uiMode"

  1. 在活动的onConfigureChanged()函数中,在切换到暗黑模式时手动更新所有的视图颜色

您可以参考这篇文章获取更多详细信息: https://proandroiddev.com/daynight-applying-dark-mode-without-recreating-your-app-c8a62d51092d


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接