본문 바로가기
안드로이드/코틀린

Kotlin으로 만든 커스텀 버튼

by 나이아카 2021. 2. 9.

 많은 블로그를 보면 커스텀 버튼에 대한 이야기가 많습니다. 그러나 xml로 디자인 정도만 바꾼 기초적인 버튼이 대다수를 차지하고 있습니다. 그 부분은 매우 중요한 부분이 맞으나 아쉽게도 제가 원하는 부분은 코틀린 class를 통해 button을 상속받아 만든 버튼 클래스 였기 때문에 이 글에서는 그 부분을 소개하도록 하겠습니다.

 

 먼저 제가 만든 버튼은 실제로 사용해야하는 버튼이라 여러 기능이 있지만 그 것을 가지고 와서 소개하기에는 조금 부담스러울 수 있기 때문에 간략하게 소개하기 위해 check 기능이 추가된 커스텀 버튼을 소개하겠습니다. 


class CheckButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    style: Int = 0
) : AppCompatButton(context, attrs, style), View.OnClickListener

 먼저 생성입니다. Button을 만들기 위함이기 때문에 기본적으로 버튼을 상속 받습니다. 또한 클릭이벤트를 기본적으로 넣기 위해서 클릭이벤트도 상속해줍니다. 여기서 View.OnClickListener는 이벤트 리스너를 기존 view들처럼 코드 내에서 제작하거나 xml에서 onClick으로 넣기 위한다면 굳이 상속받지 않아도 됩니다.

 

여기서는 크게 알아볼 수 있는 부분이 없지만 생소할 수도 있다고 느껴지는 부분을 살펴보겠습니다.

 

 @JvmOverloads는 코틀린에서 default로 이루어진 생성자 파라미터를 사용했을 때, 자바로 변환할 때 문제가 발생할 수 있는데 그 부분을 해소하기 위해 사용하는 어노테이션입니다. 자바에는 default 값이 없기 때문에 저 어노테이션이 없는 경우 자바 코드로 제대로 변환되지 않을 수 있습니다.

 

public class CheckButton extends AppCompatButton {
    public CheckButton(Context context) {
        super(context);
    }
    public CheckButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CheckButton(Context context, AttributeSet attrs, int style) {
        super(context, attrs, style);
    }
}

 

 실제로 @JvmOverloads 어노테이션을 사용할 경우 자바로 변환된 코드에서 생성자가 여러개가 생성되어 있는 것을 확인할 수 있습니다.

 

var checked: Boolean by Delegates.observable(true) { _, _, newValue ->
    if (newValue) {
        onEvent(this)
    }
    else {
        offEvent(this)
    }
}
var onEvent: (AppCompatButton?) -> Unit = {}
var offEvent: (AppCompatButton?) -> Unit = {}

 먼저 check를 확인하기 위한 변수입니다. 추가로 Delegates.observable을 넣어 checked의 값이 변경될 때 마다 안에 있는 내용을 실행할 수 있도록 했습니다. 사실 이렇게 처리하지 않고 OnClickListener에서 바로 처리해주더라도 지장이 없습니다.

 여기서는 지웠지만 클릭이 되었을 때와 아닐 때의 디자인도 코드로 수정을 해 두었기 때문에 저 말고 다른 분들은 사용하실 때 더 편한 방식으로 관리를 해 주시면 되겠습니다.

 

 onEvent와 offEvent는 커스텀 버튼이 하나의 기능이 아니라 각 버튼 별로 매번 다른 기능을 할 것이라고 가정하기 때문에 생성했습니다. 이 기능 역시 클릭이벤트와 골라서 사용 가능합니다.

 

init {
    val attrSet = context.obtainStyledAttributes(attrs, R.styleable.CheckButton)
    checked = attrSet.getBoolean(R.styleable.CheckButton_checked, true)
    setTypeface(null, Typeface.BOLD)
    if (checked) {
        //코드 작성
    } else {
        //...
    }
    setOnClickListener(this)
}
override fun onClick(v: View?) {
    checked = !checked
}

 

 마지막으로 init 코드에 위의 checked의 실행 코드와 동일한 코드를 넣거나 초기화시키고 싶은 다른 기능이 있다면 넣을 수 있습니다.

 attrSet을 통해 xml에서 직접 만든 변수를 사용할 수 있는데요.

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:app="http://schemas.android.com/apk/[PackageName]"

 

 두 개 중 사용 가능한 nameSpace를 지정함으로서 xml에서 사용할 준비를 마칠 수 있습니다.

 위의 res-auto는 자동적으로 자신이 설정한 attrSet과 같은 데이터의 패키지를 자동으로 찾아 지정해주는 역할을 합니다. 아마 아래의 xmlns:app="http://schemas.android.com/apk/[PackageName]" 와 같은 것은 안드로이드 스튜디오에서 권장되지 않는 모양인지 위의 res-auto로 변경을 요구합니다.

app:checked="true"

 

 그리고 위처럼 app이라는 미리 설정해둔 nameSpace를 통해 버튼 안에서 변수를 사용할 수 있습니다. 직접 만든 view 클래스인데 변수를 사용할 수 없다면 반쪽짜리가 되는 거라서 이 부분까지 숙지하셔야 진정으로 원하는 view를 만들 수 있습니다. (물론 더 충분한 기능을 위해서는 많은 공부가 더 필요하긴 합니다.)

 

<declare-styleable name="CheckButton">
    <attr name="checked" format="boolean"/>
</declare-styleable>

 

 또한 이런식으로 안드로이드의 value 패키지 내부에 있는 attrs.xml에 사용할 변수명을 선언해주신 후 사용하셔야 합니다. name을 통해 사용할 수 있게 되고, format의 형태를 맞춰야 빌드시 에러가 나지 않습니다. (boolean 타입인데 10을 넣는다던가 하는 형태라면 당연히 오류가 납니다...)

 


 

 이런식으로 작성하게 되면 가장 기본적으로 toggleButton이 생겨났습니다. 이 기능만 사용한다면 toggleButton을 쓰는 것이 더 좋을 수 있지만 커스텀의 매력은 역시 라이브러리에 있는 기능이 자신의 프로젝트와 맞지 않는 부분이 생기면서 시작되는 것이라 생각합니다.

 이 글에서 소개한 부분에서도 글을 하나 써야 할 정도로 꽤 공부해야 하는 부분들이 있지만, 이 글을 통해 그런 것들의 키워드를 알아가길 바라고, 또한 커스텀을 해보면서 안드로이드의 기초에 대한 이해가 깊어질 수 있으면 좋겠습니다.

댓글