Landroid

TextView 안에 #붙은 단어에만 스팬 적용하기 본문

안드로이드

TextView 안에 #붙은 단어에만 스팬 적용하기

silso 2020. 10. 17. 16:19

안녕하세요~ Landroid입니다!

 

가끔 개발하다 보면 텍스트 뷰 안에 특정 부분에만 색상이나 폰트 같은걸 적용해야 하는 경우를 마주할 수 있습니다. 그래서 안드로이드에서는 스팬이라는 걸 사용해서 특정 부분에만 효과를 줄 수 있습니다.

 

하지만 만약 # 태그에만 스팬으로 처리해야 한다면 어떻게 해야 할까요?

구글링으로 찾아본 결과.........

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

없습니다.

 

??????

 

보이는 곳마다 스팬에서 시작 값과 끝 값을 항상 임의로 숫자를 주는 예제만 있고 텍스트가 동적으로 변할 때 적용하는 방법을 설명하는 곳이 단 한 군데도 없었습니다. ㅠㅠ

 

게다가 #태그만 스팬으로 처리하는 방법을 구글링해도 찾을 수가 없었습니다. ㅠㅠ

 

 

 

 

 

 

 

 

 

그래서 준비했습니다!

 

#태그만 스팬으로 처리하는 코드를!

<예제>

버튼이 간지나 보이는 것은 기분탓(?)

 

 

우선 메인 xml 코드입니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/main_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:bufferType="spannable"			// 스팬 적용하기 위해 이건 필수에요
        android:text="Hello World!"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/main_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:ems="10"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/main_result"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/main_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="변환!"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="@+id/main_edit"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/main_edit"
        app:layout_constraintTop_toTopOf="@+id/main_edit" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

그다음 onClick에 들어갈 메서드입니다.(feat.kotlin)

fun firstSolve(view: View) {
        val inputText = main_edit.text.toString()
        main_result.text = inputText		//1
        var isCharacter = false		// 2
        val span: Spannable = main_result.text as Spannable
        var index = 0		// 3

        while (inputText.size >= count + 1) {
            if (inputText[index] == '#') isCharacter = !isCharacter		// 4
            if (inputText[index] == ' ') isCharacter = false
            
            if (isCharacter) {		// 5
                span.setSpan(
                    RelativeSizeSpan(1.5f),
                    index,
                    index + 1,		// 6
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                )
            }

            index++
        }
    }

1. 입력받은 문자열을 main_result에 넣습니다.

2. true인 구간은 태그입니다.

3. 현재 인덱스 위치를 가지는 변수입니다.

4. 현재 확인하고 있는 문자가 #이면 공백 나오기 전까지는 true입니다.

5. 만약 참이면 그 문자만 스팬을 적용합니다.

6. 한 글자만 적용하기 위해 + 1 해줍니다.

 

이렇게 하면 #태그인 부분만 스팬을 적용시킬 수 있습니다.

 

하지만 단점은 태그를 단순히 적용시킨 것이기 때문에 태그를 가져오거나 할 순 없습니다.

 

그래서 두 번째 해결 방안이 있습니다.

fun secondSolve(view: View) {
        val inputText = main_edit.text.toString()
        main_result.text = inputText

        var copy = "$inputText "		// 끝에 공백 추가 안하면 인덱스 벗어남;;
        val span: Spannable = main_result.text as Spannable
        val result = ArrayList<String>()		// 태그값을 저장할 배열
        var count = 0		// 태그 갯수
        var startIndex = 0		// 각 태그의 시작 인덱스를 저장할 변수

        for (element in copy) {		// 1
            if (element == '#') count++
        }

        for (i in 0 until count) {		// 2
            for (j in copy) startIndex = copy.indexOf('#')		// 3

            try {
                result.add(		// 4
                    copy.substring(
                        startIndex,
                        (copy.substring(startIndex).indexOf(" ") + startIndex)
                    )
                )
                
                // 5
                copy = copy.substring((copy.substring(startIndex).indexOf(" ") + startIndex) + 1)
            } catch (e: Exception) {
                println(e.message)
                Toast.makeText(this, "#를 붙이지 마세요", Toast.LENGTH_LONG).show()
                return
            }
        }

        for (i in result) {		// 태그만 가지고 스팬 적용
            span.setSpan(
                RelativeSizeSpan(1.5f),
                main_result.text.toString().indexOf(i),
                main_result.text.toString().indexOf(i) + i.length,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }

1. 태그의 개수를 셉니다.

2. 태그 개수만큼 루프를 돕니다.

3. 각 태그의 시작 인덱스를 얻습니다.

4. result 변수에 copy로부터 분리된 태그를 추가합니다.

5. 문자열에서 맨 앞에 태그를 제거합니다.

 

이렇게 하면 #태그인 부분만 스팬을 적용시킬 수 있고 태그 값도 얻을 수 있습니다.

 

하지만 이것도 단점이 있다면 코드가 복잡한 건 둘째치고 "#A#B"처럼 태그가 붙어있으면 에러가 발생한다는 것입니다.

 

 

휴~ 좀 더 연구를 해봐야겠네요. ^^

 

 

Comments