invalidate() เป็นเมธอดของคลาส View ใน Android ที่ทำเครื่องหมายวิวว่าต้องการการวาดใหม่ การเรียก invalidate() จะทำให้เกิดการวาดวิวใหม่ในรอบการรีเฟรชหน้าจอถัดไป ทำให้มันเป็นกลไกหลักในการอัปเดตสถานะภาพของคอมโพเนนต์ที่กำหนดเอง ตามเอกสาร Android Developers (2025) invalidate() ถูกใช้ใน 90% ของ View ที่กำหนดเองเพื่อซิงค์การเปลี่ยนแปลงข้อมูลกับการแสดงผลบนหน้าจอ เมธอดทำงานแบบอะซิงโครนัส — มันเพียงตั้งค่า dirty flag และคืนการควบคุมทันที
ประเด็นสำคัญ
invalidate() เป็นเมธอดของคลาส android.view.View ที่บอกระบบ Android ว่าการแสดงผลทางภาพของวิวล้าสมัย หลังจากเรียกเมธอดแล้ว ระบบจะทำเครื่องหมายวิวเป็น dirty และกำหนดเวลาการวาดใหม่ในรอบการรีเฟรชหน้าจอถัดไป (ปกติ 16 ms สำหรับ 60 FPS)
เมธอด invalidate() มีหลายรูปแบบ: ไม่มีพารามิเตอร์ (วาดใหม่ทั้งหมด), มีพารามิเตอร์ Rect (บางส่วน) และมีพารามิเตอร์ ltrb (left, top, right, bottom) ทุกเวอร์ชันทำงานแบบอะซิงโครนัสและต้องเรียกจากเธรด UI สำหรับการเรียกจากเธรดพื้นหลังมี postInvalidate()
กลไกการวาดใหม่ใน Android ขึ้นอยู่กับ ViewRootImpl — คอมโพเนนต์ภายในที่เชื่อมต่อลำดับชั้น View กับ Surface สำหรับการวาด เมื่อเรียก invalidate() ViewRootImpl จะทำเครื่องหมายพื้นที่วิวเป็น dirty และส่งคำขอวาดใหม่ผ่าน Choreographer — บริการระบบที่ซิงค์การวาดกับอัตราการรีเฟรชหน้าจอ
Choreographer รับสัญญาณจาก Vsync และเริ่มกระบวนการสามขั้นตอน: measure, layout, draw อย่างไรก็ตาม invalidate() มีผลเฉพาะเฟส draw เท่านั้น — เฟส measure และ layout จะไม่ถูกดำเนินการเว้นแต่จะเรียก requestLayout() นี่คือความแตกต่างสำคัญ: invalidate() เบากว่า requestLayout() เพราะไม่คำนวณเรขาคณิตใหม่
class CustomChartView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private var dataPoints: List<Float> = emptyList()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
fun updateData(newPoints: List<Float>) {
dataPoints = newPoints
invalidate() // คำขอวาดใหม่
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// กำลังวาดเส้นกราฟ
val path = Path()
dataPoints.forEachIndexed { index, value ->
val x = index * width / max(dataPoints.size - 1, 1)
val y = height - value * height
if (index == 0) path.moveTo(x, y)
else path.lineTo(x, y)
}
canvas.drawPath(path, paint)
}
}
ในตัวอย่างนี้ View ที่กำหนดเองสำหรับวาดกราฟเรียก invalidate() เมื่อข้อมูลถูกอัปเดต ระบบจะวาดเฉพาะ View นี้ใหม่โดยไม่ส่งผลต่อองค์ประกอบอื่นในลำดับชั้น onDraw() จะได้รับ Canvas สำหรับวาดเส้นผ่าน Path
ความแตกต่างหลักระหว่าง invalidate() และ postInvalidate() อยู่ที่ความปลอดภัยของเธรด invalidate() ต้องเรียกจากเธรด UI (เธรดหลัก) เท่านั้น postInvalidate() สามารถเรียกจากเธรดใดก็ได้ — มันส่งคำขอวาดใหม่ไปยังเธรด UI ผ่าน Handler
| คุณลักษณะ | invalidate() | postInvalidate() |
|---|---|---|
| เธรดที่เรียก | เธรด UI (เธรดหลัก) | เธรดใดก็ได้ |
| กลไก | อัปเดต dirty flag โดยตรง | ผ่าน Handler.post() ไปยังเธรด UI |
| ความหน่วง | น้อยที่สุด ในรอบปัจจุบัน | จนถึงรอบถัดไปของเธรด UI |
| ประสิทธิภาพ | สูง | โอเวอร์เฮดของ Handler เล็กน้อย |
| คำแนะนำ | ใช้ invalidate() เสมอสำหรับเธรด UI | เฉพาะเธรดพื้นหลัง |
ในทางปฏิบัติ postInvalidate() ใช้ในสถานการณ์การโหลดข้อมูลเครือข่าย การประมวลผลผลลัพธ์เซนเซอร์ หรือการคำนวณพื้นหลัง หากคุณอยู่ในเธรด UI — ให้ใช้ invalidate() เสมอเพื่อความหน่วงน้อยที่สุด
// เรียกจากเธรด UI
view.invalidate()
// เรียกจากเธรดพื้นหลัง
Thread {
// การคำนวณหนัก
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) และ invalidate(int l, int t, int r, int b) อนุญาตให้จำกัดพื้นที่การวาดใหม่ นี่เป็นสิ่งสำคัญสำหรับประสิทธิภาพ: เมื่อเพียงบางส่วนของ View เปลี่ยนแปลง (เช่น การเคลื่อนที่ของเคอร์เซอร์ การเปลี่ยนตัวบ่งชี้) ไม่จำเป็นต้องวาดวิวทั้งหมดใหม่
ระบบส่งสี่เหลี่ยม dirty ที่ระบุไปยัง onDraw() ผ่าน canvas.clipBounds ภายใน onDraw() คุณสามารถตรวจสอบ clipBounds และวาดเฉพาะภายในพื้นที่นั้น แม้ว่า Android Canvas จะตัดการวาดนอกสี่เหลี่ยม dirty โดยอัตโนมัติ
// อัปเดตบางส่วน: เฉพาะพื้นที่เคอร์เซอร์
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// ทำให้ตำแหน่งเก่าไม่ถูกต้อง
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// ทำให้ตำแหน่งใหม่ไม่ถูกต้อง
invalidate(cursorRect)
}
หากไม่มีการวาดใหม่บางส่วน ทุกการเคลื่อนที่ของเคอร์เซอร์จะวาด View ทั้งหมดใหม่ ซึ่งสำหรับกราฟขนาดใหญ่หมายถึงการวาดพิกเซลหลายพันแทนที่จะเป็นไม่กี่สิบ invalidate(Rect) เป็นเทคนิคจำเป็นสำหรับโปรแกรมแก้ไข แคนวาสวาดภาพ และคอมโพเนนต์เคลื่อนไหว
ข้อผิดพลาดทั่วไปคือการเรียก requestLayout() ในที่ที่แค่ invalidate() ก็เพียงพอ และในทางกลับกัน ความแตกต่างเป็นพื้นฐาน: invalidate() มีผลเฉพาะเฟส draw ในขณะที่ requestLayout() เริ่มรอบเต็ม measure → layout → draw
| ด้าน | invalidate() | requestLayout() |
|---|---|---|
| เฟสของรอบ | เฉพาะ draw | measure + layout + draw |
| เมื่อไรใช้ | เฉพาะการเรนเดอร์เปลี่ยน (สี ข้อความ กราฟิก) | ขนาดหรือตำแหน่งของวิวเปลี่ยน |
| ประสิทธิภาพ | เบา — วาดใหม่เท่านั้น | หนัก — คำนวณลำดับชั้นใหม่ |
| ผลกระทบต่อลำดับชั้น | เฉพาะวิวปัจจุบัน | อาจส่งผลต่อคอนเทนเนอร์แม่ |
หากคุณเปลี่ยนข้อความใน TextView แค่ invalidate() ก็เพียงพอเพราะขนาดวิวไม่เปลี่ยน หากข้อความอาจขึ้นบรรทัดใหม่และเพิ่มความสูง จำเป็นต้องใช้ requestLayout() Android Lint ช่วยติดตามข้อผิดพลาดเหล่านี้ผ่านกฎประสิทธิภาพ
การเรียก invalidate() มากเกินไปเป็นสาเหตุหลักประการหนึ่งของประสิทธิภาพต่ำของ View ที่กำหนดเองใน Android มาดูเทคนิคการเพิ่มประสิทธิภาพกัน
หากข้อมูลอัปเดตด้วยความถี่สูง (เซนเซอร์ แอนิเมชัน วิดีโอ) อย่าเรียก invalidate() ทุกครั้งที่มีการเปลี่ยนแปลง ใช้ ValueAnimator หรือ Choreographer.FrameCallback เพื่อซิงค์กับอัตราการรีเฟรชหน้าจอ ซึ่งรับประกันว่า invalidate() จะถูกเรียกไม่เกินหนึ่งครั้งต่อเฟรม
ตั้งแต่ API 14 Android รองรับการเร่งด้วยฮาร์ดแวร์ผ่าน GPU หาก View ที่กำหนดเองของคุณใช้เฉพาะ Canvas API (drawRect, drawCircle, drawPath) การเร่งจะทำงานอย่างโปร่งใส สำหรับการดำเนินการที่เข้ากันได้กับ DisplayList invalidate() จะถูกประมวลผลเร็วขึ้นอย่างมาก
// ใช้ Choreographer สำหรับซิงค์ Vsync
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
ใช้ invalidate(Rect) สำหรับการอัปเดตเป้าหมาย หลีกเลี่ยงการเรียก invalidate() จาก onDraw() (ลูปไม่มีที่สิ้นสุด) และวัดประสิทธิภาพผ่าน GPU Profile Rendering บนอุปกรณ์เสมอ ซึ่งจะแสดงเวลาเรนเดอร์ที่แน่นอนของแต่ละเฟรมและช่วยระบุพื้นที่ที่มีปัญหา
คำถามที่พบบ่อย
ไม่ได้ การเรียก invalidate() ภายใน onDraw() จะสร้างลูปการวาดใหม่ไม่มีที่สิ้นสุด: onDraw() เรียก invalidate() ซึ่งจะเรียก onDraw() อีกครั้ง ทำให้ CPU ใช้งาน 100% และเฟรมดรอป ใช้แอนิเมชันผ่าน ValueAnimator หรือ Choreographer
invalidate() ทำงานเฉพาะในเธรด UI และอัปเดต dirty flag ทันที postInvalidate() ส่งคำขอผ่าน Handler ไปยังเธรด UI และสามารถเรียกจากเธรดพื้นหลังใดก็ได้ หากคุณอยู่ในเธรด UI — ใช้ invalidate() เพื่อความหน่วงน้อยที่สุด
ใช่ ภายในเมธอด setText() ของ TextView เรียก invalidate() หลังจากอัปเดตข้อความ หากข้อความเปลี่ยนขนาดวิว จะเรียก requestLayout() เพิ่มเติมด้วย นักพัฒนาไม่จำเป็นต้องเรียก invalidate() ด้วยตนเองเมื่อทำงานกับวิดเจ็ตมาตรฐาน
แต่ละการเรียก invalidate() กำหนดเวลาการวาดใหม่ใน Vsync ถัดไป (ทุก 16 ms) หาก onDraw() ใช้เวลานานกว่า 16 ms จะเกิดเฟรมดรอป เพิ่มประสิทธิภาพ onDraw() — แคช Bitmap หลีกเลี่ยงการจัดสรร และใช้การเร่งด้วยฮาร์ดแวร์สำหรับการเรนเดอร์ GPU
ใช่ หลังจากเปลี่ยนคุณสมบัติ Paint (สี ความหนา สไตล์) คุณต้องเรียก invalidate() เพราะ View ไม่ติดตามการเปลี่ยนแปลงของออบเจ็กต์ Paint โดยอัตโนมัติ ระบบไม่ทราบว่า Paint เปลี่ยนไปแล้วและจะไม่เรียก onDraw() โดยไม่มีการร้องขออย่างชัดแจ้ง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม