主题依赖的Android字符串

时间:2014-06-21 15:48:01

标签: android string resources themes customization

假设我有2个主题的应用程序:男性化和女性化。主题只是改变了调色板和一些drawables,以吸引用户的首选品味。

非常感谢http://www.androidengineer.com/2010/06/using-themes-in-android-applications.html提示他做这项工作的提示。

但现在让我们说我想对应用程序进行一些调整,不仅要更改颜色和drawable,还要更改字符串。例如,我可能想要添加一个盗版主题,然后“提交”将是“Arrrrgh!”

所以,我的基本问题是:如何通过用户可选择的主题更改我的应用程序中的字符串?

编辑:

解决这个问题:该应用程序有12个按钮和32个文本视图我想要主题依赖,我想在没有巨大的映射或大量定制的attrs的情况下实现这一目标。

目前所有3种解决方案都有效。寻找更干净的东西虽然我不知道这样的野兽存在。

5 个答案:

答案 0 :(得分:7)

是的,它可以完成,以及如何:首先你必须定义一个主题属性,如下所示:

<attr name="myStringAttr" format="string|reference" />

然后,在您的主题中,添加此行

<item name="myStringAttr">Yarrrrr!</item>

<item name="myStringAttr">@string/yarrrrr</item>

然后,您可以在XML文件中使用此属性(请注意?而不是@)。

<TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="?attr/myStringAttr" />

或者,从代码中,像这样:

public CharSequence resolveMyStringAttr(Context context) 
{
    Theme theme = context.getTheme();
    TypedValue value = new TypedValue();

    if (!theme.resolveAttribute(R.attr.myStringAttr, value, true)) {
        return null;
    }

    return value.string;
}

答案 1 :(得分:2)

  

假设我有2个主题的应用程序:男性化和女性化。主题只是改变了调色板和一些drawables,以吸引用户的首选品味。

我们假装你正在做别的事情怎么样?这是一种设计反模式,基于性别关联特定颜色(例如,“像粉红色的女孩”)。

这并不是说你的技术目标很糟糕,只是这是一个真正的陈规定型的例子。

  

例如,我可能想要添加一个盗版主题,然后“提交”将是“Arrrrgh!”

仅当“取消”映射到“Avast!”时才会显示。

  

如何通过用户可选主题更改整个应用中的字符串?

你还没有说过这些字符串的来源。他们是字符串资源吗?数据库条目?您是从Web服务检索的?还有别的吗?

我现在假设这些是字符串资源。根据定义,您需要有N个字符串副本,每个主题一个。

由于性别和盗版状态不是Android作为可能的资源集限定符跟踪的事物,因此您不能将这些字符串资源放在不同的资源集中。虽然它们可能位于不同的文件中(例如res/values/strings_theme1.xml),但文件名不是字符串的资源标识符的一部分。因此,您将不得不使用某种前缀/后缀来跟踪哪些字符串属于哪些主题(例如@string/btn_submit_theme1)。

如果这些字符串在运行时没有变化 - 它只是布局资源中的任何内容 - 您可以从Chris Jenkins' Calligraphy library获取页面。他有自己的LayoutInflater子类,用于重载某些标准XML属性。在他的情况下,他的重点是android:fontFamily,他支持映射到资产中的字体文件。

在您的情况下,您可以重载android:text。在您的布局文件中,您可以将其作为所需字符串资源的基本名称,而不是任何主题标识符(例如,如果真实字符串为@string/btn_submit_theme1和亲属,你可以android:text="btn_submit")。您的LayoutInflater子类将获取该值,附加主题名称后缀,在getIdentifier()上使用Resources来查找实际的字符串资源ID,并从那里获取与您的主题相关联的字符串

对此的变体是将基本名称放在android:tag而不是android:text中。 android:text可能指向您的一个真正的字符串资源,以帮助GUI设计等。您的LayoutInflater将获取标记并使用它在运行时派生正确的字符串。

如果要将文本替换为从基于主题的字符串资源中提取的其他文本,则可以将get-the-string-given-base-name逻辑隔离到可以应用的某个静态实用程序方法中。 / p>

虽然最初获得此权限需要一些工作,但就受影响的UI小部件和字符串的数量而言,它将扩展到任意复杂性。您仍然需要记住为您定义的任何新字符串添加所有主题的值(创建自定义Lint检查的奖励点或用于验证此项的Gradle任务)。

答案 2 :(得分:1)

由于资源只是一个int,因此您可以在运行时存储其中的一些资源,并在使用它们时在程序上替换它们。


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="OK_NORMAL">Okay</string>
    <string name="OK_PIRATE">Yaaarrrr!</string>
    <string name="OK_NINJA">Hooooaaa!</string>
</resources>

public enum ThemeMode {
    PIRATE,
    NINJA,
    NORMAL;
}

public class MyThemeStrings {
    public static int OK_PIRATE = R.string.OK_PIRATE;
    public static int OK_NINJA = R.string.OK_NINJA;
    public static int OK_NORMAL = R.string.OK_NORMAL;
}

public setOkButtonText(ThemeMode themeMode) {
    // buttonOk is instantiated elsewhere
    switch (themeMode) {
        case PIRATE:
            buttonOk.setText(MyThemeStrings.OK_PIRATE);
            break;
        case NINJA:
            buttonOk.setText(MyThemeStrings.OK_NINJA);
            break; 
        default:
            Log.e(TAG, "Unhandled ThemeMode: " + themeMode.name());
            // no break here so it flows into the NORMAL base case as a default
        case NORMAL:
            buttonOk.setText(MyThemeStrings.OK_NORMAL);
            break;
    }
}

虽然编写了所有这些内容,但通过单独的XML文件可能有更好的方法来完成所有这些操作。我现在会调查一下,如果找到第二个解决方案,我会写一个。

答案 3 :(得分:1)

好的,我有第二个选项可能实际上更容易维护并保持代码更清晰,尽管由于为每个String加载数组可能会导致更多的资源浪费。我没有对它进行基准测试,但会将其作为另一种选择,但如果您提供太多主题选择,我就不会使用它。


public enum ThemeMode {
    NORMAL(0),
    PIRATE(1),
    NINJA(2);

    private int index;
    private ThemeMode(int index) {
        this.index = index;
    }

    public int getIndex() {
        return this.index;
    }
}

<resources>
    <!-- ALWAYS define strings in the correct order according to 
         the index values defined in the enum -->
    <string-array
        name="OK_ARRAY">
        <item>OK</item>
        <item>Yaarrrr!</item>
        <item>Hooaaaa!</item>
    </string-array>
    <string-array
        name="CANCEL_ARRAY">
        <item>Cancel</item>
        <item>Naarrrrr!</item>
        <item>Wha!</item>
    </string-array>
</resources>

public setButtonTexts(Context context, ThemeMode themeMode) {
    // buttons are instantiated elsewhere
    buttonOk.setText(context.getResources()
            .getStringArray(R.array.CANCEL_ARRAY)[themeMode.getIndex()]);
    buttonCancel.setText(context.getResources()
            .getStringArray(R.array.OK_ARRAY)[themeMode.getIndex()]);
}

答案 4 :(得分:1)

所以,我没有机会对此进行测试,但是通过阅读Locale上的文件,您可以创建自己的位置。

http://developer.android.com/reference/java/util/Locale.html

并从另一个stackoverflow获得帮助

Set Locale programmatically

一点点组合让我:

Locale pirate = new Locale("Pirate");
Configuration config = new Configuration();
config.locale = pirate;
this.getActivity().getBaseContext().getResources()
   .updateConfiguration(config, 
    this.getActivity().getBaseContext().getResources().getDisplayMetrics());

我相信这会让你有res / values-pirate / strings作为一个实际的有效资源,当你是一个盗版者时会被使用。您不会覆盖的任何字符串或设置默认情况下将恢复为res / values / ...因此您可以根据需要为多个主题执行此操作。再假设它有效。