EditText를 상속받아 입력 값을 검증하고, 이에 따라 UI를 동적으로 변화시키는 텍스트 입력 컴포넌트입니다.
EditText와 Button 같은 기본 뷰들을 사용하여 동작.입력 폼에서 EditText와 같은 기본 제공 뷰들은 원시적인 기능만 제공하기 때문에, 더 나은 UI/UX를 위해 상태 변화에 따른 디자인과 기능을 추가하여 컴포넌트화하는 작업이 필요했습니다.
입력 폼에 대한 컴포넌트는 두 개의 클래스에 분리하여 작성했습니다.
1. CustomEditText: AppCompatEditText를 상속받아 텍스트 입력 기능에 집중.
2. CustomInput: LinearLayout을 상속받아, EditText 외적인 UI 요소를 추가한 레이아웃.
이로 인해 CustomEditText는 텍스트 입력 및 검증 기능을 담당하고, CustomInput은 추가적인 레이아웃과 UI 관련 작업을 담당합니다. 대부분의 화면에서 CustomInput을 사용하지만, CustomEditText도 단독으로 사용 가능하며 두 클래스 간 호환성을 유지하도록 설계했습니다.
open class CustomEditText @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatEditText(context, attrs, defStyleAttr)
class CustomInput @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr)
기획 담당 팀원으로부터 피그마 디자인이 제공되었고, 그에 따른 요구 사항은 다음과 같습니다:


EditText에 포커스가 가거나 값 검증 결과에 따라 밑줄 색상을 동적으로 변경하는 기능을 추가했습니다.
init {
setBackgroundResource(R.drawable.bg_custom_input)
setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
setUnderlineColor(R.color.primary)
} else {
if (!isValid()) { // <- 1-2-2에서 다루겠습니다.
setUnderlineColor(R.color.error)
} else {
setUnderlineColor(R.color.disable)
}
}
}
... 다른 메서드들
}
밑줄의 색상 변경은 setUnderlineColor() 메서드를 통해 이루어지며, 이를 통해 사용자가 입력 중인 상태인지, 입력 값이 유효한지 시각적으로 알 수 있게 했습니다. TextView의 기본 제공 메서드인 setError()를 오버라이드하여 에러 발생 시 밑줄 색상도 함께 변경하도록 처리했습니다.
텍스트 입력 폼의 값 검증과 에러 상태를 연동하여 사용자가 입력 오류를 쉽게 인지하도록 했습니다.
입력 폼마다 다른 유효성 검사 규칙을 적용할 수 있도록 IsValidListener 인터페이스를 만들어, 외부에서 다양한 검증 로직을 지정할 수 있게 설계했습니다.
interface IsValidListener {
fun isValid(text: String): Boolean
}
그리고 CustomEditText 내부에서 입력 값 변화에 따라 isValid() 메서드가 호출되도록 구현하여, 사용자 입력에 따라 동적으로 UI가 변경되도록 했습니다.
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val text = s.toString()
if (isValid(text)) {
setUnderlineColor(R.color.primary)
} else {
setUnderlineColor(R.color.error)
}
}
private fun isValid(): Boolean {
val text = text.toString()
return isValidListener?.isValid(text) ?: text.isNotEmpty()
}
이를 통해 텍스트 입력 필드의 유효성 검사를 외부에서 확장 가능하게 했으며, 아래와 같은 방식으로 CustomInput에서 사용할 수 있습니다.
binding.etAge.setIsValidListener(object : IsValidListener {
override fun isValid(text: String): Boolean {
val ageText = if (text.isEmpty()) 0 else text.toInt()
updateButtonState()
return ValidationUtils.isValidAge(ageText)
}
})
결과적으로, 입력에 대한 값 검증과 이에 따른 UI 변경이 이루어져 사용자가 명확하게 입력 오류를 인식할 수 있게 되었습니다.
