从strings.xml中的另一个字符串引用一个字符串?


230

我想从我的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

Answers:


253

一个很好的方式插入XML频繁使用的字符串(如应用程序名称),而不使用Java代码:

    <?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 /原始文件/实体:

<!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>

9
这是OP的正确答案。该解决方案还有其他好处: (1)您可以在外部文件中定义DTD并从任何资源文件中引用它,以将替换应用于资源中的所有位置。(2) android studio让您重命名/引用/重构已定义的实体。虽然,它不会在写入时自动完成名称。(androidstudio 2.2.2)
Mauro Panzeri's

3
@RJFares您可以采用2种方式:(a) “包括”整个实体文件EG:请看此答案。OR (B) :包括如图外部的DTD中定义的每个单独的实体这里。请注意,这两者都不是完美的,因为使用(a)您将失去“重构”能力,并且(b)非常冗长
Mauro Panzeri

5
请注意,如果您在Android库项目中使用它,则不能在应用程序中覆盖它。
zyamys

4
在gradle文件中定义风味时是否可以更改实体值?
Myoch

7
Android Studio不再支持外部实体,因为这里讨论了一个错误:stackoverflow.com/a/51330953/8154765
Davide Cannizzo

181

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

<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

53
它不应显示为“只要您引用整个字符串”(根据定义,您始终要引用该字符串),而是“只要引用字符串资源仅由引用名称组成”。
sschuberth 2012年

54
这并不是一个真正的参考,它只是一个别名,不能解决组成字符串的问题。
Eric Woodruff 2013年

2
这不能回答问题,他们希望将一个字符串嵌入另一个字符串中。
intrepidis

1
FWIW,这对我非常有用。谢谢。
艾伦·斯佩特斯

95

我想你做不到 但是您可以根据需要“格式化”字符串:

<?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

34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));比较干净。
安德烈·诺维科夫

34

在Android中,您无法在xml中串联字符串

不支持以下内容

<string name="string_default">@string/string1 TEST</string>

在下面查看此链接以了解如何实现

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


16
好吧,有趣的故事:这是我对您所引用的类似问题的回答:-)
dbm

有没有办法做到这一点?

它是@Francesco Laurita答案的重复内容,如何以编程方式替换字符串。
CoolMind

14

我创建了简单的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文件中

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插件版本3.0及更高版本,因为该插件的新版本使用aapt2,它将资源打包为.flat二进制格式,因此打包后的资源不适用于该库。作为临时解决方案,您可以通过在gradle.properties文件中设置android.enableAapt2 = false来禁用aapt2。


我喜欢这个主意..但是它对我失败,并出现以下错误:> Could not get unknown property 'referencedString'
jpage4500

这与aapt2中断
Snicolas

2
:我创建了一个插件,确实几乎相同,并与aapt2工作github.com/LikeTheSalad/android-string-reference :)
塞萨尔·穆尼奥斯

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

2

我知道这是一篇较旧的文章,但我想分享我为我的一个项目想出的快速解决方案。它仅适用于TextViews,但也可以适用于其他小部件。请注意,这要求链接必须用方括号括起来(例如[@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为a String,如果您传递类似a之类的东西,则会出现问题SpannableString;对于我的项目来说,这不是一个问题,因为我只是出于TextViews不需要使用的访问权限而使用它Activity


这是最接近该问题答案的东西。我认为我们需要研究挂钩xml解析。
埃里克·伍德拉夫

2

使用新的数据绑定,您可以在xml中进行连接并执行更多操作。

例如,如果您收到message1和message2,则可以:

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

您甚至可以导入一些文本utils并调用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: //stackoverflow.com/a/39870268/9400836

似乎存在一个编译错误“&entity;已被引用,但未声明”,可以通过引用这样的外部声明来解决

RES /原始文件/实体

<!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

1

您可以使用字符串占位符(%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$s%2$s而不是%s
CoolMind

@CoolMind可以提及副本吗?
Ismail Iqbal '18

1

我创建了一个小库,该库可让您在构建时解析这些占位符,因此您无需添加任何Java / Kotlin代码即可实现所需的功能。

根据您的示例,您必须像这样设置字符串:

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

然后,该插件将负责生成以下内容:

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

它也适用于本地化字符串和风味字符串,并且每当您对模板或其值进行更改时,生成的字符串也将保持更新。

更多信息在这里:https : //github.com/LikeTheSalad/android-string-reference


我试过你的图书馆。按照您给出的步骤进行。它不断给出构建错误。库没有在所有的工作
developer1011

我知道了,很遗憾听到这个消息。如果您愿意,请在此处创建一个问题:github.com/LikeTheSalad/android-string-reference/issues with the logs,我可以尽快解决,以防它是功能问题,或者如果是配置问题,我也可以为您提供帮助你在那里。👍
塞萨尔·穆尼奥斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.