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, но често е по-лесно да провери 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("РЕЖИМ ЗА ПРЕГЛЕД",
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 трансформации.
Ако е необходимо наслагване на графика под дъщерни елементи (фонов ефект), трябва да се използва 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, общото време е пропорционално на броя на децата. При хардуерно ускорение добавянето на 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също