Development/Android

[Android / Kotlin] TimePicker의 개별 EditText 입력 값 처리

SeungYong.Lee 2025. 3. 6. 17:28
반응형

- 이런 구조의 TimePicker를 가진 Sheet가 존재하는데, TimePicker의 각 hour, minute 부분은 별도의 EditText로서 사용자의 포커스 온이 가능합니다.

 

- 그런데, 키보드에서 '다음' 또는 '확인' 버튼을 누르지 않는 이상 전체 TimePicker에 해당 값이 전달되지 않으면 UI 및 데이터 업데이트에 문제가 있었습니다.

 

- 사실 아래 설정을 통해 아예 사용자 입력을 비활성화시키고 스크롤만 가능하게 할 수도 있습니다. 

android:descendantFocusability="blocksDescendants"

 

- 하지만 최대한 사용자의 입력을 존중(?)하기 위해 사용자가 Sheet를 닫을 시, 각 EditText의 포커스 상태를 검사하고 값을 가져오는 처리를 진행했습니다.

 

private fun setTimePickerInputFilter(timePicker: TimePicker) {
    timePicker.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            timePicker.viewTreeObserver.removeOnGlobalLayoutListener(this)

            val editTextList = mutableMapOf<Int, EditText>()
            var index = 0

            fun findEditTexts(viewGroup: ViewGroup) {
                for (i in 0 until viewGroup.childCount) {
                    val child = viewGroup.getChildAt(i)
                    if (child is EditText) {
                        editTextList[index] = child
                        index ++
                    } else if (child is ViewGroup) {
                        findEditTexts(child)
                    }
                }
            }

            findEditTexts(timePicker)

            editTextList.forEach { (i, editText) ->
                editText.addTextChangedListener(object : TextWatcher {
                    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

                    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                        val enteredValue = s.toString().toIntOrNull()
                        if (enteredValue != null) {
                            when (i) {
                                1 -> hourEditText = editText
                                2 -> minuteEditText = editText
                            }
                        }
                    }

                    override fun afterTextChanged(s: Editable?) {}
                })
            }
        }
    })
}

- View의 상태를 안정적으로 체크하기 위해 viewTreeObserver.addOnGlobalLayoutListener를 활용

 

- TimePicker에는 n개의 EditText가 존재합니다. 이를 순번, EditText로 구분하기 위해 Map을 사용했고, 재귀 함수 호출로 EditText가 발견될 때마다 Map에 put 해줬습니다.

val editTextList = mutableMapOf<Int, EditText>()
var index = 0

fun findEditTexts(viewGroup: ViewGroup) {
    for (i in 0 until viewGroup.childCount) {
        val child = viewGroup.getChildAt(i)
        if (child is EditText) {
            editTextList[index] = child
            index ++
        } else if (child is ViewGroup) {
            findEditTexts(child)
        }
    }
}

findEditTexts(timePicker)

 

- 이제 구한 EditText를 hour인지, minute인지 구분하여 개별 변수로 등록합니다.

 

- TimePicker에서 좌 -> 우 방향으로 순차적 레이아웃 구조이기 때문에 0 : 오전/오후 1 : 시 2 : 분을 의미하게 되겠습니다.

 

- 이것이 사용자가 실제로 EditText에 입력을 하는 순간 확인되어야 하므로 addTextChangedListener 등록을 해줘야합니다.

editTextList.forEach { (i, editText) ->
    editText.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            val enteredValue = s.toString().toIntOrNull()
            if (enteredValue != null) {
                when (i) {
                    1 -> hourEditText = editText
                    2 -> minuteEditText = editText
                }
            }
        }

        override fun afterTextChanged(s: Editable?) {}
    })
}

 

- 이후에는 값을 가져올 때 다음처럼 EditText 포커스 여부에 따라 값을 가져올 수 있습니다.

val isHourFocusing = hourEditText?.isFocused ?: false
val isMinFocusing = minuteEditText?.isFocused ?: false

val hourValue = if (isHourFocusing) hourEditText?.editableText.toString().toIntOrNull()
    ?: timeWheel.hour else timeWheel.hour
val minValue = if (isMinFocusing) minuteEditText?.editableText.toString().toIntOrNull()
    ?: timeWheel.minute else timeWheel.minute
반응형