2021. 10. 4. 15:15ㆍAndroid
EditText를 사용해 사용자로부터 글자를 입력받을 때, 사용자가 글자를 입력하는 동안 특정 로직에 의해 특정 부분의 글자만 색상을 바꿔줘야 할 때가 있다. 이를 구현하는 방법을 소개한다.
목차
결과물
editText 의 끝에 세 글자만 노란색으로 하이라이트를 하는 로직을 적용했을 경우에 대한 결과물이다.
1. TextView 또는 EditText 의 특정 글자색 변경하기
우리는 TextView/EditText 를 사용할 때 setTextColor() 메서드를 통해 글자색을 지정한다.
그런데, 가끔 해당 컴포넌트 내에서도 특정 부분의 글자만 색을 변경해야 할 때가 있다. 예를 들어 특정 문구를 하이라이트를 통해 강조할 경우 등.
SpannableStringBuilder.setSpan() 를 통해 쉽게 구현할 수 있다.
예시를 통해 코드를 살펴보자.
textView 에 "코틀린 좋아요! 진짜 좋아요"라는 문구를 넣을 건데, 여기서 끝에 세글자인 '좋아요' 에만 노란색으로 하이라이트를 하고 싶다.
val text = "코틀린 좋아요! 진짜 좋아요"
val spannableStringBuilder = SpannableStringBuilder(text)
/*
* [text]의 마지막 index.
* endExclusive index 이기 때문에, 사실은 마지막 index 가 아닌, (마지막 index + 1) 값이다.
* 즉, [text.length] 와 동일한 값.
*/
val endIndexExclusive = text.length
val startIndex = endIndexExclusive - 3
// startIndex ~ endIndexExclusive 에 어떤 색상을 입혀주세요~ 라는 역할을 한다.
spannableStringBuilder.setSpan(
ForegroundColorSpan(getColor(R.color.colorPrimaryDark)),
startIndex,
endIndexExclusive,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
// 위에서 span 을 입힌 [spannableStringBuilder] 을 textView.text 에 넣어준다
binding.textView.text = spannableStringBuilder
여기서 주의해야할 점은, setSpan 에 들어가는 endIndex 값은 exclusive 값이라는 것.
즉, 아래와 같이 구하면 된다. 하이라이트 하려는 문구의 startIndex 에 해당 문구의 길이만큼을 더해주면 되는 것!
- endIndexExclusive = startIndex + {하이라이트 할 문구}.length
2. EditText 입력 시 실시간으로 글자색 변경하기
1번을 통해 EditText 에서 특정 글자색만 변경하는 법을 배웠다. 그렇다면, EditText 에서 사용자가 글자를 입력할 때, 실시간으로 특정 조건에 부합하는 글자의 색을 변경해보자.
방법
- EditText 의 TextWatcher 를 통해 실시간으로 변경되는 텍스트 값을 받아온다
- 하이라이트 하려는 글자(색상을 변경하려는 글자)의 startIndex, endIndexExclusive 값을 구한다
- editText.getText().setSpan() 을 통해 span 처리를 해준다. Editable 의 setSpan() 을 사용하는 것.
끝! 정말 쉽다.
예시
상단의 결과물 처럼 editText 의 끝에 세 글자만 노란색으로 하이라이트를 하고 싶다.
우선, 아래와 같이 editText 에 TextWatcher 를 추가해준다.
// editText 에 textWatcher 추가
binding.editText.addTextChangedListener(object : TextWatcher {
...
override fun afterTextChanged(s: Editable?) {
// 새로 글자가 추가될 때마다 로직 체크를 통해 조건에 맞는 글자만 하이라이트처리 해줌.
s?.let { highlightText(it) }
}
})
editText에 글자가 변경될 때 마다 아래의 highlightText() 를 통해 조건에 맞는 글자만 하이라이트 해준다.
이때, setSpan() 으로 하이라이트를 해주기 전에, removeSpan() 을 통해 기존에 추가된 span 들을 제거해줘야 한다. 제거해 주지 않으면, 이전에 설정해놓은 문구에 그대로 색상이 적용되기 때문.
그 후에 끝에 3글자만 하이라이트 되도록, binding.editText.text.setSpan() 처리를 해준다.
private fun highlightText(text: Editable) {
// 기존에 설정해놓은 span 제거
binding.editText.text?.let { editable ->
// binding.editText.text 에 추가된 모든 span 들을 리스트로 얻는다.
val spans = editable.getSpans(0, editable.length, ForegroundColorSpan::class.java)
spans.forEach { span ->
editable.removeSpan(span)
}
}
// editText의 끝에 세글자만 span 처리를 통해 색을 입힌다.
// 이 로직은 예시일 뿐이고, 각자 원하는 로직대로 하이라이트 하려는 문구의 start/end index만 구해주면 된다.
val endIndex = text.length
val startIndex = if (endIndex < 3) 0 else (endIndex - 3)
binding.editText.text?.setSpan(
ForegroundColorSpan(getColor(R.color.colorPrimaryDark)),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
그럼 끝!! 여기까지만 해주면, 위에 올린 영상처럼 실시간으로 특정 글자만 하이라이트 된다.
이렇게 보면 굉장히 간단한데, 이렇게 하기까지 트러블 슈팅 과정이 있었다...
🚧 Trouble Shooting
1️⃣ Editable.setSpan() 을 몰랐어..
처음에는 editText.getText().setSpan() 의 존재를 몰랐다. 즉, Editable 에 setSpan 메소드가 있는 줄 몰랐던 거지.
그래서 어떻게 했냐? 내가 알았던 방식은 위에 1번 뿐이었다. 즉, 나는 SpannableStringBuilder 를 통해서만 setSpan 을 해줄 수 있는 줄!!!!!!!
그래서 글자가 실시간으로 변경될 때마다 SpannableStringBuilder를 생성하고 editText.setText 처리를 해줬다... 아래와 같이.. (물론 아래처럼 완전 실시간은 아니고 debounce 처리를 해주긴 했는데 여기서는 관련이 없기 때문에 생략)
binding.editText.addTextChangedListener(object : TextWatcher {
...
override fun afterTextChanged(s: Editable?) {
val spannableStringBuilder = SpannableStringBuilder(s?.toString() ?: "")
spannableStringBuilder.setSpan(
ForegroundColorSpan(getColor(R.color.colorPrimaryDark)),
startIndex,
endIndexExclusive,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
// 나는 멍청이 ~~ twit twit
binding.editText.text = spannableStringBuilder
}
})
😖 이렇게 하면 어떤 문제가 발생하냐?
사용자가 입력하는 도중에 setText() 해주기 때문에, 아래와 같은 문제들이 발생한다. 이 외에도 다양한 이슈들이 존재.
- 한글의 경우, 쌍자음/받침 등이 있는데 이를 다 입력하는데는 시간이 좀 걸리는데(1-2초 정도?), 이걸 입력하는 와중에 setText 가 되어버리니, '쌉'을 느리게 입력할 경우 ㅆㅏㅂ 같이 입력이 되는 경우도 있고,,
- 특수문자를 입력하기 위해 키보드에서 시프트를 눌러 특수문자 입력 키보드가 되었는데, setText() 가 되면 키보드가 초기화됨.
2️⃣ Editable.clearSpans() 대신, Editable.removeSpan() 을 사용한 이유
위에 코드를 보면, editable.getSpans(0, editable.length, ForegroundColorSpan::class.java) 를 통해 editText 에 적용된 span 들을 구하고 이를 각각 removeSpan() 을 통해 제거해줬다.
그런데, Editable 속성을 보면, clearSpans() 라는 메소드가 있다. 이 메소드를 사용하면 for 문 없이 clearSpans() 만 호출해주면 되는 데 사용하지 않은 이유는??
👉 editText 의 동작이 이상해진다;
이상해진다는 의미가 뭐냐면, 막 커서가 2개 생기고 심지어 하나는 투명 커서야.. 글자 입력도 이상해지고..
다른 분들은 이런 실수를 하시지 않길 바라며.. 트러블 슈팅기를 공유합니다.
'Android' 카테고리의 다른 글
Android 디버깅 방법 및 Tip (Debug mode 사용하기) (0) | 2022.01.16 |
---|---|
[Android] Room - Entity(table) 간의 관계(relationship) 정의하기 (1) | 2021.11.28 |
[RxJava] debounce vs throttle 함수 차이점 비교 (2) | 2021.09.08 |
[Android] Recyclerview 클릭한 아이템을 가운데로 scroll 되게 하기 (2) | 2021.08.16 |
[Android] Progress 를 나타내는 Custom ProgressBar 구현하기 (0) | 2021.08.08 |