实现用户选择主题

40
我想让用户在几个不同的主题之间选择,并想知道这样做是否可以。我用这种方法做了一个小测试,它可以工作,但我认为可能有更好的方法,而且可能会在以后造成一些问题,因此想问一下。
我想为每个主题创建一个不同的布局,在 onCreate 中只需要使用 setContentView() 方法的开关。我会先加载一个保存的 SharedPreference 值(整数),根据该值显示相应的布局。显然,用户可以通过按钮或其他方式更改 SharedPreference 值。
由于这些布局基本上是相同的,只是颜色不同,所以我希望在每个布局文件中使用相同的 TextViews 和其他视图的ID。我主要的问题是这样做是否会引起问题?
对不起没有代码的文字墙。我想获得这种情况的一般最佳实践的概念。提前感谢您。
4个回答

51
我在我的应用程序中实际上有这个功能,此外,我允许用户在运行时更改主题。由于从偏好设置中读取值需要一些时间,因此我通过全局可访问函数获取一个主题ID,该函数保存了缓存值。
正如已经指出的那样 - 使用此指南创建一些Android主题:(http://developer.android.com/guide/topics/ui/themes.html)。你的styles.xml文件中将至少有两个<style>项目。例如:
<style name="Theme.App.Light" parent="@style/Theme.Light">...</style>
<style name="Theme.App.Dark" parent="@style/Theme">...</style>

现在,您需要将这些样式之一应用到您的活动中。我将在活动onCreate方法中执行此操作,在任何其他调用之前:

setTheme(MyApplication.getThemeId());

getThemeId 是一个返回缓存主题 ID 的方法:

public static int getThemeId()
{
    return themeId;
}

该字段正在被另一种方法更新:

public static void reloadTheme()
{
    themeSetting = PreferenceManager.getDefaultSharedPreferences(context).getString("defaultTheme", "0");
    if(themeSetting.equals("0"))
        themeId = R.style.Theme_Light;
    else
        themeId = R.style.Theme_Dark;
}

每当偏好设置改变时(以及在启动时),就会调用这两个方法。这两个方法位于MyApplication类中,该类扩展了Application。偏好更改监听器在此帖子末尾描述,并驻留在主活动类中。

最后一个非常重要的事情是 - 主题在活动启动时应用。假设您只能在偏好设置屏幕中更改主题,并且只有一种从一个(主要)活动进入该屏幕的方式,则退出偏好设置屏幕后,该活动不会重新启动 - 仍然使用旧主题。这是解决此问题的方法(重新启动主要活动):

@Override
protected void onResume() {
    super.onResume();
    if(schduledRestart)
    {
        schduledRestart = false;
        Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(i);
    }
}

scheduledRestart 是一个布尔变量,最初设置为 false。当主题被此监听器更改时,它将设置为 true,并更新之前提到的缓存主题 ID:

private class ThemeListener implements OnSharedPreferenceChangeListener{

    @Override
    public void onSharedPreferenceChanged(SharedPreferences spref, String key) {
        if(key.equals("defaultTheme") && !spref.getString(key, "0").equals(MyApplication.getThemeSetting()))
        {
            MyApplication.reloadTheme();
            schduledRestart = true;
        }
    }


sp = PreferenceManager.getDefaultSharedPreferences(this);

listener = new ThemeListener();
sp.registerOnSharedPreferenceChangeListener(listener);

记得持有监听器对象的引用,否则它会被垃圾回收(并且将停止工作)。


我原以为可以不扩展Application来解决问题,但问题在于如果我执行了 getApplication().setTheme(myThemeId) ,则 getApplicationInfo().theme 不会更新... 所以是的,你的方法是正确的。 - vault
7
看起来现在有一种更简单的方法重新启动一个活动:recreate(https://developer.android.com/reference/android/app/Activity.html#recreate%28%29)。 - BlueMonkMN
真的需要重新启动吗?有些应用程序不需要吗? - RoCkDevstack

7
如果您正在使用Material Components themes并遵循浅色和深色主题的指南,则可以通过AppCompatDelegate实现。这些主题可以在运行时更改/应用,而无需重新启动应用程序。
private fun handleThemeChange(theme: String) {
        when (newTheme) {
            getString(R.string.light) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            getString(R.string.dark) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            getString(R.string.system) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)

        }
    }

3

您还可以使用以下方式动态更改主题:

ContextThemeWrapper w = new ContextThemeWrapper(this, <newTHEMEId>);
getTheme().setTo(w.getTheme());

在每个活动的 onCreate 方法之前。


我可以将这个应用于Application类中,而不是在每个Activity中都这样做吗? - blueware

2
如果你这样做,它是有效的,并且我不认为会有任何问题,但这似乎有点麻烦(你必须将所有布局都乘以你想要添加的所有主题。如果稍后你想修改布局中的资源,你必须在所有主题中进行修改。你肯定会忘记其中一个)。
为什么不使用 Android 的 样式和主题功能呢?
它们可以轻松地应用于整个活动:
<activity android:theme="@style/my_theme">

这样,当您检测到SharedPreferences值的更改时(例如在首选项活动中的按钮上),您可以轻松地切换样式。或者更好的是,在运行时(创建活动时)读取您的首选项值并相应地应用正确的样式/主题。

我会仔细阅读样式和主题功能,并尝试以这种方式进行操作,感谢您提供的信息。一个小时后我会给你点赞,因为昨天我已经用完了:P - Matt Harris

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