onDraw() — มันคืออะไร กลไกการเรียกและการเปิดเพิ่มเมธอด

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-22 เวลาอ่าน: 10 นาที

onDraw() เป็นเมธอดสำคัญของคลาส View ใน Android SDK ที่รับผิดชอบในการวาดเนื้อหาของมุมมอง ระบบจะเรียก onDraw ทุกครั้งที่รูปลักษณ์ของ View มีการอัพเดต — ระหว่างการเรนเดอร์เบรื้องต้นใช้งาน หลังจาก invalidate() หรือเมื่อขนาดเปลี่ยนแปลง ตามเอกสาร Android Developers (2026) การเปิดเพิ่ม onDraw โดยใช้ Canvas API เป็นวิธีหลักในการสร้างกราฟิกส์ที่กำหนดเองในแอปพลิเคชัน Android

หัวข้อสำคัญ

  • onDraw(Canvas) — เมธอด View ที่ระบบเรียกเพื่อวาดเนื้อหาบน Canvas
  • invalidate() — คำขอวาด View ใหม่ นำไปสู่การเรียก onDraw ในวงจรกระเพราะถัดไป
  • Canvas API มีเมธอด drawCircle, drawBitmap, drawText, drawPath และอื่นๆ สำหรับการวาดรูป
  • ระบบ Android ดำเนินการวาดรูบน UI thread ดังนั้น onDraw ต้องเบาและเร็ว
  • Hardware acceleration เร่งการวาดรูปโดยอัตโนมัติผ่าน GPU ตั้งแต่ Android 3.0 (API 11)

onDraw() คืออะไร?

onDraw(Canvas canvas) เป็นเมธอด protected ของคลาส android.view.View ซึ่งระบบ Android เรียกเมื่อ View ต้องการถูกวาด นักพัฒนาเปิดเพิ่มเมธอดนี้เพื่อเพิ่มการวาดรูปที่กำหนดเอง: รูปร่าง, ข้อความ, รูปภาพ, กราเดียนต์, แอนิเมชัน พารามิเตอร์ Canvas เป็นพื้นผิวกราฟิกที่ใช้ดำเนินการวาดรูปทั้งหมด

ลายเซ็นเมธอดง่ายๆ: override fun onDraw(canvas: Canvas) ภายในเมธอดนี้ ไม่ควรสร้างออบเจกต์ใหม่ (เพื่อหลีกเลี่ยง GC overload), ดำเนินการที่ใช้เวลานาน หรือทำงานกับเครือข่าย View ยังมีเมธอด onDraw ในวงจรการทำงานของมันระหว่าง onLayout (การคำนวณตำแหน่ง) และ dispatchDraw (การวาดองค์ประกอบย่อย)

เมื่อสืบทอดจาก View, การเปิดเพิ่ม onDraw เป็นสิ่งจำเป็นสำหรับการแสดงเนื้อหาใดๆ — มิฉะนั้น View จะว่างเปล่า เมื่อสืบทอดจากซับคลาสที่มีอยู่แล้ว (TextView, ImageView, Button), การเปิดเพิ่ม onDraw เป็นตัวเลือก และใช้เพื่อเพิ่มกราฟิกเพิ่มเติมเทืองเนื้อหามาตรฐาน

วงจรการวาดรูป View

Android ดำเนินการวาดรูป View ในลำดับที่กำหนดอย่างเข้มงวด ซึ่งเรียกจากเมนไธร์สรีด (UI thread) วงจรเต็มประกอบด้วยสามเรียง: measure (การวัดขนาด), layout (การคำนวณตำแหน่ง) และ draw (การเรนเดอร์) onDraw เป็นส่วนหนึ่งของเรียง draw และถูกเรียกหลังจากการกำหนดขนาดและตำแหน่งของ View แล้ว

เรียง draw: dispatchDraw และ onDraw

draw(Canvas canvas) เป็นเมธอดสาธารณะของ View ที่เรียก onDraw และ dispatchDraw เมธอด draw ไม่ได้ออกแบบให้เปิดเพิ่ม — แทนที่จะเปิดเพิ่ม onDraw หลังจาก onDraw ทำงานสำเร็จสำหรับ View ปัจจุบัน, dispatchDraw จะถูกเรียก ซึ่งเรียก draw แบบทอดซ้ำสำหรับ View ย่อยทั้งหมด (ถ้า View ปัจจุบันเป็นคอนเทนเนอร์ ViewGroup)

เมื่อไหร่ที่ระบบเรียก onDraw

onDraw ถูกเรียกภายใต้เงื่อนไขต่อไปนี้: 1) View แสดงครั้งแรก (หลัง setVisibility(VISIBLE)); 2) แอพเรียก invalidate() บน View นี้หรือแม่ของมัน; 3) ขนาด View เปลี่ยนไป (layout ใหม่); 4) แอนิเมชันที่เกี่ยวข้องกับ View เปลี่ยนไป (ผ่าน ValueAnimator หรือ ObjectAnimator); 5) สถานะ View เปลี่ยนไป (pressed, focused, enabled)

การเร่งฮาร์ดแวร์

ตั้งแต่ Android 3.0 (API 11), การดำเนินงาน Canvas ส่วนใหญ่ถูกเร่งด้วยฮาร์ดแวร์ผ่าน GPU นักพัฒนาสามารถตรวจสอบได้ว่าเปิดใช้การเร่งหรือไม่ผ่าน canvas.isHardwareAccelerated() ด้วยการเร่งฮาร์ดแวร์, บางการดำเนินงาน Canvas ไม่ได้รับการสนับสนุน (ตัวอย่าง, clipPath กับบริเวณที่ไม่ใช่สี่เหลี่ยมสมผล) และอาจถูกละเลยหรือทำให้เกิดข้อผิดพลาด

Canvas API สำหรับกราฟิกส์ที่กำหนดเอง

Canvas มีชุดเมธอดการวาดรูปที่หลากหลาย เมธอดทั้งหมดทำงานในระบบพิกัดปัจจุบัน, ซึ่งสามารถเปลี่ยนแปลงโดยการแปลงแปลง (translate, rotate, scale, skew) แต่ละเมธอดรับออบเจกต์ Paint ที่กำหนดสี, รูปแบบ, ความหนาเส้น, เอฟเฟกต์ และการลบรอบหยาว

เมธอด Canvasวัตถุประสงค์พารามิเตอร์สำคัญของ Paint
drawCircleวาดวงกลมcenterX, centerY, radius, paint
drawRectวาดสี่เหลี่ยมมุมฉากleft, top, right, bottom, paint
drawLineวาดเส้นstartX, startY, endX, endY, paint
drawPathวาดเส้นทางตามอำเองpath, paint
drawBitmapวาดรูบิตแมพbitmap, srcRect, dstRect, paint
drawTextวาดข้อความtext, x, y, paint
drawArcวาดส่วนโค้งหรือเซกเตอร์ovalRect, startAngle, sweepAngle, useCenter, paint
drawOvalวาดรูปไข่ovalRect, paint

ออบเจกต์ Paint

Paint กำหนดรูปแบบการวาด: สี (setColor), ความหนาเส้น (setStrokeWidth), รูปแบบการเติม (setStyle — FILL, STROKE, FILL_AND_STROKE), การลบรอบหยาว (setAntiAlias), เอฟเฟกต์ (setShadowLayer, setMaskFilter) และกราเดียนต์ (setShader) ควรสร้างและกำหนดค่า Paint หนึ่งครั้งในคอนสตรักเตอร์ของ View, ไม่ใช่ใน onDraw, เพื่อหลีกเลี่ยงการสร้างออบเจกต์ในวงจรการวาดรูป

พิกัดและการแปลงแปลง

Canvas ใช้ระบบพิกัดที่แกน X ชี้ไปทางขวา และแกน Y ชี้ไปทางล่าง จุดกำเนิด (0, 0) คือมุมบนซ้ายของ View ก่อนเรียกเมธอด draw, นักพัฒนาสามารถเปลี่ยนระบบพิกัดได้ผ่าน canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py) การวาดรูปทั้งหมดต่อไปจะดำเนินการในระบบที่แปลงแปลงแล้ว

ประสิทธิภาพ onDraw

onDraw ทำงานบนเมนไธร์สรีด UI, และระยะเวลาของมันส่งผลโดยตรงต่ออัตราเฟรม (fps) เพื่อรักษา 60 fps, เมธอด onDraw ต้องสมบูรณ์ภายใน 16 มิลลิวินาที ความช้าใดๆ อาจนำไปสู่การตกเฟรม (jank), ซึ่งมองเห็นได้เป็นการกระตุกของแอนิเมชันหรือความล่าในการเลื่อนไฟล์

กฎการประสิทธิภาพ onDraw

กฎหลัก สำหรับการเพิ่มประสิทธิภาพ onDraw ได้แก่: 1) อย่าสร้างออบเจกต์ภายใน onDraw — ออบเจกต์ Paint, Path, Rect ทั้งหมดควรเป็นฟิลด์ของคลาส; 2) อย่าจัดสรรหหน่วยความจำ (new) — สิ่งนี้ทำให้เกิดการเก็บขยะ (GC) และการตกเฟรม; 3) แคชเอฟเฟกต์ที่ซับซ้อนใน Bitmap ผ่าน Bitmap.createBitmap และวาด Bitmap ที่เตรียมแล้ว; 4) อย่าทำการ I/O หรือโหลดทรัพยากร

การใช้ clipRect เพื่อการเพิ่มประสิทธิภาพ

clipRect จำกัดพื้นที่การวาดรูปให้อยู่ภายในสี่เหลี่ยมมุมฉากที่ระบุ ใช้เพื่อป้องกันไม่ให้ Canvas เสียเวลากับการวาดพิกเซลนอกพื้นที่ที่มองเห็น มีประสิทธิภาพโดยเฉพาะเมื่อเลื่อนรายการขนาดใหญ่หรือซูม: ตั้ง clipRect เป็นพื้นที่ที่มองเห็น (dirty rect), และ Canvas จะวาดเฉพาะสิ่งที่มองเห็น

Layer — การบันทึกและคืนค่า

canvas.save() บันทึกสถานะปัจจุบันของ Canvas (การแปลงแปลงและ clip) ไว้ในสแต็ก canvas.restore() คืนค่าสถานะที่บันทึกไว้ล่าสุด จำเป็นสำหรับการแปลงแปลงชั่วคราว: บันทึกสถานะ, ใช้ translate/rotate, วาด, แล้วคืนค่า save และ restore เบาและไม่มีผลต่อประสิทธิภาพเมื่อใช้อย่างสมเหตุสมผล (น้อยกว่า 10–15 ระดับการซ้อนกัน)

invalidate และ postInvalidate

invalidate() เป็นเมธอดของ View ที่แจ้งให้ระบบทราบว่าเนื้อหาของ View ล้าสมัยและต้องการวาดใหม่ การเรียก invalidate จะเริ่มวงจร draw ใหม่ซึ่ง onDraw จะถูกเรียก invalidate ต้องเรียกจาก UI เมนไธร์สรีด (เมนไธร์สรีดหลัก) เท่านั้น สำหรับการเรียกจากเมนไธร์สรีดพื้นหลัง ให้ใช้ postInvalidate

postInvalidate() เป็นเมธอดที่ปลอดภัยจากเมนไธร์สรีดของ invalidate ที่ส่งคำขอวาดใหม่ไปยังคิวของเมนไธร์สรีดหลักผ่าน Handler ใช้ในงานพื้นหลังเมื่อ View ต้องการอัพเดตหลังจากการโหลดข้อมูลหรือการคำนวณสำเร็จ สามารถใช้ร่วมกับ runOnUiThread สำหรับการซังโครไนซ์กับ UI

สำหรับ การวาดใหม่บางส่วน, ใช้ invalidate(Rect dirty) หรือ invalidate(int l, int t, int r, int b) สิ่งนี้บอกระบบว่าเฉพาะพื้นที่ที่ระบุเท่านั้นที่เปลี่ยนแปลง, ไม่ใช่ View ทั้งหมด Android ปรับปรุงการวาดรูปโดยการวาดเฉพาะ dirty rect, ซึ่งช่วยเพิ่มประสิทธิภาพสำหรับแอนิเมชันที่มีการอัพเดตบางส่วน

ตัวอย่างโค้ด Kotlin

มาดูตัวอย่างที่ใช้ได้จริงของการเปิดเพิ่ม onDraw เพื่อสร้าง View ที่กำหนดเองใน Android โดยใช้ Kotlin

View ที่กำหนดเอง — ความคืบหน้าแบบวงกลม

ตัวอย่างนี้สร้างตัวชี้ความคืบหน้าแบบวงกลมพร้อมการเติมแบบแอนิเมชัน onDraw วาดวงกลมสองวง: วงกลมพื้นหลังสีเทาและวงกลมเติมสีที่มีส่วนโค้งกำหนดโดยความคืบหน้าปัจจุบัน การเรียก invalidate ใน setProgress ช่วยให้มั่นใจได้ว่าจะมีการวาดใหม่เมื่อค่าเปลี่ยนไป

kotlin
class CircularProgressView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.parseColor("#E0E0E0")
        isAntiAlias = true
    }

    private val progressPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.BLUE
        isAntiAlias = true
        strokeCap = Paint.Cap.ROUND
    }

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 1f)
            invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val size = minOf(width, height) - 24f
        val oval = RectF(12f, 12f,
                          12f + size, 12f + size)

        canvas.drawArc(oval, 270f, 360f, false, bgPaint)
        canvas.drawArc(oval, 270f,
                        progress * 360f,
                        false, progressPaint)
    }
}

View ที่กำหนดเอง — พื้นหลังกราเดียนต์พร้อมข้อความ

ตัวอย่างนี้วาดสี่เหลี่ยมมุมฉากที่มีกราเดียนต์และข้อความอยู่ด้านบน กราเดียนต์ถูกสร้างผ่าน LinearGradient (Shader), และข้อความผ่าน drawText ออบเจกต์ Paint ทั้งสองถูกสร้างขึ้นหนึ่งครั้งในคอนสตรักเตอร์, ในขณะที่ onDraw เพียงแค่อัพเดตขนาดของสี่เหลี่ยมมุมฉากตามความกว้างปัจจุบันของ View

kotlin
class GradientTextView(context: Context)
    : View(context) {

    private val shaderPaint = Paint().apply {
        isAntiAlias = true
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 48f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val gradient = LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            Color.parseColor("#FF6B35"),
            Color.parseColor("#FF0040"),
            Shader.TileMode.CLAMP
        )
        shaderPaint.shader = gradient
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), shaderPaint)

        val xCenter = width / 2f
        val yCenter = height / 2f
        val yOffset = (textPaint.descent() +
                        textPaint.ascent()) / 2f

        canvas.drawText("Hello from Canvas",
                      xCenter, yCenter - yOffset,
                      textPaint)
    }
}

นาฬิกาแอนิเมชันโดยใช้ invalidate

ตัวอย่างนี้สร้างนาฬิกาแอนิเมชันที่อัพเดตการวาดทุก 1000 มิลลิวินาที postDelayed ภายใน onDraw เรียก invalidate หลังจากหนึ่งวินาที, สร้างวงจรการวาดใหม่ไม่สิ้นสุด เข็มนาฬิกาถูกวาดผ่าน drawLine โดยมีความยาวและความหนาต่างกัน

kotlin
class AnalogClockView(context: Context)
    : View(context) {

    private val handPaint = Paint().apply {
        style = Paint.Style.STROKE
        isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val cx = width / 2f
        val cy = height / 2f
        val radius = minOf(cx, cy) - 20f

        val now = Calendar.getInstance()
        val hours = now.get(Calendar.HOUR) % 12
        val minutes = now.get(Calendar.MINUTE)
        val seconds = now.get(Calendar.SECOND)

        val secondAngle = Math.toRadians(
            seconds * 6f - 90f)
        handPaint.strokeWidth = 2f
        handPaint.color = Color.RED
        canvas.drawLine(cx, cy,
            cx + cos(secondAngle) * radius * 0.8f,
            cy + sin(secondAngle) * radius * 0.8f,
            handPaint)

        postDelayed({ invalidate() }, 1000)
    }
}

คำถามที่พบบ่อย

สามารถเรียก onDraw โดยตรงได้หรือไม่?

ไม่, ไม่ควรเรียก onDraw โดยตรง หากต้องการเรียกวาดใหม่, ให้ใช้ invalidate() (จาก UI thread) หรือ postInvalidate() (จากเมนไธร์สรีดพื้นหลัง) ระบบจะตัดสินใจเองว่าเมื่อไหร่ที่จะเรียก onDraw และจะปรับปรุงการวาดรูปโดยการรวมการเรียก invalidate หลายครั้งไว้ในการผ่านครั้งเดียว

ทำไม onDraw ถูกเรียกหลังจาก invalidate?

สาเหตุ: View ถูกซ่อน (setVisibility(GONE) หรือ INVISIBLE); View ไม่ได้ถูกเพิ่มในลำดับชั้นของ Window; ขนาด View เท่ากับ 0 (ความกว้างหรือความสูง = 0); มีการเรียก setWillNotDraw(true) (ค่าเริ่มต้นเป็น true สำหรับ ViewGroup) ตรวจสอบแต่ละข้อเหล่านี้เมื่อแก้ไขปัญหา

onDraw สามารถถูกเรียกได้บ่อยแค่ไหน?

ที่ 60 fps, onDraw สามารถถูกเรียกได้มากถึง 60 ครั้งต่อวินาที ที่ 120 fps (บนอุปกรณ์ที่มีหน้าจอ 120 Hz) — มากถึง 120 ครั้ง แต่ละครั้งต้องสมบูรณ์ภายในประมาณ 8–16 มิลลิวินาที ระหว่างการแอนิเมชันผ่าน ObjectAnimator หรือ ValueAnimator, ความถี่จะถูกกำหนดโดยจำนวนเฟรมแอนิเมชัน

ความแตกต่างระหว่าง onDraw และ dispatchDraw?

onDraw มีหน้าที่วาดเนื้อหาของ View เอง dispatchDraw มีหน้าที่วาด View ย่อย (ใน ViewGroup) dispatchDraw ถูกเรียกหลังจาก onDraw และเดินทางแบบทอดซ้ำผ่านองค์ประกอบย่อยทั้งหมด สำหรับ View ทั่วไป (ไม่ใช่ ViewGroup), dispatchDraw จะว่างเปล่า

สามารถใช้ Canvas ภายนอก onDraw ได้หรือไม่?

ได้, สามารถสร้าง Canvas สำหรับ Bitmap ผ่าน Canvas(bitmap) และวาดบนมันในเมนไธร์สรีดใดๆ ใช้สำหรับการเรนเดอร์กราฟิกส์ที่ซับซ้อนล่วงหน้าในพื้นหลัง อย่างไรก็ตาม, Canvas ที่ส่งต่อไปยัง onDraw เป็นของระบบ และสามารถใช้งานได้เฉพาะบน UI thread ภายในเมธอด onDraw

สรุป

  • onDraw(Canvas) — เมธอดสำคัญของ View สำหรับการวาดรูปที่กำหนดเองโดยใช้ Canvas API ใน Android
  • วงจรการวาดรูป ประกอบด้วยขั้นตอน measure, layout และ draw — onDraw ถูกเรียกในขั้นตอน draw
  • Canvas API ประกอบด้วยเมธอด drawCircle, drawRect, drawPath, drawBitmap, drawText และอื่นๆ
  • invalidate() ขอวาดใหม่จาก UI thread, postInvalidate() จากเมนไธร์สรีดพื้นหลัง
  • การวาดใหม่บางส่วน ผ่าน invalidate(Rect) ช่วยปรับปรุงการเรนเดอร์โดยการอัพเดตเฉพาะบริเวณที่เปื้อน
  • ประสิทธิภาพ onDraw มีความสำคัญยิ่ง: การสร้างออบเจกต์ภายในเมธอดทำให้เกิด GC และการตกเฟรม
  • การเร่งฮาร์ดแวร์ (ตั้งแต่ API 11) เร่งการดำเนินงาน Canvas โดยอัตโนมัติผ่าน GPU

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม