- 이런 구조의 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