선생님, 개발을 잘하고 싶어요.

[Custom View] 안드로이드 커스텀 뷰, 커스텀 상태 state 선언하고 사용하기 본문

개발/android 개발

[Custom View] 안드로이드 커스텀 뷰, 커스텀 상태 state 선언하고 사용하기

알고싶은 승민 2022. 6. 3. 19:19

도입

뷰를 enabled, checked 여부에 따라서 다른 리소스를 사용해서 보여줘야 하는 경우가 많다. 이런 경우 우리는 selector를 활용한다. enabled, checked와 같이 안드로이드 플랫폼에서 미리 정의된 상태에 대해서는 이미 편하게 selector를 만들고 사용하지만 개발을 하다보면 새로운 요구 사항을 만족하는 화면을 그려야 할 때가 있다. 가령 예를 들어 TextField의 내용에 에러가 있을 때는 빨간 테두리를 그려주고 그러지 않을 때는 검은 테두리를 그려야 하는 요구사항이 있을 수 있다. 하지만 state_enabed는 있어도 state_error와 같은 커스텀 상태는 존재하지 않는다.

이번 포스팅에서는 이러한 커스텀 상태를 정의하고 사용하는 가이드를 보여주려고 한다.

 

작업하기

추가하고 싶은 state를 attribute로 선언한다.

우선 추가하고 싶은 state를 custom attribute로 추가해야한다.

<!-- res/values/state_attrs.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="state_error" format="boolean" />
</resources>

커스텀 state를 selector에서 app name space로 접근한다.

이렇게 추가한 custom state는 app name space를 통해서 접근할 수 있다. android:state_enabled와 마찬가지로 접근하면 된다.

이 selector는 state_error drawableState가 true일 때는 빨강을, 그러지 않을 때는 검정색을 노출하는 selector다.

<!-- res/color/selector_black_red.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:color="#ff0000" app:state_error="true" />
    <item android:color="#000000" />
</selector>

selector_color는 다른 color와 마찬가지로 활용한다.

커스텀 state를 활용한 color라고 독특한 사용법이 있는 것은 아니다. 평소에 color resource 사용하는 대로 사용한다.

error 상태에 따라서 border를 빨갛게 그릴지 검게 그릴지 지정한다.

<!-- error_aware_border.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="2dp"
        android:color="@color/selector_black_red" />
</shape>

진짜로 view에 state를 추가한다.

이제 진짜 CustomView가 selector가 알아먹을 수 있는 error state를 가지도록 설정해보자.

  1. 추가 할 custom attr의 intArray를 선언한다. 이게 drawableState에 포함되면 해당 view는 state_error=true 상태가 된다.
  2. onCreateDrawableState(...)를 재정의한다. CustomView는 isError 속성에 따라서 drawableState에 custom state인 error를 추가할 지 말지 선택하게 된다.
  3. 실제로 CustomView의 property인 isError가 세팅되는 시점에 drawableState를 갱신한다. 이를 위해서 refreshDrawableState() 함수를 호출하자.
class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    companion object {
        private val ERROR_STATE_SET = intArrayOf(R.attr.state_error)
    }

    override fun onCreateDrawableState(extraSpace: Int): IntArray {
        val drawableState = super.onCreateDrawableState(extraSpace + 1)
        if (isError) {
            mergeDrawableStates(drawableState, ERROR_STATE_SET)
        }
        return drawableState
    }

    var isError: Boolean = false
        set(value) {
            if (field == value) return
            field = value
            refreshDrawableState()
        }

}

xml을 통해서 CustomView에 error 상태를 설정할 수 있도록 한다. (옵션)

테스트 과정에서 쉽게 결과를 확인할 수 있도록 styleable attr를 추가하자. 이를 추가하면 xml 상에서 state_error 값을 설정할 수 있다.

<!-- res/values/state_attrs.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="state_error" format="boolean" />

    <declare-styleable name="CustomView">
        <attr name="state_error" />
    </declare-styleable>
</resources>

실제로 xml에 선언된 state_error 값을 읽어서 CustomView의 초기값에 반영하도록 한다. 이 경우 isError setter가 호출되며 refreshDrawaleState가 호출되고 isError 속성에 따라서 적절한 drawableState가 생성되게 된다.

init {

    context.withStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr) {
        isError = getBoolean(R.styleable.CustomView_state_error, false)
    }
}

selector를 먹인 drawable을 background로 지정한다. 하나는 error, 나머지는 일반 상태로 하자.

지금까지 정의한 것들을 확인하기 위해서 sample_layout.xml을 작성했다.

layout editor에서 정상적으로 보이는 모습, 위는 state_error, 아래는 아무런 설정 없을 때

custom state인 state_error에 따라서 border가 다르게 그려진 것을 layout editor를 통해서 쉽게 확인할 수 있다.

Comments