How does the remove property of the SpannableStringBuilder class really work? TextEditor in Android (Kotlin)

50 Views Asked by At

I'm developing a small native Android project using Kotlin. I intend to create a small text editor with the various buttons bold, italic, underline and so on. Unfortunately I've been really trying for days; also doing research and questioning the AI too, but I can't figure it out. I don't understand how the remove property of the SpannableStringBuilder class works.

Let's start with a simple idea, of which I will also include the code: I have a button and an EditText. When you press the button, subsequent characters will be underlined. When you press the button again, the underline should disappear for subsequent characters.

    val editTextContent : EditText = findViewById(R.id.editTextContent)


    val btnUnderline: Button = findViewById(R.id.button)
    var isUnderlineApplied = false

    btnUnderline.setOnClickListener {



        val startSelection = editTextContent.selectionStart
        val endSelection = editTextContent.selectionEnd

        val spannableString = SpannableStringBuilder(editTextContent.text)

        if (isUnderlineApplied) {

            // Remove the underline

            spannableString.removeSpan(UnderlineSpan())
        } else {
            // Apply underlining

            spannableString.setSpan(
                UnderlineSpan(),
                startSelection,
                endSelection,
                Spannable.SPAN_INCLUSIVE_INCLUSIVE
            )

        }
        
        editTextContent.text = spannableString
        editTextContent.setSelection(endSelection)

        isUnderlineApplied = !isUnderlineApplied


    }

This doesn't happen here. I do not understand why. I also tried to make a totally new project, believing that there were some settings inserted incorrectly, but nothing. This is why I ask you, please, how does this remover work and what should I do to make it work properly? Thanks for your attention.

2

There are 2 best solutions below

0
Fantastico On BEST ANSWER

I found the solution! The idea of the user Cheticamp is correct, we need to preserve the span and not create a new one. Once this was applied, there was the problem of when the remove was performed: everything was deleted.

In the end I found a solution: the span, when added, does so in a certain range; so why not save these ranges?

Therefore every time I have to deactivate a span, I save the range, given by the initial value in which I applied the span (here I called it "start") and at the end of the selection (called "endSelection"). At this point I remove the span and through the loop I apply the span to all the ranges. Doing so gives the illusion of span activation/deactivation (in this case the underline).

I don't know if it was the optimal solution, if it was best practice but it works. I'm sharing the final code with you all.

    val editTextContent: EditText = findViewById(R.id.editTextContent)
    val btnUnderline: Button = findViewById(R.id.button)
    var isUnderlineApplied = false

    var underlineSpan: UnderlineSpan? = null


    data class Range(val start: Int, val end: Int)
    val rangeList = mutableListOf<Range>()


    var start = 0


    btnUnderline.setOnClickListener {
        val startSelection = editTextContent.selectionStart
        val endSelection = editTextContent.selectionEnd
        val spannableString = SpannableStringBuilder(editTextContent.text)



        if (isUnderlineApplied) {
            // Remove the underline

            spannableString.removeSpan(underlineSpan)

            val newRange = Range(start, endSelection)
            rangeList.add(newRange)

            for (range in rangeList) {
                spannableString.setSpan(
                    UnderlineSpan(),
                    range.start,
                    range.end,
                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE
                )
            }


        } else {

            start = startSelection

            // Apply underlining
            underlineSpan = UnderlineSpan()

            spannableString.setSpan(
                underlineSpan,
                startSelection,
                endSelection,
                Spannable.SPAN_INCLUSIVE_INCLUSIVE
            )
        }

        editTextContent.text = spannableString
        editTextContent.setSelection(endSelection)

        isUnderlineApplied = !isUnderlineApplied
    }
3
Cheticamp On

The problem is with the following line:

spannableString.removeSpan(UnderlineSpan())

You want to remove a span from the string builder, but this line creates a new span that doesn't exist in the string, so nothing happens.

Here is an example of how to retrieve spans from a string and to remove them. You could also just capture the underline span that you create with the setSpan code and reference it later for removal.