dispatchDraw()는 Android 계층 구조에서 모든 자식 View의 재귀적 그리기를 담당하는 ViewGroup 클래스의 메서드입니다. Android Developers Documentation (2026)에 따르면 dispatchDraw는 기본 View의 draw() 메서드에서 onDraw 후에 자동으로 호출되며 모든 자식 요소를 순회하면서 각각의 draw()를 호출합니다. 개발자는 사용자 정의 ViewGroup에서 dispatchDraw를 재정의하여 효과 추가, 자식 위에 그래픽 오버레이, 그리기 순서 변경 등을 수행합니다.
핵심 요점
dispatchDraw(Canvas canvas)는 ViewGroup 클래스의 protected 메서드로, Android 시스템이 현재 컨테이너의 모든 자식 View를 그리기 위해 호출합니다. dispatchDraw는 표준 그리기 파이프라인의 일부입니다: 먼저 onDraw가 실행되고(View 자체 콘텐츠 그리기), 다음으로 dispatchDraw(자식의 재귀적 그리기), 마지막으로 onDrawForeground(전경 및 스크롤바 그리기)가 실행됩니다. 이 메서드는 애플리케이션 코드에서 직접 호출하기 위한 것이 아닙니다.
ViewGroup의 표준 dispatchDraw 구현은 모든 자식 View를 순회하며 가시성을 확인하고 각각에 대해 draw(Canvas)를 호출합니다. 순회 순서는 자식 인덱스(0부터 childCount - 1까지)에 해당합니다. 자식 View가 겹치는 경우 목록에서 뒤에 있는 것이 앞에 있는 것 위에 그려집니다. dispatchDraw에서 순서를 변경하면 그리기의 Z-order를 수정할 수 있습니다.
View(ViewGroup이 아닌)의 경우 dispatchDraw 메서드는 비어 있습니다. 일반 View에는 자식 요소가 없으므로 그릴 것이 없습니다. 이 메서드는 기본 View 클래스에 존재하지만 ViewGroup 인스턴스에서만 호출됩니다. 개발자는 dispatchDraw의 기본 동작을 통해 View에 자식 요소가 있는지 확인할 수 있지만, instanceof ViewGroup을 확인하는 것이 더 간단한 경우가 많습니다.
View 클래스의 draw(Canvas) 메서드는 3단계 그리기 파이프라인을 구성합니다. 첫 번째 단계는 onDraw(canvas) 호출로, View가 자체 콘텐츠를 그립니다. 두 번째 단계는 dispatchDraw(canvas) 호출로, 자식 View의 그리기를 시작합니다. 세 번째 단계는 onDrawForeground(canvas)로, 전경 레이어, 스크롤바 및 리플 효과를 담당합니다. 이 순서는 그래픽 레이어의 올바른 오버레이 순서를 보장합니다.
onDraw는 항상 dispatchDraw보다 먼저 실행됩니다. 즉, 부모 View의 콘텐츠가 자식 View 아래에 표시됩니다. 자식 위에 무언가를 그려야 하는 경우 onDrawForeground에서 수행하거나 재정의된 dispatchDraw에서 super.dispatchDraw(canvas)를 호출한 후 위에 그립니다. 배경은 onDraw보다도 먼저 draw() 메서드 내의 drawBackground(canvas)에서 그려집니다.
하드웨어 가속을 사용하면 그리기 파이프라인이 GPU를 통해 작동합니다. View는 DisplayList(캐시되고 재사용되는 그리기 명령 목록)로 그려집니다. 하드웨어 가속 모드에서 dispatchDraw는 자식 View의 DisplayList를 장면의 전체 DisplayList에 추가합니다. dispatchDraw를 수정하면 DisplayList가 무효화되고 캐시 재구축이 발생하여 성능에 영향을 줄 수 있습니다.
| 메서드 | 호출 순서 | 목적 |
|---|---|---|
| drawBackground | 1 | 배경 그리기(background drawable) |
| onDraw | 2 | View 자체 콘텐츠 그리기 |
| dispatchDraw | 3 | 모든 자식 View 그리기(ViewGroup만) |
| onDrawForeground | 4 | 전경, 스크롤바 및 리플 효과 그리기 |
onDraw는 View 자체의 콘텐츠(이 특정 요소에 속하는 도형, 텍스트, 이미지)를 그리는 역할을 합니다. dispatchDraw는 자식 View(ViewGroup 내에 포함된 모든 요소)를 그리는 역할을 합니다. ViewGroup이 dispatchDraw를 재정의하지 않으면 ViewGroup의 구현이 사용되며, childCount를 재귀적으로 순회하면서 각 자식에 대해 draw를 호출합니다.
자체 콘텐츠를 그리지 않는 ViewGroup(예: FrameLayout, LinearLayout)의 경우 setWillNotDraw(true)를 설정하여 onDraw를 최적화할 수 있습니다. 이 경우 onDraw는 호출되지 않아 리소스를 절약합니다. dispatchDraw는 계속 작동하며 자식 요소에 대해 draw를 호출합니다. 모든 표준 ViewGroup(LinearLayout, RelativeLayout, ConstraintLayout)은 setWillNotDraw(true)를 사용합니다.
super.dispatchDraw(canvas)를 호출하지 않고 dispatchDraw를 재정의하면 자식 View가 그려지지 않습니다. 이는 모든 자식을 일시적으로 숨기는 데 유용할 수 있지만, 대부분의 경우 오류를 발생시킵니다. 권장 방법: 재정의된 메서드의 시작 부분에서 super.dispatchDraw(canvas)를 호출한 다음 자식 위에 자체 그래픽(예: 알파 채널이 있는 오버레이)을 추가합니다.
OverlayViewGroup을 만들어 보겠습니다. 모든 자식 View 위에 텍스트 레이블이 있는 반투명 레이어를 추가하는 사용자 정의 ViewGroup입니다. dispatchDraw는 먼저 super를 호출하고(모든 자식 그리기), 그런 다음 오버레이 사각형과 텍스트를 그립니다. Paint는 그리기 루프에서 할당을 피하기 위해 생성자에서 생성됩니다.
class OverlayViewGroup(context: Context)
: ViewGroup(context) {
private val overlayPaint = Paint().apply {
color = Color.parseColor("#66000000")
style = Paint.Style.FILL
}
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 36f
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
init {
setWillNotDraw(false)
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
canvas.drawRect(0f, 0f,
width.toFloat(),
height.toFloat(), overlayPaint)
canvas.drawText("미리보기 모드",
width / 2f,
height / 2f, textPaint)
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
var top = t
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
child.layout(l, top, l + cw, top + ch)
top += ch
}
}
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)
val maxWidth = resolveSize(
getChildAt(0).measuredWidth,
widthMeasureSpec)
var totalHeight = 0
for (i in 0 until childCount) {
totalHeight += getChildAt(i).measuredHeight
}
setMeasuredDimension(maxWidth,
resolveSize(totalHeight, heightMeasureSpec))
}
}
다음 예제는 자식 요소 그리기 순서 변경을 보여줍니다. CircularRevealLayout은 dispatchDraw를 재정의하고 자식을 역순으로 그려 뒤집힌 Z-order 효과를 만듭니다. 애니메이션을 위해 애니메이션화된 offset 값을 기반으로 그리기 인덱스의 순환 이동이 추가됩니다.
class ReverseOrderLayout(context: Context)
: ViewGroup(context) {
private var reverse = false
override fun dispatchDraw(canvas: Canvas) {
if (!reverse) {
super.dispatchDraw(canvas)
return
}
for (i in childCount - 1 downTo 0) {
val child = getChildAt(i)
if (child.visibility == GONE) continue
drawChild(canvas, child, getDrawingTime())
}
}
fun toggleReverse() {
reverse = !reverse
invalidate()
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
var left = l
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
child.layout(left, t,
left + cw, t + child.measuredHeight)
left += cw
}
}
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)
var totalWidth = 0
for (i in 0 until childCount) {
totalWidth += getChildAt(i).measuredWidth
}
setMeasuredDimension(
resolveSize(totalWidth, widthMeasureSpec),
resolveSize(getChildAt(0).measuredHeight,
heightMeasureSpec))
}
}
dispatchDraw는 ViewGroup 자체의 콘텐츠에 영향을 주지 않고 자식 View의 그리기 프로세스에 영향을 주어야 하는 시나리오에서 사용됩니다. 주요 시나리오: 모든 자식 위에 공통 효과(오버레이, 그림자, 그라데이션) 적용; 깊이 효과를 만들기 위해 Z-order 변경; Canvas 변환을 통한 자식 요소의 나타나기 또는 사라지기 애니메이션.
자식 요소 아래에 그래픽을 적용해야 하는 경우(배경 효과) onDraw를 사용하세요. dispatchDraw는 그 후에 호출됩니다. 그래픽이 자식 위에 있어야 하는 경우 먼저 super를 호출한 다음 Canvas.draw를 사용하는 dispatchDraw를 사용하세요. 전역 색상이나 투명도 변경이 필요한 경우 dispatchDraw에서 자식 그리기를 감싸기 위해 canvas.saveLayerAlpha()를 사용하는 것이 편리합니다.
권장되지 않는 dispatchDraw 사용: 1) 실시간으로 복잡한 애니메이션 그리기(invalidate 및 onDraw 사용); 2) ViewGroup의 스크린샷 생성(buildDrawingCache() 또는 View.draw(Canvas) 사용); 3) 자식 View 수정(좌표, 크기는 onLayout의 작업이지 dispatchDraw의 작업이 아닙니다). dispatchDraw는 시각적 효과 적용만을 위한 것입니다.
자주 묻는 질문
아니요, dispatchDraw는 protected 메서드로, Android 시스템이 공개 draw() 메서드 내에서 호출합니다. dispatchDraw를 직접 호출하는 것은 의미가 없습니다. 준비 작업(Canvas 저장, 배경 및 전경 그리기)을 수행하지 않기 때문입니다. 대신 프로그래밍 방식으로 임의의 Canvas에 그리려면 View.draw(Canvas)를 사용하세요.
자체 그리기 작업 후 super.dispatchDraw(canvas)를 호출하면 자식 View가 사용자 정의 그래픽 위에 그려집니다. 이는 레이어 순서를 변경합니다: 먼저 사용자 정의 레이어가 그려지고 그다음 자식이 그려집니다. 반대 상황(자식 위에 그래픽)이 필요한 경우 먼저 super.dispatchDraw를 호출한 다음 자체 작업을 수행하세요.
dispatchDraw는 각 자식 View에 대해 draw()를 호출하며, 총 시간은 자식 수에 비례합니다. 하드웨어 가속을 사용하는 경우 dispatchDraw에 Canvas 작업을 추가하면 DisplayList가 무효화되고 캐시 재구축이 발생할 수 있습니다. 5~10개의 자식에서는 영향이 최소화되지만, 50개 이상의 자식에서는 복잡한 오버레이를 캐시하는 것이 좋습니다.
View(ViewGroup이 아닌)에는 자식 요소가 없으므로 dispatchDraw가 유용한 작업을 수행하지 않습니다. 그러나 다형성을 위해 기본 View 클래스에 메서드가 존재합니다. draw()의 코드는 모든 View에 대해 dispatchDraw를 호출하지만 View의 구현에는 자식 그리기 로직이 포함되어 있지 않습니다. dispatchDraw를 재정의하는 것은 ViewGroup뿐입니다.
네, dispatchDraw는 Canvas를 받아들이며 super.dispatchDraw를 호출하기 전에 변환(translate, rotate, scale)할 수 있습니다. 이는 전체 자식 세트를 하나의 단위로 애니메이션화하는 데 사용됩니다. super.dispatchDraw가 완료된 후에는 Canvas를 원래 상태로 복원하는 것이 좋습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.