면접 질문실전 질문꼬리 질문

XML을 활용한 안드로이드 커스텀 뷰 (Custom Views in Android Using XML)

skydovesJaewoong Eum (skydoves)||8분 소요

XML을 활용한 안드로이드 커스텀 뷰 (Custom Views in Android Using XML)

안드로이드의 뷰 시스템은 기본 View 클래스를 상속하고, 그리기(drawing), 측정(measurement), 레이아웃(layout) 메서드를 오버라이드하여 커스텀 UI 컴포넌트를 직접 만들 수 있도록 설계되어 있습니다. 여기에 커스텀 XML 속성(attribute)까지 결합하면, 다른 개발자들이 레이아웃 파일에서 기본 제공 뷰처럼 선언적으로 설정할 수 있는 재사용 가능한 위젯을 만들 수 있습니다. 안정적인 커스텀 컴포넌트를 만들려면 커스텀 뷰의 생명주기, 속성 시스템, 그리고 측정 규약(measurement contract)에 대한 이해가 필수적입니다. 특히 면접에서도 이 주제는 안드로이드 UI 시스템에 대한 깊은 이해를 검증하는 단골 질문이므로, 각 단계의 역할과 동작 원리를 확실히 파악해 두시는 것이 좋습니다. 이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • View 생성자 오버로드의 목적과 안드로이드가 어떤 상황에서 호출하는지 설명할 수 있습니다.
  • onDraw()를 오버라이드하여 Canvas를 사용한 커스텀 그래픽을 렌더링할 수 있습니다.
  • attrs.xml에서 커스텀 속성을 정의하고 TypedArray를 통해 읽어올 수 있습니다.
  • onMeasure()를 오버라이드하여 뷰가 자신의 크기를 보고하는 방식을 제어할 수 있습니다.
  • 커스텀 뷰 구현에서 성능과 관련된 모범 사례를 적용할 수 있습니다.

View 생성자와 인플레이션(Inflation)

안드로이드 View 클래스는 뷰가 생성되는 방식에 따라 특정 생성자 오버로드를 필요로 합니다. XML에서 인플레이트될 때 시스템은 ContextAttributeSet 두 개의 매개변수를 받는 생성자를 호출합니다. 세 번째 매개변수가 추가된 생성자는 기본 스타일 속성(default style attribute)을 지정할 수 있도록 합니다. 코틀린의 @JvmOverloads 어노테이션을 사용하면 하나의 선언으로 세 가지 오버로드를 모두 자동 생성할 수 있어, 보일러플레이트 코드를 크게 줄일 수 있습니다.

class CircleView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)

AttributeSet 매개변수에는 뷰 태그에 지정된 모든 XML 속성이 담겨 있습니다. defStyleAttr 매개변수는 뷰의 스타일 속성에 대한 기본값을 제공하는 테마 속성을 가리킵니다. Context만으로 프로그래밍 방식으로 뷰를 생성하면 attrsnull이 되며, XML 속성을 사용할 수 없습니다. 따라서 XML 인플레이션과 프로그래밍 방식 생성 모두를 지원하려면 생성자 설계에 주의를 기울여야 합니다.

onDraw()와 Canvas를 활용한 그리기

onDraw() 메서드는 뷰의 콘텐츠를 렌더링하는 핵심 진입점(entry point)입니다. invalidate()가 호출되거나 뷰가 처음 화면에 표시될 때 안드로이드가 이 메서드를 호출하며, 다양한 그리기 프리미티브(primitive)를 제공하는 Canvas 객체를 매개변수로 전달합니다.

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.RED
    style = Paint.Style.FILL
}

override fun onDraw(canvas: Canvas) {
    val cx = width / 2f
    val cy = height / 2f
    val radius = minOf(cx, cy) * 0.8f
    canvas.drawCircle(cx, cy, radius, paint) // 뷰 중심에 원 그리기
}

onDraw() 내부에서는 두 가지 성능 규칙을 반드시 지켜야 합니다. 첫째, 객체를 절대 할당하면 안 됩니다. 애니메이션 도중에는 매 프레임마다 이 메서드가 호출되기 때문에, 객체 할당이 발생하면 가비지 컬렉션(garbage collection) 일시 정지가 유발되어 프레임 드롭이 발생합니다. Paint, Rect, Path 등의 그리기 객체는 클래스 필드로 선언하여 재사용해야 합니다. 둘째, 그리기 영역을 최소화해야 합니다. 가능한 경우 canvas.clipRect()를 사용하여 무효화된(invalidated) 영역만 그리도록 제한하면 불필요한 렌더링 비용을 줄일 수 있습니다.

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기