dispatchDraw() — це метод класу ViewGroup, відповідальний за рекурсивне малювання всіх дочірніх View в ієрархії Android. За даними Android Developers Documentation (2026), dispatchDraw викликається автоматично після onDraw в методі draw() основного View і проходить по всіх дочірніх елементах, викликаючи їхній власний draw(). Розробники перевизначають dispatchDraw у кастомних ViewGroup для додавання ефектів, накладання графіки поверх дітей або зміни порядку малювання.
Головне
dispatchDraw(Canvas canvas) — це protected метод класу ViewGroup, який система Android викликає для малювання всіх дочірніх View поточного контейнера. dispatchDraw є частиною стандартного конвеєра draw: спочатку виконується onDraw (малювання вмісту самого View), потім dispatchDraw (рекурсивне малювання дітей), потім onDrawForeground (малювання переднього плану та смуг прокрутки). Метод не призначений для прямого виклику з коду додатка.
Стандартна реалізація dispatchDraw у ViewGroup перебирає всі дочірні View, перевіряє їх видимість і викликає для кожного draw(Canvas). Порядок обходу відповідає індексам дітей (від 0 до childCount - 1). Якщо дочірні View перекриваються, останні у списку малюються поверх перших. Змінюючи порядок у dispatchDraw, можна змінювати Z-order малювання.
Для View (не ViewGroup) метод dispatchDraw порожній — у звичайного View немає дочірніх елементів, тому малювати нічого. Метод існує в базовому класі View, але викликається лише в екземплярах ViewGroup. Розробник може перевірити, чи є у View дочірні елементи, через поведінку dispatchDraw за замовчуванням, але часто простіше перевіряти instanceof ViewGroup.
Метод draw(Canvas) класу View організовує триетапний конвеєр малювання. Перший етап — виклик onDraw(canvas), у якому View малює свій вміст. Другий етап — виклик dispatchDraw(canvas), що запускає малювання дочірніх View. Третій етап — onDrawForeground(canvas), відповідальний за передній план, смуги прокрутки та ripple-ефекти. Ця послідовність гарантує правильний порядок накладання графічних шарів.
onDraw завжди виконується перед dispatchDraw. Це означає, що вміст батьківського View відображається під дочірніми View. Якщо потрібно намалювати щось поверх дітей, це робиться в onDrawForeground або в перевизначеному dispatchDraw з викликом super.dispatchDraw(canvas) і наступним малюванням поверх. Фон малюється ще до onDraw — у drawBackground(canvas) всередині методу draw().
При апаратному прискоренні конвеєр draw працює через GPU. View малюється в DisplayList — список команд малювання, який кешується та повторно використовується. dispatchDraw в апаратно-прискореному режимі додає DisplayList дочірніх View у загальний DisplayList сцени. Зміна dispatchDraw може призвести до інвалідації DisplayList і перебудови кешу, що впливає на продуктивність.
| Метод | Порядок виклику | Призначення |
|---|---|---|
| drawBackground | 1 | Малювання фону (background drawable) |
| onDraw | 2 | Малювання вмісту самого View |
| dispatchDraw | 3 | Малювання всіх дочірніх View (лише для ViewGroup) |
| onDrawForeground | 4 | Малювання переднього плану, смуги прокрутки та ripple-ефекту |
onDraw відповідає за малювання вмісту самого View — фігур, тексту, зображень, які належать цьому конкретному елементу. dispatchDraw відповідає за малювання дочірніх View — усіх елементів, які знаходяться всередині ViewGroup. Якщо ViewGroup не перевизначає dispatchDraw, використовується реалізація з ViewGroup, яка просто рекурсивно проходить по childCount і викликає draw для кожної дитини.
Для ViewGroup, яка не малює власний вміст (наприклад, FrameLayout, LinearLayout), onDraw можна оптимізувати встановленням setWillNotDraw(true). У цьому випадку onDraw ніколи не викликається, що економить ресурси. dispatchDraw при цьому продовжує працювати та викликати draw для дочірніх елементів. Усі стандартні ViewGroup (LinearLayout, RelativeLayout, ConstraintLayout) використовують setWillNotDraw(true).
Якщо перевизначити dispatchDraw без виклику super.dispatchDraw(canvas), дочірні View не будуть намальовані. Це може бути корисним для тимчасового приховування всіх дітей, але в більшості випадків викликає помилки. Рекомендована практика: викликати super.dispatchDraw(canvas) на початку перевизначеного методу, а потім додавати свою графіку (наприклад, overlay з альфа-каналом) поверх дітей.
Створимо OverlayViewGroup — кастомну ViewGroup, яка додає напівпрозорий шар із текстовою міткою поверх усіх дочірніх View. dispatchDraw спочатку викликає super (малювання всіх дітей), потім малює overlay-прямокутник і текст. Paint створюється в конструкторі для уникнення алокацій у циклі draw.
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 використовується в сценаріях, де потрібно вплинути на процес малювання дочірніх View, не зачіпаючи власний вміст ViewGroup. Основні сценарії: накладання загального ефекту (overlay, shadow, gradient) поверх усіх дітей; зміна порядку Z-order для створення ефекту глибини; анімація появи або зникнення дочірніх елементів через Canvas-трансформації.
Якщо потрібно накладання графіки під дочірніми елементами (background-ефект), слід використовувати onDraw — dispatchDraw викликається після нього. Якщо графіка має бути поверх дітей — використовуйте dispatchDraw з викликом super, а потім Canvas.draw. Якщо потрібна глобальна зміна кольору або прозорості, зручно використовувати canvas.saveLayerAlpha() у dispatchDraw, обгортаючи малювання дітей.
Не рекомендується використовувати dispatchDraw для: 1) малювання складної анімації в реальному часі (використовуйте invalidate та onDraw); 2) створення скріншотів ViewGroup (використовуйте buildDrawingCache() або View.draw(Canvas)); 3) модифікації дочірніх View (координати, розміри — це завдання onLayout, не dispatchDraw). dispatchDraw — тільки для накладання візуальних ефектів.
Часті запитання
Ні, dispatchDraw — protected метод, що викликається системою Android всередині публічного методу draw(). Прямий виклик dispatchDraw не має сенсу, оскільки він не виконує підготовчих дій (збереження Canvas, малювання фону та foreground). Натомість використовуйте View.draw(Canvas) для програмного малювання в довільний Canvas.
Якщо викликати super.dispatchDraw(canvas) після власних операцій малювання, дочірні View будуть намальовані поверх кастомної графіки. Це змінює накладання шарів: спочатку малюється кастомний шар, потім діти. Якщо потрібна зворотна ситуація (графіка поверх дітей), спочатку викликайте super.dispatchDraw, потім свої операції.
dispatchDraw викликає draw() для кожного дочірнього View, сумарний час пропорційний кількості дітей. При hardware acceleration додавання Canvas-операцій у dispatchDraw може призвести до інвалідації DisplayList та перебудови кешу. Для 5–10 дітей вплив мінімальний, для 50+ дітей рекомендується кешувати складні накладання.
View (не ViewGroup) не має дочірніх елементів, тому dispatchDraw не виконує корисної роботи. Однак метод існує в базовому класі View для поліморфізму: код у draw() викликає dispatchDraw для будь-якого View, але реалізація View не містить логіки малювання дітей. Тільки ViewGroup перевизначає dispatchDraw.
Так, dispatchDraw приймає Canvas, і його можна трансформувати перед викликом super.dispatchDraw (translate, rotate, scale). Це застосовується для анімації всього набору дітей як єдиного цілого. Після завершення super.dispatchDraw рекомендується відновити Canvas до початкового стану.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також