39

我想让用户在几个不同的主题之间进行选择,并且想知道这是否是一种正确的做事方式。我用这种方法做了一点测试,它确实有效,但我认为可能有更好的方法,并且认为它可能会在以后引起一些问题,所以想问一下。

我正在考虑为每个主题创建不同的布局,并且onCreate只是对setContentView()方法进行切换。我会SharedPreference先加载一个保存的值(整数),然后根据该值显示相应的布局。显然,用户可以SharedPreference通过按钮或其他东西更改值。

由于这些布局基本相同,但颜色不同,我想TextViews在每个布局文件中为我的视图和其他视图使用相同的 ID。我的主要问题是这会导致问题吗?

抱歉,没有代码的文字墙。我只是想大致了解这种情况下的良好做法。提前致谢。

4

4 回答 4

51

实际上,我的应用程序中具有此功能,此外,我允许用户在运行时更改主题。由于从首选项中读取值需要一些时间,我通过保存缓存值的全局可访问函数获取主题 ID。

正如已经指出的 - 创建一些 Android 主题,使用本指南。您的文件中至少有两个<style>项目styles.xml。例如:

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

请记住持有对侦听器对象的引用,否则它将被垃圾收集(并且将停止工作)。

于 2012-01-10T23:13:19.787 回答
6

如果您使用Material Components 主题并遵循 Light and Dark 主题指南,那么您可以从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)

        }
    }
于 2020-04-05T17:21:24.597 回答
3

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

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

在每个活动的 onCreate 之前。

于 2013-09-06T21:26:03.713 回答
2

如果你这样做,它确实有效,我认为它不会引起任何问题,但它似乎很麻烦(你必须将所有布局乘以你想要添加的所有主题。如果以后你想要修改布局中的资源,你必须在所有主题中修改它。你肯定会忘记一个)

为什么不使用Android的样式和主题功能?

它们可以很容易地应用于整个活动:

<activity android:theme="@style/my_theme">

因此,当您检测到您使用的值发生变化SharedPreferences(偏好活动上的按钮或其他内容)时,您只需切换样式即可。或者更好的是,您可以设置样式以在运行时(创建活动时)读取您的偏好值,并相应地应用正确的样式/主题。

于 2012-01-10T22:45:05.490 回答