Uing? Uing!!

[안드로이드 삽질기록] TextWatcher의 함정: 키보드마다 동작이 다르다? 본문

Android

[안드로이드 삽질기록] TextWatcher의 함정: 키보드마다 동작이 다르다?

Uing!! 2021. 10. 23. 02:30
반응형

발단

EditText에서 사용자의 입력을 추적하기 위해 흔히 사용되는 방법이 이렇게 TextWatcher를 붙이는 것이다.

binding.editTextView.addTextChangedListener(object: TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    	// 텍스트 변경 전
        // s: 기존 문자열, start: 커서 시작 위치, count: 변경 대상 문자 수, after: 변경 후 문자 수
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    	// 텍스트 변경 시
        // s: 변경된 문자열, start: 커서 시작 위치, before: 변경 대상 문자 수, count: 변경 후 문자 수
    }

    override fun afterTextChanged(s: Editable?) {
    	// 텍스트 변경 후
        // s: 변경된 문자열
    }
})

그런데, 에뮬레이터에서는 잘 동작하던 이 TextWatcher가 어찌된 일인지 실기기에서는 이상하게 동작했다.

무슨 일인고 하니, 키보드마다 조금씩 다르게 동작을 하는 것이다.

 

삽질

키보드마다 다르게 동작하는 이유가 무엇인지 알아보기 위해 테스트를 진행했다.

테스트 방법

키보드마다 어떤 차이가 있는 것인지 확인하기 위해 로그를 남겨 보았다.

binding.editTextView.addTextChangedListener(object: TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        Log.d("beforeTextChanged", " s: $s, start: $start, count: $count, after: $after")
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        Log.d("onTextChanged", "s: $s, start: $start, before: $before, count: $count")
    }

    override fun afterTextChanged(s: Editable?) {
        Log.d("afterTextChanged", "s: $s")
    }
})

테스트 과정에서는 영어, 한글 각각 하나씩의 시나리오를 활용했다.

1) 'ab'에 'c'를 추가해서 'abc'를 만드는 경우

2) '기니'에 'ㅣ'를 추가해서 '기니디'를 만드는 경우 ('ㅏ'의 경우 키보드간 타이핑 차이가 있어 'ㅣ'로 선택) 

 

또한 키보드 종류로는 아래 6가지를 사용했다.

1) 에뮬레이터 키보드(윈도우 환경, Nexus 5, API 30)

2) 에뮬레이터 키보드(맥 환경, Nexus 5, API 30)

3) 삼성 키보드(자동완성 껐을 때)

4) 삼성 키보드(자동완성 켰을 때)

5) 딩굴 키보드

6) 모아 키보드

 

테스트 결과

1) 'ab'에 'c'를 추가해서 'abc'를 만드는 경우

// 에뮬레이터 키보드 (윈도우 환경, Nexus 5, API 30)
D/beforeTextChanged:  s: ab, start: 0, count: 2, after: 3
D/onTextChanged: s: abc, start: 0, before: 2, count: 3 // 커서 0부터 2글자("ab")를 3글자("abc")로 변경
D/afterTextChanged: s: abc

// 에뮬레이터 키보드 (맥 환경, Nexus 5, API 30)
D/beforeTextChanged:  s: ab, start: 2, count: 0, after: 1
D/onTextChanged: s: abc, start: 2, before: 0, count: 1 // 커서 2부터 0글자("")를 1글자("c")로 변경
D/afterTextChanged: s: abc

// 삼성키보드 (자동완성 켰을 때)
D/beforeTextChanged:  s: ab, start: 0, count: 2, after: 3
D/onTextChanged: s: abc, start: 0, before: 2, count: 3 // 커서 0부터 2글자("ab")를 3글자("abc")로 변경
D/afterTextChanged: s: abc

// 삼성키보드 (자동완성 껐을 때)
D/beforeTextChanged:  s: ab, start: 2, count: 0, after: 1
D/onTextChanged: s: abc, start: 2, before: 0, count: 1 // 커서 2부터 0글자("")를 1글자("c")로 변경
D/afterTextChanged: s: abc

// 딩굴 키보드
D/beforeTextChanged:  s: ab, start: 2, count: 0, after: 1
D/onTextChanged: s: abc, start: 2, before: 0, count: 1 // 커서 2부터 0글자("")를 1글자("c")로 변경
D/afterTextChanged: s: abc

// 모아 키보드
D/beforeTextChanged:  s: ab, start: 2, count: 0, after: 1
D/onTextChanged: s: abc, start: 2, before: 0, count: 1 // 커서 2부터 0글자("")를 1글자("c")로 변경
D/afterTextChanged: s: abc

2) '기니'에 'ㅣ'를 추가해서 '기니디'를 만드는 경우

// 에뮬레이터 키보드 (윈도우 환경, Nexus 5, API 30)
D/beforeTextChanged:  s: 기닏, start: 0, count: 2, after: 3
D/onTextChanged: s: 기니디, start: 0, before: 2, count: 3 // 커서 0부터 2글자("기닏")을 3글자("기니디")로 변경
D/afterTextChanged: s: 기니디
D/beforeTextChanged:  s: 기니디, start: 0, count: 3, after: 3
D/onTextChanged: s: 기니디, start: 0, before: 3, count: 3 // 커서 0부터 3글자("기니디")을 3글자("기니디")로 변경
D/afterTextChanged: s: 기니디

// 에뮬레이터 키보드 (맥 환경, Nexus 5, API 30)
D/beforeTextChanged:  s: 기닏, start: 0, count: 2, after: 3
D/onTextChanged: s: 기니디, start: 0, before: 2, count: 3 // 커서 0부터 2글자("기닏")을 3글자("기니디")로 변경
D/afterTextChanged: s: 기니디
D/beforeTextChanged:  s: 기니디, start: 0, count: 3, after: 3
D/onTextChanged: s: 기니디, start: 0, before: 3, count: 3 // 커서 0부터 3글자("기니디")을 3글자("기니디")로 변경
D/afterTextChanged: s: 기니디

// 삼성키보드 (자동완성 켰을 때)
D/beforeTextChanged:  s: 기닏, start: 0, count: 2, after: 3
D/onTextChanged: s: 기니디, start: 0, before: 2, count: 3 // 커서 0부터 2글자("기닏")을 3글자("기니디")로 변경
D/afterTextChanged: s: 기니디

// 삼성키보드 (자동완성 껐을 때)
D/beforeTextChanged:  s: 기닏, start: 1, count: 1, after: 1
D/onTextChanged: s: 기니, start: 1, before: 1, count: 1
D/afterTextChanged: s: 기니 // 1) 커서 1부터 1글자("닏")를 1글자("니")로 변경
D/beforeTextChanged:  s: 기니, start: 2, count: 0, after: 1
D/onTextChanged: s: 기니디, start: 2, before: 0, count: 1 // 2) 커서 2부터 0글자("")를 1글자("디")로 변경
D/afterTextChanged: s: 기니디

// 딩굴 키보드
D/beforeTextChanged:  s: 기닏, start: 1, count: 1, after: 1
D/onTextChanged: s: 기니, start: 1, before: 1, count: 1
D/afterTextChanged: s: 기니 // 1) 커서 1부터 1글자("닏")를 1글자("니")로 변경
D/beforeTextChanged:  s: 기니, start: 2, count: 0, after: 1
D/onTextChanged: s: 기니디, start: 2, before: 0, count: 1 // 2) 커서 2부터 0글자("")를 1글자("디")로 변경
D/afterTextChanged: s: 기니디

// 모아 키보드
D/beforeTextChanged:  s: 기닏, start: 1, count: 1, after: 1
D/onTextChanged: s: 기니, start: 1, before: 1, count: 1
D/afterTextChanged: s: 기니 // 1) 커서 1부터 1글자("닏")를 1글자("니")로 변경
D/beforeTextChanged:  s: 기니, start: 2, count: 0, after: 1
D/onTextChanged: s: 기니디, start: 2, before: 0, count: 1 // 2) 커서 2부터 0글자("")를 1글자("디")로 변경
D/afterTextChanged: s: 기니디

테스트 결과, 각 키보드마다 문자 변경 파라미터가 다르게 들어오고 있다는 것을 알 수 있었다.

 

 

심지어 키보드의 종류뿐 아니라 같은 사양의 안드로이드 에뮬레이터의 경우에도 맥/윈도우 환경에 차이가 있었고(영문에서만),

같은 삼성 키보드임에도 자동완성을 켜고 끄는 설정에 따라 차이가 있었다.

* 삼성키보드의 경우, 자동완성을 켰을 때 삼성 키보드는 한 단어가 2글자에서 3글자가 된다면 그 2글자를 통째로 대체하는 것으로 인식한다. 반면 자동완성을 껐을 때에는 한 글자 한 글자가 변경되는 것으로 인식한다.

 

또한 키보드와 환경, 설정에 따라 단순히 파라미터 값만 변하는 것이 아니라 호출 횟수 및 문자열의 변화 양상도 달라졌다.

그리고 맥 에뮬레이터의 케이스에서는 마지막에 '기니디' -> '기니디'로 의미 없이 한 번 더 바뀌는 것도 예상하지 못한 결과였다.

 

결론

TextWatcher를 달면 텍스트의 대략적인 변경 여부와 시점은 알 수 있지만, 이것만으로 모든 것을 파악하기는 어렵다.

키보드의 종류, 심지어 같은 키보드이더라도 그 내부 설정에 따라서 TextWatcher에 서로 다른 파라미터가 들어올 수 있다.

예를 들어, s: ab, start: 0, before: 2, count: 3이라는 정보만으로는 'ab'를 선택해서 'abc'를 복사-붙여넣기한 것인지, 'c'를 추가 입력한 것인지 확신할 수는 없다. 

 

반응형
Comments