커스텀 뷰의 초기화 코드를 깔끔하게 작성하는 방법
커스텀 뷰를 만들 때, 뷰에서 필요한 초기화 작업은 대부분 생성자(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)
)에서는 필히 초기화 코드를 작성한 생성자를 호출하도록 처리해 주어야 합니다.
이 부분만 주의한다면, 커스텀 뷰 코드를 훨씬 깔끔하고 오류 없이 작성하는데 많은 도움이 될 것입니다.