Посилаєтеся на один рядок з іншого рядка в 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" як чіткий текст. Не те, що я хочу. Я хотів би, щоб текст повідомлення надрукував "У вас ще немає елементів! Додайте його, натиснувши кнопку" Додати елемент "."

Чи є відомий спосіб досягти того, що я хочу?

RATIONALE: У
моїй програмі є список елементів, але коли цей список порожній, я показую TextView "@android: id / empty". Текст у цьому TextView - це інформування користувача про те, як додати новий елемент. Я хотів би зробити свою версію дурною до змін (так, я дурень у питанні :-)


3
Ця відповідь на інше подібне запитання спрацювала для мене. Не потрібна Java, але вона працює лише в одному файлі ресурсів.
mpkuth

Відповіді:


253

Хороший спосіб вставити часто використовуваний рядок (наприклад, ім'я програми) у xml, не використовуючи код Java: 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 / subjekti.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
це правильна відповідь на ОП. Це рішення має й інші великі переваги: (1) ви можете визначити DTD у зовнішньому файлі та посилати його з будь-якого файлу ресурсів, щоб застосувати заміну скрізь у ваших ресурсах. (2) андроїд-студія дозволяє перейменовувати / посилатися / рефакторировать визначені сутності. Хоча вони не заповнюють автозаповнення імен під час написання. (androidstudio 2.2.2)
Мауро Панцері

3
@RJFares Ви можете піти двома способами: (a) "включаючи" цілий файл образу: EG: подивіться на цю відповідь . АБО (b) : включаючи кожну сутність, визначену у зовнішньому DTD, як показано тут . Зауважте, що жоден з них не є ідеальним, тому що з (а) ви втратите "перефабрикуючі" можливості та (б) дуже багатослівний
Мауро Панцері

5
Будьте обережні, якщо ви використовуєте це в проекті бібліотеки Android - ви не можете перезаписати його у своєму додатку.
зямис

4
чи можна змінити значення сутності при визначенні ароматів у файлі gradle?
Міох

7
Зовнішні об'єкти не підтримуються Android Studio більше, так як помилка обговорюється тут: stackoverflow.com/a/51330953/8154765
Давида 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скрізь у своєму коді, і ви зможете легко змінити типовий режим у будь-який час.


Що також відповідає на питання: як я посилаюсь на рядок_назви додатка в заголовку активності. :)
Стівен Хоскінг

53
Це не повинно читати "до тих пір, поки ви посилаєтесь на весь рядок" (що ви завжди визначаєте за визначенням), але "до тих пір, поки ресурс, що посилається на рядок, складається лише з імені посилання".
sschuberth

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
Я насправді сподівався на "нелогічне" рішення. Тим не менш, досить справедлива відповідь (і велика швидкість її дає вам зелену галочку :-)
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 бінарний формат, тому запаковані ресурси недоступні для бібліотеки. Як тимчасове рішення ви можете відключити aapt2, встановивши android.enableAapt2 = false у вашому файлі gradle.properties.


Мені подобається ця ідея .. але мені ця помилка не вдається:> Could not get unknown property 'referencedString'
jpage4500

Ця перерва з aapt2
Snicolas

2
Я створив плагін, який працює майже так само і працює з aapt2: 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));

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.


Це найближча відповідь до цього питання. Я думаю, що нам потрібно розібратися в підключенні до розбору оформлення 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}"

2

Окрім вищезазначеної відповіді Франческо Лауріта https://stackoverflow.com/a/39870268/9400836

Здається, є помилка компіляції "& ent; було посилання, але не оголошено", що може бути вирішено шляхом посилання на зовнішнє оголошення, як це

res / raw / subjekt.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>

і в яві

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


Я спробував вашу бібліотеку. Дотримуючись поданих вами кроків. Він продовжує давати помилки в побудові. Ваша бібліотека взагалі не працює
розробник1011

Бачу, шкода, що це чую. Якщо вам подобається, будь ласка, створіть проблему тут: github.com/LikeTheSalad/android-string-reference/sissue з журналами, і я можу вирішити його якнайшвидше у випадку, якщо це проблема з функціями, або якщо це проблема з конфігурацією, я також можу допомогти ви там. 👍
Сезар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.