27
loading...
This website collects cookies to deliver better user experience
CharSequences
instead. Android supports some html tags out of the box, and those can be defined in the xml string resource, for instance<string name="lorem_ipsum">
This is <font color="red">red</font> and this
<b><i>bold and italic (nested); </i>this just bold</b>,
<u>underlined</u>
</string>
CharSequence
by calling context.getText(stringRes: Int)
which takes care of all the supported HTML tags to style the text without us needing to do anything else%{digit}${type}
, for instance %1$s
is a string passed as first vararg and %2$d
is a decimal number passed as second vararg in String.format(text: String, vararg args: String)
, which is called to resolve the placeholders.<string name="lorem_ipsum">
This is <font color="red">red</font> and this
<b><i>bold and italic (nested); </i>this just bold</b>, <u>underlined</u>
and here the placeholder = %1s
</string>
String.format(text: String, vararg args: String)
, its first argument requires a String instead of a CharSequence
. This means, the dynamic text placeholders will be correctly replaced, but our CharSequence
has to be converted to String
, throwing away its styling.'<'
, which becomes '<'
<string name="lorem_ipsum">
This is <font color="red">red</font>
and this <b><i>bold and italic (nested); </i>this just bold</b>,
<u>underlined</u> and here the placeholder = %1s
</string>
<string name="lorem_ipsum">
<![CDATA[This is <font color="red">red</font> and this <b><i>bold and italic (nested); </i>this just bold</b>, <u>underlined</u> and here the placeholder = %1s]]>
</string>
val text = context.getString(R.string.lorem_ipsum)
val dynamicText = String.format(text, "placeholder1")
val dynamicStyledText = HtmlCompat.fromHtml(
dynamicText,
HtmlCompat.FROM_HTML_MODE_COMPACT
)
textView.text = dynamicStyledText
<
, >
, &
, \
or "
, like in <placeholder1>
, leading to the result belowHtmlCompat.fromHtml()
. We solve that by encoding the placeholders before using HtmlCompat, like thisval text = context.getString(R.string.lorem_ipsum)
val encodedPlaceholder = TextUtils.htmlEncode("<placeholder1>")
val dynamicText = String.format(text, encodedPlaceholder)
val dynamicStyledText = HtmlCompat.fromHtml(
dynamicText,
HtmlCompat.FROM_HTML_MODE_COMPACT
)
textView.text = dynamicStyledText
context.getText(R.string.lorem_ipsum)
returns the string resource styled as a CharSequence
. If the string resource has a placeholder, it will be shown the same as in the xml.HtmlCompat.fromHtml()
processes "some" HTML tags. Its inverse method exists and does exactly the opposite: takes a Spanned
object and converts it to a string with the corresponding HTML tags. The flag we pass to the method also matters: HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
also adds a new line at the end of the HTML string and we have to account for that. Therefore, we can get the desired HTML string as follows// step 2 - toHtml()
val spannedString = SpannedString(styledString)
val htmlString = HtmlCompat.toHtml(
spannedString,
HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
)
.substringBeforeLast('>')
.plus(">")
String.format(text: String, vararg args: String)
for that. It would not work with a CharSequence
, but that is why we converted it into its equivalent HTML string in the first place.// step 3 - String.format()
val dynamicHtmlString = String.format(htmlString, args)
HtmlCompat.FROM_HTML_MODE_COMPACT
, since it is the inverse of the HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
we've previously used// step 4 - fromHtml()
val result = HtmlCompat.fromHtml(
dynamicStyledString,
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.removeSuffix("\n") // fromHtml() adds a new line at the end
Strings
containing unsafe characters, they do not show up. Therefore, do not forget that we need to encode the string values that will substitute the placeholders. A Kotlin extension function following all the aforementioned steps would look like thisfun Context.getHtmlStyledText(
@StringRes htmlStringRes: Int,
vararg args: Any
): CharSequence {
// step 0 - Encode string placeholders
val escapedArgs = args.map {
if (it is String) TextUtils.htmlEncode(it) else it
}.toTypedArray()
// step 1 - getText()
val styledString = Context.getText(htmlStringRes)
// step 2 - toHtml()
val spannedString = SpannedString(styledString)
val htmlString = HtmlCompat.toHtml(
spannedString,
HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
)
.substringBeforeLast('>')
.plus(">")
// step 3 - String.format()
val dynamicStyledString = String.format(htmlString, *escapedArgs)
// step 4 - fromHtml()
return HtmlCompat.fromHtml(
dynamicStyledString,
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.removeSuffix("\n") //fromHtml() adds one new line at the end
}
// step 1 - getText()
val styledString = context.getText(R.string.lorem_ipsum)
// step 1 - getText()
val styledString = context.resources.getQuantityText(R.plural.lorem_ipsum, quantity)