如何在strings.xml中从一个字符串引用另一个字符串?

271

我希望能够从strings.xml文件中的另一个字符串引用一个字符串,就像下面这样(特别注意"message_text"字符串内容末尾):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the \'@string/button_text\' button.</string>
</resources>

我尝试了上述语法,但是文本会将"@string/button_text"打印成明文。这不是我想要的。我希望消息文本打印为“您还没有任何项目!按‘添加项目’按钮添加一个。”。
是否有已知的方法可以实现我的需求?
原因: 我的应用程序有一个项目列表,但是当该列表为空时,我会显示一个“@android:id/empty” TextView。该TextView中的文本旨在告知用户如何添加新项目。我想使我的布局防止更改(是的,我就是那个傻瓜 :-))。

3
这个回答对我也有用,不需要使用Java,但只能在同一个资源文件中使用。 - mpkuth
12个回答

313

在不使用Java代码的情况下,在xml中插入常用字符串(例如应用程序名称)的好方法:来源

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE resources [
      <!ENTITY appname "MyAppName">
      <!ENTITY author "MrGreen">
    ]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

更新:

您甚至可以全局定义实体,例如:

res/raw/entities.ent:

<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">

res/values/string.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY % ents SYSTEM "./res/raw/entities.ent">
    %ents;   
]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

11
这是对原帖的正确答案。这个解决方案还有其他很棒的好处: (1) 你可以在一个外部文件中定义DTD,并且从任何资源文件引用它以在所有的资源中应用该替换。 (2) Android Studio让你重命名/引用/重构已定义的实体。然而,它在编写时不会自动补全名称。(Android Studio 2.2.2) - Mauro Panzeri
3
你有两种选择:**(a)** 包含一个完整的实体文件,例如参考这个回答。或者 **(b)**:包含在外部DTD中定义的每个单独的实体,就像这里所示http://www.w3schools.com/xml/xml_dtd_entities.asp。请注意,两者都不完美,因为选择(a)会丢失"重构"功能,而(b)则非常冗长。 - Mauro Panzeri
5
如果您将此用于Android库项目中,请注意-您无法在应用程序中覆盖它。 - zyamys
7
在Gradle文件中定义flavors时,是否可能更改实体值? - Myoch
15
自从在这里讨论的一个错误(https://dev59.com/bVUL5IYBdhLWcg3wDUW2#51330953)之后,Android Studio不再支持外部实体。 - Davide Cannizzo
显示剩余16条评论

200

只要你的整个字符串都是引用名称,就可以在一个引用内引用另一个引用。例如,这将起作用:

<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>

对于设置默认值,它甚至更加有用:

<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
<string name="string_default">@string/string1</string>

现在您可以在代码中的任何位置使用string_default,并且随时可以轻松更改默认值。


这也回答了一个问题:我如何在活动标题中引用app_name字符串。 :) - Stephen Hosking
61
应该这样翻译:"只要所引用的字符串资源仅包含引用名称",而不是 "只要引用整个字符串"(根据定义,您始终如此)。请注意保留原意并确保易于理解。 - sschuberth
69
这并不是一个真正的参考,这只是一个别名,它并不能解决组合字符串的问题。 - Eric Woodruff
4
这真的毫无用处,我想不出任何情况下它可以有用。直接引用实际的字符串而不是“别名”。 - Zam Sunk
4
这并没有回答问题,他们想要将一个字符串嵌入到另一个字符串中。 - intrepidis
如果 app_name 看起来像 <string name="app_name">My App %s$1</string>,我能否引用它并传递一个字符串值来替换 %s$1 吗?(在程序中类似于 getString(R.String.app_name, "replacement");) - MahNas92

102

我认为你不能这样做。但是你可以按照自己的意愿“格式化”一个字符串:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the %1$s button.</string>
</resources>

在代码中:
Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));

5
我本来希望得到一个“非逻辑”的解决方案。尽管如此,这个答案还是足够合理的(而且你的回答速度之快也赢得了绿色的勾勾) :-) - dbm
39
String text = res.getString(R.string.message_text, res.getString(R.string.button_text)); 这段代码稍微更简洁一些。 - Andrey Novikov
这似乎是最干净的解决方案。其他所有方法都很混乱。 - lenooh

35
在Android中,你不能在xml中连接字符串。 以下内容是不支持的。
<string name="string_default">@string/string1 TEST</string>

点击下面的链接了解如何实现它:

如何在Android XML中连接多个字符串?


20
好的,有趣的故事:这是我回答类似问题时的答案,与你参考的问题相似 :-) - dbm
有没有办法实现这个? - user801116
这是@Francesco Laurita答案的副本,介绍如何以编程方式替换字符串。 - CoolMind

16

我创建了一个简单的 Gradle 插件,它允许你从另一个字符串中引用一个字符串。你可以引用在另一个文件里定义的字符串,比如在不同的构建变体或者库里。这种方法的缺点是 IDE 重构无法找到这样的引用。

使用 {{string_name}} 语法来引用一个字符串:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="super">Super</string>
    <string name="app_name">My {{super}} App</string>
    <string name="app_description">Name of my application is: {{app_name}}</string>
</resources>

为了集成这个插件,只需将下面的代码添加到您的应用或库模块级别的 build.gradle 文件中即可。

To integrate the plugin, just add next code into you app or library module level build.gradle file

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.android-text-resolver:buildSrc:1.2.0"
  }
}

apply plugin: "com.icesmith.androidtextresolver"

更新: 由于新版本的Android gradle插件使用aapt2将资源打包成.flat二进制格式,因此打包的资源对该库不可用,因此该库与Android gradle插件3.0及以上版本不兼容。 暂时的解决方法是在gradle.properties文件中设置android.enableAapt2=false来禁用aapt2。


我喜欢这个想法,但是它在我的电脑上出现了错误: > 无法获取未知属性 'referencedString' - jpage4500
这会导致aapt2出现错误。 - Snicolas
3
我已经创建了一个插件,几乎做同样的事情,并且可以与aapt2一起使用:https://github.com/LikeTheSalad/android-string-reference :) - César Muñoz

7
您可以使用自己的逻辑来递归解析嵌套字符串。
/**
 * Regex that matches a resource string such as <code>@string/a-b_c1</code>.
 */
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";

/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";

/**
 * Recursively replaces resources such as <code>@string/abc</code> with
 * their localized values from the app's resource strings (e.g.
 * <code>strings.xml</code>) within a <code>source</code> string.
 * 
 * Also works recursively, that is, when a resource contains another
 * resource that contains another resource, etc.
 * 
 * @param source
 * @return <code>source</code> with replaced resources (if they exist)
 */
public static String replaceResourceStrings(Context context, String source) {
    // Recursively resolve strings
    Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
    Matcher m = p.matcher(source);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
        String stringFromResources = getStringByName(context, m.group(1));
        if (stringFromResources == null) {
            Log.w(Constants.LOG,
                    "No String resource found for ID \"" + m.group(1)
                            + "\" while inserting resources");
            /*
             * No need to try to load from defaults, android is trying that
             * for us. If we're here, the resource does not exist. Just
             * return its ID.
             */
            stringFromResources = m.group(1);
        }
        m.appendReplacement(sb, // Recurse
                replaceResourceStrings(context, stringFromResources));
    }
    m.appendTail(sb);
    return sb.toString();
}

/**
 * Returns the string value of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param name
 * @return the value of the string resource or <code>null</code> if no
 *         resource found for id
 */
public static String getStringByName(Context context, String name) {
    int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
    if (resourceId != 0) {
        return context.getString(resourceId);
    } else {
        return null;
    }
}

/**
 * Finds the numeric id of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param defType
 *            Optional default resource type to find, if "type/" is not
 *            included in the name. Can be null to require an explicit type.
 * 
 * @param name
 *            the name of the desired resource
 * @return the associated resource identifier. Returns 0 if no such resource
 *         was found. (0 is not a valid resource ID.)
 */
private static int getResourceId(Context context, String defType,
        String name) {
    return context.getResources().getIdentifier(name, defType,
            context.getPackageName());
}

例如,从一个Activity中调用它的方式如下:
replaceResourceStrings(this, getString(R.string.message_text));

3
我知道这是一篇较旧的文章,但我想分享一下我为我的一个项目想出的快速“脏”解决方案。它仅适用于TextView,但也可以适应其他小部件。请注意,它需要将链接括在方括号中(例如[@string/foo])。
public class RefResolvingTextView extends TextView
{
    // ...

    @Override
    public void setText(CharSequence text, BufferType type)
    {
        final StringBuilder sb = new StringBuilder(text);
        final String defPackage = getContext().getApplicationContext().
                getPackageName();

        int beg;

        while((beg = sb.indexOf("[@string/")) != -1)
        {
            int end = sb.indexOf("]", beg);
            String name = sb.substring(beg + 2, end);
            int resId = getResources().getIdentifier(name, null, defPackage);
            if(resId == 0)
            {
                throw new IllegalArgumentException(
                        "Failed to resolve link to @" + name);
            }

            sb.replace(beg, end + 1, getContext().getString(resId));
        }

        super.setText(sb, type);
    }
}

这种方法的缺点是setText()CharSequence转换为String,如果您传递像SpannableString这样的东西,这是一个问题;对于我的项目来说,这不是问题,因为我只在TextViews中使用它,而且我不需要从我的Activity中访问它们。

这是对这个问题最接近的答案。我认为我们需要研究一下如何钩入布局 XML 解析。 - Eric Woodruff

2

通过新的数据绑定功能,可以在xml中进行字符串拼接以及其他更多操作。

例如,如果你有message1和message2,你可以:

android:text="@{@string/message1 + ': ' + @string/message2}"

你甚至可以导入一些文本工具并调用String.format和其他方法。
不幸的是,如果你想在多个地方重复使用它,会变得混乱,你不希望这些代码到处都是。而且你不能在一个地方定义它们的XML(至少我不知道)。
因此,你可以创建一个类来封装这些组合:
public final class StringCompositions {
    public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}

然后您可以使用它(您需要通过数据绑定导入类)。

android:text="@{StringCompositions.completeMessage}"

2
除了Francesco Laurita的上面的答案https://dev59.com/ZG445IYBdhLWcg3wq8Lp#39870268之外,似乎还存在编译错误"&entity was referenced, but not declared",这可以通过像这样引用外部声明来解决:

res/raw/entities.ent


<!ENTITY appname "My App Name">

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY appname SYSTEM "/raw/entities.ent">
]>
<resources>
    <string name="app_name">&appname;</string>
</resources

尽管它可以编译和运行,但其值为空。也许有人知道如何解决这个问题。我本来想发表评论的,但最低声望要求是50。

1
现在你有53。 - CoolMind

2
您可以使用字符串占位符(%s),并在运行时使用Java替换它们。
<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>

并且在Java中

String final = String.format(getString(R.string.message_text),getString(R.string.button_text));

然后将其设置到使用该字符串的位置。

1
你抄袭了其他的解决方案。当引用多个字符串时,请使用%1$s%2$s等,而不是%s - CoolMind
1
@CoolMind,你能提及拷贝的参考吗? - Ismail Iqbal

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