dispatchDraw() เป็นเมธอดของคลาส ViewGroup ที่รับผิดชอบการวาด View ย่อยทั้งหมดในลำดับชั้นของ Android แบบเรียกซ้ำ ตามเอกสารสำหรับนักพัฒนา Android (2026) dispatchDraw ถูกเรียกโดยอัตโนมัติหลังจาก onDraw ในเมธอด draw() ของ View หลักและวนผ่านองค์ประกอบย่อยทั้งหมด เรียก draw() ของพวกมันเอง นักพัฒนาโอเวอร์ไรด์ dispatchDraw ใน ViewGroup ที่กำหนดเองเพื่อเพิ่มเอฟเฟกต์ วางกราฟิกซ้อนทับบนองค์ประกอบย่อย หรือเปลี่ยนลำดับการวาด
ประเด็นสำคัญ
dispatchDraw(Canvas canvas) เป็นเมธอด protected ของคลาส ViewGroup ที่ระบบ Android เรียกเพื่อวาด View ย่อยทั้งหมดของคอนเทนเนอร์ปัจจุบัน dispatchDraw เป็นส่วนหนึ่งของไปป์ไลน์การวาดมาตรฐาน: ขั้นแรก 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) ซึ่งรับผิดชอบเลเยอร์พื้นหน้า แถบเลื่อน และเอฟเฟกต์ระลอกคลื่น ลำดับนี้รับประกันลำดับการซ้อนทับของเลเยอร์กราฟิกที่ถูกต้อง
onDraw ทำงานก่อน dispatchDraw เสมอ ซึ่งหมายความว่าเนื้อหาของ View หลักจะแสดงใต้ View ย่อย หากคุณต้องการวาดบางสิ่งเหนือองค์ประกอบย่อย ให้ทำใน onDrawForeground หรือใน dispatchDraw ที่โอเวอร์ไรด์โดยเรียก super.dispatchDraw(canvas) แล้ววาดทับ พื้นหลังจะถูกวาดก่อน onDraw ด้วยซ้ำ — ใน drawBackground(canvas) ภายในเมธอด draw()
ด้วย การเร่งด้วยฮาร์ดแวร์ ไปป์ไลน์การวาดทำงานผ่าน GPU View จะถูกวาดลงใน DisplayList — รายการคำสั่งวาดที่ถูกแคชและนำกลับมาใช้ใหม่ dispatchDraw ในโหมดเร่งด้วยฮาร์ดแวร์จะเพิ่ม DisplayLists ของ View ย่อยลงใน 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) สามารถปรับ onDraw ให้เหมาะสมโดยตั้งค่า setWillNotDraw(true) ในกรณีนี้ onDraw จะไม่ถูกเรียกเลย ซึ่งช่วยประหยัดทรัพยากร dispatchDraw ยังคงทำงานและเรียก draw สำหรับองค์ประกอบย่อย ViewGroup มาตรฐานทั้งหมด (LinearLayout, RelativeLayout, ConstraintLayout) ใช้ setWillNotDraw(true)
หากคุณโอเวอร์ไรด์ dispatchDraw โดยไม่เรียก super.dispatchDraw(canvas) View ย่อยจะไม่ถูกวาด ซึ่งอาจมีประโยชน์สำหรับการซ่อนองค์ประกอบย่อยทั้งหมดชั่วคราว แต่ในกรณีส่วนใหญ่จะทำให้เกิดข้อผิดพลาด แนวทางปฏิบัติที่แนะนำ: เรียก super.dispatchDraw(canvas) ที่จุดเริ่มต้นของเมธอดที่โอเวอร์ไรด์ จากนั้นเพิ่มกราฟิกของคุณเอง (เช่น การซ้อนทับด้วยช่องอัลฟา) ทับองค์ประกอบย่อย
มาสร้าง OverlayViewGroup — ViewGroup ที่กำหนดเองซึ่งเพิ่มเลเยอร์โปร่งแสงพร้อมป้ายข้อความทับ View ย่อยทั้งหมด dispatchDraw เรียก super ก่อน (วาดองค์ประกอบย่อยทั้งหมด) จากนั้นวาดสี่เหลี่ยมซ้อนทับและข้อความ 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 เอง สถานการณ์หลัก: การใช้เอฟเฟกต์ทั่วไป (โอเวอร์เลย์ เงา เกรเดียนต์) ทับองค์ประกอบย่อยทั้งหมด การเปลี่ยน 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 เรียกภายในเมธอด public draw() การเรียก dispatchDraw โดยตรงนั้นไม่มีประโยชน์เพราะไม่ได้ดำเนินการเตรียมการ (บันทึก Canvas วาดพื้นหลังและพื้นหน้า) ให้ใช้ 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 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม