Span이 적용된 텍스트를 잘라서 붙일 때 제대로 나오지 않는다면?

Span이 적용된 텍스트를 잘라서 붙여야 하는 경우가 있다. 예를 들면 원문 중간에 새로운 텍스트를 삽입하거나 필요없는 텍스트를 제거해야 하는 경우가 그렇다. 나의 경우도 별반 다르지 않았기 때문에 Span이 적용되어 있는 텍스트를 자른뒤 다시 이어 붙였지만 이상하게도 원하는대로 나오지 않았다.

Span이 적용된 텍스트를 잘라보자

ABC라는 문자열이 있다고 가정하자. 이 문자열을 ABC로 자른뒤 다시 이어붙이면 어떻게 될까?

ABC출력되길 기대했지만, 실제 출력된 것은 ABC 다. 

val original = SpannableString("ABC")
val boldSpan = StyleSpan(Typeface.BOLD)
original.setSpan(boldSpan, 0, original.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

val sequence1 = original.subSequence(0, 1)
tv1.text = sequence1 //A

val sequence2 = original.subSequence(1, original.length)
tv2.text = sequence2 //BC

val concatenated = TextUtils.concat(sequence1,sequence2)
tv3.text = concatenated //ABC

문제의 원인

잘라놓은 sequence1과 sequence2는 bold 속성이 표현된 것을 확인 할 수 있다. 하지만 이 둘을 합쳤을 때는 A만 bold가 적용된 모습을 확인할 수 있다. 

SpannableString을 subSequence() 메서드를 통해 자른 각각의 두 객체(sequence1, sequence2)는 original 로 부터 물려받은 동일한 StyleSpan(Bold) 인스턴스를 가지고 있게 되는데, sequence1를 sequence2와 합칠 때 Span 플래그가 Exclusive로 지정 되어있기 때문에 sequence1에서 bold가 적용되고 끝나는 문제로 파악된다.

문제의 해결

동일한 span 인스턴스를 두 객체가 같이 참조하는 것 부터가 문제인 것 같다. original을 쪼갤 때 original에 적용된 span들을 파악한 뒤, 두 객체(sequence1, sequence2)에 새로운 span 인스턴스를 똑같이 생성해주는 방법을 적용해보자.

/**
 * @author Charlezz
 */
object SpanHelper {

    fun subSequence(original: Spanned, start: Int, end: Int): SpannableString {
        val result = SpannableString(original.subSequence(start, end))
        result.clearSpans()

        val spans = original.getSpans(
            start,
            end,
            CharacterStyle::class.java
        )
        for (span in spans) {
            val originalSpanStart = original.getSpanStart(span)
            val originalSpanEnd = original.getSpanEnd(span)

            val spanStart = max(originalSpanStart, start) - start
            val spanEnd = min(originalSpanEnd - start, result.length)

            result.setSpan(cloneSpan(span),
                spanStart,
                spanEnd,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }

        return result
    }

    private fun cloneSpan(characterStyle: CharacterStyle): CharacterStyle {
        if (characterStyle is StyleSpan) {
            return StyleSpan(characterStyle.style)
        } else if (...){ //기타, 추가하고 싶은 Span을 생성
            ...
        }
        return characterStyle
    }
}
val original = SpannableString("ABC")
val boldSpan = StyleSpan(Typeface.BOLD)
original.setSpan(boldSpan, 0, original.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

val sequence1 = SpanHelper.subSequence(original,0,1)
tv1.text = sequence1 //A

val sequence2 = SpanHelper.subSequence(original,1, original.length)
tv2.text = sequence2 //BC

val concatenated = TextUtils.concat(sequence1,sequence2)
tv3.text = concatenated //ABC

Buy me a coffeeBuy me a coffee
카테고리: Android

0개의 댓글

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.