strings.xml内の別の文字列から1つの文字列を参照しますか?


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

回答:


253

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>

9
これがOPの正解で​​す。このソリューションには、他にも大きなメリットがあります。 (1)外部ファイルでDTDを定義し、それを任意のリソースファイルから参照して、リソース内のどこにでも置換を適用できます。(2) android studioでは、定義されたエンティティの名前変更/参照/リファクタリングを行うことができます。ただし、書き込み中は名前のオートコンプリートは行われません。(androidstudio 2.2.2)
Mauro Panzeri 2016

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文字列を参照するにはどうすればよいですか。:)
スティーブン・ホスキング

53
これは、「文字列全体を参照している限り」(定義により常に参照している限り)ではなく、「参照している文字列リソースが参照名のみで構成されている限り」と読む必要があります。
sschuberth

54
これは実際には参照ではなく、単なるエイリアスであり、文字列の作成に関する問題を解決しません。
Eric Woodruff

2
これは質問の答えにはなりません。ある文字列を別の文字列に埋め込む必要がありました。
intrepidis 2017年

1
FWIW、これは私にとって非常に役に立ちました。ありがとうございました。
Ellen Spertus

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));少しきれいです。
Andrey Novikov

34

Androidでは、XML内で文字列を連結することはできません

以下はサポートされていません

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

それを達成する方法を知るために以下のこのリンクをチェックしてください

Android XMLで複数の文字列を連結する方法は?


16
まあ、面白い話:それはあなたが参照している同様の質問への私の答えです:-)
dbm '30

これを達成する方法はありますか?

これは、@ Francesco Lauritaの回答の複製であり、プログラムによって文字列を置き換える方法です。
CoolMind 2018

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"

更新: プラグインの新しいバージョンは、リソースを.flatバイナリ形式にパックするaapt2を使用するため、ライブラリはAndroid gradleプラグインバージョン3.0以降では動作しません。一時的な解決策として、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

私はこれが古い投稿であることを知っていますが、私のプロジェクトのために思いついた簡単で汚れた解決策を共有したいと思いました。これは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で1か所で定義することはできません(私が知っていることではありません)。そのため、これらの構成をカプセル化するクラスを作成できます。

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

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 2018

@CoolMindは、コピーへの参照について言及できますか。
Ismail Iqbal

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ログを使用してください。機能の問題や設定の問題の場合は、できるだけ早く対処できます。そこのあなた。👍
セザールムニョス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.