Simple&Natural

TextWatcher를 사용하여 안드로이드 EditText 에 단위를 붙여보자 본문

안드로이드(Android)/이것저것 만들어보기

TextWatcher를 사용하여 안드로이드 EditText 에 단위를 붙여보자

Essense 2023. 1. 10. 17:35
728x90

UI를 구현하거나 앱들을 쓰다 보면 보면 특정 editText에 콤마나 단위 등을 고정하여 표시하는 경우가 있습니다.

 

예를 들면 은행 앱에서 출금 시 자동으로 콤마를 붙여주고 뒤에 '원'이 붙는 경우를 한 번 쯤은 보셨을 겁니다.

 

금액 입력 전

 

 

금액 입력 후

 

이런 화면은 어떻게 구현해야 할까요?

 

바로 TextWatcher를 사용하면 됩니다.

 

 

TextWatcher는 안드로이드에서 제공하는 인터페이스로서 EditText의 텍스트 입력이 들어올 때 마다 콜백이 호출됩니다.

 

3가지 함수를 오버라이드 하게 되는데요.

beforeTextChanged

onTextChanged

afterTextChanged

가 있습니다.

 

beforeTextChanged는 바뀌기 전의 입력을 알 수 있고

onTextChanged는 바뀐 후의 입력을 알 수 있습니다.

 

그렇다면 afterTextChanged는 언제 사용할까요?

여기서는 변경된 문자열을 기준으로 추가적인 수정을 처리할 때 사용합니다.

문서에 보시면 친절하게 아래와 같이 설명되어 있습니다.

... It is legitimate to make further changes to s from this callback, but be careful not to get yourself into an infinite loop, ...

해당 콜백에서 텍스트를 변경해도 되지만 무한루프에 빠지지 않게 주의하라네요.

 

그럼 이제 실제로 어떻게 구현하는지 아래 소스코드를 첨부드리겠습니다.

 

abstract class SuffixedTextWatcher(
    private val editText: EditText,
    private val suffix: String
) : TextWatcher {
    var beforeText = ""
    var afterText = ""
    var startPos = 0
    var endPos = 0

    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        beforeText = p0.toString()
    }

    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        afterText = p0.toString()
        startPos = p2
        endPos = p3
    }

    override fun afterTextChanged(e: Editable?) {
        if (e == null) return

        // 무한루프를 방지
        if (beforeText == afterText) return

        // suffix 가 수정된 경우 마지막 문자열을 suffix 로 만들어 주고 커서를 suffix 바로 앞으로 이동시키는 부분
        if (!e.toString().endsWith(suffix, false)) {
        	// 첫 입력인 경우 suffix 를 세팅해준다
            if (beforeText.isEmpty()) {
                // 값이 입력된 경우 값도 앞에 붙여줌
                if (afterText.isNotBlank()) {
                    val suffixedText = afterText.plus(suffix)
                    editText.setText(suffixedText)
                    editText.setSelection(suffixedText.length - suffix.length)
                    return
                }
                editText.setText(suffix)
                editText.setSelection(0)
                return
            }
            
            editText.setText(beforeText)
            
            if (beforeText.length < suffix.length) {
                // 문자를 지운 경우
                editText.setSelection(0)
            } else {
                // 문자가 삽입된 경우
                editText.setSelection(afterText.length - suffix.length)
            }
            return
        }

        // 이하 suffix 에 문제가 없는 경우
        
        // 빈값이 들어오면 0으로 리턴해줍니다
        val num = e.toString()
            .replace(",", "")
            .removeSuffix(suffix)
            .toBigIntegerOrNull()
            ?: kotlin.run {
                onNumberChanged(BigInteger.ZERO)
                return
            }

        // 정상적으로 입력값이 들어온 경우
        if (beforeText != afterText) {
            // 값에 suffix 를 붙여주고
            val str = NumberFormat.getInstance().format(num).plus(suffix)
            editText.setText(str)
            
            // suffix 바로 앞으로 커서를 이동시켜줍니다
            val selectedPos = if (editText.text.length - suffix.length < 0) {
                0
            } else {
                editText.text.length - suffix.length
            }
            editText.setSelection(selectedPos)
        }

        // 마지막으로 콜백도 호출해줍니다, 현재는 정수형 입출력만 가능하도록 되어 있습니다
        // 만약 실수형의 데이터를 사용하고 싶다면 데이터 타입을 바꿔줘야 합니다, 유의하세요!
        onNumberChanged(num)
    }

    abstract fun onNumberChanged(number: BigInteger)

}

 

단, 현재 기준은 정수형만 사용이 가능하기 때문에 아래와 같이 inputType을 number로 설정해주어야 오류가 나지 않습니다.

실수형을 사용하고자 하는 경우 약간의 커스터마이징이 필요한데 필요하시면 실수형 버전도 올려보도록 할게요!

<EditText
    android:id="@+id/et_amount"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="금액 입력"
    android:maxLength="9"
    android:textColorHint="@color/color_cfd6e4"
    android:textSize="40sp"
    android:fontFamily="@font/pretendard_regular"
    android:paddingStart="40sp"
    android:paddingEnd="40sp"
    android:paddingBottom="16dp"
    android:inputType="number"
    android:layout_marginTop="8dp"
    android:layout_gravity="center_horizontal"
    android:gravity="center"/>

 

 

 

 

실제로는 이런 식으로 사용하시면 됩니다.

val suffix = " CC"
binding.etAmount.addTextChangedListener(object : SuffixedTextWatcher(binding.etAmount, suffix) {
    override fun onNumberChanged(number: BigInteger) {
    	// do something
    }
})

 

모르거나 궁금하신 부분 그리고 개선할 점이 있다면 언제든 의견 남겨주세요!

728x90