假设我有2个主题的应用程序:男性化和女性化。主题只是改变了调色板和一些drawables,以吸引用户的首选品味。
非常感谢http://www.androidengineer.com/2010/06/using-themes-in-android-applications.html提示他做这项工作的提示。
但现在让我们说我想对应用程序进行一些调整,不仅要更改颜色和drawable,还要更改字符串。例如,我可能想要添加一个盗版主题,然后“提交”将是“Arrrrgh!”
所以,我的基本问题是:如何通过用户可选择的主题更改我的应用程序中的字符串?
编辑:
解决这个问题:该应用程序有12个按钮和32个文本视图我想要主题依赖,我想在没有巨大的映射或大量定制的attrs的情况下实现这一目标。
目前所有3种解决方案都有效。寻找更干净的东西虽然我不知道这样的野兽存在。
答案 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获得帮助
一点点组合让我:
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 / ...因此您可以根据需要为多个主题执行此操作。再假设它有效。