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 (отрисовка foreground и scrollbars). Метод не предназначен для прямого вызова из кода приложения.
Стандартная реализация dispatchDraw в ViewGroup перебирает все дочерние View, проверяет их видимость и вызывает для каждого draw(Canvas). Порядок обхода соответствует индексам детей (от 0 до childCount - 1). Если дочерние View перекрываются, последние в списке рисуются поверх первых. Изменяя порядок в dispatchDraw, можно менять Z-order отрисовки.
Для View (не ViewGroup) метод dispatchDraw пуст — у обычного View нет дочерних элементов, поэтому рисовать нечего. Метод существует в базовом классе View, но вызывается только у экземпляров ViewGroup. Разработчик может проверить, есть ли у View дочерние элементы, через dispatchDraw's дефолтное поведение, но зачастую проще проверять instanceof ViewGroup.
Метод draw(Canvas) класса View организует трёхэтапный конвейер отрисовки. Первый этап — вызов onDraw(canvas), в котором View рисует своё содержимое. Второй этап — вызов dispatchDraw(canvas), запускающий рисование дочерних View. Третий этап — onDrawForeground(canvas), отвечающий за foreground-слой, scrollbars и ripple-эффекты. Эта последовательность гарантирует правильный порядок наложения графических слоёв.
onDraw всегда выполняется перед dispatchDraw. Это означает, что содержимое родительского View отображается под дочерними View. Если нужно нарисовать что-то поверх детей, это делается в onDrawForeground или в переопределённом dispatchDraw с вызовом super.dispatchDraw(canvas) и последующим рисованием поверх. Задний фон (background) рисуется ещё до onDraw — в drawBackground(canvas) внутри метода draw().
При аппаратном ускорении (hardware acceleration) конвейер draw работает через GPU. View рисуется в DisplayList — список команд рисования, который кэшируется и повторно используется. dispatchDraw в аппаратно-ускоренном режиме добавляет DisplayList дочерних View в общий DisplayList сцены. Изменение dispatchDraw может привести к инвалидации DisplayList и перестроению кэша, что влияет на производительность.
| Метод | Порядок вызова | Назначение |
|---|---|---|
| drawBackground | 1 | Рисование фона (background drawable) |
| onDraw | 2 | Рисование содержимого самого View |
| dispatchDraw | 3 | Рисование всех дочерних View (только для ViewGroup) |
| onDrawForeground | 4 | Рисование foreground, scrollbar и 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("PREVIEW MODE",
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) рисования сложной анимации в real-time (используйте 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также