/ UI, ANDROID

커스텀 뷰의 초기화 코드를 깔끔하게 작성하는 방법

커스텀 뷰를 만들 때, 뷰에서 필요한 초기화 작업은 대부분 생성자(Constructor)에서 수행합니다. 때문에, 일반적으로 다음과 같이 init() 메서드를 추가하여 초기화 작업을 수행하는 부분을 구현한 후 이를 각 생성자에서 호출하는 형태로 구성합니다.

public class MyCustomView extends TextView {

    public MyCustomView(Context context) {
        super(context);
        init();
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyCustomView(Context context, AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        // Initializes a view
    }
}

위와 같은 구현은 init() 메서드만 각 생성자에 빼먹지 않고 추가한다면 큰 문제는 없습니다. 하지만, 실수로 init() 메서드를 특정 생성자에 넣지 않는다면 뷰 초기화 작업이 제대로 되지 않아 원치 않는 결과를 얻을 수 있습니다.

따라서, init() 메서드를 사용하는 방법 보다 특정 생성자 내부에 직접 초기화 코드를 구현하고, 커스텀 뷰의 각 생성자에서 초기화 코드를 구현한 생성자를 호출하게끔 구현하는 것을 추천합니다.

대략 다음과 같은 형태가 되겠네요.

public class MyCustomView extends TextView {

    public MyCustomView(Context context) {
        // 자신의 생성자를 호출합니다.
        this(context, null);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        // 자신의 생성자를 호출합니다.
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs,
            int defStyleAttr) {
        // 부모 클래스의 생성자를 호출합니다.
        super(context, attrs, defStyleAttr);

        // Initializes a view
    }
}

안드로이드 내부 소스에서도 대부분 위와 같은 패턴으로 커스텀 뷰를 구현합니다. 다음은 디자인 서포트 라이브러리에 있는 NavigationView 소스 코드의 일부입니다.

public class NavigationView extends ScrimInsetsFrameLayout {

    // ... 생략 ...

    public NavigationView(Context context) {
        this(context, null);
    }

    public NavigationView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        ThemeUtils.checkAppCompatTheme(context);
        // Create the menu
        mMenu = new NavigationMenu(context);
        // Custom attributes
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.NavigationView, defStyleAttr,
                R.style.Widget_Design_NavigationView);
        //noinspection deprecation
        setBackgroundDrawable(a.getDrawable(R.styleable.NavigationView_android_background));
        if (a.hasValue(R.styleable.NavigationView_elevation)) {
            ViewCompat.setElevation(this, a.getDimensionPixelSize(
                    R.styleable.NavigationView_elevation, 0));
        }

        // ... 생략 ...
    }

init() 메서드를 하는 방식 대비 위 방법의 장점으로는 XML 속성이나 스타일 값이 필요한 경우 적절히 해당 파라미터가 지원되는 생성자를 선택할 수 있다는 것입니다. XML 속성이 필요할 경우 init() 메서드를 사용하면 init(AttributeSet) 과 같은 형태로 메서드를 구현해야 하고, AtttributeSet 이 지원되지 않는 생성자에서는 init(null) 과 같이 초기화 메서드를 호출해야 하므로 보기에도 썩 좋지가 않죠.

물론, 이 방법을 사용할 때도 유의할 점은 있습니다. 초기화 코드를 작성하지 않는 생성자 (위에서는 NavigationView(Context)NavigationView(Context, AttributeSet))에서는 필히 초기화 코드를 작성한 생성자를 호출하도록 처리해 주어야 합니다.

이 부분만 주의한다면, 커스텀 뷰 코드를 훨씬 깔끔하고 오류 없이 작성하는데 많은 도움이 될 것입니다.

kunny

커니

안드로이드와 오픈소스, 코틀린(Kotlin)에 관심이 많습니다. 한국 GDG 안드로이드 운영자 및 GDE 안드로이드로 활동했으며, 현재 구글에서 애드몹 기술 지원을 담당하고 있습니다.

Read More