Spoiler text formatting on Android

Mark Njung’e
3 min readFeb 5, 2019

--

Inspired by Discord now having spoiler tags, I decided to look into how this can be done. I was able to achieve a similar effect using SpannableString.

You can find the final source code on my Github.

So let’s get started.

The UI for the demo is pretty simple. An EditText, a Button and a TextView. When the Button is clicked, the text in the EditText is formatted and set in the TextView.

You will then need to choose what you want the spoiler tag to be. In my case, I chose the same as Discord; two pipe characters.

private val spoilerTag = "||"

Detecting the spoiler text

Code first, in case you want to copy-paste

First we remove the spoiler tags from the text and save it for later. This is the text we will apply the formatting to.

Since we want to support multiple spolier blocks, we will store the ranges in a list of pairs. The first int in the pair represents the start and the second represents the end.

Getting the start and end indices of the tags is pretty straightforward using indexOf.
For the end tag, we start from the index if the starting tag plus the length of the spoiler tag. Then we offset it by the length of the spoiler tag. This is because the index will be at the end of the tag, instead of the beginning.
Since we now have the start and the end, we can add them to the list.

We then need to remove the spoiler tags. We use replaceRange since replace will remove all instances while we only want to remove the ones we’ve accounted for.

Before removing trying to remove the end tag, we need to ensure that there actually is an end tag. This can occur if the text is badly formatted.
To do that, we check if the end index is less than the start.

Usually, the value returned when indexOf doesn’t find any occurrence is -1 but in our case it is less than that because we deducted the length of the spoiler tag.

Formatting the text

In order for ClickableSpan to work, we need to first add this

textView.movementMethod = LinkMovementMethod.getInstance()

Then we call a function to create the SpannableString and update the text view. Into this function we pass the original text (the one without the tags) and the list of ranges.

updateTextView(original, ranges)

We are wrapping this in a function because we will need to call it again when the user clicks on a block.

Again, code first

When setting the spans, we loop through the ranges so that each block is separate. This is so that the user can click each block to reveal it’s contents, instead of all at once.

For the hidden effect, we use a BackgroundColorSpan. You also need to set a ForegroundColorSpan so that the text color is the same as the background, otherwise the text will still be visible.

For the text to be clickable, we use a ClickableSpan.
Within the onClick, we remove the current range from the list then call the function again with the “new” list and the original text.

That’s it. Now we have a working spoiler textview.

--

--