【问题标题】:Theme Dependent Android Strings主题相关的 Android 字符串
【发布时间】:2014-06-21 15:48:01
【问题描述】:

假设我有一个包含 2 个主题的应用程序:男性和女性。主题只是改变了调色板和一些可绘制对象,以迎合用户的喜好。

非常感谢 http://www.androidengineer.com/2010/06/using-themes-in-android-applications.html 提供的提示。

但是现在假设我想通过应用程序变得更可爱一点,不仅要更改颜色和可绘制对象,还要更改字符串。例如,我可能想添加一个海盗主题,然后“提交”将是“Arrrrgh!”

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

编辑:

组成:该应用有 12 个按钮和 32 个文本视图,我希望依赖于主题,并且我希望在没有巨大映射或大量自定义属性的情况下完成此任务。

当前所有 3 种解决方案都可以使用。寻找更干净的东西,虽然我不知道有这样的野兽。

【问题讨论】:

  • @IllegalArgument 我在那篇博文中一定遗漏了一些东西。我不明白他为什么要做他正在做的事情,因为他仍在定义 3 个单独的字符串,并且有 3 个单独的按钮指向这些字符串。

标签: android string resources themes customization


【解决方案1】:

是的,可以这样做,方法如下:首先您必须定义一个主题属性,如下所示:

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

【讨论】:

  • 太棒了。它可以扩展吗?所以,有(组成)12 个按钮和 32 个 TextView。如果我正确阅读了您的解决方案;对于我想让主题依赖的每个字符串,我都必须有一个特殊的属性,对吗?
  • 嗯,这就是这种方法的缺点。在这种情况下,您必须添加 44 个新属性,但我想不出仅使用 XML 的任何其他方法。
  • 非常感谢。我也想不出任何其他方式,但我不想通过给他们播种答案来阻碍任何人的创造力:)
【解决方案2】:

假设我有一个包含 2 个主题的应用程序:男性和女性。主题只是改变了调色板和一些可绘制对象来吸引用户的偏好。

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

这并不是说你的技术目标不好,只是说这是一个非常典型的例子。

例如,我可能想添加一个海盗主题,然后“提交”将是“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 和 kin,您可以有android:text="btn_submit")。您的 LayoutInflater 子类将获取该值,附加主题名称后缀,在您的 Resources 上使用 getIdentifier() 来查找实际的字符串资源 ID,然后从那里获取与您的主题相关联的字符串。

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

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

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

【讨论】:

  • 我想我可以使用麝香/华丽而不是男性/女性,但我确实说明了“用户的”偏好,因此用户的性别可能与他们的主题决定无关;)你是对的假设我想使用字符串资源。我喜欢你拿这个的地方,这似乎是可行的。当我把一些代码放在一起时,我会在这里发布一个跟进。问候!
【解决方案3】:

由于资源本质上只是一个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 文件来完成所有这些。如果我找到了,我现在会研究它并编写第二个解决方案。

【讨论】:

  • 我担心一个巨大的映射可能是我所期待的。您的解决方案会起作用,但该死的最终会是很多代码吗:)
  • 只要您可以将其全部整理/抽象到自己的包和 string_something.xml 文件中,然后只公开一个接受 enum Theme 每个字符串的参数的getter方法我会说它不是一个太笨拙的选项,并且允许使用单独的 values-locale 文件夹进行国际化。但是,是的,在扩展功能时需要注意一些事项。
【解决方案4】:

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


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()]);
}

【讨论】:

  • 这减少了一些代码。好的。我将继续挖掘一段时间,但这可能是我的首选解决方案。
  • 就像我在顶部说的那样,小心使用这个主题选择太多的主题,因为每个主题都会向每个数组添加另一个字符串。根据您在任何时候加载的数量以及您想要提供的主题数量,您的应用程序的资源使用量可能会显着增加。如果您打算在整个应用程序中使用大量资源,那么绝对值得对此处提供的三种解决方案进行一些基准测试。如果它只是针对几个地方作为一个小小的新奇事物,而不是仅仅使用最容易实现的地方,因为它并不重要。
【解决方案5】:

所以,我还没有机会对此进行测试,但通过阅读 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/...,因此您可以根据需要对任意数量的主题执行此操作。再次假设它有效。

【讨论】:

  • 这太棒了。我将不得不为这个答案整理一个例子;)
  • 如果它有效,我认为它会比其他方法少很多。您只需要在某处选择这种新的“语言”,然后将其保存为该应用的 SharedPreferences 的一部分。
  • 这不起作用。 Android 不允许您创建res/values-pirate/ 目录...啊!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-20
  • 2012-12-12
  • 2020-07-03
  • 1970-01-01
  • 2012-07-05
  • 2014-01-10
  • 1970-01-01
相关资源
最近更新 更多