androidandroid-edittexteditorspannablestringbuilder

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


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.


Solution

  • 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
        }