java - tipo - google maps android sgoliver
¿Cómo usar cadenas formateadas junto con marcadores de posición en Android? (4)
De forma similar a la respuesta aceptada, intenté escribir un método de extensión de Kotlin para esto.
Aquí está la respuesta aceptada en 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(context.getText(id))), *escapedArgs))
}
El problema con la respuesta aceptada es que no parece funcionar cuando los propios argumentos de formato tienen un estilo (es decir, Spanned, no String). Por experimento, parece hacer cosas extrañas, posiblemente relacionadas con el hecho de que no estamos escapando de las secuencias de caracteres no de cadena. Estoy viendo eso si llamo
context.getText(R.id.my_format_string, myHelloSpanned)
donde R.id.my_format_string es:
<string name="my_format_string">===%1$s===</string>
y myHelloSpanned es un Spanned que se parece a <b> hello </ b> (es decir, tendría HTML <i><b>hello</b></i>
) luego me sale === hello === ( es decir, HTML ===<b>hello</b>===
).
Eso está mal, debería obtener === <b> hola </ b> ===.
Traté de solucionar esto convirtiendo todas las secuencias de caracteres a HTML antes de aplicar String.format
, y aquí está mi código resultante.
@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)
}
}
Sin embargo, esto no funcionó porque cuando llamas a Html.toHtml
coloca etiquetas <p>
alrededor de todo incluso cuando ese relleno extra no estaba en la entrada. Dicho de otra manera, Html.fromHtml(Html.toHtml(myHelloSpanned))
no es igual a myHelloSpanned
, tiene un relleno extra. No sabía cómo resolver esto bien.
En Android, es posible usar marcadores de posición en cadenas, como:
<string name="number">My number is %1$d</string>
y luego en código Java (dentro de una subclase de Activity
):
String res = getString(R.string.number);
String formatted = String.format(res, 5);
o incluso más simple:
String formatted = getString(R.string.number, 5);
También es posible usar algunas etiquetas HTML en recursos de cadenas de Android:
<string name="underline"><u>Underline</u> example</string>
Dado que el String
sí no puede contener ninguna información sobre el formateo, se debe usar el getText(int)
lugar de getString(int)
:
CharSequence formatted = getText(R.string.underline);
La CharSequence
devuelta se puede pasar luego a los widgets de Android, como TextView
, y la frase marcada estará subrayada.
Sin embargo, no pude encontrar la manera de combinar estos dos métodos, usando una cadena formateada junto con marcadores de posición:
<string name="underlined_number">My number is <u>%1$d</u></string>
¿Cómo procesar el recurso anterior en el código de Java para mostrarlo en un TextView
, sustituyendo %1$d
con un entero?
Finalmente logré encontrar una solución de trabajo y escribí mi propio método para reemplazar marcadores de posición, preservando el formato:
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));
}
Este enfoque no requiere escapar etiquetas HTML manualmente ni en una cadena formateada ni en cadenas que reemplazan marcadores de posición.
Para el caso simple en el que desee reemplazar un marcador de posición sin formato de número (es decir, ceros a la izquierda, números después de la coma), puede usar la biblioteca de Phrase cuadrada.
El uso es muy simple: primero tiene que cambiar los marcadores de posición en su recurso de cadena a este formato más simple:
<string name="underlined_number">My number is <u> {number} </u></string>
entonces puedes hacer el reemplazo así:
CharSequence formatted = Phrase.from(getResources(), R.string.underlined_number)
.put("number", 5)
.format()
El CharSequence
formateado también tiene un estilo. Si necesita formatear sus números, siempre puede preformatearlos usando String.format("%03d", 5)
y luego usar la cadena resultante en la función .put()
.
<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);
Más información aquí: http://developer.android.com/guide/topics/resources/string-resource.html