Dans Android il est possible d'utiliser des espaces réservés dans les chaînes, tels que:
<string name="number">My number is %1$d</string>
puis dans Java (à l'intérieur d'une sous-classe de Activity
):
String res = getString(R.string.number);
String formatted = String.format(res, 5);
ou encore plus simple:
String formatted = getString(R.string.number, 5);
Il est également possible d'utiliser certaines balises HTML dans les ressources de chaîne Android:
<string name="underline"><u>Underline</u> example</string>
Puisque le String
lui-même ne peut contenir aucune information sur le formatage, il faut utiliser la méthode getText(int)
au lieu de getString(int)
:
CharSequence formatted = getText(R.string.underline);
Le CharSequence
retourné peut ensuite être passé à Android, tels que TextView
, et la phrase marquée sera soulignée.
Cependant, je n'ai pas pu trouver comment combiner ces deux méthodes, en utilisant une chaîne formatée avec des espaces réservés:
<string name="underlined_number">My number is <u>%1$d</u></string>
Comment traiter la ressource ci-dessus dans le code Java pour l'afficher dans un TextView
, en remplaçant %1$d
Par un entier?
Enfin, j'ai réussi à trouver une solution de travail et j'ai écrit ma propre méthode pour remplacer les espaces réservés, en préservant le formatage:
public static CharSequence getText(Context context, int id, Object... args) {
for(int i = 0; i < args.length; ++i)
args[i] = args[i] instanceof String? TextUtils.htmlEncode((String)args[i]) : args[i];
return Html.fromHtml(String.format(Html.toHtml(new SpannedString(context.getText(id))), args));
}
Cette approche ne nécessite pas d'échapper manuellement les balises HTML, ni dans une chaîne en cours de formatage, ni dans des chaînes qui remplacent les espaces réservés.
<resources>
<string name="welcome_messages">Hello, %1$s! You have <b>%2$d new messages</b>.</string>
</resources>
Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);
CharSequence styledText = Html.fromHtml(text);
Plus d'infos ici: http://developer.Android.com/guide/topics/resources/string-resource.html
Pour le cas simple où vous souhaitez remplacer un espace réservé sans mise en forme numérique (c'est-à-dire des zéros non significatifs, des chiffres après une virgule), vous pouvez utiliser la bibliothèque Square Phrase .
L'utilisation est très simple: vous devez d'abord modifier les espaces réservés dans votre ressource de chaîne en ce format plus simple:
<string name="underlined_number">My number is <u> {number} </u></string>
alors vous pouvez faire le remplacement comme ceci:
CharSequence formatted = Phrase.from(getResources(), R.string.underlined_number)
.put("number", 5)
.format()
Le CharSequence
formaté est également stylisé. Si vous devez formater vos nombres, vous pouvez toujours les pré-formater à l'aide de String.format("%03d", 5)
, puis utiliser la chaîne résultante dans la fonction .put()
.
textView.text = context.getText(R.string.html_formatted, "Hello in bold")
<string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B>]]></string>
texte en gras: Bonjour en gras
/**
* Create a formatted CharSequence from a string resource containing arguments and HTML formatting
*
* The string resource must be wrapped in a CDATA section so that the HTML formatting is conserved.
*
* Example of an HTML formatted string resource:
* <string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B> ]]></string>
*/
fun Context.getText(@StringRes id: Int, vararg args: Any?): CharSequence {
val text = String.format(getString(id), *args)
return if (Android.os.Build.VERSION.SDK_INT >= 24)
Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT)
else
Html.fromHtml(text)
}
Semblable à la réponse acceptée, j'ai essayé d'écrire une méthode d'extension Kotlin pour cela.
Voici la réponse acceptée dans Kotlin
@Suppress("DEPRECATION")
fun Context.getText(id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is String) TextUtils.htmlEncode(it) else it
}.toTypedArray()
return Html.fromHtml(String.format(Html.toHtml(SpannedString(getText(id))), *escapedArgs))
}
Le problème avec la réponse acceptée, c'est qu'elle ne semble pas fonctionner lorsque les arguments de format eux-mêmes sont stylisés (c'est-à-dire Spanned, pas String). Par expérience, cela semble faire des choses étranges, peut-être avec le fait que nous n'échappons pas aux CharSequences non-String. Je vois que si j'appelle
context.getText(R.id.my_format_string, myHelloSpanned)
où R.id.my_format_string est:
<string name="my_format_string">===%1$s===</string>
et myHelloSpanned est un Spanned qui ressemble à <b> bonjour </b> (c'est-à-dire qu'il aurait du HTML <i><b>hello</b></i>
) alors j'obtiens === bonjour=== (ie HTML ===<b>hello</b>===
).
C'est faux, je devrais avoir === <b> bonjour </b> ===.
J'ai essayé de résoudre ce problème en convertissant toutes les CharSequences en HTML avant d'appliquer String.format
, Et voici mon code résultant.
@Suppress("DEPRECATION")
fun Context.getText(@StringRes resId: Int, vararg formatArgs: Any): CharSequence {
// First, convert any styled Spanned back to HTML strings before applying String.format. This
// converts the styling to HTML and also does HTML escaping.
// For other CharSequences, just do HTML escaping.
// (Leave any other args alone.)
val htmlFormatArgs = formatArgs.map {
if (it is Spanned) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.toHtml(it, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
} else {
Html.toHtml(it)
}
} else if (it is CharSequence) {
Html.escapeHtml(it)
} else {
it
}
}.toTypedArray()
// Next, get the format string, and do the same to that.
val formatString = getText(resId);
val htmlFormatString = if (formatString is Spanned) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.toHtml(formatString, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
} else {
Html.toHtml(formatString)
}
} else {
Html.escapeHtml(formatString)
}
// Now apply the String.format
val htmlResultString = String.format(htmlFormatString, *htmlFormatArgs)
// Convert back to a CharSequence, recovering any of the HTML styling.
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(htmlResultString, Html.FROM_HTML_MODE_LEGACY)
} else {
Html.fromHtml(htmlResultString)
}
}
Cependant, cela n'a pas vraiment fonctionné parce que lorsque vous appelez Html.toHtml
, Il place des balises <p>
Autour de tout, même lorsque ce remplissage supplémentaire n'était pas dans l'entrée. Autrement dit, Html.fromHtml(Html.toHtml(myHelloSpanned))
n'est pas égal à myHelloSpanned
- il a un rembourrage supplémentaire. Je ne savais pas comment résoudre ça bien.
Voici une extension Kotlin plus lisible qui n'utilise pas d'API obsolètes, fonctionne sur toutes les versions Android et ne nécessite pas de chaînes à encapsuler dans les sections CDATA:
fun Context.getText(id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is String) TextUtils.htmlEncode(it) else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = HtmlCompat.toHtml(resource, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
Vous pouvez ajouter un alias en tant qu'extension de Fragment - n'oubliez pas de répartir les arguments entre:
fun Fragment.getText(id: Int, vararg args: Any) = requireContext().getText(id, *args)