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

Posted

技术标签:

【中文标题】从strings.xml 中的另一个字符串引用一个字符串?【英文标题】:Reference one string from another string in strings.xml? 【发布时间】:2011-06-12 09:01:44 【问题描述】:

我想从我的 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 中的文本用于通知用户如何添加新项目。我想让我的布局万无一失(是的,我就是这个傻瓜:-)

【问题讨论】:

This answer 到另一个对我有用的类似问题。不需要java,但它只能在同一个资源文件中工作。 【参考方案1】:

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

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

【讨论】:

这是对 OP 的正确答案。此解决方案还有其他很大的好处:(1) 您可以在外部文件中定义 DTD,并从任何资源文件中引用它,以便在资源中的任何位置应用替换。 (2) android studio 让你重命名/引用/重构定义的实体。虽然,它不会在编写时自动完成名称。 (androidstudio 2.2.2) @RJFares 您可以采用 2 种方式:(a) “包括”整个实体文件 EG:查看 this answer。或 (b):包括在外部 DTD 中定义的每个实体,如 here 所示。请注意,两者都不是完美的,因为(a)您将失去“重构”功能,并且(b)非常冗长 如果您在 Android 库项目中使用它,请小心 - 您不能在您的应用中覆盖它。 在gradle文件中定义flavors时是否可以改变实体值? Android Studio 不再支持外部实体,因为这里讨论了一个错误:***.com/a/51330953/8154765【参考方案2】:

只要您的整个字符串包含引用名称,就可以在另一个中引用。例如,这将起作用:

<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 字符串。 :) 这不应该读作“只要你引用整个字符串”(你总是按照定义)而是“只要引用的字符串资源只包含引用名称”。 这不是真正的引用,这只是一个别名,并不能解决组合字符串的问题。 这完全没用,我想不出任何有用的情况。只需引用实际字符串而不是“别名” 这并不能回答问题,他们希望将一个字符串嵌入到另一个字符串中。【参考方案3】:

我认为你不能。但是你可以随意“格式化”一个字符串:

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

【讨论】:

我实际上希望有一个“非逻辑”的解决方案。尽管如此,一个足够公平的答案(而且它的绝对速度会给你一个绿色的复选标记:-) String text = res.getString(R.string.message_text, res.getString(R.string.button_text)); 有点干净。 这似乎是最干净的解决方案。其他的都很乱。【参考方案4】:

在 Android 中,您不能在 xml 中连接字符串

不支持关注

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

查看下面的链接了解如何实现它

How to concatenate multiple strings in android XML?

【讨论】:

好吧,有趣的故事:这是我对您引用的类似问题的回答:-) 有没有办法做到这一点? 这是@Francesco Laurita 答案的副本,如何以编程方式替换字符串。【参考方案5】:

我创建了简单的 gradle plugin,它允许您从一个字符串引用另一个字符串。您可以引用在另一个文件中定义的字符串,例如在不同的构建变体或库中。这种方法的缺点 - 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。

【讨论】:

我喜欢这个主意.. 但它对我来说失败了这个错误:&gt; Could not get unknown property 'referencedString' 这与 aapt2 中断 我创建了一个与 aapt2 几乎相同的插件:github.com/LikeTheSalad/android-string-reference :)【参考方案6】:

您可以使用自己的逻辑,递归地解析嵌套字符串。

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

【讨论】:

【参考方案7】:

我知道这是一篇较旧的帖子,但我想分享一下我为我的项目提出的快速解决方案。它仅适用于 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 转换为 String,如果您传递 SpannableString 之类的东西,这是一个问题;对于我的 项目这不是问题,因为我只将它用于我不需要的TextViews 从我的Activity 访问。

【讨论】:

这是最接近这个问题的答案。我认为我们需要考虑挂钩到布局 xml 解析。【参考方案8】:

使用新的data binding,您可以在 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"

【讨论】:

【参考方案9】:

除了上述回答 Francesco Laurita https://***.com/a/39870268/9400836

似乎有一个编译错误“&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。

【讨论】:

现在你有 53 个了。【参考方案10】:

您可以使用字符串占位符 (%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 你能提到对副本的引用吗?【参考方案11】:

我创建了一个小型库,可让您在构建时解析这些占位符,因此您无需添加任何 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

【讨论】:

我试过你的图书馆。按照您给出的步骤进行操作后。它不断给出构建错误。你的图书馆根本不工作 我明白了,很抱歉听到这个消息。如果您愿意,请在此处创建一个问题:github.com/LikeTheSalad/android-string-reference/issues 使用日志,如果这是功能问题,或者如果是配置问题,我可以尽快解决它,我也可以在那里为您提供帮助。 ?

以上是关于从strings.xml 中的另一个字符串引用一个字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 strings.xml 中引用其他字符串? [复制]

为啥源码中的strings.xml中有msgid属性

从 AppWidgetProvider 中的 strings.xml 获取字符串

从代码中引用字符串资源

Android中strings.xml字符串中的粗体字

将实体从库引用到 JPA 中的另一个库