dispatchDraw() là một phương thức của lớp ViewGroup chịu trách nhiệm vẽ đệ quy tất cả các View con trong hệ thống phân cấp Android. Theo Tài liệu dành cho nhà phát triển Android (2026), dispatchDraw được gọi tự động sau onDraw trong phương thức draw() của View chính và duyệt qua tất cả các phần tử con, gọi draw() riêng của chúng. Các nhà phát triển ghi đè dispatchDraw trong ViewGroup tùy chỉnh để thêm hiệu ứng, phủ đồ họa lên trên các phần tử con hoặc thay đổi thứ tự vẽ.
Những điểm chính
dispatchDraw(Canvas canvas) là phương thức protected của lớp ViewGroup mà hệ thống Android gọi để vẽ tất cả các View con của container hiện tại. dispatchDraw là một phần của đường ống draw tiêu chuẩn: đầu tiên onDraw được thực thi (vẽ nội dung của chính View), sau đó dispatchDraw (vẽ đệ quy các phần tử con), sau đó onDrawForeground (vẽ lớp nền trước và thanh cuộn). Phương thức này không được thiết kế để gọi trực tiếp từ mã ứng dụng.
Triển khai tiêu chuẩn của dispatchDraw trong ViewGroup duyệt qua tất cả các View con, kiểm tra khả năng hiển thị của chúng và gọi draw(Canvas) cho mỗi View. Thứ tự duyệt tương ứng với chỉ số con (từ 0 đến childCount - 1). Nếu các View con chồng lên nhau, các View ở cuối danh sách được vẽ lên trên các View ở đầu. Thay đổi thứ tự trong dispatchDraw cho phép sửa đổi Z-order của việc vẽ.
Đối với View (không phải ViewGroup), phương thức dispatchDraw trống — một View thông thường không có phần tử con, do đó không có gì để vẽ. Phương thức này tồn tại trong lớp cơ sở View nhưng chỉ được gọi trên các phiên bản ViewGroup. Nhà phát triển có thể kiểm tra xem View có phần tử con hay không thông qua hành vi mặc định của dispatchDraw, nhưng thường kiểm tra instanceof ViewGroup đơn giản hơn.
Phương thức draw(Canvas) của lớp View tổ chức một đường ống vẽ ba giai đoạn. Giai đoạn đầu tiên là gọi onDraw(canvas), nơi View vẽ nội dung của chính nó. Giai đoạn thứ hai là gọi dispatchDraw(canvas), kích hoạt việc vẽ các View con. Giai đoạn thứ ba là onDrawForeground(canvas), chịu trách nhiệm cho lớp nền trước, thanh cuộn và hiệu ứng gợn sóng. Trình tự này đảm bảo thứ tự lớp đồ họa chính xác.
onDraw luôn được thực thi trước dispatchDraw. Điều này có nghĩa là nội dung của View cha được hiển thị bên dưới các View con. Nếu bạn cần vẽ thứ gì đó lên trên các phần tử con, việc này được thực hiện trong onDrawForeground hoặc trong dispatchDraw được ghi đè bằng cách gọi super.dispatchDraw(canvas) và sau đó vẽ lên trên. Nền được vẽ trước cả onDraw — trong drawBackground(canvas) bên trong phương thức draw().
Với tăng tốc phần cứng, đường ống vẽ hoạt động thông qua GPU. View được vẽ vào DisplayList — một danh sách các lệnh vẽ được lưu trong bộ nhớ đệm và tái sử dụng. dispatchDraw trong chế độ tăng tốc phần cứng thêm DisplayLists của các View con vào DisplayList tổng thể của cảnh. Sửa đổi dispatchDraw có thể làm mất hiệu lực DisplayList và gây ra xây dựng lại bộ nhớ đệm, ảnh hưởng đến hiệu suất.
| Phương thức | Thứ tự gọi | Mục đích |
|---|---|---|
| drawBackground | 1 | Vẽ nền (background drawable) |
| onDraw | 2 | Vẽ nội dung của chính View |
| dispatchDraw | 3 | Vẽ tất cả các View con (chỉ ViewGroup) |
| onDrawForeground | 4 | Vẽ nền trước, thanh cuộn và hiệu ứng gợn sóng |
onDraw chịu trách nhiệm vẽ nội dung của chính View — hình dạng, văn bản, hình ảnh thuộc về phần tử cụ thể này. dispatchDraw chịu trách nhiệm vẽ các View con — tất cả các phần tử chứa bên trong ViewGroup. Nếu ViewGroup không ghi đè dispatchDraw, triển khai từ ViewGroup được sử dụng, chỉ đơn giản là duyệt đệ quy childCount và gọi draw cho mỗi phần tử con.
Đối với ViewGroup không vẽ nội dung riêng (ví dụ: FrameLayout, LinearLayout), onDraw có thể được tối ưu hóa bằng cách đặt setWillNotDraw(true). Trong trường hợp này, onDraw không bao giờ được gọi, tiết kiệm tài nguyên. dispatchDraw tiếp tục hoạt động và gọi draw cho các phần tử con. Tất cả ViewGroups tiêu chuẩn (LinearLayout, RelativeLayout, ConstraintLayout) đều sử dụng setWillNotDraw(true).
Nếu bạn ghi đè dispatchDraw mà không gọi super.dispatchDraw(canvas), các View con sẽ không được vẽ. Điều này có thể hữu ích để tạm thời ẩn tất cả các phần tử con, nhưng trong hầu hết các trường hợp, nó gây ra lỗi. Thực hành được khuyến nghị: gọi super.dispatchDraw(canvas) ở đầu phương thức được ghi đè, sau đó thêm đồ họa của riêng bạn (ví dụ: lớp phủ với kênh alpha) lên trên các phần tử con.
Hãy tạo OverlayViewGroup — một ViewGroup tùy chỉnh thêm một lớp bán trong suốt với nhãn văn bản lên trên tất cả các View con. dispatchDraw đầu tiên gọi super (vẽ tất cả các phần tử con), sau đó vẽ hình chữ nhật lớp phủ và văn bản. Paint được tạo trong hàm tạo để tránh cấp phát trong vòng lặp 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("CHẾ ĐỘ XEM TRƯỚC",
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))
}
}
Ví dụ sau minh họa thay đổi thứ tự vẽ của các phần tử con. CircularRevealLayout ghi đè dispatchDraw và vẽ các phần tử con theo thứ tự ngược lại, tạo hiệu ứng Z-order đảo ngược. Để tạo hoạt ảnh, một sự dịch chuyển tuần hoàn của chỉ mục vẽ được thêm vào dựa trên giá trị offset được tạo hoạt ảnh.
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 được sử dụng trong các tình huống cần ảnh hưởng đến quá trình vẽ của các View con mà không ảnh hưởng đến nội dung riêng của ViewGroup. Các tình huống chính: áp dụng hiệu ứng chung (lớp phủ, bóng, gradient) lên trên tất cả các phần tử con; thay đổi Z-order để tạo hiệu ứng chiều sâu; tạo hoạt ảnh xuất hiện hoặc biến mất của các phần tử con thông qua các phép biến đổi Canvas.
Nếu bạn cần áp dụng đồ họa bên dưới các phần tử con (hiệu ứng nền), hãy sử dụng onDraw — dispatchDraw được gọi sau đó. Nếu đồ họa phải ở trên các phần tử con — hãy sử dụng dispatchDraw với việc gọi super trước, sau đó Canvas.draw. Nếu bạn cần thay đổi màu sắc hoặc độ trong suốt toàn cục, thuận tiện để sử dụng canvas.saveLayerAlpha() trong dispatchDraw, bao bọc việc vẽ các phần tử con.
Không được khuyến nghị sử dụng dispatchDraw cho: 1) vẽ hoạt ảnh phức tạp theo thời gian thực (sử dụng invalidate và onDraw); 2) tạo ảnh chụp màn hình của ViewGroup (sử dụng buildDrawingCache() hoặc View.draw(Canvas)); 3) sửa đổi các View con (tọa độ, kích thước — đó là nhiệm vụ của onLayout, không phải dispatchDraw). dispatchDraw chỉ để áp dụng các hiệu ứng trực quan.
Các câu hỏi thường gặp
Không, dispatchDraw là phương thức protected được hệ thống Android gọi bên trong phương thức public draw(). Gọi trực tiếp dispatchDraw không có ý nghĩa vì nó không thực hiện các hành động chuẩn bị (lưu Canvas, vẽ nền và nền trước). Thay vào đó, hãy sử dụng View.draw(Canvas) để vẽ theo chương trình lên một Canvas bất kỳ.
Nếu bạn gọi super.dispatchDraw(canvas) sau các thao tác vẽ của riêng mình, các View con sẽ được vẽ lên trên đồ họa tùy chỉnh. Điều này thay đổi thứ tự lớp: lớp tùy chỉnh được vẽ trước, sau đó các phần tử con. Nếu bạn cần tình huống ngược lại (đồ họa trên các phần tử con), hãy gọi super.dispatchDraw trước, sau đó các thao tác của riêng bạn.
dispatchDraw gọi draw() cho mỗi View con và tổng thời gian tỷ lệ thuận với số lượng phần tử con. Với tăng tốc phần cứng, thêm các thao tác Canvas trong dispatchDraw có thể làm mất hiệu lực DisplayList và gây xây dựng lại bộ nhớ đệm. Đối với 5–10 phần tử con, tác động là tối thiểu, đối với 50+ phần tử con, nên lưu vào bộ nhớ đệm các lớp phủ phức tạp.
View (không phải ViewGroup) không có phần tử con, do đó dispatchDraw không thực hiện công việc hữu ích nào. Tuy nhiên, phương thức này tồn tại trong lớp cơ sở View để đa hình: mã trong draw() gọi dispatchDraw cho bất kỳ View nào, nhưng triển khai của View không chứa logic vẽ phần tử con. Chỉ ViewGroup ghi đè dispatchDraw.
Có, dispatchDraw chấp nhận Canvas và nó có thể được biến đổi trước khi gọi super.dispatchDraw (translate, rotate, scale). Điều này được sử dụng để tạo hoạt ảnh cho toàn bộ tập hợp các phần tử con như một đơn vị duy nhất. Sau khi super.dispatchDraw hoàn thành, nên khôi phục Canvas về trạng thái ban đầu.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm