Canvas ใน Android: คืออะไร คลาส Canvas และวิธีการ draw

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

Canvas ใน Android — เป็นคลาสจากแพ็คเกจ android.graphics ที่ให้ API สำหรับการเรนเดอร์ 2D บน View Canvas ทำหน้าที่เหมือนผืนผ้าใบที่นักพัฒนาวาดรูปทรงเรขาคณิต ข้อความ รูปภาพ และเส้นทางโดยใช้วิธีการ draw* ต่างๆ ตามเอกสารสำหรับนักพัฒนา Android (2025) Canvas เป็นเครื่องมือหลักสำหรับการเรนเดอร์แบบกำหนดเองใน Android และใช้ร่วมกับคลาส Paint เพื่อกำหนดสไตล์และสี แต่ละการเรียก onDraw() จะได้รับอินสแตนซ์ Canvas ที่พร้อมใช้งาน ซึ่งเชื่อมโยงกับบัฟเฟอร์บิตแมปของวิว

ประเด็นสำคัญ

  • Canvas — คลาสสำหรับการเรนเดอร์ 2D ใน Android ให้วิธีการ drawRect, drawCircle, drawText, drawBitmap และอื่นๆ
  • Paint — คลาสที่กำหนดสไตล์การเรนเดอร์: สี ความหนา การเติม เอฟเฟกต์ (เงา การไล่สี)
  • onDraw(Canvas) — วิธีการของ View ที่การเรนเดอร์ทั้งหมดเกิดขึ้นผ่าน Canvas ที่ได้รับ
  • Canvas.save() และ restore() — การจัดการสแต็กการแปลงสำหรับการแปลงระบบพิกัดแบบซ้อน
  • Path — คลาสสำหรับสร้างเส้นขอบและเส้นโค้งตามอำเภอใจ ส่งต่อไปยัง drawPath(Path, Paint)

Canvas ใน Android คืออะไร

Canvas — เป็นคลาสกราฟิก 2D หลักใน Android ที่ให้วิธีการสำหรับการวาดบนระนาบ Canvas แต่ละอันเชื่อมโยงกับ Bitmap — พื้นที่หน่วยความจำที่บันทึกผลลัพธ์ของการดำเนินการวาด เมื่อ View ถูกวาดใหม่ ระบบจะส่งต่อไปยัง onDraw() ซึ่งเป็น Canvas ที่เชื่อมโยงกับบัฟเฟอร์ของ View นั้นแล้ว

Canvas ทำงานในระบบพิกัดที่ (0,0) คือมุมซ้ายบน แกน X ไปทางขวา แกน Y ลงด้านล่าง การดำเนินการวาดทั้งหมดใช้สถานะปัจจุบันของ Canvas รวมถึงเมทริกซ์การแปลง พื้นที่คลิป และสไตล์ Paint ตามเอกสาร Android (2025) Canvas API มีวิธีการ draw* มากกว่า 30 วิธีสำหรับประเภทต่าง ๆ ของพรอมิทีฟกราฟิก

Canvas และการเร่งด้วยฮาร์ดแวร์

ตั้งแต่ Android 3.0 (API 11) Canvas รองรับการเร่งด้วยฮาร์ดแวร์ผ่าน DisplayList ในโหมดเร่งความเร็ว การดำเนินการของ Canvas จะถูกบันทึกลงใน DisplayList และดำเนินการบน GPU ซึ่งให้ประสิทธิภาพที่เพิ่มขึ้นอย่างมาก แต่มีข้อจำกัด: การดำเนินการ Canvas บางอย่าง (clipPath กับคลิปที่ไม่ใช่ Path) ไม่ได้รับการสนับสนุนในโหมดเร่งความเร็ว

วิธีการเรนเดอร์พื้นฐานของ Canvas

Canvas มีชุดวิธีการสำหรับวาดพรอมิทีฟพื้นฐาน แต่ละวิธีการรับพารามิเตอร์ทางเรขาคณิตและออบเจ็กต์ Paint ที่กำหนดสไตล์ภาพ

วิธีการวัตถุประสงค์ตัวอย่าง
drawRect()วาดสี่เหลี่ยมผืนผ้าcanvas.drawRect(10f, 10f, 100f, 100f, paint)
drawCircle()วาดวงกลมcanvas.drawCircle(50f, 50f, 30f, paint)
drawLine()วาดเส้นcanvas.drawLine(0f, 0f, 100f, 100f, paint)
drawText()วาดข้อความcanvas.drawText(“Hello”, 10f, 20f, paint)
drawBitmap()วาดรูปภาพcanvas.drawBitmap(bitmap, 0f, 0f, paint)
drawPath()วาดเส้นทางตามอำเภอใจcanvas.drawPath(path, paint)
drawOval()วาดรูปวงรีcanvas.drawOval(rectF, paint)
drawArc()วาดส่วนโค้งหรือเซกเตอร์canvas.drawArc(rectF, 0f, 90f, true, paint)

แต่ละวิธีสามารถเรียกใช้ในลำดับใดก็ได้ — Canvas จะวาดตามลำดับการเรียก โดยวางองค์ประกอบใหม่ทับองค์ประกอบก่อนหน้า สำหรับองค์ประกอบที่ซับซ้อน จะใช้เทคนิคการเรนเดอร์หลายชั้นพร้อมการบันทึกและกู้คืนสถานะของ Canvas

คลาส Paint: การกำหนดค่าสไตล์และเอฟเฟกต์

Paint — เป็นคลาสที่กำหนดว่า Canvas จะวาดอย่างไร หากไม่มี Paint วิธีการของ Canvas ก็ไร้ประโยชน์ — มันกำหนดสี ความหนาของเส้น สไตล์การเติม ขนาดข้อความ เอฟเฟกต์ (เงา การไล่สี การเบลอ) และโหมดการผสม

Paint มีคุณสมบัติสำคัญ: color (สี), style (FILL, STROKE หรือ FILL_AND_STROKE), strokeWidth (ความหนาของเส้น), textSize (ขนาดข้อความ), isAntiAlias (การลดรอยหยัก), shader (การไล่สีหรือรูปภาพ) การสร้างออบเจ็กต์ Paint เป็นการดำเนินการที่ใช้ทรัพยากรมาก ดังนั้นควรสร้างครั้งเดียวและนำกลับมาใช้ใหม่

kotlin
private val fillPaint = Paint().apply {
    color = Color.parseColor("#FF6200EE")
    style = Paint.Style.FILL
    isAntiAlias = true
}

private val strokePaint = Paint().apply {
    color = Color.WHITE
    style = Paint.Style.STROKE
    strokeWidth = 4f
    isAntiAlias = true
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawCircle(100f, 100f, 80f, fillPaint)
    canvas.drawCircle(100f, 100f, 80f, strokePaint)
}

ในตัวอย่างนี้ ใช้ออบเจ็กต์ Paint สองอัน: อันหนึ่งสำหรับเติมวงกลมด้วยสีม่วง อีกอันสำหรับเส้นขอบสีขาว การแยกการเติมและเส้นขอบเป็นแนวปฏิบัติมาตรฐานที่ช่วยให้ควบคุมลักษณะที่ปรากฏได้อย่างยืดหยุ่นโดยไม่ต้องสร้างออบเจ็กต์ใหม่

Shader และการไล่สี

สำหรับการเติมแบบไล่สี Paint รับ Shader — คลาสย่อย LinearGradient, RadialGradient, SweepGradient Shader เชื่อมโยงกับ Paint ผ่านเมธอด setShader() และกำหนดว่าสีเปลี่ยนแปลงอย่างไรทั่วพื้นที่วาด

การแปลง Canvas: translate, rotate, scale

Canvas รองรับการแปลงเมทริกซ์ของระบบพิกัด: translate() (การเลื่อน), rotate() (การหมุน), scale() (การปรับขนาด) และ skew() (การเอียง) การแปลงจะสะสม: แต่ละการเรียกที่ตามมาจะถูกนำไปใช้ทับรายการก่อนหน้า

การจัดการสแต็กการแปลงทำผ่าน save() และ restore() save() บันทึกสถานะปัจจุบันของ Canvas (เมทริกซ์, คลิป) ลงในสแต็ก restore() กู้คืนสถานะที่บันทึกล่าสุด ระหว่าง save() และ restore() สามารถใช้การแปลงแบบซ้อนได้

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

    canvas.save() // Save initial state
    canvas.translate(100f, 100f) // Translate origin

    // First square
    canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)

    canvas.save() // Save after translate
    canvas.rotate(45f, 25f, 25f) // Rotate around square center

    // Rotated square
    canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
    canvas.restore() // Restore after rotate

    canvas.restore() // Restore initial state
}

ใช้ save() ก่อนเริ่มการแปลงและ restore() หลังจากเสร็จสิ้นเสมอ คู่ save/restore ที่ไม่สมดุลนำไปสู่การสะสมของเมทริกซ์และการเรนเดอร์องค์ประกอบที่ตามมาอย่างไม่ถูกต้อง

Canvas กับ Bitmap และ Path สำหรับกราฟิกที่ซับซ้อน

สำหรับกราฟิกที่ซับซ้อน Canvas จะรวมกับ Bitmap และ Path Bitmap ใช้สำหรับโหลดรูปภาพและแคชเฟรมที่เรนเดอร์แล้ว Path ใช้สำหรับสร้างเส้นโค้ง Bezier ตามอำเภอใจ รูปหลายเหลี่ยม และเส้นขอบที่ซับซ้อน

บัฟเฟอร์ Bitmap นอกหน้าจอ

เทคนิคการเรนเดอร์นอกหน้าจอ: สร้าง Bitmap วาดฉากที่ซับซ้อนลงบนนั้นผ่าน Canvas ที่แยกต่างหาก จากนั้น Bitmap ที่เสร็จแล้วจะถูกแสดงผลใน onDraw() ผ่าน drawBitmap() ซึ่งช่วยให้สามารถอัปเดตกราฟิกที่ซับซ้อนได้โดยไม่ต้องวาดใหม่ทั้งหมดในทุกเฟรม

kotlin
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null

fun buildOffscreenScene(width: Int, height: Int) {
    offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    offscreenCanvas = Canvas(offscreenBitmap)

    // Render scene once
    offscreenCanvas!!.drawColor(Color.WHITE)
    drawGrid(offscreenCanvas!!)
    drawComplexShape(offscreenCanvas!!)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}

Path ใช้สำหรับสร้างเส้นขอบที่ซับซ้อน: เส้นโค้ง Bezier (quadTo, cubicTo), ส่วนโค้ง (arcTo), เส้นตรง (lineTo) Path สามารถปิด (close()) สำหรับรูปทรงที่เติมหรือเปิดสำหรับเส้น วิธีการหลัก drawPath() รวม Path และ Paint สำหรับการเรนเดอร์ขั้นสุดท้าย

การเพิ่มประสิทธิภาพการเรนเดอร์ Canvas

ประสิทธิภาพการเรนเดอร์ Canvas ส่งผลโดยตรงต่อ FPS ของแอปพลิเคชัน ปัญหาหลัก: การจัดสรรหน่วยความจำมากเกินไปใน onDraw(), การเรียก invalidate() บ่อยครั้ง และการใช้การเร่งด้วยฮาร์ดแวร์ที่ไม่เหมาะสม

สร้างออบเจ็กต์ภายนอก onDraw()

Paint, Path, Rect, RectF และออบเจ็กต์อื่นๆ ควรสร้างครั้งเดียวในตัวเริ่มต้นหรือเมธอด setup() การจัดสรรแต่ละครั้งใน onDraw() สร้างภาระให้กับตัวเก็บขยะ ซึ่งนำไปสู่การกระตุกขนาดเล็ก ข้อยกเว้นคือ static หรือ companion object สำหรับออบเจ็กต์ที่ไม่เปลี่ยนรูป

ใช้ clipRect() เพื่อจำกัดพื้นที่

หาก View ส่วนใหญ่ถูกซ่อนอยู่นอกพื้นที่ที่มองเห็น (เช่น ใน ScrollView) ให้ใช้ canvas.clipRect() เพื่อจำกัดการเรนเดอร์ การเร่งด้วยฮาร์ดแวร์ในกรณีนี้จะปรับการคลิปพิกเซลที่มองไม่เห็นให้เหมาะสมยิ่งขึ้น

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

    // Clip to visible area
    canvas.save()
    canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)

    // Render large scene
    renderScene(canvas)
    canvas.restore()
}

ใช้ View.setLayerType() กับ LAYER_TYPE_HARDWARE สำหรับ View ที่มีกราฟิก Canvas ซับซ้อนซึ่งอัปเดตไม่บ่อย สำหรับแอนิเมชันที่อัปเดตบ่อย ให้ใช้ LAYER_TYPE_NONE เพื่อหลีกเลี่ยงการอัปเดตเลเยอร์ฮาร์ดแวร์ที่มีต้นทุนสูง ทำการโปรไฟล์โดยใช้ GPU Profile Rendering และ Android Studio Profiler

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

สามารถสร้าง Canvas โดยไม่มี View ได้หรือไม่?

ได้ คุณสามารถสร้าง Canvas(Bitmap) — คอนสตรักเตอร์ที่รับ Bitmap ซึ่งใช้สำหรับการเรนเดอร์นอกหน้าจอ: คุณวาดบน Bitmap จากนั้นแสดงผลผ่าน drawBitmap() Canvas สามารถสร้างจาก Surface (สำหรับ SurfaceView) หรือผ่าน Picture ได้เช่นกัน

Canvas แตกต่างจาก SurfaceView อย่างไร?

Canvas — เป็น API สำหรับวาดบน View ในเธรด UI SurfaceView — เป็น Surface ที่แยกต่างหากซึ่งสามารถอัปเดตจากเธรดพื้นหลังผ่าน Canvas ของตัวเอง (lockCanvas() / unlockCanvasAndPost()) SurfaceView เหมาะสำหรับเกมและวิดีโอ

ทำไม Canvas ไม่วาดหลังจากการแปลง?

สาเหตุที่พบบ่อยที่สุดคือคู่ save() และ restore() ที่ไม่สมดุล หากหลังจากการแปลง (translate, rotate) ไม่เรียก restore() Canvas จะยังคงอยู่ในระบบพิกัดที่ถูกปรับเปลี่ยน และองค์ประกอบที่ตามมาจะถูกวาดด้วยค่าชดเชย ตรวจสอบคู่ save()/restore() ของคุณ

วิธีการวาดข้อความกึ่งกลางบน Canvas?

ใช้ Paint.setTextAlign(Paint.Align.CENTER) สำหรับการจัดกึ่งกลางแนวนอน และ Paint.getTextBounds() หรือ Paint.descent() / ascent() สำหรับแนวตั้ง การจัดกึ่งกลาง: canvas.drawText(ข้อความ, centerX, centerY - (ascent + descent) / 2, paint)

Canvas รองรับแอนิเมชันหรือไม่?

Canvas เองไม่ได้สร้างแอนิเมชัน — มันเพียงแค่วาดสถานะปัจจุบัน แอนิเมชันทำได้โดยการเรียก invalidate() (หรือ postInvalidateOnAnimation()) อย่างต่อเนื่อง โดยวาดเฟรมใหม่ใน onDraw() แต่ละครั้ง สำหรับแอนิเมชันที่ราบรื่น ให้ใช้ ValueAnimator หรือ Choreographer เพื่อซิงค์กับ Vsync

สรุป

  • Canvas — คลาสการเรนเดอร์ 2D หลักใน Android ให้วิธีการ draw* สำหรับวาดพรอมิทีฟ ข้อความ และรูปภาพ
  • Paint — กำหนดสไตล์ภาพ (สี ความหนา การเติม shader) สร้างครั้งเดียวและนำกลับมาใช้ใหม่
  • save() / restore() — การจัดการสแต็กการแปลง แต่ละแปลงต้องอยู่ในคู่ save/restore
  • Path — การสร้างเส้นขอบตามอำเภอใจและเส้นโค้ง Bezier สำหรับกราฟิกเวกเตอร์ที่ซับซ้อน
  • Offscreen Bitmap — เทคนิคการแคชฉากที่ซับซ้อนผ่าน Canvas(Bitmap) เพื่อลดภาระบน onDraw()
  • การเร่งด้วยฮาร์ดแวร์ — การเร่ง GPU ของ Canvas ตั้งแต่ Android 3.0 จำกัดการดำเนินการบางอย่าง (clipPath)
  • การโปรไฟล์ — GPU Profile Rendering และ Android Studio Profiler จำเป็นสำหรับการหาจุดคอขวดใน onDraw()

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

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

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

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