Ссылаться на одну строку из другой строки в 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" в виде открытого текста. Не то, что я хочу. Мне бы хотелось, чтобы в тексте сообщения было напечатано: «У вас еще нет элементов! Добавьте один, нажав кнопку« Добавить элемент »».

Есть ли какой-нибудь известный способ добиться того, чего я хочу?

ОБОСНОВАНИЕ: У
моего приложения есть список элементов, но когда этот список пуст, вместо этого я показываю TextView «@android: id / empty». Текст в этом TextView должен информировать пользователя о том, как добавить новый элемент. Я хотел бы сделать мой макет защищенным от изменений (да, я дурак, о котором идет речь :-)

дБмВт
источник
3
Этот ответ на другой подобный вопрос работал для меня. Не требуется Java, но он работает только в том же файле ресурсов.
mpkuth

Ответы:

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>

ОБНОВИТЬ:

Вы даже можете определить свою сущность глобально, например:

разреш / сырой / entities.ent:

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

Рез / значения / 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>
Beeing Jk
источник
9
это правильный ответ на ФП. Это решение имеет другие большие преимущества: (1) вы можете определить DTD во внешнем файле и ссылаться на него из любого файла ресурсов, чтобы применить подстановку везде в ваших ресурсах. (2) Android Studio позволяет переименовывать / ссылаться / реорганизовывать определенные объекты. Тем не менее, он не автозаполнение имен во время записи. (androidstudio 2.2.2)
Мауро Панцери
3
@RJFares Вы можете пойти двумя путями: (а) «включая» весь файл сущности EG: посмотрите на этот ответ . ИЛИ (b) : включая каждый отдельный объект, определенный во внешнем DTD, как показано здесь . Обратите внимание, что ни один из них не идеален, потому что с (а) вы потеряете способности «рефакторинга» и (б) очень многословно
Мауро Панцери
5
Будьте осторожны, если вы используете это в проекте библиотеки Android - вы не можете перезаписать его в своем приложении.
Зямис
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
Это должно читать не «до тех пор, пока вы ссылаетесь на всю строку» (что вы всегда делаете по определению), а «до тех пор, пока ресурс ссылающейся строки состоит только из имени ссылки».
Щуберт
54
На самом деле это не ссылка, а псевдоним, который не решает проблему создания строк.
Эрик Вудрафф
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
Я действительно надеялся на «нелогическое» решение. Тем не менее справедливый достаточно ответ (и явная скорость он дает вам зеленый галочки :-)
дБмВт
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, поэтому упакованные ресурсы для библиотеки недоступны. В качестве временного решения вы можете отключить aapt2, установив android.enableAapt2 = false в файле gradle.properties.

Валерий Катков
источник
Мне нравится эта идея .. но она терпит неудачу для меня с этой ошибкой:> Could not get unknown property 'referencedString'
jpage4500
Это разрывается с aapt2
Сниколас
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));
schnatterer
источник
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()преобразование в CharSequencea String, что является проблемой, если вы передаете такие вещи, как a SpannableString; для моего проекта это не было проблемой, так как я использовал его только для того, TextViewsчтобы мне не нужно было получать доступ от моего Activity.

jclehner
источник
Это самая близкая вещь к ответу на этот вопрос. Я думаю, что нам нужно разобраться в подключении разбора макета XML.
Эрик Вудрафф
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}"
ndori
источник
2

В дополнение к вышеупомянутому ответу Франческо Лаурита https://stackoverflow.com/a/39870268/9400836

Кажется, что есть ошибка компиляции "& entity; была указана, но не объявлена", которая может быть решена путем ссылки на внешнее объявление следующим образом

разреш / сырой / entities.ent

<!ENTITY appname "My App Name">

RES / значения / 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.

pumnao
источник
1
Теперь у вас есть 53.
CoolMind
1

Вы можете использовать строковые заполнители (% s) и заменить их с помощью Java во время выполнения

<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>

и в Яве

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

а затем установить его на то место, где он использует строку

Исмаил Икбал
источник
Вы копируете другие решения. Когда ссылки на несколько строк, использование %1$s, %2$sи т.д. вместо %s.
CoolMind
@CoolMind вы можете упомянуть ссылку на копию.
Исмаил Икбал
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 с журналами, и я могу решить ее как можно скорее в случае, если это проблема с функцией, или если это проблема конфигурации, я также могу помочь ты там. 👍
Сезар Муньос