Odwołaj się do jednego ciągu z drugiego w strings.xml?

230

Chciałbym odwoływać się do ciągu z innego ciągu w moim pliku strings.xml, jak poniżej (zwróć uwagę na koniec treści ciągu „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>

Próbowałem powyższej składni, ale potem tekst wypisuje „@ string / button_text” jako czysty tekst. Nie to, czego chcę. Chciałbym wydrukować tekst wiadomości „Nie masz jeszcze żadnych elementów! Dodaj jeden, naciskając przycisk„ Dodaj element ”.

Czy jest jakiś znany sposób na osiągnięcie tego, czego chcę?

UZASADNIENIE:
Moja aplikacja ma listę elementów, ale gdy ta lista jest pusta, pokazuję tekst „@android: id / empty” TextView. Tekst w tym TextView ma poinformować użytkownika, jak dodać nowy element. Chciałbym, aby mój układ był odporny na zmiany (tak, jestem głupcem, o którym mowa :-)

dbm
źródło
3
Ta odpowiedź na inne podobne pytanie działała dla mnie. Nie jest wymagana Java, ale działa tylko w tym samym pliku zasobów.
mpkuth,

Odpowiedzi:

253

Dobry sposób na wstawienie często używanego ciągu (np. Nazwy aplikacji) do pliku xml bez użycia kodu 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>

AKTUALIZACJA:

Możesz nawet zdefiniować swoją jednostkę globalnie, np .:

res / raw / entity.ent:

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

res / wartości / 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
źródło
9
to jest poprawna odpowiedź na PO. To rozwiązanie ma inne wspaniałe zalety: (1) możesz zdefiniować DTD w pliku zewnętrznym i odwoływać się do niego z dowolnego pliku zasobów, aby zastosować podstawienie w dowolnym miejscu w swoich zasobach. (2) Android Studio pozwala zmienić nazwę / referencje / zmienić określone podmioty. Chociaż nie pisze autouzupełniania nazw podczas pisania. (androidstudio 2.2.2)
Mauro Panzeri,
3
@RJFares Możesz przejść na 2 sposoby: (a) „w tym” plik całego bytu EG: spójrz na tę odpowiedź . LUB (b) : obejmujący każdą jednostkę zdefiniowaną w zewnętrznym DTD, jak pokazano tutaj . Zauważ, że żadne z nich nie jest idealne, ponieważ z (a) stracisz możliwości „refaktoryzacji”, a (b) jest bardzo gadatliwy
Mauro Panzeri
5
Ostrożnie, jeśli używasz tego w projekcie biblioteki Androida - nie możesz go zastąpić w swojej aplikacji.
zyamys
4
czy można zmienić wartość encji podczas definiowania smaków w pliku stopni?
Myoch
7
Jednostki zewnętrzne nie są już obsługiwane przez Android Studio, ponieważ omawiany tutaj błąd: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

Można się do siebie odwoływać, o ile cały łańcuch składa się z nazwy odwołania. Na przykład to zadziała:

<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>

Jest to nawet bardziej przydatne do ustawiania wartości domyślnych:

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

Teraz możesz używać string_defaultwszędzie w swoim kodzie i możesz łatwo zmienić domyślną w dowolnym momencie.

Barry Fruitman
źródło
Co również odpowiada na pytanie: jak odwołać się do ciągu nazwa_aplikacji w tytule działania. :)
Stephen Hosking
53
Nie powinno to brzmieć „tak długo, jak odwołujesz się do całego ciągu” (do którego zawsze się odwołujesz z definicji), ale „tak długo, jak zasób ciągu odsyłającego składa się tylko z nazwy odwołania”.
sschuberth
54
To nie jest tak naprawdę odniesienie, to tylko alias, który nie rozwiązuje problemu komponowania łańcuchów.
Eric Woodruff,
2
To nie odpowiada na pytanie, chcieli, aby jeden ciąg był osadzony w innym.
intrepidis,
1
FWIW, to było dla mnie bardzo przydatne. Dziękuję Ci.
Ellen Spertus,
95

Myślę, że nie możesz. Ale możesz „sformatować” ciąg, jak chcesz:

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

W kodzie:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
źródło
5
Miałem nadzieję na rozwiązanie „nielogiczne”. Niemniej jednak dość uczciwa odpowiedź (a sama jej szybkość zapewnia zielony znacznik wyboru :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));jest trochę czystszy.
Andrey Novikov,
34

W Androidzie nie można łączyć napisów wewnątrz xml

Następujące nie jest obsługiwane

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

Sprawdź poniższy link, aby dowiedzieć się, jak to osiągnąć

Jak połączyć wiele ciągów w Androidzie XML?

Mayank Mehta
źródło
16
Cóż, zabawna historia: to moja odpowiedź na podobne pytanie, na które się powołujesz :-)
dbm
Czy istnieje sposób na osiągnięcie tego?
Jest to kopia odpowiedzi @Francesco Laurita, jak programowo zastąpić ciąg.
CoolMind
14

Stworzyłem prostą wtyczkę gradle która pozwala odsyłać jeden ciąg znaków do drugiego. Możesz odwoływać się do łańcuchów zdefiniowanych w innym pliku, na przykład w innym wariancie kompilacji lub bibliotece. Wady tego podejścia - refaktor IDE nie znajdzie takich referencji.

Użyj {{string_name}}składni, aby odnieść się do ciągu:

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

Aby zintegrować wtyczkę, po prostu dodaj następny kod do aplikacji lub build.gradlepliku na poziomie modułu biblioteki

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"

AKTUALIZACJA: Biblioteka nie działa z wtyczką stopniową Androida w wersji 3.0 i wyższej, ponieważ nowa wersja wtyczki używa aapt2, który pakuje zasoby do formatu binarnego .flat, więc spakowane zasoby są niedostępne dla biblioteki. Jako tymczasowe rozwiązanie możesz wyłączyć aapt2, ustawiając android.enableAapt2 = false w pliku gradle.properties.

Walerij Katkow
źródło
Podoba mi się ten pomysł ... ale nie udaje mi się z tym błędem:> Could not get unknown property 'referencedString'
jpage4500
To zrywa z aapt2
Snicolas
2
Stworzyłem wtyczkę, która działa prawie tak samo i współpracuje z aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Możesz użyć własnej logiki, która rekurencyjnie rozwiązuje zagnieżdżone ciągi.

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

Na Activityprzykład nazwij to tak

replaceResourceStrings(this, getString(R.string.message_text));
schnatterer
źródło
2

Wiem, że jest to starszy post, ale chciałem podzielić się szybkim i brudnym rozwiązaniem, które wymyśliłem dla mojego projektu. Działa tylko w przypadku TextView, ale można go również dostosować do innych widżetów. Pamiętaj, że wymaga to umieszczenia łącza w nawiasach kwadratowych (np [@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);
    }
}

Minusem tego podejścia jest to, że setText()konwertuje to CharSequencena a String, co jest problemem, jeśli zdasz takie rzeczy SpannableString; w moim projekcie nie był to problem, ponieważ użyłem go tylko do TextViewstego, że nie musiałem uzyskiwać dostępu z mojego Activity.

jclehner
źródło
To jest najbliższa odpowiedź na to pytanie. Myślę, że musimy przyjrzeć się przechwyceniu analizy składni xml.
Eric Woodruff,
2

Dzięki nowemu powiązaniu danych możesz łączyć i robić znacznie więcej w swoim pliku XML.

na przykład jeśli masz wiadomość 1 i wiadomość 2, możesz:

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

możesz nawet zaimportować niektóre narzędzia tekstowe i wywołać String.format i znajomych.

niestety, jeśli chcesz ponownie użyć go w kilku miejscach, może się bałaganić, nie chcesz wszędzie tego fragmentu kodu. i nie można ich zdefiniować w xml w jednym miejscu (nie wiem o tym), dlatego możesz stworzyć klasę, która będzie zawierać te kompozycje:

public final class StringCompositions {
    public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}

wtedy możesz go użyć (musisz zaimportować klasę z powiązaniem danych)

android:text="@{StringCompositions.completeMessage}"
ndori
źródło
2

Oprócz powyższej odpowiedzi Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Wygląda na to, że wystąpił błąd kompilacji „i encja; została przywołana, ale nie zadeklarowana”, którą można rozwiązać, odwołując się do zewnętrznej deklaracji w ten sposób

res / raw / entity.ent

<!ENTITY appname "My App Name">

res / wartości / 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

Chociaż kompiluje się i uruchamia, ma pustą wartość. Może ktoś wie, jak to rozwiązać. Chciałbym napisać komentarz, ale minimalna reputacja to 50.

pumnao
źródło
1
Teraz masz 53.
CoolMind
1

Można użyć symboli zastępczych ciągów (% s) i zastąpić je przy użyciu języka Java w czasie wykonywania

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

i w java

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

a następnie ustaw go w miejscu, w którym używa łańcucha

Ismail Iqbal
źródło
Kopiujesz inne rozwiązania. Kiedy odwołać kilka napisów, wykorzystanie %1$s, %2$sitd zamiast %s.
CoolMind
@CoolMind możesz wspomnieć o odwołaniu do kopii.
Ismail Iqbal,
1

Stworzyłem małą bibliotekę, która pozwala na rozwiązywanie tych symboli zastępczych podczas kompilacji, więc nie będziesz musiał dodawać żadnego kodu Java / Kotlin, aby osiągnąć to, co chcesz.

W oparciu o twój przykład będziesz musiał skonfigurować swoje ciągi w następujący sposób:

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

A następnie wtyczka zajmie się generowaniem następujących elementów:

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

Działa również w przypadku ciągów zlokalizowanych i smakowych, również wygenerowane ciągi będą się aktualizować za każdym razem, gdy wprowadzisz zmiany w szablonach lub ich wartościach.

Więcej informacji tutaj: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
źródło
Próbowałem twojej biblioteki. Po wykonaniu podanych kroków. Nadal powoduje błędy kompilacji. Twoja biblioteka w ogóle nie działa
programista
Przykro mi to słyszeć. Jeśli chcesz, utwórz problem tutaj: github.com/LikeTheSalad/android-string-reference/issues z dziennikami i mogę rozwiązać go jak najszybciej, jeśli jest to problem z funkcją lub jeśli jest to problem z konfiguracją, mogę również pomóc ty tam. 👍
César Muñoz,