Fare riferimento a una stringa da un'altra stringa in strings.xml?


230

Vorrei fare riferimento a una stringa di un'altra stringa nel mio file strings.xml, come di seguito (notare in particolare la fine del contenuto della stringa "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>

Ho provato la sintassi sopra ma poi il testo stampa "@ string / button_text" come testo in chiaro. Non quello che voglio. Vorrei stampare il testo del messaggio "Non hai ancora nessun articolo! Aggiungine uno premendo il pulsante 'Aggiungi elemento'."

Esiste un modo noto per ottenere ciò che voglio?

MOTIVAZIONE: La
mia applicazione ha un elenco di elementi, ma quando tale elenco è vuoto mostro invece un TextView "@android: id / empty". Il testo in quel TextView è di informare l'utente come aggiungere un nuovo elemento. Vorrei rendere il mio layout infallibile alle modifiche (sì, sono lo sciocco in questione :-)


3
Questa risposta a un'altra domanda simile ha funzionato per me. Non è necessario java, ma funziona solo all'interno dello stesso file di risorse.
mpkuth,

Risposte:


253

Un bel modo per inserire una stringa di uso frequente (ad esempio il nome dell'app) in XML senza usare il codice 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>

AGGIORNARE:

Puoi persino definire la tua entità a livello globale, ad esempio:

res / raw / entities.ent:

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

res / Valori / 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
questa è la risposta corretta all'OP. Questa soluzione ha altri grandi vantaggi: (1) è possibile definire il DTD in un file esterno e fare riferimento a esso da qualsiasi file di risorse per applicare la sostituzione ovunque nelle risorse. (2) Android Studio ti consente di rinominare / fare riferimento / refactificare le entità definite. Tuttavia, non completa automaticamente i nomi durante la scrittura. (androidstudio 2.2.2)
Mauro Panzeri,

3
@RJFares Puoi andare in 2 modi: (a) "incluso" un intero file di entità EG: guarda questa risposta . OPPURE (b) : compresa ogni singola entità definita in un DTD esterno come mostrato qui . Nota che nessuno dei due è perfetto perché con (a) perderai le capacità di "refactoring" e (b) è molto dettagliato
Mauro Panzeri,

5
Fai attenzione se lo usi in un progetto di libreria Android: non puoi sovrascriverlo nella tua app.
Zyamys,

4
è possibile modificare il valore dell'entità quando si definiscono i sapori nel file gradle?
Myoch,

7
Le entità esterne non sono più supportate da Android Studio, poiché un bug discusso qui: stackoverflow.com/a/51330953/8154765
Davide Cannizzo

181

È possibile fare riferimento l'uno all'interno dell'altro purché l'intera stringa sia composta dal nome di riferimento. Ad esempio questo funzionerà:

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

È ancora più utile per impostare i valori predefiniti:

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

Ora puoi usare string_defaultovunque nel tuo codice e puoi facilmente cambiare l'impostazione predefinita in qualsiasi momento.


Che risponde anche alla domanda: come posso fare riferimento alla stringa app_name in un titolo di attività. :)
Stephen Hosking,

53
Questo non dovrebbe leggere "fintanto che fai riferimento all'intera stringa" (a cui fai sempre riferimento per definizione) ma "fintanto che la risorsa della stringa di riferimento è composta solo dal nome di riferimento".
sschuberth,

54
Questo non è davvero un riferimento, questo è solo un alias, che non risolve il problema della composizione delle stringhe.
Eric Woodruff,

2
Questo non risponde alla domanda, volevano una stringa incorporata in un'altra.
intrepidis,

1
FWIW, questo è stato estremamente utile per me. Grazie.
Ellen Spertus,

95

Penso che non puoi. Ma puoi "formattare" una stringa come preferisci:

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

Nel codice:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));

5
In realtà speravo in una soluzione "non logica". Tuttavia una risposta abbastanza equa (e la sua estrema velocità ti dà un segno di spunta verde :-)
dbm

34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));è un po 'più pulito.
Andrey Novikov,

34

In Android non puoi concatenare stringhe all'interno di xml

Di seguito non è supportato

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

Controlla questo link qui sotto per sapere come raggiungerlo

Come concatenare più stringhe in XML Android?


16
Beh, storia divertente: questa è la mia risposta a una domanda simile a cui ti riferisci :-)
dbm

C'è un modo per raggiungere questo obiettivo?

È un duplicato della risposta @Francesco Laurita, come sostituire a livello di codice una stringa.
CoolMind,

14

Ho creato un semplice plugin Gradle che ti consente di fare riferimento a una stringa da un'altra. È possibile fare riferimento a stringhe definite in un altro file, ad esempio in diverse varianti di build o librerie. Contro di questo approccio: il refactor IDE non troverà tali riferimenti.

Utilizzare la {{string_name}}sintassi per fare riferimento a una stringa:

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

Per integrare il plug-in, basta aggiungere il codice successivo nel build.gradlefile a livello di modulo dell'app o della libreria

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"

AGGIORNAMENTO: La libreria non funziona con il plug-in Android Gradle versione 3.0 e successive perché la nuova versione del plug-in utilizza aapt2 che raggruppa le risorse in formato binario .flat, quindi le risorse compresse non sono disponibili per la libreria. Come soluzione temporanea puoi disabilitare aapt2 impostando android.enableAapt2 = false nel tuo file gradle.properties.


Mi piace questa idea .. ma fallisce con questo errore:> Could not get unknown property 'referencedString'
jpage4500

Ciò si interrompe con aapt2
Snicolas il

2
Ho creato un plugin che fa praticamente lo stesso e funziona con aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz,

7

È possibile utilizzare la propria logica, che risolve ricorsivamente le stringhe nidificate.

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

Da un Activity, per esempio, chiamalo così

replaceResourceStrings(this, getString(R.string.message_text));

2

Sono consapevole che questo è un post più vecchio, ma volevo condividere la soluzione rapida e sporca che ho escogitato per un mio progetto. Funziona solo con TextViews ma potrebbe essere adattato anche ad altri widget. Si noti che richiede che il collegamento sia racchiuso tra parentesi quadre (ad es [@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);
    }
}

Il rovescio della medaglia di questo approccio è che setText()converte la CharSequencein a String, che è un problema se passi cose come una SpannableString; per il mio progetto questo non è stato un problema poiché l'ho usato solo per TextViewsquello che non avevo bisogno di accedere dal mio Activity.


Questa è la cosa più vicina a una risposta a questa domanda. Penso che dobbiamo esaminare l'aggancio nel layout xml di analisi.
Eric Woodruff il

2

Con il nuovo data binding puoi concatenare e fare molto di più nel tuo XML.

per esempio se hai message1 e message2 puoi:

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

puoi persino importare alcuni programmi di testo e chiamare String.format e amici.

sfortunatamente, se si desidera riutilizzarlo in diversi punti, può diventare disordinato, non si desidera che questo codice sia ovunque. e non puoi definirli in XML in un unico posto (non che io sappia) in modo da poter creare una classe che incapsuli quelle composizioni:

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

quindi puoi usarlo invece (dovrai importare la classe con associazione dati)

android:text="@{StringCompositions.completeMessage}"

2

Oltre alla risposta di cui sopra di Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Sembra che ci sia un errore di compilazione "& entity; è stato referenziato, ma non dichiarato" che può essere risolto facendo riferimento alla dichiarazione esterna come questa

res / raw / entities.ent

<!ENTITY appname "My App Name">

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

Sebbene venga compilato ed eseguito, ha un valore vuoto. Forse qualcuno sa come risolverlo. Avrei pubblicato un commento ma la reputazione minima è 50.


1
Ora hai 53.
CoolMind,

1

È possibile utilizzare segnaposto di stringa (% s) e sostituirli utilizzando java in fase di esecuzione

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

e a java

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

e quindi impostarlo nel punto in cui utilizza la stringa


Copi altre soluzioni. Quando riferimento più stringhe, uso %1$s, %2$secc invece di %s.
CoolMind,

@CoolMind puoi menzionare il riferimento alla copia.
Ismail Iqbal,

1

Ho creato una piccola libreria che ti consente di risolvere questi segnaposto in fase di compilazione, quindi non dovrai aggiungere alcun codice Java / Kotlin per ottenere ciò che desideri.

Sulla base del tuo esempio, dovresti impostare le stringhe in questo modo:

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

E quindi il plugin si occuperà di generare quanto segue:

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

Funziona anche per stringhe localizzate e di sapore, inoltre le stringhe generate si manterranno aggiornate ogni volta che apporti modifiche ai tuoi modelli o ai suoi valori.

Maggiori informazioni qui: https://github.com/LikeTheSalad/android-string-reference


Ho provato la tua biblioteca. Dopo aver seguito i passaggi che hai indicato. Continua a dare errori di compilazione. La tua libreria non funziona affatto
developer1011

Capisco, mi spiace sentirlo. Se lo desideri, crea un problema qui: github.com/LikeTheSalad/android-string-reference/issues con i log e posso risolverlo al più presto nel caso in cui si tratti di un problema di funzionalità o se è un problema di configurazione, posso anche aiutarti Ehi, tu. 👍
César Muñoz il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.